feat(credit): 更新客户信息编辑组件功能
- 替换城市输入框为省市区级联选择组件 - 新增步骤下拉选择框用于流程状态管理 - 集成文件选择组件替代原有文件路径输入框 - 实现文件上传和删除功能 - 添加文件列表管理和同步逻辑 - 优化搜索组件中关键词搜索框位置 - 完善查询参数过滤逻辑支持数组和单一值处理
This commit is contained in:
@@ -62,12 +62,24 @@
|
|||||||
<!-- />-->
|
<!-- />-->
|
||||||
<!-- </a-form-item>-->
|
<!-- </a-form-item>-->
|
||||||
<a-form-item label="所在城市" name="city">
|
<a-form-item label="所在城市" name="city">
|
||||||
<a-input
|
<RegionsSelect
|
||||||
allow-clear
|
v-model:value="regions"
|
||||||
placeholder="请输入所在城市"
|
type="provinceCity"
|
||||||
v-model:value="form.city"
|
valueField="label"
|
||||||
|
placeholder="请选择省/市"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="步骤" name="step">
|
||||||
|
<a-select v-model:value="form.step" placeholder="请选择步骤" allow-clear>
|
||||||
|
<a-select-option
|
||||||
|
v-for="item in stepOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
<!-- <a-form-item label="所在辖区" name="region">-->
|
<!-- <a-form-item label="所在辖区" name="region">-->
|
||||||
<!-- <a-input-->
|
<!-- <a-input-->
|
||||||
<!-- allow-clear-->
|
<!-- allow-clear-->
|
||||||
@@ -76,10 +88,12 @@
|
|||||||
<!-- />-->
|
<!-- />-->
|
||||||
<!-- </a-form-item>-->
|
<!-- </a-form-item>-->
|
||||||
<a-form-item label="文件路径" name="files">
|
<a-form-item label="文件路径" name="files">
|
||||||
<a-input
|
<SelectFile
|
||||||
allow-clear
|
:data="fileList"
|
||||||
placeholder="请输入文件路径"
|
:limit="9"
|
||||||
v-model:value="form.files"
|
placeholder="请选择附件"
|
||||||
|
@done="onFileChoose"
|
||||||
|
@del="onFileDelete"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<!-- <a-form-item label="是否有数据" name="hasData">-->
|
<!-- <a-form-item label="是否有数据" name="hasData">-->
|
||||||
@@ -131,9 +145,11 @@
|
|||||||
import { CreditMpCustomer } from '@/api/credit/creditMpCustomer/model';
|
import { CreditMpCustomer } from '@/api/credit/creditMpCustomer/model';
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
|
||||||
import { FormInstance } from 'ant-design-vue/es/form';
|
import { FormInstance } from 'ant-design-vue/es/form';
|
||||||
import { FileRecord } from '@/api/system/file/model';
|
import { FileRecord } from '@/api/system/file/model';
|
||||||
|
import RegionsSelect from '@/components/RegionsSelect/index.vue';
|
||||||
|
import SelectFile from '@/components/SelectFile/index.vue';
|
||||||
|
import { isImage } from '@/utils/common';
|
||||||
|
|
||||||
// 是否是修改
|
// 是否是修改
|
||||||
const isUpdate = ref(false);
|
const isUpdate = ref(false);
|
||||||
@@ -160,7 +176,17 @@
|
|||||||
const maxable = ref(true);
|
const maxable = ref(true);
|
||||||
// 表格选中数据
|
// 表格选中数据
|
||||||
const formRef = ref<FormInstance | null>(null);
|
const formRef = ref<FormInstance | null>(null);
|
||||||
const images = ref<ItemType[]>([]);
|
const regions = ref<string[]>();
|
||||||
|
const fileList = ref<any[]>([]);
|
||||||
|
|
||||||
|
const stepOptions = [
|
||||||
|
{ value: 0, text: '未受理' },
|
||||||
|
{ value: 1, text: '已受理' },
|
||||||
|
{ value: 2, text: '材料提交' },
|
||||||
|
{ value: 3, text: '合同签订' },
|
||||||
|
{ value: 4, text: '执行回款' },
|
||||||
|
{ value: 5, text: '完结' }
|
||||||
|
];
|
||||||
|
|
||||||
// 用户信息
|
// 用户信息
|
||||||
const form = reactive<CreditMpCustomer>({
|
const form = reactive<CreditMpCustomer>({
|
||||||
@@ -175,6 +201,7 @@
|
|||||||
city: undefined,
|
city: undefined,
|
||||||
region: undefined,
|
region: undefined,
|
||||||
files: undefined,
|
files: undefined,
|
||||||
|
step: 0,
|
||||||
hasData: undefined,
|
hasData: undefined,
|
||||||
recommend: undefined,
|
recommend: undefined,
|
||||||
status: undefined,
|
status: undefined,
|
||||||
@@ -204,22 +231,103 @@
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
const chooseImage = (data: FileRecord) => {
|
const guessNameFromUrl = (url: string) => {
|
||||||
images.value.push({
|
const cleanUrl = (url.split('?')[0] ?? '').trim();
|
||||||
uid: data.id,
|
const last = cleanUrl.split('/').pop() || '';
|
||||||
url: data.path,
|
try {
|
||||||
status: 'done'
|
return decodeURIComponent(last) || url;
|
||||||
});
|
} catch {
|
||||||
form.files = data.path;
|
return last || url;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDeleteItem = (index: number) => {
|
const normalizeFiles = (raw: unknown) => {
|
||||||
images.value.splice(index, 1);
|
if (!raw) return [];
|
||||||
form.files = '';
|
if (Array.isArray(raw)) {
|
||||||
|
return raw
|
||||||
|
.map((item: any) => {
|
||||||
|
if (!item) return null;
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
return {
|
||||||
|
url: item,
|
||||||
|
name: guessNameFromUrl(item),
|
||||||
|
thumbnail: item,
|
||||||
|
isImage: isImage(item)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const url = item.url || item.path || item.href;
|
||||||
|
if (!url || typeof url !== 'string') return null;
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
name: typeof item.name === 'string' ? item.name : guessNameFromUrl(url),
|
||||||
|
thumbnail: typeof item.thumbnail === 'string' ? item.thumbnail : undefined,
|
||||||
|
isImage: typeof item.isImage === 'boolean' ? item.isImage : isImage(url)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
if (typeof raw === 'string') {
|
||||||
|
const text = raw.trim();
|
||||||
|
if (!text) return [];
|
||||||
|
try {
|
||||||
|
let parsed: any = JSON.parse(text);
|
||||||
|
if (typeof parsed === 'string') parsed = JSON.parse(parsed);
|
||||||
|
if (Array.isArray(parsed)) return normalizeFiles(parsed);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return normalizeFiles(text.includes(',') ? text.split(',') : [text]);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncFilesToForm = () => {
|
||||||
|
const payload = (fileList.value ?? [])
|
||||||
|
.map((f: any) => {
|
||||||
|
const url = f?.url;
|
||||||
|
if (!url) return null;
|
||||||
|
return {
|
||||||
|
name: f?.name || guessNameFromUrl(url),
|
||||||
|
url,
|
||||||
|
thumbnail: f?.thumbnail,
|
||||||
|
isImage: typeof f?.isImage === 'boolean' ? f.isImage : isImage(url)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
form.files = payload.length ? JSON.stringify(payload) : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFileChoose = (data: FileRecord) => {
|
||||||
|
const url = data.url || data.downloadUrl || data.path;
|
||||||
|
if (!url) return;
|
||||||
|
const exists = fileList.value.some((d: any) => d?.url === url);
|
||||||
|
if (exists) return;
|
||||||
|
fileList.value.push({
|
||||||
|
url,
|
||||||
|
name: data.name || guessNameFromUrl(url),
|
||||||
|
thumbnail: data.thumbnail,
|
||||||
|
isImage: isImage(url)
|
||||||
|
});
|
||||||
|
syncFilesToForm();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFileDelete = (index: number) => {
|
||||||
|
fileList.value.splice(index, 1);
|
||||||
|
syncFilesToForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
const { resetFields } = useForm(form, rules);
|
const { resetFields } = useForm(form, rules);
|
||||||
|
|
||||||
|
// 级联选择回填到表单字段
|
||||||
|
watch(
|
||||||
|
regions,
|
||||||
|
(val) => {
|
||||||
|
form.province = val?.[0];
|
||||||
|
form.city = val?.[1];
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
/* 保存编辑 */
|
/* 保存编辑 */
|
||||||
const save = () => {
|
const save = () => {
|
||||||
if (!formRef.value) {
|
if (!formRef.value) {
|
||||||
@@ -252,22 +360,22 @@
|
|||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
images.value = [];
|
regions.value = undefined;
|
||||||
|
fileList.value = [];
|
||||||
if (props.data) {
|
if (props.data) {
|
||||||
assignObject(form, props.data);
|
assignObject(form, props.data);
|
||||||
if(props.data.files){
|
regions.value = form.province && form.city ? [form.province, form.city] : undefined;
|
||||||
images.value.push({
|
fileList.value = normalizeFiles(props.data.files);
|
||||||
uid: uuid(),
|
syncFilesToForm();
|
||||||
url: props.data.files,
|
|
||||||
status: 'done'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
isUpdate.value = true;
|
isUpdate.value = true;
|
||||||
} else {
|
} else {
|
||||||
isUpdate.value = false;
|
isUpdate.value = false;
|
||||||
|
form.step = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resetFields();
|
resetFields();
|
||||||
|
regions.value = undefined;
|
||||||
|
fileList.value = [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
|
|||||||
@@ -24,14 +24,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<span>批量删除</span>
|
<span>批量删除</span>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-input-search
|
|
||||||
allow-clear
|
|
||||||
v-model:value="keywords"
|
|
||||||
placeholder="请输入关键词"
|
|
||||||
style="width: 220px"
|
|
||||||
@search="handleSearch"
|
|
||||||
@pressEnter="handleSearch"
|
|
||||||
/>
|
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="step"
|
v-model:value="step"
|
||||||
allow-clear
|
allow-clear
|
||||||
@@ -47,6 +39,14 @@
|
|||||||
{{ item.text }}
|
{{ item.text }}
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
<a-input-search
|
||||||
|
allow-clear
|
||||||
|
v-model:value="keywords"
|
||||||
|
placeholder="请输入关键词"
|
||||||
|
style="width: 220px"
|
||||||
|
@search="handleSearch"
|
||||||
|
@pressEnter="handleSearch"
|
||||||
|
/>
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -143,9 +143,23 @@ const datasource: DatasourceFunction = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const params: CreditMpCustomerParam = {...(where as CreditMpCustomerParam)};
|
const params: CreditMpCustomerParam = {...(where as CreditMpCustomerParam)};
|
||||||
if (filters) {
|
if (filters) {
|
||||||
(params as any).status = filters.status;
|
const filterStatus = (filters as any).status;
|
||||||
|
if (Array.isArray(filterStatus)) {
|
||||||
|
if (filterStatus.length) {
|
||||||
|
(params as any).status = filterStatus[0];
|
||||||
|
}
|
||||||
|
} else if (filterStatus !== undefined && filterStatus !== null) {
|
||||||
|
(params as any).status = filterStatus;
|
||||||
|
}
|
||||||
|
|
||||||
const stepFilter = (filters as any).step;
|
const stepFilter = (filters as any).step;
|
||||||
(params as any).step = Array.isArray(stepFilter) ? stepFilter[0] : stepFilter;
|
if (Array.isArray(stepFilter)) {
|
||||||
|
if (stepFilter.length) {
|
||||||
|
(params as any).step = stepFilter[0];
|
||||||
|
}
|
||||||
|
} else if (stepFilter !== undefined && stepFilter !== null) {
|
||||||
|
(params as any).step = stepFilter;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return pageCreditMpCustomer({
|
return pageCreditMpCustomer({
|
||||||
...params,
|
...params,
|
||||||
|
|||||||
Reference in New Issue
Block a user