## 问题分析
- 组件 `coupon-dialog.vue` 在 `setup` 中使用了：
  ```ts
  watch(() => props.data, (newData) => { ... else { resetForm() } }, { immediate: true })
  ```
- `resetForm` 在源码中以 `const resetForm = () => { ... }` 形式定义在 `watch` 之后。由于 `immediate: true` 会在初始化阶段立即执行回调，导致在 `resetForm` 尚未初始化之前被调用，抛出 `ReferenceError: Cannot access 'resetForm' before initialization`。

## 修复方案
1) 将 `resetForm` 改为函数声明并上移到 `watch` 之前：
```ts
function resetForm() {
  form.value = { name:'', coupon_type:1, discount_type:1, discount_value:0, valid_days:30, status:1, remark:'' }
}
```
- 函数声明具备提升（hoisting），不会出现“未初始化”问题。
- 位置调整到 `rules` 与 `dialogTitle` 定义之后、`watch(props.data, ...)` 之前。

2) 保留 `immediate: true`（业务期望首次打开时即填充/重置），无需改动；如需更稳健可补充 `flush: 'post'`，但此处核心是函数提升顺序问题。

3) 可选强化
- 如同时监听 `visible`，建议统一在 `watch` 中使用已声明的 `resetForm` 与 `fillFormFromProps(newData)` 两个函数，避免将逻辑直接写在回调内。
- 若依赖 `formRef.resetFields()` 的时机，考虑在 `handleClose` 使用 `nextTick` 保证 DOM 与表单实例可用。

## 变更清单
- 文件：`web/admin/src/views/operations/coupons/modules/coupon-dialog.vue`
- 改动：
  - 将 `resetForm` 改为函数声明并移动到 `watch` 之前。
  - （可选）在 `watch` 的第三参数加入 `flush: 'post'`。

## 验收
- 打开/切换数据源时不再出现 `ReferenceError`。
- 创建/编辑模式下首次加载和重置行为保持一致（`immediate: true` 生效）。

确认后我将按上述方案修改代码并验证。