From cd71a3a457bbd841c68ae50a825a06a34f6cc6d0 Mon Sep 17 00:00:00 2001
From: 86156 <823267011@qq.com>
Date: Mon, 20 Apr 2026 15:56:29 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=8A=9E=E5=85=AC=EF=BC=8C?=
=?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AE=A1=E6=89=B9=EF=BC=8C=E7=BC=BA=E5=B0=91?=
=?UTF-8?q?=E7=94=B5=E5=AD=90=E7=AD=BE=E7=AB=A0=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
api/hrm/flow.js | 8 +
api/hrm/travel.js | 7 +
components/hrm/CityPicker.vue | 187 ++++--
components/hrm/DateTimePicker.vue | 30 +-
components/hrm/GlobalCityPicker.vue | 242 ++++----
components/hrm/RequestForm.vue | 556 ++++++++++++++----
components/hrm/detailPanels/appropriation.vue | 211 ++++---
components/hrm/detailPanels/leave.vue | 22 +-
components/hrm/detailPanels/reimburse.vue | 10 +-
manifest.json | 10 +-
pages/hrm/approve/approve.vue | 173 +++++-
pages/workbench/hrm/apply/apply.vue | 160 ++++-
pages/workbench/hrm/approve/approve.vue | 4 -
pages/workbench/hrm/detail/detail.vue | 223 ++++---
pages/workbench/hrm/leave/leave.vue | 2 +-
pages/workbench/hrm/reimburse/reimburse.vue | 158 ++++-
pages/workbench/hrm/seal/seal.vue | 4 +-
pages/workbench/hrm/travel/travel.vue | 321 ++++++++--
18 files changed, 1791 insertions(+), 537 deletions(-)
diff --git a/api/hrm/flow.js b/api/hrm/flow.js
index 2416c9e..f6740b9 100644
--- a/api/hrm/flow.js
+++ b/api/hrm/flow.js
@@ -42,6 +42,14 @@ export function getTodoTaskByBiz(bizType, bizId, assigneeUserId) {
})
}
+export function getFlowTaskDetailByBiz(bizType, bizId) {
+ return request({
+ url: '/hrm/flow/task/detailByBiz',
+ method: 'get',
+ params: { bizType, bizId }
+ })
+}
+
export function approveFlowTask(taskId, data) {
return request({
url: `/hrm/flow/task/${taskId}/approve`,
diff --git a/api/hrm/travel.js b/api/hrm/travel.js
index 16007b4..b49aa96 100644
--- a/api/hrm/travel.js
+++ b/api/hrm/travel.js
@@ -32,6 +32,13 @@ export function editTravelReq(data) {
})
}
+export function earlyEndTravelReq(bizId) {
+ return request({
+ url: `/hrm/travel/earlyEnd/${bizId}`,
+ method: 'put'
+ })
+}
+
export function delTravelReq(bizIds) {
return request({
url: `/hrm/travel/${bizIds}`,
diff --git a/components/hrm/CityPicker.vue b/components/hrm/CityPicker.vue
index 6f98548..f635b22 100644
--- a/components/hrm/CityPicker.vue
+++ b/components/hrm/CityPicker.vue
@@ -2,56 +2,175 @@
- {{ pathLabel || '请选择目的地' }}
-
-
- {{ item.name }}
-
-
- {{ item.name }}
-
-
- {{ item }}
-
-
-
\ No newline at end of file
+.mask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:flex-end}.sheet{width:100%;background:#fff;border-radius:24rpx 24rpx 0 0;padding:20rpx;box-sizing:border-box}.sheet-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16rpx}.sheet-title{font-size:30rpx;font-weight:800}.sheet-close{font-size:26rpx;color:#1677ff}.tip-box{padding:12rpx 16rpx;border-radius:16rpx;background:#eff6ff;margin-bottom:12rpx}.tip-main{display:block;font-size:26rpx;font-weight:700;color:#1d4ed8}.tip-sub{display:block;margin-top:6rpx;font-size:22rpx;color:#1e40af;line-height:1.5}.add-box{display:flex;gap:10rpx;flex-wrap:wrap;margin-bottom:12rpx}.add-input{flex:1;min-width:240rpx;height:72rpx;padding:0 16rpx;border:1px solid #e5e7eb;border-radius:16rpx;background:#fff}.add-btn{width:100%;height:72rpx;line-height:72rpx;border-radius:16rpx;background:#10b981;color:#fff;font-size:26rpx}.city-path{padding:12rpx 16rpx;margin-bottom:12rpx;border-radius:16rpx;background:#f8fafc;color:#334155;font-size:24rpx;line-height:1.5}.selector-wrap{height:420rpx;border:1px solid #eef2f7;border-radius:16rpx;overflow:hidden}.picker-view{height:100%}.picker-item{height:84rpx;line-height:84rpx;text-align:center;font-size:28rpx;color:#111827}.actions{display:flex;gap:12rpx;margin-top:16rpx}.btn{flex:1;border-radius:16rpx}.btn.ghost{background:#f3f4f6;color:#334155}.btn.primary{background:#1677ff;color:#fff}.btn.primary[disabled]{background:#9cc2ff;color:#fff}
+
diff --git a/components/hrm/DateTimePicker.vue b/components/hrm/DateTimePicker.vue
index 59bff2a..d7e77b6 100644
--- a/components/hrm/DateTimePicker.vue
+++ b/components/hrm/DateTimePicker.vue
@@ -5,7 +5,7 @@
选择时间
关闭
-
+
{{ d }}
@@ -30,31 +30,41 @@ export default {
dateOptions() { const res = []; const base = new Date(); base.setHours(0,0,0,0); for (let i = 0; i < this.days; i++) { const d = new Date(base); d.setDate(d.getDate() + i); res.push(this.formatYMD(d)) } return res },
hourOptions() { return Array.from({ length: 24 }, (_, i) => `${i < 10 ? '0' : ''}${i}时`) }
},
- watch: {
- visible(v) { if (v) setTimeout(() => this.initValue(), 100) }
+ watch: {
+ visible(v) {
+ if (v) {
+ this.$nextTick(() => {
+ setTimeout(() => this.initValue(), 50)
+ })
+ }
+ }
},
methods: {
initValue() {
if (this.value) {
const v = String(this.value).replace('T', ' ')
const parts = v.split(' ')
- const ymd = parts[0].split('-')
- const hm = parts[1]?.split(':') || [0, 0]
- const hour = parseInt(hm[0]) || 0
- const dateIndex = this.dateOptions.indexOf(ymd.join('-'))
+ const ymd = (parts[0] || '').split('-')
+ const hm = (parts[1] || '00:00:00').split(':')
+ const hour = Math.max(0, Math.min(23, parseInt(hm[0], 10) || 0))
+ const dateKey = ymd.length === 3 ? ymd.join('-') : this.dateOptions[0]
+ const dateIndex = this.dateOptions.indexOf(dateKey)
this.pickerValue = [dateIndex >= 0 ? dateIndex : 0, hour]
} else {
this.pickerValue = [0, 0]
}
this.$nextTick(() => {
const pv = this.$refs.picker
- if (pv) pv.setValue(this.pickerValue)
+ if (pv && pv.setValue) pv.setValue(this.pickerValue)
})
},
- onChange(e) { this.pickerValue = e.detail.value },
+ onChange(e) {
+ const val = e.detail.value || [0, 0]
+ this.pickerValue = [Number(val[0]) || 0, Number(val[1]) || 0]
+ },
confirm() {
const date = this.dateOptions[this.pickerValue[0]] || this.dateOptions[0]
- let hour = this.pickerValue[1] || 0
+ let hour = Number(this.pickerValue[1]) || 0
if (hour >= 24) hour = 23
this.$emit('change', `${date}T${hour < 10 ? '0' : ''}${hour}:00:00.000+08:00`)
this.close()
diff --git a/components/hrm/GlobalCityPicker.vue b/components/hrm/GlobalCityPicker.vue
index 6964899..e54659c 100644
--- a/components/hrm/GlobalCityPicker.vue
+++ b/components/hrm/GlobalCityPicker.vue
@@ -2,151 +2,171 @@
- {{ currentPath }}
+
+ 仅精确到城市即可
+ 如果列表里没有想要的城市,可以先在上方补录后再选择。
+
-
-
-
- {{ item.name }}
-
-
-
-
- {{ item.name }}
-
-
-
-
- {{ item.name }}
-
-
-
+
+
+
+
+
+
+ {{ currentCountry && currentCity ? (currentCountry + ' / ' + currentCity) : '请选择城市' }}
+
+
+
+
+ {{ item.name }}
+
+
+ {{ item.cityName }}
+
+
+
\ No newline at end of file
+.picker-mask{position:fixed;inset:0;background:rgba(15,23,42,.45);z-index:1000;display:flex;align-items:flex-end}.picker-sheet{width:100%;background:#fff;border-radius:24rpx 24rpx 0 0;padding:20rpx;box-sizing:border-box}.picker-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16rpx}.picker-title{font-size:30rpx;font-weight:800;color:#111827}.picker-close{font-size:26rpx;color:#1677ff}.tip-box{padding:12rpx 16rpx;border-radius:16rpx;background:#eff6ff;margin-bottom:12rpx}.tip-main{display:block;font-size:26rpx;font-weight:700;color:#1d4ed8}.tip-sub{display:block;margin-top:6rpx;font-size:22rpx;color:#1e40af;line-height:1.5}.add-box{display:flex;gap:10rpx;flex-wrap:wrap;margin-bottom:12rpx}.add-input{flex:1;min-width:240rpx;height:72rpx;padding:0 16rpx;border:1px solid #e5e7eb;border-radius:16rpx;background:#fff}.add-btn{width:100%;height:72rpx;line-height:72rpx;border-radius:16rpx;background:#10b981;color:#fff;font-size:26rpx}.city-path{padding:12rpx 16rpx;margin-bottom:12rpx;border-radius:16rpx;background:#f8fafc;color:#334155;font-size:24rpx;line-height:1.5}.selector-wrap{height:420rpx;border:1px solid #eef2f7;border-radius:16rpx;overflow:hidden}.picker-view{height:100%}.picker-item{height:84rpx;line-height:84rpx;text-align:center;font-size:28rpx;color:#111827}.picker-footer{display:flex;gap:12rpx;margin-top:16rpx}.btn{flex:1;border-radius:16rpx}.btn.ghost{background:#f3f4f6;color:#334155}.btn.primary{background:linear-gradient(135deg,#1677ff,#4f9dff);color:#fff}.btn.primary[disabled]{background:#9cc2ff;color:#fff}
+
diff --git a/components/hrm/RequestForm.vue b/components/hrm/RequestForm.vue
index 675f616..3ebf73f 100644
--- a/components/hrm/RequestForm.vue
+++ b/components/hrm/RequestForm.vue
@@ -1,40 +1,167 @@
-
-
+
+
{{ section.title }}
-
- {{ field.label }}*
- onSelect(field.key, field.options, e)">{{ form[field.key] || field.placeholder }}
- {{ formatDateTime(field.key) || field.placeholder }}
- {{ form[field.key] || field.placeholder }}
- {{ computedFieldText(field.key) || field.placeholder || '—' }}
+
+
+
+ {{ field.label }}
+ *
+
+
+
+
+
+ {{ getFieldDisplayValue(field) }}
+
+
+
+
+
+
+ {{ getFieldDisplayValue(field) }}
+
+
+
+
+
+ {{ getFieldDisplayValue(field) || field.placeholder || '选择城市' }}
+
+
+
+
+
+ {{ getFieldDisplayValue(field) }}
+
+
+
-
+
{{ field.placeholder || '点击上传文件' }}
- 支持文件上传,点击后选择文件
+ {{ uploadingFieldKey === field.key ? '上传中,请稍候...' : (field.uploadTip || '支持文件上传,点击后选择文件') }}
-
-
+
+
+ 仅支持 {{ field.accept }} 格式文件
+
+
+
+
+ 文件正在上传中...
+
+
+
+
{{ file.originalName || file.fileName || file.url }}
删除
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
- 审批方式审批人*{{ assigneeLabel || '点击选择审批人' }}
- 抄送抄送人{{ ccUsers.length ? `已选择 ${ccUsers.length} 人` : '点击选择抄送人' }}
- 提交
-
+
+
+ 审批方式
+
+ 审批人*
+
+ {{ assigneeLabel || '点击选择审批人' }}
+
+
+
+
+
+ 抄送
+
+ 抄送人
+
+ {{ ccLabel }}
+
+
+
+
+
+ 提交
+
+
+
+
-
-
-
+
+
+
@@ -50,117 +177,328 @@ import XFilePicker from '@/components/x-native-uploader/x-file-picker.vue'
export default {
components: { EmployeePicker, DateTimePicker, CityPicker, XFilePicker },
- props: { title: String, subtitle: String, bizType: String, requestApi: Function, initialForm: Object, sections: Array, flowFields: { type: Array, default: () => [] } },
- data() { return { submitting: false, form: JSON.parse(JSON.stringify(this.initialForm || {})), employees: [], employeeSheetVisible: false, employeeMode: 'assignee', assignee: null, ccUsers: [], cachedAssigneeKey: '', dateTimeSheetVisible: false, datetimeFieldKey: '', citySheetVisible: false, cityFieldKey: '', imageFields: {}, fileFields: {}, currentEmpId: null } },
- computed: { assigneeLabel() { return this.assignee ? this.employeeName(this.assignee) : '' } },
- async created() {
- this.cachedAssigneeKey = `hrm_manual_assignee_${this.bizType || 'default'}`;
- await this.loadEmployees();
- await this.loadCurrentEmpId()
- this.restoreAssignee()
+ props: {
+ title: String,
+ subtitle: String,
+ bizType: String,
+ requestApi: Function,
+ initialForm: Object,
+ sections: Array,
+ flowFields: { type: Array, default: () => [] }
},
- watch: {
- 'form.startTime'(val) { this.autoFillHours() },
- 'form.endTime'(val) { this.autoFillHours() }
+ data() {
+ return {
+ submitting: false,
+ form: JSON.parse(JSON.stringify(this.initialForm || {})),
+ employees: [],
+ employeeSheetVisible: false,
+ employeeMode: 'assignee',
+ assignee: null,
+ ccUsers: [],
+ cachedAssigneeKey: '',
+ dateTimeSheetVisible: false,
+ datetimeFieldKey: '',
+ citySheetVisible: false,
+ cityFieldKey: '',
+ fileFields: {},
+ currentFileFieldKey: '',
+ currentFileFieldConfig: null,
+ uploadingFieldKey: '',
+ currentInputFieldKey: '',
+ currentEmpId: null
+ }
+ },
+ computed: {
+ assigneeLabel() {
+ return this.assignee ? this.employeeName(this.assignee) : ''
+ },
+ ccLabel() {
+ return this.ccUsers.length ? '已选择 ' + this.ccUsers.length + ' 人' : '点击选择抄送人'
+ }
+ },
+ async created() {
+ this.cachedAssigneeKey = 'hrm_manual_assignee_' + (this.bizType || 'default')
+ await this.loadEmployees()
+ await this.loadCurrentEmpId()
+ this.restoreAssignee()
+ },
+ watch: {
+ 'form.startTime'() {
+ this.autoFillHours()
+ },
+ 'form.endTime'() {
+ this.autoFillHours()
+ }
},
methods: {
- onSelect(fieldKey, options, e) {
- const idx = Number(e.detail.value)
- this.$set(this.form, fieldKey, options[idx])
+ getFieldValue(fieldKey) {
+ return this.form[fieldKey]
+ },
+ getFieldDisplayValue(field) {
+ if (field.type === 'datetime') return this.formatDateTime(field.key) || field.placeholder || ''
+ if (field.type === 'computed') return this.computedFieldText(field.key) || field.placeholder || '—'
+ var value = this.getFieldValue(field.key)
+ return value !== undefined && value !== null && value !== '' ? value : (field.placeholder || '')
+ },
+ getFileList(fieldKey) {
+ return this.fileFields[fieldKey] || []
+ },
+ normalizeInputType(inputType) {
+ return inputType || 'text'
+ },
+ onInputFocus(fieldKey) {
+ this.currentInputFieldKey = fieldKey
+ },
+ onInput(fieldKey, e) {
+ var value = e && e.detail ? e.detail.value : e
+ this.$set(this.form, fieldKey, value)
+ },
+
+ onSelect(field, e) {
+ var idx = Number(e.detail.value)
+ var options = field.options || []
+ this.$set(this.form, field.key, options[idx])
+ },
+ employeeName(emp) {
+ return emp && (emp.empName || emp.nickName || emp.userName || emp.realName) || ('员工' + ((emp && (emp.userId || emp.id)) || ''))
+ },
+ employeeDept(emp) {
+ return (emp && emp.dept && emp.dept.deptName) || (emp && emp.deptName) || '未分配部门'
+ },
+ async loadEmployees() {
+ try {
+ var res = await listUser({ pageNum: 1, pageSize: 500 })
+ this.employees = res.rows || res.data || []
+ } catch (e) {
+ this.employees = []
+ }
+ },
+ async loadCurrentEmpId() {
+ try {
+ var oaId = uni.getStorageSync('oaId')
+ if (!oaId) return
+ var emp = await getEmployeeByUserId(oaId)
+ if (emp && emp.data && emp.data.empId) {
+ this.currentEmpId = emp.data.empId
+ }
+ } catch (e) {
+ console.log('[loadCurrentEmpId] error', e)
+ }
+ },
+ restoreAssignee() {
+ try {
+ var raw = uni.getStorageSync(this.cachedAssigneeKey)
+ if (!raw) return
+ var cached = typeof raw === 'string' ? JSON.parse(raw) : raw
+ var id = String(cached.userId || cached.empId || '')
+ var hit = this.employees.find(function (e) {
+ return String(e.userId || e.id) === id
+ })
+ if (hit) this.assignee = hit
+ } catch (e) {}
+ },
+ openEmployeePicker(mode) {
+ this.employeeMode = mode
+ this.employeeSheetVisible = true
+ },
+ onEmployeeChange(val) {
+ if (this.employeeMode === 'assignee') {
+ this.assignee = val
+ try {
+ uni.setStorageSync(this.cachedAssigneeKey, JSON.stringify({
+ userId: val && (val.userId || val.id),
+ empId: val && val.empId || '',
+ empName: this.employeeName(val),
+ deptName: this.employeeDept(val)
+ }))
+ } catch (e) {}
+ } else {
+ this.ccUsers = val || []
+ }
},
- employeeName(emp) { return emp?.empName || emp?.nickName || emp?.userName || emp?.realName || `员工${emp?.userId || emp?.id || ''}` },
- employeeDept(emp) { return emp?.dept?.deptName || emp?.deptName || '未分配部门' },
- async loadEmployees() { try { const res = await listUser({ pageNum: 1, pageSize: 500 }); this.employees = res.rows || res.data || [] } catch (e) { this.employees = [] } },
- async loadCurrentEmpId() { try { const oaId = uni.getStorageSync('oaId'); if (!oaId) return; const emp = await getEmployeeByUserId(oaId); if (emp?.data?.empId) this.currentEmpId = emp.data.empId } catch (e) { console.log('[loadCurrentEmpId] error', e) } },
- restoreAssignee() { try { const raw = uni.getStorageSync(this.cachedAssigneeKey); if (!raw) return; const cached = typeof raw === 'string' ? JSON.parse(raw) : raw; const id = String(cached.userId || cached.empId || ''); const hit = this.employees.find(e => String(e.userId || e.id) === id); if (hit) this.assignee = hit } catch (e) {} },
- openEmployeePicker(mode) { this.employeeMode = mode; this.employeeSheetVisible = true },
- onEmployeeChange(val) { if (this.employeeMode === 'assignee') { this.assignee = val; try { uni.setStorageSync(this.cachedAssigneeKey, JSON.stringify({ userId: val.userId || val.id, empId: val.empId || '', empName: this.employeeName(val), deptName: this.employeeDept(val) })) } catch (e) {} } else { this.ccUsers = val || [] } },
openFilePicker(fieldKey) {
this.currentFileFieldKey = fieldKey
- console.log('[RequestForm] openFilePicker', fieldKey, this.$refs.globalFilePicker)
- const picker = this.$refs.globalFilePicker
+ this.currentFileFieldConfig = this.flowFields.find(function (f) { return f.key === fieldKey }) || null
+ var picker = this.$refs.globalFilePicker
if (picker && picker.open) {
picker.open()
} else {
- console.warn('[RequestForm] file picker ref missing or open() unavailable,', fieldKey, picker)
+ console.warn('[RequestForm] file picker ref missing or open() unavailable', fieldKey)
}
},
async onGlobalFileSelect(payload) {
- const fieldKey = this.currentFileFieldKey
- console.log('[RequestForm] file select', fieldKey, payload)
+ var fieldKey = this.currentFileFieldKey
if (!fieldKey) return
- const files = Array.isArray(payload) ? payload : [payload]
+ var files = Array.isArray(payload) ? payload : [payload]
if (!this.fileFields[fieldKey]) this.$set(this.fileFields, fieldKey, [])
- for (const f of files.filter(Boolean)) {
- try {
- const uploaded = await uploadFile({
- path: f.path || f.url || '',
- name: f.name || f.fileName || 'file',
- size: f.size || 0,
- type: f.type || ''
- })
- this.fileFields[fieldKey].push({
- name: uploaded.fileName || f.name || f.fileName || 'file',
- extname: (f.name || f.fileName || '').split('.').pop() || '',
- url: uploaded.url || f.path || f.url || '',
- ossId: uploaded.ossId || uploaded.url || f.path || f.url || '',
- fileName: uploaded.fileName || f.fileName || f.name || '',
- originalName: f.name || f.fileName || uploaded.fileName || ''
- })
- } catch (e) {
- uni.showToast({ title: e.message || '上传失败', icon: 'none' })
+ var cfg = this.currentFileFieldConfig || {}
+ var allowedExt = (cfg.accept || '').toLowerCase().split(',').map(function (x) { return x.trim() }).filter(Boolean)
+ if (cfg.key === 'applyFileIds' && this.bizType === 'seal' && allowedExt.indexOf('.pdf') === -1) {
+ allowedExt = ['.pdf']
+ }
+
+ this.uploadingFieldKey = fieldKey
+ uni.showLoading({ title: '上传中...' })
+ try {
+ for (var i = 0; i < files.length; i++) {
+ var f = files[i]
+ if (!f) continue
+ var name = String(f.name || f.fileName || '').toLowerCase()
+ var ext = name.indexOf('.') >= 0 ? '.' + name.split('.').pop() : ''
+ if (allowedExt.length && allowedExt.indexOf(ext) === -1) {
+ uni.showToast({ title: '仅支持上传 PDF 文件', icon: 'none' })
+ continue
+ }
+ try {
+ var uploaded = await uploadFile({
+ path: f.path || f.url || '',
+ name: f.name || f.fileName || 'file',
+ size: f.size || 0,
+ type: f.type || ''
+ })
+ this.fileFields[fieldKey].push({
+ name: uploaded.fileName || f.name || f.fileName || 'file',
+ extname: ext.replace('.', '') || (f.name || f.fileName || '').split('.').pop() || '',
+ url: uploaded.url || f.path || f.url || '',
+ ossId: uploaded.ossId || uploaded.url || f.path || f.url || '',
+ fileName: uploaded.fileName || f.fileName || f.name || '',
+ originalName: f.name || f.fileName || uploaded.fileName || ''
+ })
+ } catch (e) {
+ uni.showToast({ title: e.message || '上传失败', icon: 'none' })
+ }
+ }
+ this.syncFileIds(fieldKey)
+ } finally {
+ uni.hideLoading()
+ this.uploadingFieldKey = ''
+ this.currentFileFieldKey = ''
+ this.currentFileFieldConfig = null
+ }
+ },
+ removeFile(fieldKey, idx) {
+ var arr = (this.fileFields[fieldKey] || []).slice()
+ arr.splice(idx, 1)
+ this.$set(this.fileFields, fieldKey, arr)
+ this.syncFileIds(fieldKey)
+ },
+ syncFileIds(fieldKey) {
+ var arr = this.fileFields[fieldKey] || []
+ this.$set(this.form, fieldKey, arr.map(function (x) { return x.ossId }).join(','))
+ },
+ openDateTime(fieldKey) {
+ this.datetimeFieldKey = fieldKey
+ this.dateTimeSheetVisible = true
+ },
+ formatDateTime(key) {
+ var v = this.form[key]
+ if (!v) return ''
+ return String(v).replace('T', ' ').substring(0, 16)
+ },
+ onDateTimeChange(val) {
+ if (!this.datetimeFieldKey) return
+ this.$set(this.form, this.datetimeFieldKey, val)
+ this.autoFillHours()
+ },
+ computedFieldText(key) {
+ if (key === 'hours') {
+ var val = this.form[key]
+ return val ? val + ' 小时' : ''
+ }
+ var value = this.form[key]
+ return value !== undefined && value !== null ? value : ''
+ },
+ openCity(fieldKey) {
+ this.cityFieldKey = fieldKey
+ this.citySheetVisible = true
+ },
+ onCityChange(val) {
+ if (!this.cityFieldKey) return
+ this.$set(this.form, this.cityFieldKey, val)
+ },
+ autoFillHours() {
+ var s = this.parseDT(this.form.startTime)
+ var e = this.parseDT(this.form.endTime)
+ if (!s || !e) return
+ var ms = e.getTime() - s.getTime()
+ if (ms <= 0) return
+ var hours = (Math.round((ms / 3600000) * 2) / 2).toFixed(1).replace(/\.0$/, '')
+ this.$set(this.form, 'hours', hours)
+ },
+ parseDT(v) {
+ if (!v) return null
+ var d = new Date(String(v).replace('T', ' ').replace(/-/g, '/'))
+ return Number.isNaN(d.getTime()) ? null : d
+ },
+ validateFlowFields() {
+ for (var i = 0; i < this.flowFields.length; i++) {
+ var f = this.flowFields[i]
+ if (f.required && !this.form[f.key]) {
+ uni.showToast({ title: '请填写' + f.label, icon: 'none' })
+ return false
}
}
- this.$set(this.form, fieldKey, this.fileFields[fieldKey].map(x => x.ossId).join(','))
- this.currentFileFieldKey = ''
+ if (!this.assignee) {
+ uni.showToast({ title: '请选择审批人', icon: 'none' })
+ return false
+ }
+ return true
},
- removeFile(fieldKey, idx) { const arr = (this.fileFields[fieldKey] || []).slice(); arr.splice(idx, 1); this.$set(this.fileFields, fieldKey, arr); this.$set(this.form, fieldKey, arr.map(x => x.ossId).join(',')) },
- openDateTime(fieldKey) { this.datetimeFieldKey = fieldKey; this.dateTimeSheetVisible = true },
- formatDateTime(key) { const v = this.form[key]; if (!v) return ''; return v.replace('T', ' ').substring(0, 16) },
- onDateTimeChange(val) { if (!this.datetimeFieldKey) return; this.$set(this.form, this.datetimeFieldKey, val); this.autoFillHours() },
- computedFieldText(key) { if (key === 'hours') { const val = this.form[key]; return val ? `${val} 小时` : '' } return this.form[key] || '' },
- openCity(fieldKey) { this.cityFieldKey = fieldKey; this.citySheetVisible = true },
- onCityChange(val) { if (!this.cityFieldKey) return; this.$set(this.form, this.cityFieldKey, val) },
- onFileSelect(fieldKey) { if (!this.fileFields[fieldKey]) this.$set(this.fileFields, fieldKey, []) },
- onFileSuccess(fieldKey, e) {
- if (!this.fileFields[fieldKey]) this.$set(this.fileFields, fieldKey, [])
- const files = e?.tempFiles || []
- const current = Array.isArray(this.fileFields[fieldKey]) ? this.fileFields[fieldKey] : []
- this.fileFields[fieldKey] = [...current, ...files.map(f => ({
- name: f.name || f.fileName || 'file',
- extname: (f.name || f.fileName || '').split('.').pop() || '',
- url: f.url || f.tempFilePath || f.path || '',
- ossId: f.ossId || f.fileID || f.url || '',
- fileName: f.fileName || f.name || '',
- originalName: f.originalName || f.name || f.fileName || ''
- }))]
- this.$set(this.form, fieldKey, this.fileFields[fieldKey].map(x => x.ossId).join(','))
+ submit() {
+ this.autoFillHours()
+ if (!this.validateFlowFields()) return
+ this.doSubmit()
},
- onFileDelete(fieldKey, e) {
- const fileList = Array.isArray(this.fileFields[fieldKey]) ? this.fileFields[fieldKey] : []
- const index = e?.index ?? e?.indexs?.[0] ?? -1
- if (index >= 0) fileList.splice(index, 1)
- this.$set(this.fileFields, fieldKey, [...fileList])
- this.$set(this.form, fieldKey, fileList.map(x => x.ossId).join(','))
+ buildSubmitPayload() {
+ return {
+ ...this.form,
+ status: 'pending',
+ bizType: this.bizType,
+ manualAssigneeUserId: this.assignee.userId || this.assignee.id,
+ tplId: this.form.tplId || null,
+ empId: this.currentEmpId
+ }
},
- pickImages() {},
+ async doSubmit() {
+ this.submitting = true
+ try {
+ var payload = this.buildSubmitPayload()
+ console.log('[RequestForm] submit payload', payload)
+ var res = await this.requestApi(payload)
+ var inst = res && (res.data || res)
+ if (!inst || (!inst.instId && !inst.bizId)) {
+ console.warn('[RequestForm] submit success but no flow instance returned', res)
+ }
- autoFillHours() {
- const s = this.parseDT(this.form.startTime), e = this.parseDT(this.form.endTime)
- if (!s || !e) return;
- const ms = e.getTime() - s.getTime();
- if (ms <= 0) return;
- const hours = (Math.round((ms / 3600000) * 2) / 2).toFixed(1).replace(/\.0$/, '');
- this.$set(this.form, 'hours', hours)
- },
- parseDT(v) { if (!v) return null; const d = new Date(String(v).replace('T', ' ').replace(/-/g, '/')); return Number.isNaN(d.getTime()) ? null : d },
- submit() { this.autoFillHours(); for (const f of this.flowFields) if (f.required && !this.form[f.key]) return uni.showToast({ title: `请填写${f.label}`, icon: 'none' }); if (!this.assignee) return uni.showToast({ title: '请选择审批人', icon: 'none' }); this.doSubmit() },
- async doSubmit() { this.submitting = true; try { const payload = { ...this.form, status: 'pending', empId: this.currentEmpId, manualAssigneeUserId: this.assignee.userId || this.assignee.id }; const res = await this.requestApi(payload); const inst = res?.data || res; if (this.ccUsers.length && inst?.instId) await ccFlowTask({ instId: inst.instId, bizId: inst.bizId, bizType: this.bizType, ccUserIds: this.ccUsers.map(u => u.userId || u.id), remark: '手机端抄送', fromUserId: this.$store?.state?.user?.id || '', nodeId: 0, nodeName: '节点#0', readFlag: 0 }); uni.showToast({ title: '提交成功', icon: 'success' }); setTimeout(() => uni.navigateBack({ delta: 1 }), 500) } catch (e) { uni.showToast({ title: e.message || '提交失败', icon: 'none' }) } finally { this.submitting = false } }
+ if (this.ccUsers.length && inst && inst.instId) {
+ await ccFlowTask({
+ instId: inst.instId,
+ bizId: inst.bizId,
+ bizType: this.bizType,
+ ccUserIds: this.ccUsers.map(function (u) { return u.userId || u.id }),
+ remark: '手机端抄送',
+ fromUserId: (this.$store && this.$store.state && this.$store.state.user && this.$store.state.user.id) || '',
+ nodeId: 0,
+ nodeName: '节点#0',
+ readFlag: 0
+ })
+ }
+
+ uni.showToast({ title: '提交成功', icon: 'success' })
+ setTimeout(function () {
+ uni.navigateBack({ delta: 1 })
+ }, 500)
+ } catch (e) {
+ uni.showToast({ title: e.message || '提交失败', icon: 'none' })
+ } finally {
+ this.submitting = false
+ }
+ }
}
}
\ No newline at end of file
+.page{min-height:100vh;background:#f5f7fb}.scroll{height:100vh;padding:24rpx;box-sizing:border-box}.card{background:#fff;border-radius:20rpx;padding:24rpx;margin-bottom:20rpx}.card-title{font-size:30rpx;font-weight:700;color:#111827;margin-bottom:16rpx}.field{margin-bottom:18rpx}.label{display:block;margin-bottom:8rpx;font-size:24rpx;color:#6b7280}.req{color:#ef4444;margin-left:4rpx}.input-like,.input,.textarea{width:100%;box-sizing:border-box;border:1rpx solid #e5e7eb;border-radius:16rpx;background:#f9fafb;padding:20rpx;font-size:28rpx;color:#111827}.text-input-wrap{position:relative;z-index:20;background:transparent;pointer-events:auto}.text-input-wrap .input{position:relative;z-index:21;pointer-events:auto;touch-action:manipulation}.clickable{color:#111827}.textarea{min-height:160rpx}.computed-box{width:100%;box-sizing:border-box;padding:18rpx 20rpx;border-radius:16rpx;background:#eef6ff;color:#6b8fd6;font-size:26rpx;font-weight:500;line-height:1.5}.upload-box{position:relative;z-index:1;border:1rpx dashed #cbd5e1;border-radius:18rpx;background:linear-gradient(180deg,#fbfdff 0%,#f4f8ff 100%);padding:24rpx 22rpx;box-shadow:0 8rpx 24rpx rgba(22,119,255,.06);pointer-events:auto}.upload-hint{font-size:28rpx;color:#111827;font-weight:700}.upload-sub{margin-top:8rpx;font-size:22rpx;color:#94a3b8;line-height:1.5}.upload-rule{margin-top:8rpx;font-size:22rpx;color:#f59e0b;line-height:1.5}.upload-loading{margin-top:12rpx;display:flex;align-items:center;gap:12rpx;padding:16rpx 18rpx;border-radius:16rpx;background:#f0f7ff;color:#1677ff;font-size:24rpx;font-weight:500}.loading-dot{width:16rpx;height:16rpx;border-radius:50%;background:#1677ff;box-shadow:0 0 0 0 rgba(22,119,255,.45);animation:uploadPulse 1.2s infinite}.loading-text{flex:1}.file-list{margin-top:16rpx;display:flex;flex-direction:column;gap:12rpx}.file-item{display:flex;align-items:center;justify-content:space-between;padding:18rpx 18rpx;border-radius:16rpx;background:#f8fafc;border:1rpx solid #e5eefc}.file-name{flex:1;font-size:26rpx;color:#334155;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-right:16rpx}.remove{font-size:24rpx;color:#ef4444;flex-shrink:0}.btn.primary{width:100%;background:#1677ff;color:#fff;border-radius:16rpx}@keyframes uploadPulse{0%{transform:scale(.85);opacity:.7}50%{transform:scale(1);opacity:1}100%{transform:scale(.85);opacity:.7}}
+
diff --git a/components/hrm/detailPanels/appropriation.vue b/components/hrm/detailPanels/appropriation.vue
index a44ed83..9b0ab12 100644
--- a/components/hrm/detailPanels/appropriation.vue
+++ b/components/hrm/detailPanels/appropriation.vue
@@ -1,81 +1,84 @@
-
-
-
+
- 拨款类型
+ 拨款类型:
{{ detail.appropriationType || '无' }}
-
+
- 拨款总金额
+ 拨款总金额:
{{ detail.amount || 0 }} 元
-
+
- 收款人姓名
+ 收款人姓名:
{{ detail.payeeName || '无' }}
-
+
- 开户银行
+ 开户银行:
{{ detail.bankName || '无' }}
-
+
- 银行账号
+ 银行账号:
{{ detail.bankAccount || '无' }}
-
+
- 拨款事由
+ 拨款事由:
{{ detail.reason || '无' }}
-
+
- 备注
+ 备注:
{{ detail.remark || '无' }}
-
+
- 申请人
+ 申请人:
{{ detail.createBy || '无' }}
+
-
- 申请时间
- {{ detail.createTime || '无' }}
+
+ 申请时间:
+ {{ formatTime(detail.createTime) || '无' }}
-
+
-
- 附件:
-
+
+ 附件:
+
+
+
+ 附件正在上传中...
+
-
-
+
+
- 项目名称
+ 项目名称:
{{ detail.projectName || '无' }}
-
+
- 项目编号
+ 项目编号:
{{ detail.projectNum || '无' }}
-
+
- 项目代号
+ 项目代号:
{{ detail.projectCode || '无' }}
@@ -119,55 +122,113 @@
+
+
\ No newline at end of file
+
\ No newline at end of file
diff --git a/pages/workbench/hrm/approve/approve.vue b/pages/workbench/hrm/approve/approve.vue
index 6d45aeb..9d785d0 100644
--- a/pages/workbench/hrm/approve/approve.vue
+++ b/pages/workbench/hrm/approve/approve.vue
@@ -2,10 +2,6 @@
-
- 审批中心
- 集中查看与处理待办审批
-
{{ todoCount }}
diff --git a/pages/workbench/hrm/detail/detail.vue b/pages/workbench/hrm/detail/detail.vue
index 3af940b..cef732c 100644
--- a/pages/workbench/hrm/detail/detail.vue
+++ b/pages/workbench/hrm/detail/detail.vue
@@ -1,13 +1,58 @@
-
+
+
+ 审批信息
+ {{ statusText(detailData.flowStatus) }}
+
+
+
+ 是否通过
+ {{ detailData.approved ? '已通过' : (detailData.flowStatus === 'rejected' ? '已驳回' : '待审批') }}
+
+
+ 当前状态
+ {{ statusText(detailData.flowStatus) }}
+
+
+ 当前节点
+ {{ detailData.currentNodeName || detailData.currentNodeId || '-' }}
+
+
+ 当前审批人
+ {{ assigneeText }}
+
+
+
+
+
+ 审批流程
+
+
+
+
+
+
+ 节点:{{ item.nodeName || item.nodeId || '-' }}
+ 动作:{{ item.actionText || item.action || '-' }}
+
+
+ 任务状态:{{ statusText(item.taskStatus) }}
+
+
+
+
+
+
-
@@ -21,17 +66,14 @@
import HRMSealDetail from '@/components/hrm/detailPanels/seal.vue'
import HRMTravelDetail from '@/components/hrm/detailPanels/travel.vue'
import HRMAppropriationDetail from '@/components/hrm/detailPanels/appropriation.vue'
-
import {
approveFlowTask,
- listTodoFlowTask,
rejectFlowTask,
- getTodoTaskByBiz,
+ getFlowTaskDetailByBiz,
} from '@/api/hrm/flow';
export default {
components: {
- // 注册所有详情组件,供动态组件使用
HRMLeaveDetail,
HRMReimburseDetail,
HRMSealDetail,
@@ -42,73 +84,82 @@
return {
bizId: undefined,
bizType: undefined,
- currentTask: undefined,
- // 映射bizType到对应的组件名(需和你的bizType实际值匹配,可自行调整)
+ detailData: null,
bizTypeComponentMap: {
- leave: 'HRMLeaveDetail', // 请假
- reimburse: 'HRMReimburseDetail', // 报销
- seal: 'HRMSealDetail', // 用章
- travel: 'HRMTravelDetail' ,// 差旅
+ leave: 'HRMLeaveDetail',
+ reimburse: 'HRMReimburseDetail',
+ seal: 'HRMSealDetail',
+ travel: 'HRMTravelDetail',
appropriation: 'HRMAppropriationDetail'
}
}
},
computed: {
- // 计算属性:根据bizType获取当前要渲染的组件名
currentDetailComponent() {
return this.bizTypeComponentMap[this.bizType] || '';
},
- canApprove() {
- console.log(this.currentTask, this.$store.getters.storeOaName, this.$store.getters.storeOaId)
- return this.currentTask && this.currentTask.status === 'pending' &&
- (this.currentTask?.assigneeUserName === this.$store.getters.storeOaName
- || this.currentTask?.assigneeUserId === this.$store.getters.storeOaId)
+ currentTask() {
+ return this.detailData?.currentTask || null;
},
+ canApprove() {
+ return this.currentTask && this.currentTask.status === 'pending' &&
+ (this.currentTask?.assigneeUserName === this.$store.getters.storeOaName
+ || this.currentTask?.assigneeUserId === this.$store.getters.storeOaId)
+ },
+ assigneeText() {
+ return this.currentTask?.assigneeNickName || this.currentTask?.assigneeUserName || this.currentTask?.assigneeUserId || '-';
+ }
},
watch: {
bizId: {
- immediate: true, // 页面加载时立即执行(原代码缺失,导致首次赋值不触发)
+ immediate: true,
handler(newVal) {
if (!newVal || !this.bizType) return;
- // 获取当前审批任务信息
- getTodoTaskByBiz(this.bizType, newVal)
+ getFlowTaskDetailByBiz(this.bizType, newVal)
.then(res => {
- this.currentTask = res.data;
+ this.detailData = res.data || null;
})
.catch(err => {
- uni.showToast({
- title: '获取审批信息失败',
- icon: 'none'
- });
- console.error('获取审批任务失败:', err);
+ console.error('获取审批详情失败:', err);
});
}
}
},
methods: {
- /**
- * 审批通过
- */
+ statusText(status) {
+ const map = {
+ pending: '审批中',
+ running: '流转中',
+ approved: '已通过',
+ rejected: '已驳回',
+ reject: '已驳回',
+ withdraw: '已撤回',
+ withdrawn: '已撤回'
+ };
+ return map[status] || status || '-';
+ },
+ statusClass(status) {
+ return status || 'default';
+ },
+ formatTime(time) {
+ if (!time) return '-';
+ const d = new Date(time);
+ return Number.isNaN(d.getTime()) ? time : `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`;
+ },
handleApprove() {
if (!this.currentTask?.taskId) {
uni.showToast({ title: '暂无审批任务', icon: 'none' });
return;
}
-
- // 二次确认
uni.showModal({
title: '确认通过',
content: '是否确定通过该审批?',
- // 箭头函数保留this指向
success: (res) => {
if (res.confirm) {
approveFlowTask(this.currentTask.taskId)
.then(() => {
uni.showToast({ title: '审批通过成功' });
- // 成功后返回上一页(可根据需求调整)
- setTimeout(() => {
- uni.navigateBack();
- }, 1500);
+ setTimeout(() => uni.navigateBack(), 1500);
})
.catch(err => {
uni.showToast({ title: '审批通过失败', icon: 'none' });
@@ -118,17 +169,11 @@
}
});
},
-
- /**
- * 审批驳回
- */
handleReject() {
if (!this.currentTask?.taskId) {
uni.showToast({ title: '暂无审批任务', icon: 'none' });
return;
}
-
- // 二次确认(可扩展:添加驳回理由输入框)
uni.showModal({
title: '确认驳回',
content: '是否确定驳回该审批?',
@@ -137,10 +182,7 @@
rejectFlowTask(this.currentTask.taskId)
.then(() => {
uni.showToast({ title: '审批驳回成功' });
- // 成功后返回上一页(可根据需求调整)
- setTimeout(() => {
- uni.navigateBack();
- }, 1500);
+ setTimeout(() => uni.navigateBack(), 1500);
})
.catch(err => {
uni.showToast({ title: '审批驳回失败', icon: 'none' });
@@ -151,14 +193,9 @@
});
}
},
-
onLoad(options) {
- console.log('页面入参:', options);
- // 校验入参,避免undefined
this.bizId = options.bizId || '';
this.bizType = options.bizType || '';
-
- // 入参缺失提示
if (!this.bizId || !this.bizType) {
uni.showToast({ title: '参数缺失,无法加载详情', icon: 'none' });
}
@@ -167,46 +204,60 @@
\ No newline at end of file
+.summary-item {
+ width: calc(50% - 8rpx);
+ background: #f8fafc;
+ border-radius: 16rpx;
+ padding: 20rpx;
+}
+.label { display:block; color:#6b7280; font-size:24rpx; margin-bottom: 8rpx; }
+.value { display:block; color:#111827; font-size:28rpx; font-weight:500; }
+.timeline-item { position: relative; display:flex; padding-top: 24rpx; }
+.timeline-dot { width: 18rpx; height:18rpx; border-radius:50%; background:#cbd5e1; margin-right: 16rpx; margin-top: 8rpx; flex-shrink:0; }
+.timeline-dot.approve, .timeline-dot.approved, .timeline-dot.running { background:#22c55e; }
+.timeline-dot.reject, .timeline-dot.rejected { background:#ef4444; }
+.timeline-line { position:absolute; left: 8rpx; top: 36rpx; bottom:-4rpx; width:2rpx; background:#e5e7eb; }
+.timeline-content { flex:1; padding-bottom: 8rpx; }
+.timeline-header, .timeline-meta { display:flex; justify-content:space-between; gap: 16rpx; font-size:24rpx; color:#6b7280; }
+.timeline-name { color:#111827; font-weight:600; }
+.timeline-remark { margin-top: 8rpx; color:#374151; font-size:26rpx; }
+.approval-btn-bar { position: fixed; bottom: 0; left: 0; right: 0; display: flex; padding: 20rpx; background-color: #fff; border-top: 1px solid #eee; z-index: 99; }
+.btn { flex: 1; height: 88rpx; line-height: 88rpx; border-radius: 44rpx; font-size: 32rpx; border: none; margin: 0 10rpx; }
+.reject-btn { background-color: #fff; color: #ff4757; border: 1px solid #ff4757; }
+.approve-btn { background-color: #007aff; color: #fff; }
+
diff --git a/pages/workbench/hrm/leave/leave.vue b/pages/workbench/hrm/leave/leave.vue
index fa45e4d..d0af3af 100644
--- a/pages/workbench/hrm/leave/leave.vue
+++ b/pages/workbench/hrm/leave/leave.vue
@@ -42,7 +42,7 @@ export default {
}
},
methods: {
- submitLeave(payload) { return addLeaveReq(payload) }
+ submitLeave(payload) { return addLeaveReq({ ...payload, status: 'pending', bizType: 'leave' }) }
}
}
diff --git a/pages/workbench/hrm/reimburse/reimburse.vue b/pages/workbench/hrm/reimburse/reimburse.vue
index e4c7fcb..371a89f 100644
--- a/pages/workbench/hrm/reimburse/reimburse.vue
+++ b/pages/workbench/hrm/reimburse/reimburse.vue
@@ -1,44 +1,142 @@
-
+
+
+ 基础信息
+
+
+ 报销类型*
+
+ {{ reimburseType || '请选择报销类型' }}
+
+
+
+
+ 报销金额*
+
+
+
+
+ 附件
+ 点击上传附件
+
+
+ {{ file.originalName || file.fileName || file.name || file.url }}
+ 删除
+
+
+
+
+
+
+ 说明
+
+ 事由*
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/pages/workbench/hrm/seal/seal.vue b/pages/workbench/hrm/seal/seal.vue
index 9571623..4e75631 100644
--- a/pages/workbench/hrm/seal/seal.vue
+++ b/pages/workbench/hrm/seal/seal.vue
@@ -22,7 +22,7 @@ export default {
sections: [
{ key: 'basic', title: '基础信息', fields: [
{ key: 'sealType', label: '用印类型', type: 'select', required: true, placeholder: '请选择用印类型', options: ['合同用印', '公章', '财务章', '法人章', '其他'] },
- { key: 'applyFileIds', label: '附件', type: 'file', required: true, placeholder: '上传盖章文件' }
+ { key: 'applyFileIds', label: '附件', type: 'file', required: true, placeholder: '上传盖章文件', accept: '.pdf' }
]},
{ key: 'desc', title: '说明', fields: [
{ key: 'reason', label: '用途说明', type: 'textarea', required: true, placeholder: '请说明盖章用途与背景' },
@@ -36,7 +36,7 @@ export default {
]
}
},
- methods: { submitSeal(payload) { return addSealReq(payload) } }
+ methods: { submitSeal(payload) { return addSealReq({ ...payload, status: 'pending', bizType: 'seal' }) } }
}
diff --git a/pages/workbench/hrm/travel/travel.vue b/pages/workbench/hrm/travel/travel.vue
index 0e84c10..ff2bcc8 100644
--- a/pages/workbench/hrm/travel/travel.vue
+++ b/pages/workbench/hrm/travel/travel.vue
@@ -1,48 +1,301 @@
-
+
+
+ 基础信息
+
+
+ 出差类型*
+
+ {{ travelType || '请选择出差类型' }}
+
+
+
+
+ 开始时间*
+ {{ formatDateTime(startTime) || '请选择开始时间' }}
+
+
+
+ 结束时间*
+ {{ formatDateTime(endTime) || '请选择结束时间' }}
+
+
+
+ 目的地*
+ {{ destination || '请选择城市' }}
+
+
+
+ 预估费用
+
+
+
+
+
+ 审批信息
+
+
+ 审批人*
+ {{ employeeLoading ? '审批人加载中...' : (assigneeLabel || '请选择审批人') }}
+
+
+
+ 抄送人
+ {{ employeeLoading ? '抄送人加载中...' : ccLabel }}
+
+
+
+
+ 说明
+
+ 事由*
+
+
+
+ 附件
+ 点击上传附件文件
+
+
+ {{ file.originalName || file.fileName || file.name || file.url }}
+ 删除
+
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+