Commit a0e22287 by yuzhenWang

Merge branch 'wyz' into 'test'

发布测试

See merge request !141
parents 1d9c426b 8e482ad9
......@@ -146,44 +146,6 @@
@change="val => handleModelChange(val, item)"
/>
<!-- Upload 回显值得时候数据格式至少是[{url: '必须要传', name: 'name不是必须的根据需要传值'}]-->
<!-- <el-upload
v-else-if="item.type === 'upload'"
v-model:file-list="localModel[item.prop]"
:action="item.action"
:headers="item.headers"
:multiple="!!item.multiple"
:limit="item.limit || (item.multiple ? 999 : 1)"
:accept="item.accept"
:list-type="item.listType || 'text'"
:disabled="item.disabled"
:auto-upload="true"
:show-file-list="item.showFileList"
:on-exceed="handleExceed"
:before-upload="file => beforeUpload(file, item)"
:on-success="(res, file, fileList) => handleUploadSuccess(res, file, fileList, item)"
:on-error="(err, file, fileList) => handleUploadError(err, file, fileList, item)"
:on-remove="(file, fileList) => handleUploadRemove(file, fileList, item)"
>
<el-icon class="iconStyle" :size="20" v-if="item.uploadType === 'image'">
<Upload />
</el-icon>
<el-button
v-else
size="small"
type="primary"
:link="item.link"
:disabled="item.disabled"
>
{{ '点击上传文件' }}
</el-button>
<template #tip v-if="item.maxSize || item.accept">
<div class="el-upload__tip">
<span v-if="item.maxSize">大小不超过 {{ formatFileSize(item.maxSize) }}</span>
<span v-if="item.accept">支持格式:{{ item.accept }}</span>
</div>
</template>
</el-upload> -->
<!-- Upload 回显值时数据格式至少是 [{url: '必须', name: '可选'}] -->
<template v-else-if="item.type === 'upload'">
<!-- 🔽 默认模式:完整使用 el-upload(含自带文件列表) -->
<el-upload
......@@ -325,14 +287,12 @@ import useDictStore from '@/store/modules/dict'
import { getDicts } from '@/api/system/dict/data'
import request from '@/utils/request'
import dayjs from 'dayjs'
// ==================== 上传文件 ====================
// ==================== 文件预览弹窗 ====================
const previewDialogVisible = ref(false)
const previewUrl = ref('')
const previewFileName = ref('')
const previewFileType = ref('') // 'image', 'pdf', 'unsupported'
//新增文件上传自定义方法开始
// 预览文件(支持图片和PDF)
// 预览文件(页面内弹窗,不打开新窗口)
function previewFile(file, item) {
......@@ -364,7 +324,6 @@ function removeFile(file, item) {
const newList = fileList.filter(f => f.uid !== file.uid)
handleUploadRemove(file, newList, item) // 调用原有的删除处理函数
}
//新增文件上传自定义方法结束
// 文件大小格式化
function formatFileSize(bytes) {
......@@ -374,6 +333,7 @@ function formatFileSize(bytes) {
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
function beforeUpload(file, item) {
// 检查文件大小
if (item.maxSize && file.size > item.maxSize) {
......@@ -393,10 +353,8 @@ function beforeUpload(file, item) {
return true
}
function handleUploadSuccess(response, file, fileList, item) {
// 假设你的后端返回格式:{ code: 200, data: { url: '...', name: 'xxx.pdf' } }
// 你可以通过 item.responseMap 自定义映射,这里先用通用方式
function handleUploadSuccess(response, file, fileList, item) {
const data = response.data || response
const url = data.url || data.fileUrl || data.path
const name = data.name || data.fileName || file.name
......@@ -416,9 +374,8 @@ function handleUploadSuccess(response, file, fileList, item) {
// 触发 model 更新
handleModelChange([...fileList], item)
ElMessage.success(`文件 ${file.name} 上传成功`)
// console.log('上传成功', item)
}
function handleExceed(files, fileList) {
ElMessage.warning('超出文件数量限制')
}
......@@ -432,6 +389,7 @@ function handleUploadRemove(file, fileList, item) {
// 用户删除文件时,同步更新 model
handleModelChange([...fileList], item)
}
// ==================== 工具函数:深拷贝配置(保留函数) ====================
function deepCloneConfig(obj) {
if (obj === null || typeof obj !== 'object') return obj
......@@ -445,6 +403,7 @@ function deepCloneConfig(obj) {
}
return cloned
}
function parseToDate(str) {
if (!str) return null
if (str === 'today') {
......@@ -459,6 +418,7 @@ function parseToDate(str) {
}
return null
}
// ==================== 生成 disabledDate 函数 ====================
function getDisabledDateFn(item) {
const { minDate, maxDate } = item
......@@ -503,6 +463,7 @@ function getDisabledDateFn(item) {
return false
}
}
// ==================== Props & Emits ====================
const props = defineProps({
modelValue: { type: Object, default: () => ({}) },
......@@ -520,9 +481,7 @@ const emit = defineEmits([
// ==================== Refs ====================
const formRef = ref(null)
// 使用 shallowRef 避免深层响应式(表单通常扁平)
const localModel = ref({ ...props.modelValue })
// 记录哪些字段的字典已加载
const dictLoaded = ref(new Set())
const internalConfig = ref([])
const remoteOptions = ref({}) // { prop: [options] }
......@@ -546,7 +505,31 @@ const formRules = computed(() => {
return rules
})
// 1. 外部 modelValue 变化时,安全同步(仅当内容不同时)
// 同步 extra 字段:根据当前选中的 option 填充 extra 字段(不触发外部 emit)
function syncExtraFieldsForProp(prop, value) {
const item = internalConfig.value.find(i => i.prop === prop)
if (!item || item.type !== 'select' || !item.onChangeExtraFields) return false
const options = getSelectOptions(item)
const selectedOption = options.find(opt => String(opt.value) === String(value))
if (selectedOption && selectedOption.raw) {
let needUpdate = false
const newModel = { ...localModel.value }
for (const [targetProp, sourceKey] of Object.entries(item.onChangeExtraFields)) {
const extraValue = selectedOption.raw[sourceKey]
if (newModel[targetProp] !== extraValue) {
newModel[targetProp] = extraValue
needUpdate = true
}
}
if (needUpdate) {
localModel.value = newModel
return true
}
}
return false
}
// 监听 config 变化(支持动态 config)
watch(
() => props.config,
......@@ -559,7 +542,6 @@ watch(
const initialModel = {}
for (const item of internalConfig.value) {
const key = item.prop
// 优先用父传值,否则用默认值
if (props.modelValue?.[key] !== undefined) {
initialModel[key] = props.modelValue[key]
} else if (
......@@ -572,27 +554,22 @@ watch(
}
}
// ✅ 在这里同步 modelValue(包括 extra 字段)
localModel.value = syncModelFromProps(props.modelValue, internalConfig.value)
// console.log('子组件监测config变化', localModel.value)
},
{ immediate: true }
)
// console.log('🚀 子组件 props.modelValue 初始值:', props.modelValue)
// 监听 modelValue(用于后续外部更新)
watch(
() => props.modelValue,
newVal => {
if (!newVal || !internalConfig.value) return
// ✅ 同样使用 sync 函数
localModel.value = syncModelFromProps(newVal, internalConfig.value)
// console.log('子组件监测 modelValue 变化:', localModel.value)
},
{ deep: true }
)
// 提取同步逻辑
// 从 props 同步模型数据
function syncModelFromProps(newModelValue, newConfig) {
if (!newModelValue || !newConfig) return {}
......@@ -614,22 +591,17 @@ function syncModelFromProps(newModelValue, newConfig) {
if (!extraMap || typeof extraMap !== 'object') continue
const prop = item.prop
const idValue = newModelValue[prop] // e.g. 2
const idValue = newModelValue[prop]
let sourceObj = null
// 情况1: 如果 newModelValue[prop] 本身就是对象 → 直接用(兼容旧逻辑)
if (idValue && typeof idValue === 'object') {
sourceObj = idValue
}
// 情况2: 如果是 primitive(string/number),且有 options → 反查
else if (Array.isArray(item.options) && idValue !== undefined && idValue !== null) {
// 默认用 option.value 匹配,可配置 valueKey
} else if (Array.isArray(item.options) && idValue !== undefined && idValue !== null) {
const valueKey = item.valueKey || 'value'
sourceObj = item.options.find(opt => opt[valueKey] === idValue)
}
// 如果找到了 sourceObj,就提取 extra 字段
if (sourceObj && typeof sourceObj === 'object') {
for (const [targetKey, subPath] of Object.entries(extraMap)) {
const val = getNestedValue(sourceObj, subPath)
......@@ -646,9 +618,7 @@ function syncModelFromProps(newModelValue, newConfig) {
const extraMap = item.onChangeExtraFields
if (!extraMap || typeof extraMap !== 'object') continue
// 如果 newModelValue 中没有 sourceField,说明没有重新计算
if (newModelValue[sourceField] === undefined) {
// 那么保留 localModel 中对应的 extra 字段
for (const [targetKey, subPath] of Object.entries(extraMap)) {
if (localModel.value.hasOwnProperty(targetKey)) {
synced[targetKey] = localModel.value[targetKey]
......@@ -659,79 +629,60 @@ function syncModelFromProps(newModelValue, newConfig) {
// 4. 保留 newModelValue 中已有的 extra 字段和其他额外字段
for (const key in newModelValue) {
// 如果已经同步过了(比如主字段或第2步写入的extra),跳过
if (synced.hasOwnProperty(key)) continue
// 判断是否是某个 extra 目标字段
const isExtraTarget = newConfig.some(
item => item.onChangeExtraFields && item.onChangeExtraFields.hasOwnProperty(key)
)
// 如果是 extra 字段,且 newModelValue 里有值 → 保留它!
if (isExtraTarget) {
synced[key] = newModelValue[key]
}
// 如果不是 extra,也不是主字段 → 也保留(兼容 hidden 字段等)
else if (!newConfig.some(item => item.prop === key)) {
} else if (!newConfig.some(item => item.prop === key)) {
synced[key] = newModelValue[key]
}
}
// console.log('🚀 子组件 进行modelvalue处理:', synced)
return synced
}
function getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => current?.[key], obj)
}
// 当字典加载完成时,触发同步
function markDictLoaded(prop) {
dictLoaded.value.add(prop)
// 尝试同步该字段
if (props.modelValue?.[prop] !== undefined) {
localModel.value[prop] = props.modelValue[prop]
}
}
// 2. 用户操作导致 localModel 变化时,emit(防抖可选)
// 用户操作导致 localModel 变化时,emit
function handleModelChange(value, item) {
console.group('用户操作导致 localModel 变化时,emit(防抖可选)')
const newModel = { ...localModel.value, [item.prop]: value }
if (item?.type === 'select' && item.onChangeExtraFields) {
const options = getSelectOptions(item)
// console.log('可用 options:', options)
const opt = options.find(o => o.value === value)
// console.log('匹配的 opt:', opt)
const opt = options.find(o => String(o.value) === String(value))
if (opt?.raw) {
for (const [targetProp, sourceKey] of Object.entries(item.onChangeExtraFields)) {
const extraValue = opt.raw[sourceKey]
newModel[targetProp] = extraValue
// console.log(`✅ 设置 ${targetProp} =`, extraValue)
newModel[targetProp] = opt.raw[sourceKey]
}
}
}
localModel.value = newModel
// console.log('子组件用户操作后,modelvalue值==', newModel)
nextTick(() => {
if (!isEqualShallow(props.modelValue, newModel)) {
// console.log('如果新旧值不一样,反馈给父组件', newModel)
emit('update:modelValue', newModel)
} else {
console.log('🚫 跳过 emit:认为相等')
}
})
if (item.type === 'select') {
// console.log('如果是select类型,反馈给父组件', item.prop, value, item)
emit('selectChange', item.prop, value, item)
} else if (item.type == 'upload') {
// 传给父组件最新的上传值newModel
emit('uploadSuccess', item.prop, newModel)
} else if (item.type == 'input') {
emit('inputChange', item.prop, value, item)
}
console.groupEnd()
}
// 辅助函数:浅比较两个对象
function isEqualShallow(a, b) {
const keysA = Object.keys(a)
const keysB = Object.keys(b)
......@@ -761,84 +712,34 @@ async function loadDictOptions(dictType) {
dictStore.setDict(dictType, options)
return options
} catch (err) {
// console.error(`加载字典 ${dictType} 失败`, err)
ElMessage.error(`字典 ${dictType} 加载失败`)
return []
}
}
// ==================== 初始化 ====================
onMounted(async () => {
internalConfig.value = deepCloneConfig(props.config)
const initialData = {}
const dictTypePromises = []
const apiPromises = [] // ← 新增:收集 api 加载 promise
for (const item of internalConfig.value) {
const key = item.prop
if (localModel.value[key] == null) {
if (item.multiple) {
initialData[key] = item.defaultValue ?? []
} else if (['checkbox-group', 'daterange', 'monthrange'].includes(item.type)) {
initialData[key] = item.defaultValue ?? []
} else {
initialData[key] = item.defaultValue ?? ''
}
}
// 预加载 dictType
if (item.type === 'select' && item.dictType) {
dictTypePromises.push(
loadDictOptions(item.dictType).then(opts => {
remoteOptions.value[key] = opts
markDictLoaded(key) // ← 立即标记已加载
})
)
} // 预加载 api(远程接口)← 关键新增!
else if (item.type === 'select' && item.api) {
apiPromises.push(
loadRemoteOptionsForInit(item) // ← 专门用于初始化的加载函数
)
} else if (item.type === 'select' && item.options) {
remoteOptions.value[key] = [...item.options]
markDictLoaded(key)
}
// api 类型:延迟加载(focus 时)
}
if (Object.keys(initialData).length > 0) {
localModel.value = { ...localModel.value, ...initialData }
}
// 等待所有字典加载完成
await Promise.allSettled(dictTypePromises)
})
// ==================== 获取 select 选项 ====================
function getSelectOptions(item) {
const key = item.prop
// 字典选项
if (item.dictType || item.api) {
// 字典选项
return (remoteOptions.value[key] || []).map(opt => ({
value: opt.value,
label: opt.label,
raw: opt.raw // ← 必须存 raw
raw: opt.raw
}))
} else if (item.options) {
// 静态选项s
return item.options.map(opt => ({
value: opt.value,
label: opt.label,
raw: opt.raw // 保留原始
raw: opt.raw
}))
}
return []
}
// 初始化加载远程 API 选项(无搜索词)
async function loadRemoteOptionsForInit(item) {
const { prop, api, requestParams } = item
try {
// 构造请求体:只传 requestParams,不传 keyword
const payload = {
...(typeof requestParams === 'function' ? requestParams() : requestParams || {})
}
......@@ -854,38 +755,45 @@ async function loadRemoteOptionsForInit(item) {
? item.transform(res)
: res.data?.records || res.data || []
// 建议:统一转成字符串(或根据 item.valueType 判断)
const newOptions = list.map(i => ({
value: String(i[item.valueKey || 'value']), // ← 强制转字符串
value: String(i[item.valueKey || 'value']),
label: i[item.labelKey || 'label'],
raw: i
}))
remoteOptions.value[prop] = newOptions
markDictLoaded(prop) // ← 关键:标记已加载
markDictLoaded(prop)
// 同步 extra 字段
const currentVal = localModel.value[prop]
if (currentVal !== undefined && currentVal !== null && currentVal !== '') {
syncExtraFieldsForProp(prop, currentVal)
}
} catch (err) {
ElMessage.error(`预加载 ${item.label} 失败`)
remoteOptions.value[prop] = []
}
}
// ==================== 加载远程 API 选项(首次 focus 时加载,无搜索词) ====================
// 加载远程 API 选项(focus 时调用,无搜索词)
async function loadRemoteOptions(item) {
const { prop, api, requestParams, keywordField, debounceWait, ...rest } = item
const { prop, api, requestParams } = item
if (!api) return
// 如果已经有选项且不是强制刷新,可跳过;但为了保证初次加载,remoteOptions[prop] 为空时才加载
if (remoteOptions.value[prop] && remoteOptions.value[prop].length > 0) return
try {
remoteLoading.value[prop] = true
// 构造请求体:合并 requestParams + 分页(默认第一页)
const payload = {
...(typeof requestParams === 'function' ? requestParams() : requestParams || {})
// 注意:此时无 keyword,所以不加 keywordField
}
const res = await request({
url: api,
method: 'post', // ← 改为 POST
data: payload // ← 参数放 body
method: 'post',
data: payload
})
const list =
......@@ -893,15 +801,19 @@ async function loadRemoteOptions(item) {
? item.transform(res)
: res.data?.records || res.data || []
// 建议:统一转成字符串(或根据 item.valueType 判断)
const newOptions = list.map(i => ({
value: String(i[item.valueKey || 'value']), // ← 强制转字符串
value: String(i[item.valueKey || 'value']),
label: i[item.labelKey || 'label'],
raw: i
}))
remoteOptions.value[prop] = newOptions
// ✅ 关键:标记该字段字典已加载
markDictLoaded(prop)
// 同步 extra 字段
const currentVal = localModel.value[prop]
if (currentVal !== undefined && currentVal !== null && currentVal !== '') {
syncExtraFieldsForProp(prop, currentVal)
}
} catch (err) {
ElMessage.error(`加载 ${item.label} 失败`)
remoteOptions.value[prop] = []
......@@ -910,7 +822,7 @@ async function loadRemoteOptions(item) {
}
}
// ==================== 远程搜索(带关键词,防抖) ====================
// 远程搜索(带关键词,防抖)
let searchTimeout = null
function handleFilterChange(keyword, item) {
const { prop, api, requestParams, keywordField = 'keyword', debounceWait = 300 } = item
......@@ -921,16 +833,15 @@ function handleFilterChange(keyword, item) {
try {
remoteLoading.value[prop] = true
// 构造请求体:requestParams + 分页 + 动态关键词字段
const payload = {
...(typeof requestParams === 'function' ? requestParams() : requestParams || {}),
[keywordField]: keyword // ← 动态字段名,如 name / companyName
[keywordField]: keyword
}
const res = await request({
url: api,
method: 'post', // ← POST 请求
data: payload // ← body 传参
method: 'post',
data: payload
})
const list =
......@@ -939,9 +850,9 @@ function handleFilterChange(keyword, item) {
: res.data?.records || res.data || []
remoteOptions.value[prop] = list.map(i => ({
value: i[item.valueKey || 'value'],
value: String(i[item.valueKey || 'value']),
label: i[item.labelKey || 'label'],
raw: i // ← 保存完整对象
raw: i
}))
} catch (err) {
ElMessage.error(`搜索 ${item.label} 失败`)
......@@ -952,46 +863,6 @@ function handleFilterChange(keyword, item) {
}
// ==================== 数字输入处理 ====================
// function handleNumberInput(value, item) {
// const { inputType = 'text', decimalDigits = 2, prop } = item
// if (!prop) return
// let result = String(value ?? '').trim()
// if (inputType === 'integer') {
// // 只保留数字
// result = result.replace(/[^\d]/g, '')
// } else if (inputType === 'decimal') {
// // 1. 只保留数字和小数点
// result = result.replace(/[^\d.]/g, '')
// // 2. 去掉开头的小数点(不允许 ".5" → 改为 "0.5" 更好,但这里先简单处理)
// if (result.startsWith('.')) {
// result = '0.' + result.slice(1)
// }
// // 3. 保证最多一个小数点
// const parts = result.split('.')
// if (parts.length > 2) {
// result = parts[0] + '.' + parts.slice(1).join('')
// }
// // 4. 限制小数位数(但保留结尾的小数点!)
// if (result.includes('.')) {
// const [intPart, decPart] = result.split('.')
// // 如果小数部分超过限制,截断
// if (decPart.length > decimalDigits) {
// result = intPart + '.' + decPart.slice(0, decimalDigits)
// }
// // ✅ 不再删除结尾的 '.'
// }
// }
// // 防止重复赋值(可选优化)
// if (localModel.value[prop] !== result) {
// localModel.value = { ...localModel.value, [prop]: result }
// }
// }
function handleNumberInput(value, item) {
const { inputType = 'text', decimalDigits = 2, prop } = item
if (!prop) return
......@@ -999,83 +870,109 @@ function handleNumberInput(value, item) {
let result = String(value ?? '').trim()
if (inputType === 'integer') {
// 只保留数字和负号
result = result.replace(/[^-\d]/g, '')
// 如果有多个负号或者负号不在开头,则移除多余的负号
if ((result.match(/-/g) || []).length > 1) {
result = result.replace(/-/g, '').replace(/^/, '-') // 仅保留一个负号在最前面
result = result.replace(/-/g, '').replace(/^/, '-')
}
} else if (inputType === 'decimalNumber') {
// 可以输入正数,负数,小数
// 1. 只保留数字、小数点和负号
result = result.replace(/[^-\d.]/g, '')
// 2. 处理负号:确保最多只有一个负号且必须在开头
if ((result.match(/-/g) || []).length > 1) {
result = result.replace(/-/g, '') // 移除所有负号
result = result.replace(/-/g, '')
if (result.startsWith('-')) {
result = '-' + result.slice(1) // 确保负号在最前面
result = '-' + result.slice(1)
} else {
result = '-' + result // 如果原本没有负号但需要保留数值,可以省略此步骤
result = '-' + result
}
}
// 3. 去掉开头的小数点(不允许 ".5" → 改为 "0.5" 更好)
if (result.startsWith('.')) {
result = '0' + result
} else if (result.startsWith('-.')) {
result = '-0' + result.slice(2)
}
// 4. 保证最多一个小数点
const parts = result.split('.')
if (parts.length > 2) {
result = parts[0] + '.' + parts.slice(1).join('')
}
// 5. 限制小数位数(但保留结尾的小数点!)
if (result.includes('.')) {
const [intPart, decPart] = result.split('.')
// 如果小数部分超过限制,截断
if (decPart.length > decimalDigits) {
result = intPart + '.' + decPart.slice(0, decimalDigits)
}
// ✅ 不再删除结尾的 '.'
}
} else if (inputType === 'decimal') {
// 可以输入正整数和小数
// 1. 只保留数字和小数点
result = result.replace(/[^\d.]/g, '')
// 2. 去掉开头的小数点(不允许 ".5" → 改为 "0.5" 更好,但这里先简单处理)
if (result.startsWith('.')) {
result = '0.' + result.slice(1)
}
// 3. 保证最多一个小数点
const parts = result.split('.')
if (parts.length > 2) {
result = parts[0] + '.' + parts.slice(1).join('')
}
// 4. 限制小数位数(但保留结尾的小数点!)
if (result.includes('.')) {
const [intPart, decPart] = result.split('.')
// 如果小数部分超过限制,截断
if (decPart.length > decimalDigits) {
result = intPart + '.' + decPart.slice(0, decimalDigits)
}
// ✅ 不再删除结尾的 '.'
}
}
// 防止重复赋值(可选优化)
if (localModel.value[prop] !== result) {
localModel.value = { ...localModel.value, [prop]: result }
}
}
// ==================== 初始化 ====================
onMounted(async () => {
internalConfig.value = deepCloneConfig(props.config)
const initialData = {}
const dictTypePromises = []
const apiPromises = []
for (const item of internalConfig.value) {
const key = item.prop
if (localModel.value[key] == null) {
if (item.multiple) {
initialData[key] = item.defaultValue ?? []
} else if (['checkbox-group', 'daterange', 'monthrange'].includes(item.type)) {
initialData[key] = item.defaultValue ?? []
} else {
initialData[key] = item.defaultValue ?? ''
}
}
if (item.type === 'select' && item.dictType) {
dictTypePromises.push(
loadDictOptions(item.dictType).then(opts => {
remoteOptions.value[key] = opts
markDictLoaded(key)
})
)
} else if (item.type === 'select' && item.api) {
apiPromises.push(loadRemoteOptionsForInit(item))
} else if (item.type === 'select' && item.options) {
remoteOptions.value[key] = [...item.options]
markDictLoaded(key)
}
}
if (Object.keys(initialData).length > 0) {
localModel.value = { ...localModel.value, ...initialData }
}
// 等待所有字典和远程选项加载完成
await Promise.allSettled([...dictTypePromises, ...apiPromises])
// 所有 select 选项加载完成后,为有 onChangeExtraFields 且有值的字段填充 extra 字段
for (const item of internalConfig.value) {
if (item.type === 'select' && item.onChangeExtraFields) {
const currentVal = localModel.value[item.prop]
if (currentVal !== undefined && currentVal !== null && currentVal !== '') {
syncExtraFieldsForProp(item.prop, currentVal)
}
}
}
})
// ==================== 暴露方法 ====================
defineExpose({
getFormData() {
......@@ -1096,7 +993,7 @@ defineExpose({
if (['checkbox-group', 'daterange', 'monthrange'].includes(item.type) || item.multiple) {
resetData[key] = item.defaultValue ?? []
} else if (item.type === 'upload') {
resetData[key] = item.defaultValue ?? [] // upload 也是数组
resetData[key] = item.defaultValue ?? []
} else {
resetData[key] = item.defaultValue ?? ''
}
......@@ -1104,36 +1001,20 @@ defineExpose({
localModel.value = { ...resetData }
nextTick(() => formRef.value?.clearValidate())
},
// ✅ 新增:强制刷新某个字段的远程选项
async refreshRemoteOptions(targetProp) {
console.log(`[SearchForm] 收到刷新请求: ${targetProp}`)
// 1. 查找配置项
const item = internalConfig.value.find(i => i.prop === targetProp)
if (!item) {
console.warn(`[SearchForm] 未找到 prop 为 ${targetProp} 的配置项`)
return
}
if (item.type !== 'select' || !item.api) {
console.warn(`[SearchForm] 字段 ${targetProp} 不是远程 Select 或没有 API`)
return
}
console.log(`[SearchForm] 开始强制加载 ${targetProp} 的数据,API: ${item.api}`)
// 2. 关键:在调用前,先清空旧数据,防止子组件内部的 "已加载则跳过" 逻辑生效
// 如果你的 loadRemoteOptions 里有 "if (remoteOptions.value[prop]?.length > 0) return"
// 这里必须先清空
remoteOptions.value[targetProp] = []
remoteLoading.value[targetProp] = true // 手动开启 loading
remoteLoading.value[targetProp] = true
try {
// 3. 调用内部加载函数
// 注意:直接调用 loadRemoteOptions,它会读取最新的 requestParams
await loadRemoteOptions(item)
console.log(`[SearchForm] ${targetProp} 加载完成`)
} catch (error) {
console.error(`[SearchForm] ${targetProp} 加载失败`, error)
throw error
......@@ -1145,34 +1026,28 @@ defineExpose({
</script>
<style scoped>
/* 预览弹窗样式 */
.preview-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
.preview-image-wrapper {
text-align: center;
}
.preview-image {
max-width: 100%;
max-height: 70vh;
object-fit: contain;
}
.preview-pdf {
width: 100%;
height: 70vh;
}
.preview-unsupported {
text-align: center;
padding: 40px;
}
.preview-unsupported p {
margin: 16px 0;
color: #909399;
......@@ -1180,14 +1055,12 @@ defineExpose({
.custom-upload-wrapper {
width: 100%;
}
.custom-file-list {
margin-top: 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
background-color: #fff;
}
.file-item {
display: flex;
justify-content: space-between;
......@@ -1195,11 +1068,9 @@ defineExpose({
padding: 8px 12px;
border-bottom: 1px solid #ebeef5;
}
.file-item:last-child {
border-bottom: none;
}
.file-name {
flex: 1;
font-size: 14px;
......@@ -1209,7 +1080,6 @@ defineExpose({
white-space: nowrap;
margin-right: 16px;
}
.file-actions {
display: flex;
gap: 12px;
......@@ -1217,11 +1087,9 @@ defineExpose({
.formBox {
box-sizing: border-box;
}
.search-form-item {
margin-bottom: 20px;
}
.iconStyle {
color: #409eff;
}
......
......@@ -434,9 +434,9 @@ const searchConfig = ref([
}
},
{
type: 'daterange',
type: 'monthrange',
prop: 'payoutDate',
label: '出账(估)',
label: '出账(估)',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间'
},
......@@ -712,7 +712,7 @@ const confirmRateExchange = async () => {
rateExchangeFlag.value = false
loadTableData()
} catch (error) {
ElMessage.success('结算汇率修改失败')
ElMessage.error('结算汇率修改失败')
rateExchangeFlag.value = true
if (error.message && error.message.includes('Validation')) {
ElMessage.error('必填项不能为空')
......@@ -1099,12 +1099,20 @@ const addCheckRecordConfig = [
},
{
type: 'date',
type: 'month',
prop: 'payoutDate',
label: '出账日期',
label: '出账月(估)',
placeholder: '请选择',
maxDate: 'today',
rules: [{ required: true, message: '出账月(估)必填', trigger: 'blur' }]
},
{
type: 'month',
prop: 'actualPayoutDate',
label: '出账月(实)',
placeholder: '请选择',
maxDate: 'today',
rules: [{ required: true, message: '出账日期必填', trigger: 'blur' }]
rules: [{ required: true, message: '出账月(实)必填', trigger: 'blur' }]
},
// {
// type: 'input',
......
......@@ -156,7 +156,7 @@
/>
<el-table-column fixed="right" label="操作" min-width="120">
<template #default="{ row }">
<el-popover placement="right" :width="200" trigger="click">
<el-popover placement="right" :width="200" trigger="click" v-if="row.type == '1'">
<template #reference>
<el-icon>
<MoreFilled />
......@@ -797,16 +797,16 @@ const updatePayRecordFormConfig = [
prop: 'fortunePeriod',
label: '佣金期数',
inputType: 'decimal',
visible: formData => formData.fortuneBizType === 'R',
rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
visible: formData => formData.fortuneBizType === 'R'
// rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
},
{
type: 'input',
prop: 'fortuneTotalPeriod',
label: '总期数',
inputType: 'decimal',
visible: formData => formData.fortuneBizType === 'R',
rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
visible: formData => formData.fortuneBizType === 'R'
// rules: [{ pattern: /^\d+$/, message: '只能输入正整数', trigger: 'blur' }]
},
{
type: 'select',
......@@ -1333,7 +1333,13 @@ const handleConfirmAddPayRecord = async () => {
const handleConfirmUpdatePayRecord = async () => {
if (selectedRow.value.type == '1') {
try {
const formData = updatePayRecordFormRef.value.getFormData()
const formData = await updatePayRecordFormRef.value.validate()
console.log('====================================')
console.log('formData', formData)
console.log('====================================')
if (!formData) {
return
}
const params = {
...formData,
expectedFortuneBizId: selectedRow.value.expectedFortuneBizId
......@@ -1346,7 +1352,10 @@ const handleConfirmUpdatePayRecord = async () => {
loadPayRecordTableData(selectedRow.value.expectedFortuneBizId)
expectedFortuneListData()
} catch (error) {
ElMessage.error(error.message)
if (error.message && error.message.includes('Validation')) {
ElMessage.error('必填项不能为空')
}
ElMessage.error('更新失败')
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment