893 lines
29 KiB
Vue
893 lines
29 KiB
Vue
<template>
|
||
<view v-if="formConfig" class="page">
|
||
<scroll-view scroll-y class="page-scroll">
|
||
<common-hero :title="pageTitle" :use-image-bg="true"></common-hero>
|
||
<view class="hero-wrap">
|
||
<view class="hero-card">
|
||
<view class="hero-title">{{ pageTitle }}</view>
|
||
<view class="hero-desc">{{ moduleMeta && moduleMeta.desc ? moduleMeta.desc : '按模块维护申报记录,支持新增、编辑和提交。' }}</view>
|
||
<view class="hero-actions">
|
||
<view class="hero-action" @tap="openCreate">立即申报</view>
|
||
<view class="hero-action hero-action--secondary" @tap="loadData">刷新列表</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="loading" class="state-card">
|
||
<view class="state-title">正在加载申报记录...</view>
|
||
</view>
|
||
<view v-else-if="!list.length" class="state-card">
|
||
<view class="state-title">暂无申报记录</view>
|
||
<view class="state-desc">点击上方“立即申报”开始填写当前模块。</view>
|
||
</view>
|
||
<view v-else class="card-list">
|
||
<view v-for="item in list" :key="item.id" class="data-card" @tap="openEdit(item)">
|
||
<view class="card-top">
|
||
<view>
|
||
<view class="card-title">{{ getRecordTitle(item) }}</view>
|
||
<view class="card-subtitle">{{ getRecordSubtitle(item) }}</view>
|
||
</view>
|
||
<view class="card-year">{{ item.year || fixedYear || '-' }}</view>
|
||
</view>
|
||
<view class="meta-line">{{ getRecordMeta(item) }}</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view v-if="showEdit" class="modal-mask" @tap="closeEdit">
|
||
<view class="modal-panel" @tap.stop>
|
||
<view class="modal-head">
|
||
<view class="modal-title">{{ formData.id ? `编辑${pageTitle}` : `新增${pageTitle}` }}</view>
|
||
<view class="modal-close" @tap="closeEdit">关闭</view>
|
||
</view>
|
||
|
||
<scroll-view scroll-y class="modal-body">
|
||
<view class="hero-meta hero-meta--form">
|
||
<view class="meta-item meta-item--form">
|
||
<view class="meta-label meta-label--form">申报年度</view>
|
||
<view class="meta-value meta-value--form">{{ fixedYear || formData.year || '-' }}</view>
|
||
</view>
|
||
<view class="meta-item meta-item--form">
|
||
<view class="meta-label meta-label--form">草稿状态</view>
|
||
<view class="meta-value meta-value--form">{{ draftSavedAt ? '已保存' : '未保存' }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-for="section in formConfig.sections" :key="section.title" class="section-card">
|
||
<view class="section-head">
|
||
<view class="section-title">{{ section.title }}</view>
|
||
</view>
|
||
<view class="field-list">
|
||
<view v-for="field in section.fields" :key="field.key" class="field-item">
|
||
<view class="field-label">
|
||
{{ field.label }}
|
||
<text v-if="field.required" class="field-required">*</text>
|
||
</view>
|
||
|
||
<textarea
|
||
v-if="field.type === 'textarea'"
|
||
class="field-textarea"
|
||
:placeholder="field.placeholder"
|
||
:maxlength="-1"
|
||
:value="formData[field.key]"
|
||
@input="handleInput(field, $event)"
|
||
/>
|
||
|
||
<view
|
||
v-else-if="isGenderField(field)"
|
||
class="field-picker"
|
||
@tap="openGenderPicker(field)"
|
||
>
|
||
<text :class="formData[field.key] ? 'field-value' : 'field-placeholder'">
|
||
{{ formData[field.key] || field.placeholder }}
|
||
</text>
|
||
</view>
|
||
|
||
<view
|
||
v-else-if="isNationField(field)"
|
||
class="field-item-picker-wrap"
|
||
>
|
||
<picker
|
||
mode="selector"
|
||
:range="nationLabels"
|
||
:value="getNationIndex(field)"
|
||
:disabled="!nationOptions.length"
|
||
@change="handleNationPickerChange(field, $event)"
|
||
>
|
||
<view class="field-picker" :class="{ 'field-picker--disabled': !nationOptions.length }">
|
||
<text :class="formData[field.key] ? 'field-value' : 'field-placeholder'">
|
||
{{ formData[field.key] || (nationOptions.length ? '请选择民族' : '民族加载中') }}
|
||
</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view
|
||
v-else-if="isUvPickerField(field)"
|
||
class="field-picker"
|
||
:class="{ 'field-picker--disabled': isPickerFieldDisabled(field) }"
|
||
@tap="openSelectPicker(field)"
|
||
>
|
||
<text :class="formData[field.key] ? 'field-value' : 'field-placeholder'">
|
||
{{ getPickerDisplayText(field) }}
|
||
</text>
|
||
</view>
|
||
|
||
<view
|
||
v-else-if="isDateField(field)"
|
||
class="field-picker"
|
||
@tap="openDatePicker(field)"
|
||
>
|
||
<text :class="formData[field.key] ? 'field-value' : 'field-placeholder'">
|
||
{{ formData[field.key] || getDatePlaceholder(field) }}
|
||
</text>
|
||
</view>
|
||
|
||
<uv-input
|
||
v-else
|
||
class="field-input"
|
||
:modelValue="formData[field.key]"
|
||
:type="getInputType(field)"
|
||
:placeholder="field.placeholder"
|
||
placeholder-style="color: #c0c4cc;"
|
||
:maxlength="-1"
|
||
@input="setFieldValue(field.key, $event)"
|
||
/>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view class="modal-actions">
|
||
<view class="ghost-btn" @tap="saveDraft">保存草稿</view>
|
||
<view class="primary-btn" @tap="submitForm">提交申报</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="empty-page">
|
||
<common-hero :title="pageTitle || '申报表'" :use-image-bg="true"></common-hero>
|
||
<view class="empty-body">
|
||
<view class="empty-title">未找到对应表单</view>
|
||
<view class="empty-desc">请返回上一页重新选择申报模块。</view>
|
||
</view>
|
||
</view>
|
||
|
||
<uv-picker
|
||
ref="genderPicker"
|
||
title="选择性别"
|
||
:columns="genderPickerColumns"
|
||
:defaultIndex="[genderPickerIndex]"
|
||
keyName="text"
|
||
@close="noop"
|
||
@cancel="noop"
|
||
@confirm="confirmGenderPicker"
|
||
></uv-picker>
|
||
|
||
<uv-picker
|
||
ref="selectPicker"
|
||
:title="selectPickerTitle"
|
||
:columns="selectPickerColumns"
|
||
:defaultIndex="[selectPickerIndex]"
|
||
keyName="text"
|
||
@close="noop"
|
||
@cancel="noop"
|
||
@confirm="confirmSelectPicker"
|
||
></uv-picker>
|
||
|
||
<uv-picker
|
||
ref="datePicker"
|
||
:title="datePickerTitle"
|
||
:columns="datePickerColumns"
|
||
:defaultIndex="datePickerIndexs"
|
||
keyName="text"
|
||
@change="handleDatePickerChange"
|
||
@cancel="noop"
|
||
@confirm="confirmDatePicker"
|
||
></uv-picker>
|
||
</template>
|
||
|
||
<script>
|
||
import CommonHero from '../../components/common-hero/common-hero.vue'
|
||
import { createInitialFormData, getFormConfig, getModuleMeta } from '../../utils/gxmu/config'
|
||
import {
|
||
addSjqnForm,
|
||
addSjtbzbsjForm,
|
||
addWshqtwForm,
|
||
addWshqtzbForm,
|
||
addYxgqtdgbForm,
|
||
addYxgqtyForm,
|
||
getSjqnForm,
|
||
getSjtbzbsjForm,
|
||
getWshqtwForm,
|
||
getWshqtzbForm,
|
||
getYxgqtdgbForm,
|
||
getYxgqtyForm,
|
||
listDictData,
|
||
updateSjqnForm,
|
||
updateSjtbzbsjForm,
|
||
updateWshqtwForm,
|
||
updateWshqtzbForm,
|
||
updateYxgqtdgbForm,
|
||
updateYxgqtyForm,
|
||
userPageSjqnForm,
|
||
userPageSjtbzbsjForm,
|
||
userPageWshqtwForm,
|
||
userPageWshqtzbForm,
|
||
userPageYxgqtdgbForm,
|
||
userPageYxgqtyForm
|
||
} from './module-form-service'
|
||
|
||
const STORAGE_PREFIX = 'gxmu_form_draft_'
|
||
const POLITICS_DICT_ID = 172
|
||
const NATION_DICT_ID = 173
|
||
const MODULE_API_MAP = {
|
||
gxmu_sjqn: {
|
||
get: getSjqnForm,
|
||
add: addSjqnForm,
|
||
update: updateSjqnForm,
|
||
page: userPageSjqnForm
|
||
},
|
||
gxmu_sjtbzbsj: {
|
||
get: getSjtbzbsjForm,
|
||
add: addSjtbzbsjForm,
|
||
update: updateSjtbzbsjForm,
|
||
page: userPageSjtbzbsjForm
|
||
},
|
||
gxmu_wshqtw: {
|
||
get: getWshqtwForm,
|
||
add: addWshqtwForm,
|
||
update: updateWshqtwForm,
|
||
page: userPageWshqtwForm
|
||
},
|
||
gxmu_wshqtzb: {
|
||
get: getWshqtzbForm,
|
||
add: addWshqtzbForm,
|
||
update: updateWshqtzbForm,
|
||
page: userPageWshqtzbForm
|
||
},
|
||
gxmu_yxgqtdgb: {
|
||
get: getYxgqtdgbForm,
|
||
add: addYxgqtdgbForm,
|
||
update: updateYxgqtdgbForm,
|
||
page: userPageYxgqtdgbForm
|
||
},
|
||
gxmu_yxgqty: {
|
||
get: getYxgqtyForm,
|
||
add: addYxgqtyForm,
|
||
update: updateYxgqtyForm,
|
||
page: userPageYxgqtyForm
|
||
}
|
||
}
|
||
|
||
export default {
|
||
components: {
|
||
CommonHero
|
||
},
|
||
data() {
|
||
return {
|
||
moduleCode: '',
|
||
moduleMeta: null,
|
||
formConfig: null,
|
||
formData: {},
|
||
draftSavedAt: '',
|
||
fixedYear: '',
|
||
declareTitle: '',
|
||
nationOptions: [],
|
||
politicsOptions: [],
|
||
recordId: '',
|
||
loading: false,
|
||
showEdit: false,
|
||
list: [],
|
||
genderFieldKey: '',
|
||
genderPickerColumns: [[]],
|
||
selectFieldKey: '',
|
||
selectPickerTitle: '',
|
||
selectPickerColumns: [[]],
|
||
dateFieldKey: '',
|
||
datePickerTitle: '',
|
||
datePickerColumns: [[]],
|
||
datePickerIndexs: []
|
||
}
|
||
},
|
||
computed: {
|
||
pageTitle() {
|
||
return this.declareTitle || (this.formConfig ? this.formConfig.title : '申报表')
|
||
},
|
||
nationLabels() {
|
||
return this.nationOptions
|
||
},
|
||
genderPickerIndex() {
|
||
const options = (this.genderPickerColumns[0] || []).map((item) => item.value)
|
||
const index = options.indexOf(this.formData[this.genderFieldKey])
|
||
return index > -1 ? index : 0
|
||
},
|
||
selectPickerIndex() {
|
||
const options = (this.selectPickerColumns[0] || []).map((item) => item.value)
|
||
const index = options.indexOf(this.formData[this.selectFieldKey])
|
||
return index > -1 ? index : 0
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
const code = options.code || ''
|
||
const config = getFormConfig(code)
|
||
const meta = getModuleMeta(code)
|
||
const fixedYear = String(options.year || '').trim()
|
||
const declareTitle = decodeURIComponent(options.declareTitle || '').trim()
|
||
const recordId = String(options.id || '').trim()
|
||
console.log('this.moduleMeta', meta)
|
||
this.moduleCode = code
|
||
this.formConfig = config || null
|
||
this.moduleMeta = meta || null
|
||
this.formData = createInitialFormData(code)
|
||
this.fixedYear = fixedYear
|
||
this.declareTitle = declareTitle
|
||
this.recordId = recordId
|
||
|
||
if (config) {
|
||
uni.setNavigationBarTitle({
|
||
title: declareTitle || (meta ? meta.title : '申报表')
|
||
})
|
||
this.loadDictOptions()
|
||
this.setupPickerState()
|
||
this.loadData()
|
||
this.loadInitialDetail()
|
||
}
|
||
},
|
||
methods: {
|
||
getApiConfig() {
|
||
return MODULE_API_MAP[this.moduleCode] || null
|
||
},
|
||
createFreshFormData() {
|
||
const data = createInitialFormData(this.moduleCode)
|
||
data.year = this.fixedYear || data.year || String(new Date().getFullYear())
|
||
return data
|
||
},
|
||
async loadInitialDetail() {
|
||
if (!this.recordId) {
|
||
return
|
||
}
|
||
try {
|
||
await this.loadRemoteRecord(this.recordId)
|
||
this.showEdit = true
|
||
} catch (error) {
|
||
uni.showToast({
|
||
title: (error && error.message) || '表单详情加载失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
async loadRemoteRecord(id) {
|
||
const api = this.getApiConfig()
|
||
if (!api || !api.get || !id) {
|
||
return
|
||
}
|
||
const detail = await api.get(id)
|
||
if (!detail) {
|
||
return
|
||
}
|
||
this.formData = {
|
||
...this.createFreshFormData(),
|
||
...detail
|
||
}
|
||
if (this.fixedYear) {
|
||
this.formData.year = this.fixedYear
|
||
}
|
||
this.setupPickerState()
|
||
},
|
||
async loadData() {
|
||
const api = this.getApiConfig()
|
||
if (!api || !api.page) {
|
||
this.list = []
|
||
return
|
||
}
|
||
this.loading = true
|
||
try {
|
||
const result = await api.page({
|
||
page: 1,
|
||
limit: 50,
|
||
year: this.fixedYear || undefined
|
||
})
|
||
this.list = Array.isArray(result && result.list) ? result.list : []
|
||
} catch (error) {
|
||
this.list = []
|
||
uni.showToast({
|
||
title: (error && error.message) || '加载失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
openCreate() {
|
||
this.formData = this.createFreshFormData()
|
||
this.draftSavedAt = ''
|
||
this.restoreDraft()
|
||
this.setupPickerState()
|
||
this.showEdit = true
|
||
},
|
||
async openEdit(item) {
|
||
try {
|
||
const id = item && item.id ? item.id : ''
|
||
if (id) {
|
||
await this.loadRemoteRecord(id)
|
||
} else {
|
||
this.formData = {
|
||
...this.createFreshFormData(),
|
||
...(item || {})
|
||
}
|
||
}
|
||
this.showEdit = true
|
||
} catch (error) {
|
||
uni.showToast({
|
||
title: (error && error.message) || '加载详情失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
closeEdit() {
|
||
this.showEdit = false
|
||
},
|
||
getRecordTitle(item) {
|
||
if (!item) {
|
||
return this.pageTitle
|
||
}
|
||
const fields = ['name', 'title', 'orgName', 'branchName', 'applyType']
|
||
for (const key of fields) {
|
||
const value = String(item[key] || '').trim()
|
||
if (value) {
|
||
return value
|
||
}
|
||
}
|
||
return this.pageTitle
|
||
},
|
||
getRecordSubtitle(item) {
|
||
const fields = ['applyType', 'unit', 'collegeClass', 'organization', 'secondOrg', 'branch', 'position']
|
||
const parts = fields
|
||
.map((key) => String((item && item[key]) || '').trim())
|
||
.filter(Boolean)
|
||
.slice(0, 1)
|
||
return parts.length ? parts.join(' · ') : (this.moduleMeta && this.moduleMeta.group) || '申报记录'
|
||
},
|
||
getRecordMeta(item) {
|
||
const pairs = [
|
||
['政治面貌', item && item.politics],
|
||
['联系方式', item && (item.contact || item.phone)],
|
||
['民族', item && item.nation]
|
||
]
|
||
const matched = pairs.find((entry) => String(entry[1] || '').trim())
|
||
if (matched) {
|
||
return `${matched[0]}:${matched[1]}`
|
||
}
|
||
return `记录编号:${(item && item.id) || '-'}`
|
||
},
|
||
noop() {
|
||
return
|
||
},
|
||
setupPickerState() {
|
||
this.genderPickerColumns = [[
|
||
{ text: '男', value: '男' },
|
||
{ text: '女', value: '女' }
|
||
]]
|
||
},
|
||
async loadDictOptions() {
|
||
try {
|
||
const [nationResult, politicsResult] = await Promise.all([
|
||
listDictData({ dictId: NATION_DICT_ID }),
|
||
listDictData({ dictId: POLITICS_DICT_ID })
|
||
])
|
||
const normalize = (result) =>
|
||
(Array.isArray(result) ? result : [])
|
||
.map((item) => String(item && (item.dictDataName || item.name || item.label || item.value) || '').trim())
|
||
.filter(Boolean)
|
||
this.nationOptions = normalize(nationResult)
|
||
this.politicsOptions = normalize(politicsResult)
|
||
} catch (error) {
|
||
this.nationOptions = []
|
||
this.politicsOptions = []
|
||
}
|
||
},
|
||
getStorageKey() {
|
||
return `${STORAGE_PREFIX}${this.moduleCode}_${this.fixedYear || 'default'}`
|
||
},
|
||
getLegacyStorageKey() {
|
||
return `${STORAGE_PREFIX}${this.moduleCode}`
|
||
},
|
||
restoreDraft() {
|
||
const draft =
|
||
uni.getStorageSync(this.getStorageKey()) ||
|
||
uni.getStorageSync(this.getLegacyStorageKey())
|
||
if (!draft || !draft.formData) {
|
||
return
|
||
}
|
||
this.formData = {
|
||
...this.formData,
|
||
...draft.formData
|
||
}
|
||
if (this.fixedYear) {
|
||
this.formData.year = this.fixedYear
|
||
}
|
||
this.draftSavedAt = draft.savedAt || ''
|
||
},
|
||
handleInput(field, event) {
|
||
const value = typeof event === 'string' ? event : (event.detail && event.detail.value) || ''
|
||
this.formData[field.key] = value
|
||
},
|
||
setFieldValue(key, event) {
|
||
const value = typeof event === 'string' ? event : (event.detail && event.detail.value) || ''
|
||
this.formData[key] = value
|
||
},
|
||
findFieldByKey(key) {
|
||
if (!this.formConfig || !Array.isArray(this.formConfig.sections)) {
|
||
return null
|
||
}
|
||
for (const section of this.formConfig.sections) {
|
||
const matched = (section.fields || []).find((field) => field.key === key)
|
||
if (matched) {
|
||
return matched
|
||
}
|
||
}
|
||
return null
|
||
},
|
||
isGenderField(field) {
|
||
return field && field.key === 'gender'
|
||
},
|
||
isPoliticsField(field) {
|
||
return field && field.key === 'politics'
|
||
},
|
||
isNationField(field) {
|
||
return field && field.key === 'nation'
|
||
},
|
||
isUvPickerField(field) {
|
||
return field && (field.type === 'select' || this.isPoliticsField(field) || this.isNationField(field))
|
||
},
|
||
isPickerFieldDisabled(field) {
|
||
return (this.isPoliticsField(field) || this.isNationField(field)) && !this.getSelectOptions(field).length
|
||
},
|
||
getPickerDisplayText(field) {
|
||
if (this.formData[field.key]) {
|
||
return this.formData[field.key]
|
||
}
|
||
if (this.isPoliticsField(field)) {
|
||
return this.getSelectOptions(field).length ? '请选择政治面貌' : '政治面貌加载中'
|
||
}
|
||
if (this.isNationField(field)) {
|
||
return this.getSelectOptions(field).length ? '请选择民族' : '民族加载中'
|
||
}
|
||
return field.placeholder
|
||
},
|
||
getSelectOptions(field) {
|
||
if (this.isGenderField(field)) {
|
||
return field.options || ['男', '女']
|
||
}
|
||
if (this.isPoliticsField(field)) {
|
||
return this.politicsOptions.length ? this.politicsOptions : (field.options || [])
|
||
}
|
||
return field.options || []
|
||
},
|
||
getNationIndex(field) {
|
||
const index = this.nationOptions.findIndex((item) => item === this.formData[field.key])
|
||
return index > -1 ? index : 0
|
||
},
|
||
handleNationPickerChange(field, event) {
|
||
const index = Number((event && event.detail && event.detail.value) || 0)
|
||
this.formData[field.key] = this.nationOptions[index] || ''
|
||
},
|
||
openSelectPicker(field) {
|
||
const options = this.getSelectOptions(field)
|
||
if (!options.length) {
|
||
return
|
||
}
|
||
this.selectFieldKey = field.key
|
||
this.selectPickerTitle = `选择${field.label}`
|
||
this.selectPickerColumns = [
|
||
options.map((item) => ({ text: item, value: item }))
|
||
]
|
||
this.$nextTick(() => {
|
||
this.$refs.selectPicker && this.$refs.selectPicker.open()
|
||
})
|
||
},
|
||
confirmSelectPicker(event) {
|
||
const value = (((event || {}).value || [])[0] || {}).value || ''
|
||
if (this.selectFieldKey) {
|
||
this.formData[this.selectFieldKey] = value
|
||
}
|
||
},
|
||
openGenderPicker(field) {
|
||
this.genderFieldKey = field.key
|
||
this.genderPickerColumns = [
|
||
this.getSelectOptions(field).map((item) => ({ text: item, value: item }))
|
||
]
|
||
this.$nextTick(() => {
|
||
this.$refs.genderPicker && this.$refs.genderPicker.open()
|
||
})
|
||
},
|
||
confirmGenderPicker(event) {
|
||
const value = (((event || {}).value || [])[0] || {}).value || ''
|
||
if (this.genderFieldKey) {
|
||
this.formData[this.genderFieldKey] = value
|
||
}
|
||
},
|
||
isDateField(field) {
|
||
return ['year', 'month', 'date'].includes(field.type)
|
||
},
|
||
getDateRange(field) {
|
||
const start = String(field.start || '2000-01-01').trim()
|
||
const end = String(field.end || '2099-12-31').trim()
|
||
return {
|
||
startYear: Number(start.slice(0, 4)) || 2000,
|
||
endYear: Number(end.slice(0, 4)) || 2099
|
||
}
|
||
},
|
||
getDaysInMonth(year, month) {
|
||
return new Date(year, month, 0).getDate()
|
||
},
|
||
buildDatePickerState(field, value, nextIndexs = []) {
|
||
const { startYear, endYear } = this.getDateRange(field)
|
||
const years = []
|
||
for (let year = startYear; year <= endYear; year += 1) {
|
||
years.push({ text: `${year}`, value: `${year}` })
|
||
}
|
||
const months = Array.from({ length: 12 }, (_, index) => {
|
||
const month = String(index + 1).padStart(2, '0')
|
||
return { text: month, value: month }
|
||
})
|
||
const safeValue = String(value || '').trim()
|
||
let selectedYear = safeValue.slice(0, 4) || `${startYear}`
|
||
let selectedMonth = safeValue.slice(5, 7) || '01'
|
||
let selectedDay = safeValue.slice(8, 10) || '01'
|
||
if (Array.isArray(nextIndexs) && nextIndexs.length) {
|
||
if (typeof nextIndexs[0] === 'number' && years[nextIndexs[0]]) {
|
||
selectedYear = years[nextIndexs[0]].value
|
||
}
|
||
if (typeof nextIndexs[1] === 'number' && months[nextIndexs[1]]) {
|
||
selectedMonth = months[nextIndexs[1]].value
|
||
}
|
||
}
|
||
const dayTotal = this.getDaysInMonth(Number(selectedYear), Number(selectedMonth))
|
||
const days = Array.from({ length: dayTotal }, (_, index) => {
|
||
const day = String(index + 1).padStart(2, '0')
|
||
return { text: day, value: day }
|
||
})
|
||
if (Array.isArray(nextIndexs) && nextIndexs.length > 2 && typeof nextIndexs[2] === 'number' && days[nextIndexs[2]]) {
|
||
selectedDay = days[nextIndexs[2]].value
|
||
}
|
||
const yearIndex = Math.max(0, years.findIndex((item) => item.value === selectedYear))
|
||
const monthIndex = Math.max(0, months.findIndex((item) => item.value === selectedMonth))
|
||
const dayIndex = Math.max(0, days.findIndex((item) => item.value === selectedDay))
|
||
if (field.type === 'year') {
|
||
return {
|
||
columns: [years],
|
||
indexs: [yearIndex]
|
||
}
|
||
}
|
||
if (field.type === 'month') {
|
||
return {
|
||
columns: [years, months],
|
||
indexs: [yearIndex, monthIndex]
|
||
}
|
||
}
|
||
return {
|
||
columns: [years, months, days],
|
||
indexs: [yearIndex, monthIndex, dayIndex]
|
||
}
|
||
},
|
||
openDatePicker(field) {
|
||
this.dateFieldKey = field.key
|
||
this.datePickerTitle = `选择${field.label}`
|
||
const pickerState = this.buildDatePickerState(field, this.formData[field.key])
|
||
this.datePickerColumns = pickerState.columns
|
||
this.datePickerIndexs = pickerState.indexs
|
||
this.$nextTick(() => {
|
||
this.$refs.datePicker && this.$refs.datePicker.open()
|
||
})
|
||
},
|
||
handleDatePickerChange(event) {
|
||
const field = this.findFieldByKey(this.dateFieldKey)
|
||
if (!field || field.type !== 'date') {
|
||
this.datePickerIndexs = (event && event.indexs) || this.datePickerIndexs
|
||
return
|
||
}
|
||
const nextIndexs = (event && event.indexs) || []
|
||
const pickerState = this.buildDatePickerState(field, this.formData[field.key], nextIndexs)
|
||
this.datePickerColumns = pickerState.columns
|
||
this.datePickerIndexs = pickerState.indexs
|
||
},
|
||
formatDatePickerValue(values, field) {
|
||
const year = (values[0] && values[0].value) || ''
|
||
if (field.type === 'year') {
|
||
return year
|
||
}
|
||
const month = (values[1] && values[1].value) || '01'
|
||
if (field.type === 'month') {
|
||
return `${year}-${month}`
|
||
}
|
||
const day = (values[2] && values[2].value) || '01'
|
||
return `${year}-${month}-${day}`
|
||
},
|
||
confirmDatePicker(event) {
|
||
const field = this.findFieldByKey(this.dateFieldKey)
|
||
if (!field) {
|
||
return
|
||
}
|
||
this.formData[field.key] = this.formatDatePickerValue((event && event.value) || [], field)
|
||
this.datePickerIndexs = (event && event.indexs) || this.datePickerIndexs
|
||
},
|
||
getDatePlaceholder(field) {
|
||
if (field.type === 'year') {
|
||
return '请选择年份'
|
||
}
|
||
if (field.type === 'month') {
|
||
return '请选择年月'
|
||
}
|
||
return '请选择日期'
|
||
},
|
||
getInputType(field) {
|
||
if (field.type === 'number' || field.type === 'phone') {
|
||
return 'number'
|
||
}
|
||
return 'text'
|
||
},
|
||
validateForm() {
|
||
const requiredFields = []
|
||
this.formConfig.sections.forEach((section) => {
|
||
section.fields.forEach((field) => {
|
||
if (field.required && !String(this.formData[field.key] || '').trim()) {
|
||
requiredFields.push(field.label)
|
||
}
|
||
})
|
||
})
|
||
|
||
if (!requiredFields.length) {
|
||
return true
|
||
}
|
||
|
||
uni.showToast({
|
||
title: `请完善:${requiredFields[0]}`,
|
||
icon: 'none'
|
||
})
|
||
return false
|
||
},
|
||
saveDraft() {
|
||
const savedAt = this.formatNow()
|
||
uni.setStorageSync(this.getStorageKey(), {
|
||
moduleCode: this.moduleCode,
|
||
formData: this.formData,
|
||
savedAt
|
||
})
|
||
this.draftSavedAt = savedAt
|
||
uni.showToast({
|
||
title: '草稿已保存',
|
||
icon: 'success'
|
||
})
|
||
},
|
||
clearDraftStorage() {
|
||
uni.removeStorageSync(this.getStorageKey())
|
||
uni.removeStorageSync(this.getLegacyStorageKey())
|
||
this.draftSavedAt = ''
|
||
},
|
||
async submitForm() {
|
||
if (!this.validateForm()) {
|
||
return
|
||
}
|
||
const api = this.getApiConfig()
|
||
if (!api) {
|
||
this.clearDraftStorage()
|
||
uni.showModal({
|
||
title: '提交成功',
|
||
content: `${this.moduleMeta ? this.moduleMeta.title : '当前模块'}表单已提交,后续可继续接入后台接口。`,
|
||
showCancel: false
|
||
})
|
||
return
|
||
}
|
||
try {
|
||
const payload = {
|
||
...this.formData,
|
||
year: this.fixedYear || this.formData.year
|
||
}
|
||
const request = payload.id ? api.update : api.add
|
||
await request({
|
||
...payload,
|
||
id: payload.id || undefined
|
||
})
|
||
this.clearDraftStorage()
|
||
uni.showToast({
|
||
title: '提交成功',
|
||
icon: 'none'
|
||
})
|
||
this.closeEdit()
|
||
this.loadData()
|
||
} catch (error) {
|
||
uni.showToast({
|
||
title: (error && error.message) || '提交失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
formatNow() {
|
||
const now = new Date()
|
||
const pad = (value) => String(value).padStart(2, '0')
|
||
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}`
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.page { min-height: 100vh; background: linear-gradient(180deg, #f5fbff 0%, #eef7ff 100%); }
|
||
.page-scroll { height: 100vh; box-sizing: border-box; }
|
||
.hero-wrap{ padding:24rpx 24rpx 0; background: url("@/static/indexBg.jpg") no-repeat cover}
|
||
.hero-card,.state-card,.data-card { border-radius: 28rpx; }
|
||
.hero-card {
|
||
padding: 34rpx 36rpx;
|
||
background: rgba(255, 255, 255, 0.5);
|
||
box-shadow: 0 4rpx 12rpx 0 rgba(174, 174, 174, 0.4);
|
||
backdrop-filter: blur(24rpx);
|
||
-webkit-backdrop-filter: blur(24rpx);
|
||
border: 1rpx solid rgba(255, 255, 255, 0.5);
|
||
}
|
||
.hero-badge { display: inline-flex; padding: 10rpx 18rpx; border-radius: 999rpx; background: rgba(20,150,242,.1); font-size: 22rpx; color: #1496f2; }
|
||
.hero-title {
|
||
position: relative;
|
||
margin-top: 8rpx;
|
||
padding-left: 10rpx;
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
line-height: 1.35;
|
||
color: #18a0f7;
|
||
border-left: 5px solid #0f94ef;
|
||
}
|
||
//.hero-title::before {
|
||
// content: '';
|
||
// position: absolute;
|
||
// left: 0;
|
||
// top: 8rpx;
|
||
// width: 8rpx;
|
||
// height: 52rpx;
|
||
// border-radius: 999rpx;
|
||
// background: linear-gradient(180deg, #0f94ef 0%, #22b4ff 100%);
|
||
//}
|
||
.hero-desc { margin-top: 20rpx; font-size: 24rpx; line-height: 1.8; color: #7f7f7f; }
|
||
.hero-actions { display: flex; gap: 16rpx; margin-top: 24rpx; }
|
||
.hero-action { padding: 14rpx 24rpx; border-radius: 999rpx; background: rgba(20,150,242,.12); font-size: 24rpx; color: #1496f2; }
|
||
.hero-action--secondary { background: rgba(31,35,41,.08); color: #5f6b76; }
|
||
.state-card,.data-card { margin-top: 24rpx; padding: 24rpx; background: rgba(255,255,255,.96); box-shadow: 0 10rpx 28rpx rgba(34,94,142,.08); }
|
||
.state-title,.card-title { font-size: 30rpx; font-weight: 700; color: #1f2329; }
|
||
.state-desc,.card-subtitle,.meta-line { margin-top: 10rpx; font-size: 24rpx; line-height: 1.6; color: #786e69; }
|
||
.card-list { padding: 0 24rpx; display: flex; flex-direction: column; gap: 16rpx; }
|
||
.card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 16rpx; }
|
||
.card-year { padding: 10rpx 16rpx; border-radius: 999rpx; background: #eef5ff; color: #1554ad; font-size: 22rpx; }
|
||
.modal-mask { position: fixed; inset: 0; background: rgba(15,23,42,.34); display: flex; align-items: flex-end; z-index: 99; }
|
||
.modal-panel { width: 100%; max-height: 92vh; border-radius: 32rpx 32rpx 0 0; background: #fff; display: flex; flex-direction: column; }
|
||
.modal-head { display: flex; justify-content: space-between; align-items: center; padding: 24rpx; border-bottom: 1rpx solid #eef1f4; }
|
||
.modal-title { font-size: 30rpx; font-weight: 700; color: #1f2329; }
|
||
.modal-close { font-size: 24rpx; color: #7c8a96; }
|
||
.modal-body { max-height: 70vh; padding: 24rpx; box-sizing: border-box; }
|
||
.hero-meta--form { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-bottom: 20rpx; }
|
||
.meta-item--form { background: #f3f7fd; }
|
||
.meta-label--form { color: #6b7785; }
|
||
.meta-value--form { color: #1f2329; }
|
||
.section-card { margin-top: 24rpx; padding: 26rpx; border-radius: 30rpx; background: rgba(255,255,255,.94); border: 1rpx solid rgba(20,150,242,.08); box-shadow: 0 10rpx 28rpx rgba(34,94,142,.08); }
|
||
.section-head { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 18rpx; }
|
||
.section-title { font-size: 30rpx; font-weight: 700; color: #1f2329; }
|
||
.field-list { display: flex; flex-direction: column; gap: 22rpx; }
|
||
.field-label { margin-bottom: 12rpx; font-size: 25rpx; font-weight: 600; color: #2b3037; }
|
||
.field-required { margin-left: 8rpx; color: #1496f2; }
|
||
.field-textarea,.field-picker { width: 100%; box-sizing: border-box; padding: 22rpx 24rpx; border-radius: 22rpx; font-size: 24rpx; color: #2a2e35; }
|
||
.field-textarea { background: #f8f4f1; border: 1rpx solid #eadfd7; }
|
||
.field-picker { background: #F4FCFF; border: 1rpx solid rgba(220,220,220,1); }
|
||
.field-picker--disabled { background: #f7f9fb; color: #aaafb7; }
|
||
.field-textarea { min-height: 180rpx; }
|
||
:deep(.field-input.uv-input) { width: 100%; min-height: 88rpx; padding: 0 24rpx; border-radius: 12rpx; border: 1px solid #dcdfe6; background: #ffffff; box-sizing: border-box; transition: border-color 0.2s ease; }
|
||
:deep(.field-input.uv-input .uv-input__content__field-wrapper__field) { height: 88rpx; min-height: 88rpx; font-size: 28rpx; line-height: 88rpx; color: #303133; }
|
||
.field-placeholder { color: #aa9f98; }
|
||
.field-value { color: #2a2e35; }
|
||
.modal-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16rpx; padding: 24rpx; border-top: 1rpx solid #eef1f4; }
|
||
.ghost-btn,.primary-btn { height: 84rpx; line-height: 84rpx; border-radius: 22rpx; text-align: center; font-size: 26rpx; font-weight: 700; }
|
||
.ghost-btn { background: #FFA2DA; color: #ffffff; border: none; border-radius: 999px; }
|
||
.primary-btn { background: linear-gradient(147.22deg, rgba(0,180,255,1) 37.65%, rgba(88,206,255,1) 80.42%); color: #fff; border-radius: 999px; }
|
||
.empty-page { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 48rpx; text-align: center; }
|
||
.empty-title { font-size: 30rpx; font-weight: 700; color: #1f2329; }
|
||
.empty-desc { margin-top: 12rpx; font-size: 24rpx; line-height: 1.7; color: #7b8694; }
|
||
</style>
|