Files
hzdyjs/出国行程变更-申请行程情况字段调整.md
T
2025-10-14 16:41:19 +08:00

18 KiB
Raw Blame History

出国行程变更 - 申请行程情况字段调整

📋 修改时间

2025-10-13

🎯 需求说明

调整"二、申请行程情况"和"三、实际行程情况"部分的字段显示:

原字段布局

  • 申请出行时间
  • 地点
  • 在外停留时间

新字段布局

  • 前往国家 - 对应数据库字段 original_destination
  • 出境事由 - 对应数据库字段 original_exit_reason
  • 行程安排 - 对应数据库字段 original_itinerary

✅ 确认事项

数据库表结构

数据库表 rs_overseas_itinerary_change 已有所需字段,无需修改:

-- 原记录信息(从出国记录表冗余存储)
`original_destination` varchar(100) DEFAULT NULL COMMENT '原目的地国家',
`original_destination_city` varchar(100) DEFAULT NULL COMMENT '原目的地城市',
`original_exit_reason` varchar(100) DEFAULT NULL COMMENT '原出境事由',
`original_start_date` date DEFAULT NULL COMMENT '原出发日期',
`original_end_date` date DEFAULT NULL COMMENT '原返回日期',
`original_duration` int(11) DEFAULT NULL COMMENT '原出国天数',
`original_itinerary` text COMMENT '原行程安排',

-- 变更信息
`new_destination` varchar(100) DEFAULT NULL COMMENT '新目的地国家',
`new_destination_city` varchar(100) DEFAULT NULL COMMENT '新目的地城市',
`new_exit_reason` varchar(100) DEFAULT NULL COMMENT '新出境事由',
`new_start_date` date DEFAULT NULL COMMENT '新出发日期',
`new_end_date` date DEFAULT NULL COMMENT '新返回日期',
`new_duration` int(11) DEFAULT NULL COMMENT '新出国天数',
`new_itinerary` text COMMENT '新行程安排',

后端代码

后端实体类、VO类、Bo类已有相应字段,无需修改。

前端API类型

前端API接口类型定义已有相应字段,无需修改:

export interface ItineraryChangeVO {
  // ... 其他字段
  originalDestination: string;
  originalExitReason: string;
  originalItinerary: string;
  newDestination?: string;
  newExitReason?: string;
  newItinerary?: string;
}

📝 修改内容

一、前端表单UI修改

文件: web/src/views/rs/overseas/itinerary-change.vue

1. 二、申请行程情况(只读显示)

修改前:

<el-row :gutter="20">
  <el-col :span="12">
    <el-form-item label="申请出行时间">
      <el-input :value="formatDate(form.originalStartDate)" disabled />
    </el-form-item>
  </el-col>
  <el-col :span="12">
    <el-form-item label="地点">
      <el-input v-model="form.originalDestination" disabled />
    </el-form-item>
  </el-col>
</el-row>
<el-row :gutter="20">
  <el-col :span="12">
    <el-form-item label="在外停留时间">
      <el-input :value="`${formatDate(form.originalStartDate)} 至 ${formatDate(form.originalEndDate)} (${form.originalDuration || 0}天)`" disabled />
    </el-form-item>
  </el-col>
</el-row>

修改后:

<el-row :gutter="20">
  <el-col :span="12">
    <el-form-item label="前往国家">
      <el-input v-model="form.originalDestination" disabled />
    </el-form-item>
  </el-col>
  <el-col :span="12">
    <el-form-item label="出境事由">
      <el-input v-model="form.originalExitReason" disabled />
    </el-form-item>
  </el-col>
</el-row>
<el-row :gutter="20">
  <el-col :span="12">
    <el-form-item label="申请出行时间">
      <el-input :value="`${formatDate(form.originalStartDate)} 至 ${formatDate(form.originalEndDate)}`" disabled />
    </el-form-item>
  </el-col>
  <el-col :span="12">
    <el-form-item label="在外停留天数">
      <el-input :value="`${form.originalDuration || 0}天`" disabled />
    </el-form-item>
  </el-col>
</el-row>
<el-row :gutter="20">
  <el-col :span="24">
    <el-form-item label="行程安排">
      <el-input
        v-model="form.originalItinerary"
        type="textarea"
        :rows="3"
        disabled
        placeholder="原行程安排"
      />
    </el-form-item>
  </el-col>
</el-row>

2. 三、实际行程情况(用户填写)

修改前:

<el-row :gutter="20">
  <el-col :span="12">
    <el-form-item label="实际出行时间" prop="newStartDate">
      <el-date-picker v-model="form.newStartDate" ... />
    </el-form-item>
  </el-col>
  <el-col :span="12">
    <el-form-item label="地点" prop="newDestination">
      <el-input v-model="form.newDestination" ... />
    </el-form-item>
  </el-col>
</el-row>
<el-row :gutter="20">
  <el-col :span="12">
    <el-form-item label="在外停留时间">
      <!-- 日期范围选择器 -->
    </el-form-item>
  </el-col>
</el-row>

修改后:

<el-row :gutter="20">
  <el-col :span="12">
    <el-form-item label="前往国家" prop="newDestination">
      <el-input v-model="form.newDestination" placeholder="请输入实际前往国家" />
    </el-form-item>
  </el-col>
  <el-col :span="12">
    <el-form-item label="出境事由" prop="newExitReason">
      <el-input v-model="form.newExitReason" placeholder="请输入实际出境事由" />
    </el-form-item>
  </el-col>
</el-row>
<el-row :gutter="20">
  <el-col :span="12">
    <el-form-item label="实际出行时间" prop="newStartDate">
      <el-date-picker
        v-model="newDateRange"
        type="daterange"
        range-separator="至"
        start-placeholder="开始日期"
        end-placeholder="结束日期"
        value-format="YYYY-MM-DD"
        style="width: 100%"
      />
    </el-form-item>
  </el-col>
  <el-col :span="12">
    <el-form-item label="在外停留天数">
      <el-input :value="`${form.newDuration || 0}天`" disabled />
    </el-form-item>
  </el-col>
</el-row>
<el-row :gutter="20">
  <el-col :span="24">
    <el-form-item label="行程安排" prop="newItinerary">
      <el-input
        v-model="form.newItinerary"
        type="textarea"
        :rows="3"
        placeholder="请输入实际行程安排"
        maxlength="1000"
        show-word-limit
      />
    </el-form-item>
  </el-col>
</el-row>

二、数据自动填充逻辑修改

文件: web/src/views/rs/overseas/itinerary-change.vue

修改前:

// 填充申请行程情况
state.form.originalDestination = selectedApplication.destination;
state.form.originalDestinationCity = selectedApplication.destinationCity;
state.form.originalExitReason = selectedApplication.exitReason || selectedApplication.purposeDetail;
state.form.originalStartDate = selectedApplication.startDate;
state.form.originalEndDate = selectedApplication.endDate;
state.form.originalDuration = selectedApplication.duration;
state.form.originalItinerary = selectedApplication.itinerary;

修改后:

// 填充申请行程情况
// 前往国家:优先使用destinationCountry,其次使用destination
state.form.originalDestination = selectedApplication.destinationCountry || selectedApplication.destination;
state.form.originalDestinationCity = selectedApplication.destinationCity;
// 出境事由:优先使用exitReason,其次使用purpose
state.form.originalExitReason = selectedApplication.exitReason || selectedApplication.purpose;
// 行程安排
state.form.originalItinerary = selectedApplication.itinerary;
state.form.originalStartDate = selectedApplication.startDate;
state.form.originalEndDate = selectedApplication.endDate;
state.form.originalDuration = selectedApplication.duration;

// 默认将原信息复制到新信息
state.form.newDestination = selectedApplication.destinationCountry || selectedApplication.destination;
state.form.newDestinationCity = selectedApplication.destinationCity;
state.form.newExitReason = selectedApplication.exitReason || selectedApplication.purpose;
state.form.newItinerary = selectedApplication.itinerary;
state.form.newStartDate = selectedApplication.startDate;
state.form.newEndDate = selectedApplication.endDate;
state.form.newDuration = selectedApplication.duration;

三、日期范围计算属性(新增)

文件: web/src/views/rs/overseas/itinerary-change.vue

新增计算属性(用于日期范围选择器的双向绑定):

// 新出行时间日期范围(用于日期选择器的双向绑定)
const newDateRange = computed({
  get() {
    if (state.form.newStartDate && state.form.newEndDate) {
      return [state.form.newStartDate, state.form.newEndDate];
    }
    return null;
  },
  set(value: any) {
    if (value && value.length === 2) {
      state.form.newStartDate = value[0];
      state.form.newEndDate = value[1];
    } else {
      state.form.newStartDate = '';
      state.form.newEndDate = '';
    }
  }
});

说明:

  • 日期选择器需要绑定一个数组 [startDate, endDate]
  • 计算属性的 get 方法将两个独立的日期字段组合成数组
  • 计算属性的 set 方法将数组拆分成两个独立的日期字段
  • 现有的 watch 监听 newStartDate 和 newEndDate 自动计算天数

四、变更类型自动识别逻辑增强

文件: web/src/views/rs/overseas/itinerary-change.vue

修改前:

// 自动识别变更类型
const changeTypeList: string[] = [];
if (state.form.newDestination !== state.form.originalDestination) {
  changeTypeList.push('目的地变更');
}
if (state.form.newStartDate !== state.form.originalStartDate || 
    state.form.newEndDate !== state.form.originalEndDate) {
  changeTypeList.push('时间变更');
}
if (state.form.newItinerary && state.form.newItinerary !== state.form.originalItinerary) {
  changeTypeList.push('行程变更');
}

修改后:

// 自动识别变更类型
const changeTypeList: string[] = [];
if (state.form.newDestination !== state.form.originalDestination) {
  changeTypeList.push('目的地变更');
}
if (state.form.newExitReason !== state.form.originalExitReason) {
  changeTypeList.push('出境事由变更');  // 新增
}
if (state.form.newStartDate !== state.form.originalStartDate || 
    state.form.newEndDate !== state.form.originalEndDate) {
  changeTypeList.push('时间变更');
}
if (state.form.newItinerary && state.form.newItinerary !== state.form.originalItinerary) {
  changeTypeList.push('行程变更');
}

四、表单验证规则调整

文件: web/src/views/rs/overseas/itinerary-change.vue

修改前:

rules: {
  applicationId: [{ required: true, message: '请选择原出国申请', trigger: 'change' }],
  newDestination: [{ required: true, message: '请输入实际目的地', trigger: 'blur' }],
  newStartDate: [{ required: true, message: '请选择实际出行日期', trigger: 'change' }],
  changeReason: [{ required: true, message: '请输入变更原因', trigger: 'blur' }]
}

修改后:

rules: {
  applicationId: [{ required: true, message: '请选择原出国申请', trigger: 'change' }],
  newDestination: [{ required: true, message: '请输入实际前往国家', trigger: 'blur' }],
  newExitReason: [{ required: true, message: '请输入实际出境事由', trigger: 'blur' }],  // 新增
  newItinerary: [{ required: true, message: '请输入实际行程安排', trigger: 'blur' }],  // 新增
  changeReason: [{ required: true, message: '请输入变更原因', trigger: 'blur' }]
}

📦 涉及文件清单

前端代码(1个文件)

  1. web/src/views/rs/overseas/itinerary-change.vue - 表单页面

数据库和后端

  • 无需修改 - 字段已存在

🎨 新字段展示效果

二、申请行程情况(只读)

┌─────────────────────────────────────────────────────────────┐
│ 二、申请行程情况                                             │
├─────────────────────────────────────────────────────────────┤
│ 前往国家: [只读]        出境事由: [只读]                     │
│ 申请出行时间: [只读]    在外停留天数: [只读]                 │
│ 行程安排: [只读-多行文本框]                                  │
└─────────────────────────────────────────────────────────────┘

三、实际行程情况(可编辑)

┌─────────────────────────────────────────────────────────────┐
│ 三、实际行程情况                                             │
├─────────────────────────────────────────────────────────────┤
│ 前往国家: [输入框]      出境事由: [输入框]                   │
│ 实际出行时间: [日期范围选择器]  在外停留天数: [自动计算]     │
│ 行程安排: [多行文本框,1000字限制]                           │
└─────────────────────────────────────────────────────────────┘

🔄 数据流程

1. 用户选择原出国申请
   ↓
2. 调用 getOverseasApplication(applicationId) 获取申请详情
   ↓
3. handleApplicationChange 函数自动填充
   - 前往国家:destinationCountry 或 destination
   - 出境事由:exitReason 或 purpose
   - 行程安排:itinerary
   ↓
4. 用户填写实际情况
   - 前往国家(必填)
   - 出境事由(必填)
   - 行程安排(必填,最多1000字)
   ↓
5. 系统自动识别变更类型
   - 目的地变更
   - 出境事由变更 ← 新增
   - 时间变更
   - 行程变更
   ↓
6. 提交保存

📊 字段映射关系

出国申请表 → 行程变更表(原信息)

出国申请表字段 行程变更表字段 显示标签 说明
destinationCountry / destination original_destination 前往国家 优先使用destinationCountry
exitReason / purpose original_exit_reason 出境事由 优先使用exitReason
itinerary original_itinerary 行程安排 多行文本

用户填写 → 行程变更表(新信息)

用户输入 行程变更表字段 显示标签 验证规则
前往国家 new_destination 前往国家 必填
出境事由 new_exit_reason 出境事由 必填
行程安排 new_itinerary 行程安排 必填,最多1000字

🆚 修改对比

修改前后对比表

项目 修改前 修改后
二、申请行程情况
第一行 申请出行时间、地点 前往国家、出境事由
第二行 在外停留时间 申请出行时间、在外停留天数
第三行 - 行程安排(多行)
三、实际行程情况
第一行 实际出行时间、地点 前往国家、出境事由
第二行 在外停留时间 实际出行时间(日期选择器)、在外停留天数(自动计算)
第三行 - 行程安排(多行)
验证规则
必填字段 原申请、新目的地、新出行时间、变更原因 原申请、新国家、新事由、新行程、变更原因
变更类型识别
识别项目 目的地、时间、行程 目的地、出境事由(新增)、时间、行程

✅ 优化亮点

  1. 字段展示完整 - 保留了出行时间和停留天数,同时增加了前往国家、出境事由、行程安排
  2. 布局更合理 - 行程安排使用全宽多行文本框,便于显示完整内容
  3. 验证更完善 - 三个核心字段均为必填,确保信息完整性
  4. 变更识别增强 - 增加了"出境事由变更"的自动识别
  5. 字段映射灵活 - 兼容新旧两种API字段命名(destinationCountry/destination、exitReason/purpose)
  6. 用户体验优化 - 多行文本框带字数限制和计数器

🧪 测试要点

1. 数据自动填充测试

  • 选择原出国申请后,"前往国家"正确填充(二、三部分)
  • 选择原出国申请后,"出境事由"正确填充(二、三部分)
  • 选择原出国申请后,"申请出行时间"正确填充(二部分,只读)
  • 选择原出国申请后,"实际出行时间"正确填充(三部分,可编辑)
  • 选择原出国申请后,"在外停留天数"正确填充(二、三部分)
  • 选择原出国申请后,"行程安排"正确填充(二、三部分)
  • 字段以只读方式显示

2. 用户输入测试

  • "前往国家"输入框正常工作
  • "出境事由"输入框正常工作
  • "实际出行时间"日期范围选择器正常工作
  • 修改日期后,"在外停留天数"自动更新
  • 选择跨度天数计算正确(含起止日)
  • "行程安排"多行文本框正常工作
  • 字数限制(1000字)正常工作
  • 字数计数器正常显示

3. 表单验证测试

  • 未填写"前往国家"时显示错误提示
  • 未填写"出境事由"时显示错误提示
  • 未填写"行程安排"时显示错误提示

4. 变更类型识别测试

  • 修改"前往国家"后,识别为"目的地变更"
  • 修改"出境事由"后,识别为"出境事由变更"
  • 修改"行程安排"后,识别为"行程变更"
  • 同时修改多项时,正确组合变更类型

5. 数据保存测试

  • 提交后,新信息正确保存到数据库
  • 查看详情时,字段正确显示
  • 编辑已有变更时,字段正确回显

📝 完成状态

✅ 前端表单UI修改完成 ✅ 数据自动填充逻辑更新完成 ✅ 变更类型识别逻辑增强完成 ✅ 表单验证规则调整完成 ✅ 无编译错误和警告


修改完成时间:2025-10-13
涉及文件数量:1个
测试状态:待测试