liyu-pc-base 1.0.921 → 1.0.925
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{FieldRender-BOg7nm5z.js → FieldRender-DnOLSXTl.js} +1 -1
- package/dist/{FieldRender2-_MtcDkI5.js → FieldRender2-CvxGe238.js} +2 -2
- package/dist/{FormRender3-BtY9d91e.js → FormRender3-CAxOQgue.js} +1 -1
- package/dist/{RemoteSelectPro-CcWTS4f6.js → RemoteSelectPro-CcL_vU1K.js} +1 -1
- package/dist/{SearchRender-ByarFel1.js → SearchRender-Dr_qnvZM.js} +1 -1
- package/dist/components/index.js +7 -7
- package/dist/{dbViewConfig-RGSyUx3F.js → dbViewConfig-BF0QZ_mT.js} +1 -1
- package/dist/{detailTablePro-CqcVbn8O.js → detailTablePro-DxgfGYN3.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/liyu-pc-base.css +1 -1
- package/dist/{tableModal-DSKuV-hn.js → tableModal-DZE5wntG.js} +1 -1
- package/dist/{tablePageHost-B_BOlBNA.js → tablePageHost-hE_1hQv4.js} +82 -44
- package/dist/{tablePro-IAcxxAGI.js → tablePro-t2yDX9oW.js} +16 -11
- package/package.json +1 -1
- package/dist/AttributeNomTable-DHED22ju.js.map +0 -1
- package/dist/AttributeTable-muxdKpWW.js.map +0 -1
- package/dist/FieldRender-BOg7nm5z.js.map +0 -1
- package/dist/FieldRender2-_MtcDkI5.js.map +0 -1
- package/dist/FormRender3-BtY9d91e.js.map +0 -1
- package/dist/ImportExcelModal-Dds857L2.js.map +0 -1
- package/dist/JsonObj-CmylW4Or.js.map +0 -1
- package/dist/JsonStr-JwOPdAYW.js.map +0 -1
- package/dist/RemoteCascader-B2ytkZp7.js.map +0 -1
- package/dist/RemoteFilterSelect-BthY6Fyt.js.map +0 -1
- package/dist/RemoteModal-Dqy32RFN.js.map +0 -1
- package/dist/RemoteSelect-27xLYvyg.js.map +0 -1
- package/dist/RemoteSelectPro-CcWTS4f6.js.map +0 -1
- package/dist/RemoteTable-D_12PvNS.js.map +0 -1
- package/dist/RemoteTable2-DF7gWHYf.js.map +0 -1
- package/dist/RemoteText-DwRLI0xq.js.map +0 -1
- package/dist/RemoteTreeSelectPro-CXMOariR.js.map +0 -1
- package/dist/SearchRender-ByarFel1.js.map +0 -1
- package/dist/TinymceEditor-waI-dlC5.js.map +0 -1
- package/dist/aiGenModel-C9E1ahu8.js.map +0 -1
- package/dist/api/api-model.js.map +0 -1
- package/dist/api/typing.js.map +0 -1
- package/dist/batchCreateUser-B4zAXSR6.js.map +0 -1
- package/dist/boldenText-Bv8Zo7Vh.js.map +0 -1
- package/dist/components/FormRender/utils.js.map +0 -1
- package/dist/components/gridPage/utils.js.map +0 -1
- package/dist/components/index.js.map +0 -1
- package/dist/components/remotePages/utils.js.map +0 -1
- package/dist/dbDataSourceConfig-Hj90tvTR.js.map +0 -1
- package/dist/dbViewConfig-RGSyUx3F.js.map +0 -1
- package/dist/default-DmCqrYQi.js.map +0 -1
- package/dist/detailTablePro-CqcVbn8O.js.map +0 -1
- package/dist/exceljs.min-BPhBd8yF.js.map +0 -1
- package/dist/function-BhYSb8ls.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/init-plugin-CvgtEpwY.js.map +0 -1
- package/dist/lodash-CMVwnWxD.js.map +0 -1
- package/dist/md5-WIoeWMjT.js.map +0 -1
- package/dist/modal-tools-CrXdI5sg.js.map +0 -1
- package/dist/props-util-D7nkeN4W.js.map +0 -1
- package/dist/remotePages-CvjMLjTh.js.map +0 -1
- package/dist/role-manage-C7O8b2SF.js.map +0 -1
- package/dist/syncDataSourceConfig-OZ2mJCJs.js.map +0 -1
- package/dist/syncExportRawData-Dpi-VJuu.js.map +0 -1
- package/dist/syncModelTypeConfig-B2vV_kKb.js.map +0 -1
- package/dist/tableModal-DSKuV-hn.js.map +0 -1
- package/dist/tablePageHost-B_BOlBNA.js.map +0 -1
- package/dist/tablePro-IAcxxAGI.js.map +0 -1
- package/dist/utils/excel.js.map +0 -1
- package/dist/utils/hooks/useFetchData.js.map +0 -1
- package/dist/utils/hooks/useTableColumn.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"role-manage-C7O8b2SF.js","names":[],"sources":["../src/api/permission/role-manage.ts"],"sourcesContent":["import { useRequest } from 'liyu-pc-base/init-plugin';\n\nexport async function queryRole(params?: { [key: string]: any }): Promise<any> {\n return useRequest().post('/permission/query_role_list', params);\n}\n\nexport async function removeRole(params: Record<string, any>) {\n return useRequest().post('/permission/delete_role', params);\n}\n\nexport async function recoverRole(params: Record<string, any>) {\n return useRequest().post('/permission/recover_role', params);\n}\n\nexport async function addRole(params: Record<string, any>) {\n return useRequest().post('/permission/create_role', params);\n}\n\nexport async function query_role_by_admin_list(params?: { [key: string]: any }): Promise<any> {\n return useRequest().post('/permission/query_role_by_admin_list', params);\n}\nexport async function updateRole(params: Record<string, any>) {\n return useRequest().post('/permission/update_role', params);\n}\n\ninterface RuleItem {\n key: string | number;\n callNo: number;\n avatar: string;\n desc: string;\n disabled: false;\n href: string;\n name: string;\n owner: string;\n progress: number;\n status: number;\n updatedAt: string;\n createdAt: string;\n}\n\n// export function useRoleData() {\n// const state = reactive({\n// loading: true,\n// current: 0,\n// pageSize: 10,\n// total: 0,\n// dataSource: [] as RuleItem[],\n// });\n//\n// const fetchRole = (params?: Record<string, any>) => {\n// // 使 table 打开加载状态指示\n// state.loading = true;\n// // 发起 AJAX 请求到后端\n// queryRole(\n// Object.assign(\n// {\n// current: state.current,\n// pageSize: state.pageSize,\n// },\n// params,\n// ),\n// )\n// .then((res: RoleResponse) => {\n// // 更新数据\n// state.dataSource = res.data;\n// state.total = res.total;\n// })\n// .finally(() => {\n// // 使 table 关闭加载状态指示\n// state.loading = false;\n// });\n// };\n//\n// return {\n// state,\n// fetch: fetchRole,\n// };\n// }\n"],"mappings":";;AAEA,eAAsB,EAAU,GAA+C;CAC7E,OAAO,EAAW,CAAC,CAAC,KAAK,+BAA+B,CAAM;AAChE;AAEA,eAAsB,EAAW,GAA6B;CAC5D,OAAO,EAAW,CAAC,CAAC,KAAK,2BAA2B,CAAM;AAC5D;AAEA,eAAsB,EAAY,GAA6B;CAC7D,OAAO,EAAW,CAAC,CAAC,KAAK,4BAA4B,CAAM;AAC7D;AAEA,eAAsB,EAAQ,GAA6B;CACzD,OAAO,EAAW,CAAC,CAAC,KAAK,2BAA2B,CAAM;AAC5D;AAKA,eAAsB,EAAW,GAA6B;CAC5D,OAAO,EAAW,CAAC,CAAC,KAAK,2BAA2B,CAAM;AAC5D"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"syncDataSourceConfig-OZ2mJCJs.js","names":[],"sources":["../src/components/lysync/syncDataSourceConfig.vue","../src/components/lysync/syncDataSourceConfig.vue"],"sourcesContent":["<template>\n <a-modal\n :title=\"title\"\n width=\"880px\"\n :open=\"open\"\n :bodyStyle=\"{ maxHeight: '78vh', overflowY: 'auto', padding: '0 24px' }\"\n :confirm-loading=\"loading\"\n cancel-text=\"取消\"\n ok-text=\"确定\"\n centered\n destroy-on-close\n @cancel=\"handleCancel\"\n @ok=\"handleOk\"\n >\n <a-form\n ref=\"formRef\"\n :model=\"model\"\n layout=\"vertical\"\n size=\"small\"\n >\n <a-divider orientation=\"left\">基本信息</a-divider>\n <a-row :gutter=\"16\">\n <a-col :span=\"12\">\n <a-form-item\n label=\"数据源名称\"\n name=\"name\"\n :rules=\"rules.name\"\n >\n <a-input\n v-model:value=\"model.name\"\n placeholder=\"请填写数据源名称\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"12\">\n <a-form-item\n label=\"数据源类型\"\n name=\"source_type\"\n :rules=\"rules.source_type\"\n >\n <a-select\n v-model:value=\"model.source_type\"\n placeholder=\"请选择\"\n @change=\"onSourceTypeChange\"\n >\n <a-select-option\n v-for=\"opt in sourceTypeOptions\"\n :key=\"opt.value\"\n :value=\"opt.value\"\n >\n {{ opt.label }}\n </a-select-option>\n </a-select>\n </a-form-item>\n </a-col>\n <a-col\n v-if=\"model.source_type === 'db'\"\n :span=\"12\"\n >\n <a-form-item\n label=\"数据库类型\"\n name=\"db_type\"\n :rules=\"rules.db_type\"\n >\n <a-select\n v-model:value=\"model.db_type\"\n placeholder=\"请选择\"\n >\n <a-select-option\n v-for=\"opt in dbTypeOptions\"\n :key=\"opt.value\"\n :value=\"opt.value\"\n >\n {{ opt.label }}\n </a-select-option>\n </a-select>\n </a-form-item>\n </a-col>\n </a-row>\n\n <!-- 数据库类型:连接信息 -->\n <template v-if=\"model.source_type === 'db'\">\n <a-divider orientation=\"left\">数据库连接信息</a-divider>\n <a-row :gutter=\"16\">\n <a-col :span=\"12\">\n <a-form-item\n label=\"数据库主机\"\n name=\"db_host\"\n :rules=\"rules.db_host\"\n >\n <a-input\n v-model:value=\"model.db_host\"\n placeholder=\"如 127.0.0.1\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"12\">\n <a-form-item\n label=\"数据库端口\"\n name=\"db_port\"\n :rules=\"rules.db_port\"\n >\n <a-input-number\n v-model:value=\"model.db_port\"\n style=\"width: 100%\"\n :min=\"1\"\n :max=\"65535\"\n placeholder=\"如 3306\"\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"12\">\n <a-form-item\n label=\"数据库用户\"\n name=\"db_user\"\n :rules=\"rules.db_user\"\n >\n <a-input\n v-model:value=\"model.db_user\"\n placeholder=\"如 root\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"12\">\n <a-form-item\n label=\"数据库密码\"\n name=\"db_password\"\n >\n <a-input-password\n v-model:value=\"model.db_password\"\n placeholder=\"修改时留空则不修改密码\"\n allow-clear\n autocomplete=\"new-password\"\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"12\">\n <a-form-item\n :label=\"db_name_field_lable\"\n name=\"db_name\"\n :rules=\"rules.db_name\"\n >\n <a-input\n v-model:value=\"model.db_name\"\n placeholder=\"如 my_db\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n <a-col\n :span=\"12\"\n v-if=\"model.db_type == 'oracle'\"\n >\n <a-form-item label=\"默认Schema\">\n <a-input\n v-model:value=\"oracle_schema\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n </a-row>\n <a-form-item\n label=\"数据库选项\"\n name=\"db_options\"\n >\n <JsonStr\n v-model:value=\"model.db_options\"\n placeholder='可选,JSON 对象,如 {\"charset\":\"utf8mb4\"}'\n />\n </a-form-item>\n </template>\n\n <!-- 自定义类型:采集函数 -->\n <template v-else-if=\"model.source_type === 'custom'\">\n <a-divider orientation=\"left\">自定义采集</a-divider>\n <a-row :gutter=\"16\">\n <a-col :span=\"12\">\n <a-form-item\n label=\"自定义采集函数名称\"\n name=\"custom_func\"\n :rules=\"rules.custom_func\"\n >\n <a-select\n v-model:value=\"model.custom_func\"\n placeholder=\"请选择采集函数\"\n :options=\"customFuncOptions\"\n option-filter-prop=\"label\"\n show-search\n allow-clear\n :loading=\"funcOptionsLoading\"\n />\n </a-form-item>\n </a-col>\n </a-row>\n <a-form-item\n label=\"自定义采集函数参数(JSON)\"\n name=\"custom_func_kwargs\"\n >\n <JsonStr v-model:value=\"model.custom_func_kwargs\" />\n </a-form-item>\n </template>\n </a-form>\n </a-modal>\n</template>\n\n<script setup lang=\"ts\">\nimport { cloneDeep } from 'lodash';\nimport { message } from 'ant-design-vue';\nimport { computed, onMounted, reactive, ref } from 'vue';\nimport { event_obj } from 'liyu-pc-base/init-plugin';\nimport { getApiData } from 'liyu-pc-base/api/api-model';\nimport { useUserStore } from 'liyu-pc-base/init-plugin';\nimport JsonStr from 'liyu-pc-base/components/FormRender/JsonStr/index.vue';\ninterface Props {\n title: string;\n record: any;\n}\nconst props = defineProps<Props>();\nconst emit = defineEmits(['cancel', 'ok']);\n\nconst formRef = ref<any>(null);\nconst loading = ref(false);\nconst open = ref(true);\nconst form_type = ref('create');\nconst userStore = useUserStore();\nconst current_org = userStore.currentOrg;\nconst db_name_field_lable = computed(() => {\n return model.value.db_type === 'oracle' ? 'SID/服务名' : '数据库名称';\n});\nconst oracle_schema = computed({\n get: () => {\n if (model.value.db_options?.schema) {\n return model.value.db_options?.schema;\n }\n return '';\n },\n set: val => {\n if (typeof model.value.db_options === 'object') {\n if (val) {\n model.value.db_options.schema = val;\n } else {\n delete model.value.db_options.schema;\n }\n } else {\n if (val) {\n model.value.db_options = { schema: val };\n }\n }\n },\n});\n\nconst sourceTypeOptions = [\n { value: 'db', label: '数据库' },\n { value: 'custom', label: '自定义' },\n];\nconst dbTypeOptions = [\n { value: 'mysql', label: 'MySQL' },\n { value: 'tsql', label: 'SQL Server' },\n { value: 'oracle', label: 'Oracle' },\n];\n\n/* 自定义采集函数下拉选项(来自后端 get_collect_funcs) */\nconst customFuncOptions = ref<any[]>([]);\nconst funcOptionsLoading = ref(false);\nconst loadCustomFuncOptions = async () => {\n funcOptionsLoading.value = true;\n try {\n const res = await getApiData(\n { org_id: current_org?.id },\n `${props.record.model_app}/get_collect_funcs`,\n );\n customFuncOptions.value = res || [];\n } catch (e) {\n customFuncOptions.value = [];\n } finally {\n funcOptionsLoading.value = false;\n }\n};\n\nconst model = ref<any>({\n name: undefined,\n source_type: 'db',\n db_type: 'mysql',\n db_host: undefined,\n db_port: undefined,\n db_user: undefined,\n db_password: undefined,\n db_name: undefined,\n db_options: {},\n custom_func: undefined,\n custom_func_kwargs: {},\n});\n\nconst rules = reactive<any>({\n name: [{ required: true, message: '请填写数据源名称' }],\n source_type: [{ required: true, message: '请选择数据源类型' }],\n db_type: [{ required: true, message: '请选择数据库类型' }],\n db_host: [{ required: true, message: '请填写数据库主机' }],\n db_port: [{ required: true, message: '请填写数据库端口' }],\n db_user: [{ required: true, message: '请填写数据库用户' }],\n db_name: [{ required: true, message: '请填写数据库名称' }],\n custom_func: [{ required: true, message: '请填写自定义采集函数名称' }],\n});\n\n/* 切换数据源类型时,清空与另一类型无关的不必要校验残留 */\nconst onSourceTypeChange = () => {\n if (model.value.source_type === 'db') {\n model.value.custom_func = undefined;\n model.value.custom_func_kwargs = {};\n } else {\n model.value.db_host = undefined;\n model.value.db_port = undefined;\n model.value.db_user = undefined;\n model.value.db_password = undefined;\n model.value.db_name = undefined;\n model.value.db_options = {};\n }\n};\n\n/* 将 JSON 字符串解析为对象;空字符串返回 {};解析失败抛出错误 */\nconst parseJsonField = (str: string, fieldName: string): any => {\n if (str === undefined || str === null || String(str).trim() === '') return {};\n try {\n return JSON.parse(str);\n } catch (e) {\n throw new Error(`${fieldName} 不是合法 JSON: ${(e as Error).message}`);\n }\n};\n\n/* 兼容 getApiData 可能的返回结构(直接对象 或 {data:{...}}) */\nconst extractData = (res: any) => {\n if (res && typeof res === 'object' && !Array.isArray(res) && res.data !== undefined)\n return res.data;\n return res;\n};\n\n/* 编辑:调用公共项目提供的详情接口(<app_flag>/get_<model_name>,即 get_basemodel)\n 获取完整的 SyncDataSource 记录。不依赖列表行数据 props.record,因为它可能缺失\n 部分字段。get_basemodel 返回单条 dict(非列表),按 id 查询(参数名\n 为 <model_name>_id,即 sync_data_source_id)。 */\nconst loadDetail = async (id: any) => {\n const res = await getApiData(\n { sync_data_source_id: id, org_id: current_org?.id },\n `${props.record.model_app}/get_sync_data_source`,\n );\n return extractData(res) || {};\n};\n\n/* 构建提交数据:始终 cloneDeep(model.value),兜底默认值 */\nconst buildPayload = () => {\n const payload: any = cloneDeep(model.value);\n payload.update_field_list = [\n 'name',\n 'source_type',\n 'db_type',\n 'db_host',\n 'db_port',\n 'db_user',\n 'db_password',\n 'db_name',\n 'db_options',\n 'custom_func',\n 'custom_func_kwargs',\n ];\n const isDb = payload.source_type === 'db';\n\n // 仅保留当前类型相关字段,避免把另一类型的空值带给后端\n if (isDb) {\n delete payload.custom_func;\n delete payload.custom_func_kwargs;\n payload.custom_func_kwargs = {};\n // 编辑时密码留空则不修改:删除字段值,并从 update_field_list 中剔除,避免后端更新\n if (!payload.db_password) {\n delete payload.db_password;\n if (form_type.value === 'edit') {\n payload.update_field_list = payload.update_field_list.filter(\n (k: string) => k !== 'db_password',\n );\n }\n }\n } else {\n ['db_type', 'db_host', 'db_port', 'db_user', 'db_password', 'db_name', 'db_options'].forEach(\n k => delete payload[k],\n );\n payload.db_options = {};\n payload.custom_func_kwargs = parseJsonField(payload.custom_func_kwargs, '自定义采集函数参数');\n delete payload.custom_func_kwargs;\n }\n\n return payload;\n};\n\n/* 提交:URL 遵循 /{app_flag}/create_{model} 与 /{app_flag}/update_{model} */\nconst handleOk = async () => {\n try {\n await formRef.value.validate();\n const payload = buildPayload();\n payload.sync_data_source_id = props.record.id;\n const url =\n form_type.value === 'edit'\n ? `${props.record.model_app}/update_sync_data_source`\n : `${props.record.model_app}/create_sync_data_source`;\n if (form_type.value === 'edit') payload.id = props.record.id;\n loading.value = true;\n await getApiData({ ...payload, org_id: current_org?.id }, url);\n message.success('操作成功');\n event_obj()?.emit('refresh');\n emit('ok');\n } catch (error) {\n // 校验失败或接口异常:保持弹窗打开\n } finally {\n loading.value = false;\n }\n};\nconst handleCancel = () => emit('cancel');\n\n/* 初始化编辑:先调详情接口拿完整记录,再填充;并把 JSON 字段格式化为字符串 */\nconst initForEdit = async () => {\n const id = props.record?.id;\n // 编辑时依赖详情接口而非列表行数据(列表行可能缺失字段)\n const record = (id != null ? await loadDetail(id) : props.record) || {};\n model.value = {\n name: record.name || '',\n source_type: record.source_type || 'db',\n db_type: record.db_type || 'mysql',\n db_host: record.db_host || undefined,\n db_port: record.db_port ?? undefined,\n db_user: record.db_user || undefined,\n db_password: undefined, // 密码不回显,留空表示不修改\n db_name: record.db_name || undefined,\n db_options: record.db_options,\n custom_func: record.custom_func || undefined,\n custom_func_kwargs: record.custom_func_kwargs,\n };\n // 编辑时若已保存的采集函数不在当前选项列表中,补充进下拉避免丢失显示\n const rawCustomFunc = record.custom_func;\n if (rawCustomFunc && !customFuncOptions.value.some(o => o.value === rawCustomFunc)) {\n customFuncOptions.value = [\n { value: rawCustomFunc, label: rawCustomFunc },\n ...customFuncOptions.value,\n ];\n }\n};\n\nonMounted(async () => {\n if (props.record?.form_type) form_type.value = props.record.form_type;\n await loadCustomFuncOptions();\n if (form_type.value === 'edit') await initForEdit();\n});\n</script>\n\n<style scoped lang=\"less\">\n.field-extra {\n color: #999;\n font-size: 12px;\n line-height: 1.4;\n margin-top: 2px;\n}\n</style>\n","<template>\n <a-modal\n :title=\"title\"\n width=\"880px\"\n :open=\"open\"\n :bodyStyle=\"{ maxHeight: '78vh', overflowY: 'auto', padding: '0 24px' }\"\n :confirm-loading=\"loading\"\n cancel-text=\"取消\"\n ok-text=\"确定\"\n centered\n destroy-on-close\n @cancel=\"handleCancel\"\n @ok=\"handleOk\"\n >\n <a-form\n ref=\"formRef\"\n :model=\"model\"\n layout=\"vertical\"\n size=\"small\"\n >\n <a-divider orientation=\"left\">基本信息</a-divider>\n <a-row :gutter=\"16\">\n <a-col :span=\"12\">\n <a-form-item\n label=\"数据源名称\"\n name=\"name\"\n :rules=\"rules.name\"\n >\n <a-input\n v-model:value=\"model.name\"\n placeholder=\"请填写数据源名称\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"12\">\n <a-form-item\n label=\"数据源类型\"\n name=\"source_type\"\n :rules=\"rules.source_type\"\n >\n <a-select\n v-model:value=\"model.source_type\"\n placeholder=\"请选择\"\n @change=\"onSourceTypeChange\"\n >\n <a-select-option\n v-for=\"opt in sourceTypeOptions\"\n :key=\"opt.value\"\n :value=\"opt.value\"\n >\n {{ opt.label }}\n </a-select-option>\n </a-select>\n </a-form-item>\n </a-col>\n <a-col\n v-if=\"model.source_type === 'db'\"\n :span=\"12\"\n >\n <a-form-item\n label=\"数据库类型\"\n name=\"db_type\"\n :rules=\"rules.db_type\"\n >\n <a-select\n v-model:value=\"model.db_type\"\n placeholder=\"请选择\"\n >\n <a-select-option\n v-for=\"opt in dbTypeOptions\"\n :key=\"opt.value\"\n :value=\"opt.value\"\n >\n {{ opt.label }}\n </a-select-option>\n </a-select>\n </a-form-item>\n </a-col>\n </a-row>\n\n <!-- 数据库类型:连接信息 -->\n <template v-if=\"model.source_type === 'db'\">\n <a-divider orientation=\"left\">数据库连接信息</a-divider>\n <a-row :gutter=\"16\">\n <a-col :span=\"12\">\n <a-form-item\n label=\"数据库主机\"\n name=\"db_host\"\n :rules=\"rules.db_host\"\n >\n <a-input\n v-model:value=\"model.db_host\"\n placeholder=\"如 127.0.0.1\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"12\">\n <a-form-item\n label=\"数据库端口\"\n name=\"db_port\"\n :rules=\"rules.db_port\"\n >\n <a-input-number\n v-model:value=\"model.db_port\"\n style=\"width: 100%\"\n :min=\"1\"\n :max=\"65535\"\n placeholder=\"如 3306\"\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"12\">\n <a-form-item\n label=\"数据库用户\"\n name=\"db_user\"\n :rules=\"rules.db_user\"\n >\n <a-input\n v-model:value=\"model.db_user\"\n placeholder=\"如 root\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"12\">\n <a-form-item\n label=\"数据库密码\"\n name=\"db_password\"\n >\n <a-input-password\n v-model:value=\"model.db_password\"\n placeholder=\"修改时留空则不修改密码\"\n allow-clear\n autocomplete=\"new-password\"\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"12\">\n <a-form-item\n :label=\"db_name_field_lable\"\n name=\"db_name\"\n :rules=\"rules.db_name\"\n >\n <a-input\n v-model:value=\"model.db_name\"\n placeholder=\"如 my_db\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n <a-col\n :span=\"12\"\n v-if=\"model.db_type == 'oracle'\"\n >\n <a-form-item label=\"默认Schema\">\n <a-input\n v-model:value=\"oracle_schema\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n </a-row>\n <a-form-item\n label=\"数据库选项\"\n name=\"db_options\"\n >\n <JsonStr\n v-model:value=\"model.db_options\"\n placeholder='可选,JSON 对象,如 {\"charset\":\"utf8mb4\"}'\n />\n </a-form-item>\n </template>\n\n <!-- 自定义类型:采集函数 -->\n <template v-else-if=\"model.source_type === 'custom'\">\n <a-divider orientation=\"left\">自定义采集</a-divider>\n <a-row :gutter=\"16\">\n <a-col :span=\"12\">\n <a-form-item\n label=\"自定义采集函数名称\"\n name=\"custom_func\"\n :rules=\"rules.custom_func\"\n >\n <a-select\n v-model:value=\"model.custom_func\"\n placeholder=\"请选择采集函数\"\n :options=\"customFuncOptions\"\n option-filter-prop=\"label\"\n show-search\n allow-clear\n :loading=\"funcOptionsLoading\"\n />\n </a-form-item>\n </a-col>\n </a-row>\n <a-form-item\n label=\"自定义采集函数参数(JSON)\"\n name=\"custom_func_kwargs\"\n >\n <JsonStr v-model:value=\"model.custom_func_kwargs\" />\n </a-form-item>\n </template>\n </a-form>\n </a-modal>\n</template>\n\n<script setup lang=\"ts\">\nimport { cloneDeep } from 'lodash';\nimport { message } from 'ant-design-vue';\nimport { computed, onMounted, reactive, ref } from 'vue';\nimport { event_obj } from 'liyu-pc-base/init-plugin';\nimport { getApiData } from 'liyu-pc-base/api/api-model';\nimport { useUserStore } from 'liyu-pc-base/init-plugin';\nimport JsonStr from 'liyu-pc-base/components/FormRender/JsonStr/index.vue';\ninterface Props {\n title: string;\n record: any;\n}\nconst props = defineProps<Props>();\nconst emit = defineEmits(['cancel', 'ok']);\n\nconst formRef = ref<any>(null);\nconst loading = ref(false);\nconst open = ref(true);\nconst form_type = ref('create');\nconst userStore = useUserStore();\nconst current_org = userStore.currentOrg;\nconst db_name_field_lable = computed(() => {\n return model.value.db_type === 'oracle' ? 'SID/服务名' : '数据库名称';\n});\nconst oracle_schema = computed({\n get: () => {\n if (model.value.db_options?.schema) {\n return model.value.db_options?.schema;\n }\n return '';\n },\n set: val => {\n if (typeof model.value.db_options === 'object') {\n if (val) {\n model.value.db_options.schema = val;\n } else {\n delete model.value.db_options.schema;\n }\n } else {\n if (val) {\n model.value.db_options = { schema: val };\n }\n }\n },\n});\n\nconst sourceTypeOptions = [\n { value: 'db', label: '数据库' },\n { value: 'custom', label: '自定义' },\n];\nconst dbTypeOptions = [\n { value: 'mysql', label: 'MySQL' },\n { value: 'tsql', label: 'SQL Server' },\n { value: 'oracle', label: 'Oracle' },\n];\n\n/* 自定义采集函数下拉选项(来自后端 get_collect_funcs) */\nconst customFuncOptions = ref<any[]>([]);\nconst funcOptionsLoading = ref(false);\nconst loadCustomFuncOptions = async () => {\n funcOptionsLoading.value = true;\n try {\n const res = await getApiData(\n { org_id: current_org?.id },\n `${props.record.model_app}/get_collect_funcs`,\n );\n customFuncOptions.value = res || [];\n } catch (e) {\n customFuncOptions.value = [];\n } finally {\n funcOptionsLoading.value = false;\n }\n};\n\nconst model = ref<any>({\n name: undefined,\n source_type: 'db',\n db_type: 'mysql',\n db_host: undefined,\n db_port: undefined,\n db_user: undefined,\n db_password: undefined,\n db_name: undefined,\n db_options: {},\n custom_func: undefined,\n custom_func_kwargs: {},\n});\n\nconst rules = reactive<any>({\n name: [{ required: true, message: '请填写数据源名称' }],\n source_type: [{ required: true, message: '请选择数据源类型' }],\n db_type: [{ required: true, message: '请选择数据库类型' }],\n db_host: [{ required: true, message: '请填写数据库主机' }],\n db_port: [{ required: true, message: '请填写数据库端口' }],\n db_user: [{ required: true, message: '请填写数据库用户' }],\n db_name: [{ required: true, message: '请填写数据库名称' }],\n custom_func: [{ required: true, message: '请填写自定义采集函数名称' }],\n});\n\n/* 切换数据源类型时,清空与另一类型无关的不必要校验残留 */\nconst onSourceTypeChange = () => {\n if (model.value.source_type === 'db') {\n model.value.custom_func = undefined;\n model.value.custom_func_kwargs = {};\n } else {\n model.value.db_host = undefined;\n model.value.db_port = undefined;\n model.value.db_user = undefined;\n model.value.db_password = undefined;\n model.value.db_name = undefined;\n model.value.db_options = {};\n }\n};\n\n/* 将 JSON 字符串解析为对象;空字符串返回 {};解析失败抛出错误 */\nconst parseJsonField = (str: string, fieldName: string): any => {\n if (str === undefined || str === null || String(str).trim() === '') return {};\n try {\n return JSON.parse(str);\n } catch (e) {\n throw new Error(`${fieldName} 不是合法 JSON: ${(e as Error).message}`);\n }\n};\n\n/* 兼容 getApiData 可能的返回结构(直接对象 或 {data:{...}}) */\nconst extractData = (res: any) => {\n if (res && typeof res === 'object' && !Array.isArray(res) && res.data !== undefined)\n return res.data;\n return res;\n};\n\n/* 编辑:调用公共项目提供的详情接口(<app_flag>/get_<model_name>,即 get_basemodel)\n 获取完整的 SyncDataSource 记录。不依赖列表行数据 props.record,因为它可能缺失\n 部分字段。get_basemodel 返回单条 dict(非列表),按 id 查询(参数名\n 为 <model_name>_id,即 sync_data_source_id)。 */\nconst loadDetail = async (id: any) => {\n const res = await getApiData(\n { sync_data_source_id: id, org_id: current_org?.id },\n `${props.record.model_app}/get_sync_data_source`,\n );\n return extractData(res) || {};\n};\n\n/* 构建提交数据:始终 cloneDeep(model.value),兜底默认值 */\nconst buildPayload = () => {\n const payload: any = cloneDeep(model.value);\n payload.update_field_list = [\n 'name',\n 'source_type',\n 'db_type',\n 'db_host',\n 'db_port',\n 'db_user',\n 'db_password',\n 'db_name',\n 'db_options',\n 'custom_func',\n 'custom_func_kwargs',\n ];\n const isDb = payload.source_type === 'db';\n\n // 仅保留当前类型相关字段,避免把另一类型的空值带给后端\n if (isDb) {\n delete payload.custom_func;\n delete payload.custom_func_kwargs;\n payload.custom_func_kwargs = {};\n // 编辑时密码留空则不修改:删除字段值,并从 update_field_list 中剔除,避免后端更新\n if (!payload.db_password) {\n delete payload.db_password;\n if (form_type.value === 'edit') {\n payload.update_field_list = payload.update_field_list.filter(\n (k: string) => k !== 'db_password',\n );\n }\n }\n } else {\n ['db_type', 'db_host', 'db_port', 'db_user', 'db_password', 'db_name', 'db_options'].forEach(\n k => delete payload[k],\n );\n payload.db_options = {};\n payload.custom_func_kwargs = parseJsonField(payload.custom_func_kwargs, '自定义采集函数参数');\n delete payload.custom_func_kwargs;\n }\n\n return payload;\n};\n\n/* 提交:URL 遵循 /{app_flag}/create_{model} 与 /{app_flag}/update_{model} */\nconst handleOk = async () => {\n try {\n await formRef.value.validate();\n const payload = buildPayload();\n payload.sync_data_source_id = props.record.id;\n const url =\n form_type.value === 'edit'\n ? `${props.record.model_app}/update_sync_data_source`\n : `${props.record.model_app}/create_sync_data_source`;\n if (form_type.value === 'edit') payload.id = props.record.id;\n loading.value = true;\n await getApiData({ ...payload, org_id: current_org?.id }, url);\n message.success('操作成功');\n event_obj()?.emit('refresh');\n emit('ok');\n } catch (error) {\n // 校验失败或接口异常:保持弹窗打开\n } finally {\n loading.value = false;\n }\n};\nconst handleCancel = () => emit('cancel');\n\n/* 初始化编辑:先调详情接口拿完整记录,再填充;并把 JSON 字段格式化为字符串 */\nconst initForEdit = async () => {\n const id = props.record?.id;\n // 编辑时依赖详情接口而非列表行数据(列表行可能缺失字段)\n const record = (id != null ? await loadDetail(id) : props.record) || {};\n model.value = {\n name: record.name || '',\n source_type: record.source_type || 'db',\n db_type: record.db_type || 'mysql',\n db_host: record.db_host || undefined,\n db_port: record.db_port ?? undefined,\n db_user: record.db_user || undefined,\n db_password: undefined, // 密码不回显,留空表示不修改\n db_name: record.db_name || undefined,\n db_options: record.db_options,\n custom_func: record.custom_func || undefined,\n custom_func_kwargs: record.custom_func_kwargs,\n };\n // 编辑时若已保存的采集函数不在当前选项列表中,补充进下拉避免丢失显示\n const rawCustomFunc = record.custom_func;\n if (rawCustomFunc && !customFuncOptions.value.some(o => o.value === rawCustomFunc)) {\n customFuncOptions.value = [\n { value: rawCustomFunc, label: rawCustomFunc },\n ...customFuncOptions.value,\n ];\n }\n};\n\nonMounted(async () => {\n if (props.record?.form_type) form_type.value = props.record.form_type;\n await loadCustomFuncOptions();\n if (form_type.value === 'edit') await initForEdit();\n});\n</script>\n\n<style scoped lang=\"less\">\n.field-extra {\n color: #999;\n font-size: 12px;\n line-height: 1.4;\n margin-top: 2px;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;EA4NA,IAAM,IAAQ,GACR,IAAO,GAEP,IAAU,EAAS,IAAI,GACvB,IAAU,EAAI,EAAK,GACnB,IAAO,EAAI,EAAI,GACf,IAAY,EAAI,QAAQ,GAExB,IADY,EACE,CAAA,CAAU,YACxB,IAAsB,QACnB,EAAM,MAAM,YAAY,WAAW,YAAY,OACvD,GACK,IAAgB,EAAS;GAC7B,WACM,EAAM,MAAM,YAAY,SACnB,EAAM,MAAM,YAAY,SAE1B;GAET,MAAK,MAAO;IACV,AAAI,OAAO,EAAM,MAAM,cAAe,WAChC,IACF,EAAM,MAAM,WAAW,SAAS,IAEhC,OAAO,EAAM,MAAM,WAAW,SAG5B,MACF,EAAM,MAAM,aAAa,EAAE,QAAQ,EAAI;GAG7C;EACF,CAAC,GAEK,IAAoB,CACxB;GAAE,OAAO;GAAM,OAAO;EAAM,GAC5B;GAAE,OAAO;GAAU,OAAO;EAAM,CAClC,GACM,IAAgB;GACpB;IAAE,OAAO;IAAS,OAAO;GAAQ;GACjC;IAAE,OAAO;IAAQ,OAAO;GAAa;GACrC;IAAE,OAAO;IAAU,OAAO;GAAS;EACrC,GAGM,IAAoB,EAAW,CAAC,CAAC,GACjC,IAAqB,EAAI,EAAK,GAC9B,IAAwB,YAAY;GACxC,EAAmB,QAAQ;GAC3B,IAAI;IAKF,EAAkB,QAAQ,MAJR,EAChB,EAAE,QAAQ,GAAa,GAAG,GAC1B,GAAG,EAAM,OAAO,UAAU,mBAC5B,KACiC,CAAC;GACpC,QAAY;IACV,EAAkB,QAAQ,CAAC;GAC7B,UAAU;IACR,EAAmB,QAAQ;GAC7B;EACF,GAEM,IAAQ,EAAS;GACrB,MAAM,KAAA;GACN,aAAa;GACb,SAAS;GACT,SAAS,KAAA;GACT,SAAS,KAAA;GACT,SAAS,KAAA;GACT,aAAa,KAAA;GACb,SAAS,KAAA;GACT,YAAY,CAAC;GACb,aAAa,KAAA;GACb,oBAAoB,CAAC;EACvB,CAAC,GAEK,IAAQ,EAAc;GAC1B,MAAM,CAAC;IAAE,UAAU;IAAM,SAAS;GAAW,CAAC;GAC9C,aAAa,CAAC;IAAE,UAAU;IAAM,SAAS;GAAW,CAAC;GACrD,SAAS,CAAC;IAAE,UAAU;IAAM,SAAS;GAAW,CAAC;GACjD,SAAS,CAAC;IAAE,UAAU;IAAM,SAAS;GAAW,CAAC;GACjD,SAAS,CAAC;IAAE,UAAU;IAAM,SAAS;GAAW,CAAC;GACjD,SAAS,CAAC;IAAE,UAAU;IAAM,SAAS;GAAW,CAAC;GACjD,SAAS,CAAC;IAAE,UAAU;IAAM,SAAS;GAAW,CAAC;GACjD,aAAa,CAAC;IAAE,UAAU;IAAM,SAAS;GAAe,CAAC;EAC3D,CAAC,GAGK,UAA2B;GAC/B,AAAI,EAAM,MAAM,gBAAgB,QAC9B,EAAM,MAAM,cAAc,KAAA,GAC1B,EAAM,MAAM,qBAAqB,CAAC,MAElC,EAAM,MAAM,UAAU,KAAA,GACtB,EAAM,MAAM,UAAU,KAAA,GACtB,EAAM,MAAM,UAAU,KAAA,GACtB,EAAM,MAAM,cAAc,KAAA,GAC1B,EAAM,MAAM,UAAU,KAAA,GACtB,EAAM,MAAM,aAAa,CAAC;EAE9B,GAGM,KAAkB,GAAa,MAA2B;GAC9D,IAAI,KAA6B,QAAQ,OAAO,CAAG,CAAC,CAAC,KAAK,MAAM,IAAI,OAAO,CAAC;GAC5E,IAAI;IACF,OAAO,KAAK,MAAM,CAAG;GACvB,SAAS,GAAG;IACV,MAAU,MAAM,GAAG,EAAU,cAAe,EAAY,SAAS;GACnE;EACF,GAGM,KAAe,MACf,KAAO,OAAO,KAAQ,YAAY,CAAC,MAAM,QAAQ,CAAG,KAAK,EAAI,SAAS,KAAA,IACjE,EAAI,OACN,GAOH,IAAa,OAAO,MAKjB,EAAY,MAJD,EAChB;GAAE,qBAAqB;GAAI,QAAQ,GAAa;EAAG,GACnD,GAAG,EAAM,OAAO,UAAU,sBAC5B,CACsB,KAAK,CAAC,GAIxB,UAAqB;GACzB,IAAM,KAAA,GAAA,EAAA,UAAA,CAAyB,EAAM,KAAK;GAuC1C,OAtCA,EAAQ,oBAAoB;IAC1B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACF,GACa,EAAQ,gBAAgB,QAInC,OAAO,EAAQ,aACf,OAAO,EAAQ,oBACf,EAAQ,qBAAqB,CAAC,GAEzB,EAAQ,gBACX,OAAO,EAAQ,aACX,EAAU,UAAU,WACtB,EAAQ,oBAAoB,EAAQ,kBAAkB,QACnD,MAAc,MAAM,aACvB,QAIJ;IAAC;IAAW;IAAW;IAAW;IAAW;IAAe;IAAW;GAAY,CAAC,CAAC,SACnF,MAAK,OAAO,EAAQ,EACtB,GACA,EAAQ,aAAa,CAAC,GACtB,EAAQ,qBAAqB,EAAe,EAAQ,oBAAoB,WAAW,GACnF,OAAO,EAAQ,qBAGV;EACT,GAGM,IAAW,YAAY;GAC3B,IAAI;IACF,MAAM,EAAQ,MAAM,SAAS;IAC7B,IAAM,IAAU,EAAa;IAC7B,EAAQ,sBAAsB,EAAM,OAAO;IAC3C,IAAM,IACJ,EAAU,UAAU,SAChB,GAAG,EAAM,OAAO,UAAU,4BAC1B,GAAG,EAAM,OAAO,UAAU;IAMhC,AALI,EAAU,UAAU,WAAQ,EAAQ,KAAK,EAAM,OAAO,KAC1D,EAAQ,QAAQ,IAChB,MAAM,EAAW;KAAE,GAAG;KAAS,QAAQ,GAAa;IAAG,GAAG,CAAG,GAC7D,EAAQ,QAAQ,MAAM,GACtB,EAAU,CAAC,EAAE,KAAK,SAAS,GAC3B,EAAK,IAAI;GACX,QAAgB,CAEhB,UAAU;IACR,EAAQ,QAAQ;GAClB;EACF,GACM,UAAqB,EAAK,QAAQ,GAGlC,IAAc,YAAY;GAC9B,IAAM,IAAK,EAAM,QAAQ,IAEnB,KAAU,KAAM,OAA8B,EAAM,SAA7B,MAAM,EAAW,CAAE,MAAqB,CAAC;GACtE,EAAM,QAAQ;IACZ,MAAM,EAAO,QAAQ;IACrB,aAAa,EAAO,eAAe;IACnC,SAAS,EAAO,WAAW;IAC3B,SAAS,EAAO,WAAW,KAAA;IAC3B,SAAS,EAAO,WAAW,KAAA;IAC3B,SAAS,EAAO,WAAW,KAAA;IAC3B,aAAa,KAAA;IACb,SAAS,EAAO,WAAW,KAAA;IAC3B,YAAY,EAAO;IACnB,aAAa,EAAO,eAAe,KAAA;IACnC,oBAAoB,EAAO;GAC7B;GAEA,IAAM,IAAgB,EAAO;GAC7B,AAAI,KAAiB,CAAC,EAAkB,MAAM,MAAK,MAAK,EAAE,UAAU,CAAa,MAC/E,EAAkB,QAAQ,CACxB;IAAE,OAAO;IAAe,OAAO;GAAc,GAC7C,GAAG,EAAkB,KACvB;EAEJ;SAEA,EAAU,YAAY;GAGpB,AAFI,EAAM,QAAQ,cAAW,EAAU,QAAQ,EAAM,OAAO,YAC5D,MAAM,EAAsB,GACxB,EAAU,UAAU,UAAQ,MAAM,EAAY;EACpD,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"syncExportRawData-Dpi-VJuu.js","names":[],"sources":["../src/components/lysync/syncConditionGroup.vue","../src/components/lysync/syncConditionGroup.vue","../src/components/lysync/syncExportRawData.vue","../src/components/lysync/syncExportRawData.vue"],"sourcesContent":["<template>\n <div class=\"qb-group\">\n <div class=\"qb-group-head\">\n <a-select\n style=\"width: 110px\"\n :value=\"group.type || 'and'\"\n :options=\"[\n { label: '并且', value: 'and' },\n { label: '或者', value: 'or' },\n ]\"\n @update:value=\"(v:string) => (group.type = v)\"\n />\n <a-button\n size=\"small\"\n @click=\"addRule\"\n >\n 添加条件\n </a-button>\n <a-button\n size=\"small\"\n style=\"margin-left: 8px\"\n @click=\"addGroup\"\n >\n 添加分组\n </a-button>\n </div>\n\n <div class=\"qb-group-body\">\n <template\n v-for=\"(child, idx) in group.conditions\"\n :key=\"idx\"\n >\n <!-- 分组 -->\n <div\n v-if=\"child && typeof child === 'object' && 'type' in child\"\n class=\"qb-subgroup\"\n >\n <ConditionGroup\n :group=\"child\"\n :columns=\"columns\"\n />\n <a-button\n danger\n size=\"small\"\n style=\"margin: 4px 0\"\n @click=\"removeAt(idx)\"\n >\n 删除分组\n </a-button>\n </div>\n <!-- 规则 -->\n <div\n v-else\n class=\"qb-rule\"\n >\n <a-select\n :value=\"child.field\"\n style=\"min-width: 200px\"\n placeholder=\"选择字段\"\n :options=\"fieldOptions\"\n @update:value=\"(v:string)=> (child.field = v)\"\n />\n <a-select\n :value=\"child.op\"\n style=\"width: 200px\"\n :options=\"operators\"\n @update:value=\"(v:string)=> (child.op = v)\"\n />\n\n <!-- 值输入区域 -->\n <template v-if=\"child.op === 'in'\">\n <a-select\n mode=\"tags\"\n style=\"min-width: 220px\"\n :value=\"Array.isArray(child.value) ? child.value : []\"\n placeholder=\"输入多个值后回车\"\n @update:value=\"(v:any)=> (child.value = v)\"\n />\n </template>\n <template v-else-if=\"child.op === 'between'\">\n <a-space :size=\"4\">\n <a-input\n style=\"width: 120px\"\n :value=\"Array.isArray(child.value) ? child.value?.[0] : undefined\"\n placeholder=\"起始值\"\n @update:value=\"(v:any)=> updateBetween(child, 0, v)\"\n />\n <span style=\"padding: 0 4px; color: #999\">~</span>\n <a-input\n style=\"width: 120px\"\n :value=\"Array.isArray(child.value) ? child.value?.[1] : undefined\"\n placeholder=\"结束值\"\n @update:value=\"(v:any)=> updateBetween(child, 1, v)\"\n />\n </a-space>\n </template>\n <template v-else-if=\"child.op === 'is'\">\n <a-select\n style=\"min-width: 160px\"\n :value=\"child.value\"\n placeholder=\"选择 NULL/TRUE/FALSE\"\n @update:value=\"(v:any)=> (child.value = v)\"\n >\n <a-select-option :value=\"null\">NULL</a-select-option>\n <a-select-option :value=\"true\">TRUE</a-select-option>\n <a-select-option :value=\"false\">FALSE</a-select-option>\n </a-select>\n </template>\n <template v-else>\n <a-input\n style=\"min-width: 220px\"\n :value=\"child.value\"\n placeholder=\"输入值\"\n @update:value=\"(v:any)=> (child.value = v)\"\n />\n </template>\n\n <a-button\n danger\n size=\"small\"\n @click=\"removeAt(idx)\"\n >\n 删除\n </a-button>\n </div>\n </template>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, computed } from 'vue';\n\nexport default defineComponent({\n name: 'ConditionGroup',\n props: {\n group: { type: Object as any, required: true },\n columns: { type: Array as any, required: true },\n isRoot: { type: Boolean, default: false },\n },\n setup(props) {\n const operators = [\n { label: '等于', value: '=' },\n { label: '不等于', value: '!=' },\n { label: '大于', value: '>' },\n { label: '大于或等于', value: '>=' },\n { label: '小于', value: '<' },\n { label: '小于或等于', value: '<=' },\n { label: '包含', value: 'in' },\n { label: '模糊匹配', value: 'like' },\n { label: '介于', value: 'between' },\n ];\n\n const fieldOptions = computed(() =>\n (props.columns as any[]).map((c: any) => ({ label: c.title, value: c.dataIndex })),\n );\n\n const addRule = () => {\n (props.group as any).conditions.push({ field: '', op: '=', value: '' });\n };\n const addGroup = () => {\n (props.group as any).conditions.push({ type: 'and', conditions: [] });\n };\n const removeAt = (idx: number) => {\n (props.group as any).conditions.splice(idx, 1);\n };\n const updateBetween = (rule: any, pos: number, v: any) => {\n const arr = Array.isArray(rule.value) ? rule.value : [undefined, undefined];\n arr[pos] = v;\n rule.value = arr;\n };\n\n return {\n operators,\n fieldOptions,\n addRule,\n addGroup,\n removeAt,\n updateBetween,\n };\n },\n});\n</script>\n\n<style lang=\"less\" scoped>\n.qb-group {\n border-left: 2px solid #f0f0f0;\n padding-left: 8px;\n}\n.qb-group-head {\n display: flex;\n gap: 8px;\n margin-bottom: 8px;\n}\n.qb-group-body {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.qb-rule {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.qb-subgroup {\n padding: 6px 0 6px 8px;\n border-left: 2px dashed #f0f0f0;\n}\n</style>\n","<template>\n <div class=\"qb-group\">\n <div class=\"qb-group-head\">\n <a-select\n style=\"width: 110px\"\n :value=\"group.type || 'and'\"\n :options=\"[\n { label: '并且', value: 'and' },\n { label: '或者', value: 'or' },\n ]\"\n @update:value=\"(v:string) => (group.type = v)\"\n />\n <a-button\n size=\"small\"\n @click=\"addRule\"\n >\n 添加条件\n </a-button>\n <a-button\n size=\"small\"\n style=\"margin-left: 8px\"\n @click=\"addGroup\"\n >\n 添加分组\n </a-button>\n </div>\n\n <div class=\"qb-group-body\">\n <template\n v-for=\"(child, idx) in group.conditions\"\n :key=\"idx\"\n >\n <!-- 分组 -->\n <div\n v-if=\"child && typeof child === 'object' && 'type' in child\"\n class=\"qb-subgroup\"\n >\n <ConditionGroup\n :group=\"child\"\n :columns=\"columns\"\n />\n <a-button\n danger\n size=\"small\"\n style=\"margin: 4px 0\"\n @click=\"removeAt(idx)\"\n >\n 删除分组\n </a-button>\n </div>\n <!-- 规则 -->\n <div\n v-else\n class=\"qb-rule\"\n >\n <a-select\n :value=\"child.field\"\n style=\"min-width: 200px\"\n placeholder=\"选择字段\"\n :options=\"fieldOptions\"\n @update:value=\"(v:string)=> (child.field = v)\"\n />\n <a-select\n :value=\"child.op\"\n style=\"width: 200px\"\n :options=\"operators\"\n @update:value=\"(v:string)=> (child.op = v)\"\n />\n\n <!-- 值输入区域 -->\n <template v-if=\"child.op === 'in'\">\n <a-select\n mode=\"tags\"\n style=\"min-width: 220px\"\n :value=\"Array.isArray(child.value) ? child.value : []\"\n placeholder=\"输入多个值后回车\"\n @update:value=\"(v:any)=> (child.value = v)\"\n />\n </template>\n <template v-else-if=\"child.op === 'between'\">\n <a-space :size=\"4\">\n <a-input\n style=\"width: 120px\"\n :value=\"Array.isArray(child.value) ? child.value?.[0] : undefined\"\n placeholder=\"起始值\"\n @update:value=\"(v:any)=> updateBetween(child, 0, v)\"\n />\n <span style=\"padding: 0 4px; color: #999\">~</span>\n <a-input\n style=\"width: 120px\"\n :value=\"Array.isArray(child.value) ? child.value?.[1] : undefined\"\n placeholder=\"结束值\"\n @update:value=\"(v:any)=> updateBetween(child, 1, v)\"\n />\n </a-space>\n </template>\n <template v-else-if=\"child.op === 'is'\">\n <a-select\n style=\"min-width: 160px\"\n :value=\"child.value\"\n placeholder=\"选择 NULL/TRUE/FALSE\"\n @update:value=\"(v:any)=> (child.value = v)\"\n >\n <a-select-option :value=\"null\">NULL</a-select-option>\n <a-select-option :value=\"true\">TRUE</a-select-option>\n <a-select-option :value=\"false\">FALSE</a-select-option>\n </a-select>\n </template>\n <template v-else>\n <a-input\n style=\"min-width: 220px\"\n :value=\"child.value\"\n placeholder=\"输入值\"\n @update:value=\"(v:any)=> (child.value = v)\"\n />\n </template>\n\n <a-button\n danger\n size=\"small\"\n @click=\"removeAt(idx)\"\n >\n 删除\n </a-button>\n </div>\n </template>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, computed } from 'vue';\n\nexport default defineComponent({\n name: 'ConditionGroup',\n props: {\n group: { type: Object as any, required: true },\n columns: { type: Array as any, required: true },\n isRoot: { type: Boolean, default: false },\n },\n setup(props) {\n const operators = [\n { label: '等于', value: '=' },\n { label: '不等于', value: '!=' },\n { label: '大于', value: '>' },\n { label: '大于或等于', value: '>=' },\n { label: '小于', value: '<' },\n { label: '小于或等于', value: '<=' },\n { label: '包含', value: 'in' },\n { label: '模糊匹配', value: 'like' },\n { label: '介于', value: 'between' },\n ];\n\n const fieldOptions = computed(() =>\n (props.columns as any[]).map((c: any) => ({ label: c.title, value: c.dataIndex })),\n );\n\n const addRule = () => {\n (props.group as any).conditions.push({ field: '', op: '=', value: '' });\n };\n const addGroup = () => {\n (props.group as any).conditions.push({ type: 'and', conditions: [] });\n };\n const removeAt = (idx: number) => {\n (props.group as any).conditions.splice(idx, 1);\n };\n const updateBetween = (rule: any, pos: number, v: any) => {\n const arr = Array.isArray(rule.value) ? rule.value : [undefined, undefined];\n arr[pos] = v;\n rule.value = arr;\n };\n\n return {\n operators,\n fieldOptions,\n addRule,\n addGroup,\n removeAt,\n updateBetween,\n };\n },\n});\n</script>\n\n<style lang=\"less\" scoped>\n.qb-group {\n border-left: 2px solid #f0f0f0;\n padding-left: 8px;\n}\n.qb-group-head {\n display: flex;\n gap: 8px;\n margin-bottom: 8px;\n}\n.qb-group-body {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.qb-rule {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.qb-subgroup {\n padding: 6px 0 6px 8px;\n border-left: 2px dashed #f0f0f0;\n}\n</style>\n","<template>\n <a-modal\n title=\"原始数据 (仅返回50条数据)\"\n width=\"1300px\"\n v-model:open=\"open\"\n :maskClosable=\"false\"\n centered\n @cancel=\"handleCancel\"\n >\n <div>\n <a-card\n size=\"small\"\n :bordered=\"false\"\n style=\"margin-bottom: 12px\"\n >\n <a-space\n direction=\"vertical\"\n :size=\"8\"\n style=\"width: 100%\"\n >\n <div style=\"display: flex; justify-content: space-between; align-items: center\">\n <div style=\"font-weight: 600\">查询条件</div>\n <div>\n <a-button\n size=\"small\"\n @click=\"resetConditions\"\n >\n 重置\n </a-button>\n <a-button\n type=\"primary\"\n size=\"small\"\n style=\"margin-left: 8px\"\n @click=\"applySearch\"\n >\n 查询\n </a-button>\n </div>\n </div>\n <div class=\"conditions-scroll\">\n <ConditionGroup\n :group=\"conditionsRoot\"\n :columns=\"column\"\n :is-root=\"true\"\n />\n </div>\n <div class=\"time-range-row\">\n <span class=\"field-label\">时间范围:</span>\n <a-range-picker\n v-model:value=\"timeRange\"\n :show-time=\"{ format: 'HH:mm:ss' }\"\n format=\"YYYY-MM-DD HH:mm:ss\"\n value-format=\"YYYY-MM-DD HH:mm:ss\"\n :placeholder=\"['开始时间', '结束时间']\"\n style=\"width: 440px\"\n />\n </div>\n </a-space>\n </a-card>\n <a-spin :spinning=\"loading\">\n <s-table\n v-if=\"datasource?.length\"\n :columns=\"column\"\n :dataSource=\"datasource\"\n rowKey=\"_id\"\n :pagination=\"false\"\n :scroll=\"{ y: height, x: width }\"\n @change=\"handleTableChange\"\n >\n <template #headerCell=\"{ title, column }\">\n <a-tag\n color=\"blue\"\n v-if=\"column.isMatch\"\n >\n {{ title }}\n </a-tag>\n <span v-if=\"!column.isMatch\">\n {{ title }}\n </span>\n </template>\n </s-table>\n </a-spin>\n </div>\n <template #footer>\n <a-button\n key=\"back\"\n @click=\"handleCancel\"\n >\n 关闭\n </a-button>\n <a-button\n key=\"ok\"\n type=\"primary\"\n @click=\"handleCancel\"\n >\n 确定\n </a-button>\n </template>\n </a-modal>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, onUnmounted, ref, onMounted, computed } from 'vue';\nimport { debounce } from 'lodash';\nimport ConditionGroup from './syncConditionGroup.vue';\nimport { getApiData } from 'liyu-pc-base/api/api-model';\nimport dayjs from 'dayjs';\n\nexport default defineComponent({\n name: 'ExportRawData',\n components: { ConditionGroup },\n props: {\n title: {\n type: String,\n required: false,\n default: () => '',\n },\n record: {\n type: Object,\n required: false,\n default: () => ({}),\n },\n },\n setup(props, { emit }) {\n const height = ref(window.innerHeight - 550);\n const onresize: any = debounce(() => {\n height.value = window.innerHeight - 550;\n }, 200);\n const loading = ref(false);\n\n const datasource = ref<any[]>([]);\n const open = ref(true);\n const titleMap = ref({});\n\n const column = computed(() => {\n const res = [];\n if (datasource.value?.length) {\n for (const key of Object.keys(datasource.value[0])) {\n if (key != '_id') {\n const title = titleMap.value[key] || key;\n res.push({\n title: title,\n dataIndex: key,\n key: key,\n isMatch: !!titleMap.value[key],\n });\n }\n }\n }\n return res;\n });\n\n const get_column_config = async () => {\n const res: any = await getApiData(\n {\n export_task_id: (props as any).record?.id,\n },\n `${props.record.model_app}/query_sync_model_field_list`,\n );\n if (res?.data?.length) {\n for (const item of res.data) {\n titleMap.value[item.field_name] = item.name;\n titleMap.value[item.name] = item.name;\n }\n }\n };\n const current = ref<number>(1);\n const pageSize = ref<number>(10);\n const total = ref<number>(0);\n const width = computed(() => {\n let w = column.value.length * 200;\n return w < 1000 ? 1000 : w;\n });\n const pageOptions = ref<string[]>([\n '10',\n '20',\n '50',\n '100',\n '500',\n '1000',\n '2000',\n '5000',\n '10000',\n ]);\n\n // 条件构建器根\n const conditionsRoot = ref<any>({ type: 'and', conditions: [] });\n\n // 时间范围([start, end] 字符串,格式 YYYY-MM-DD HH:mm:ss;用于后端 start_time/end_time)\n const timeRange = ref<any>(null);\n\n // 排序状态 - 支持多字段排序\n const sortFields = ref<Array<{ field: string; order: string }>>([]);\n\n const handleCancel = () => {\n emit('cancel');\n };\n\n const get_datasource = async () => {\n loading.value = true;\n try {\n const params: any = {\n export_task_id: (props as any).record?.id,\n current: current.value,\n pageSize: pageSize.value,\n conditions: conditionsRoot.value,\n sort_fields: sortFields.value.length > 0 ? sortFields.value : undefined,\n };\n // 时间范围:前端传则带上(供后端作为 start_time/end_time 渲染 SQL),不传则回退默认区间\n if (\n timeRange.value &&\n timeRange.value.length === 2 &&\n timeRange.value[0] &&\n timeRange.value[1]\n ) {\n params.time_range = timeRange.value;\n }\n const res: any = await getApiData(params, `${props.record.model_app}/query_raw_data`);\n\n const tmpData = res?.data || [];\n let index = 0;\n for (const item of tmpData) {\n item._id = index;\n index++;\n }\n datasource.value = tmpData;\n current.value = res?.current ?? current.value;\n pageSize.value = res?.pageSize ?? pageSize.value;\n total.value = res?.total ?? 0;\n } finally {\n loading.value = false;\n }\n };\n\n const handlePageChange = (page: number, size?: number) => {\n current.value = page;\n if (size && size !== pageSize.value) pageSize.value = size;\n get_datasource();\n };\n\n const handleTableChange = (pagination: any, filters: any, sorter: any, { action }: any) => {\n if (action === 'sort') {\n if (sorter?.field) {\n const field = sorter.field;\n const order = sorter.order === 'ascend' ? 'asc' : 'desc';\n sortFields.value = [{ field: field, order: order }];\n } else {\n // 清除所有排序\n sortFields.value = [];\n }\n\n // 重置到第一页并重新查询\n current.value = 1;\n get_datasource();\n }\n };\n\n const resetConditions = () => {\n conditionsRoot.value = { type: 'and', conditions: [] };\n timeRange.value = null;\n };\n const applySearch = () => {\n current.value = 1;\n get_datasource();\n };\n\n onMounted(() => {\n window.addEventListener('resize', onresize);\n onresize();\n get_column_config();\n let start_time = '';\n if (props.record?.data_start_date) {\n start_time = props.record?.data_start_date + ' 00:00:00';\n } else {\n start_time = dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss');\n }\n timeRange.value = [start_time, dayjs().format('YYYY-MM-DD HH:mm:ss')];\n });\n onUnmounted(() => {\n window.removeEventListener('resize', onresize);\n });\n return {\n height,\n handleCancel,\n open,\n column,\n width,\n datasource,\n current,\n pageSize,\n total,\n handlePageChange,\n pageOptions,\n // 条件\n conditionsRoot,\n resetConditions,\n applySearch,\n loading,\n handleTableChange,\n // 时间范围\n timeRange,\n };\n },\n});\n</script>\n<style lang=\"less\" scoped>\n.qb-group {\n border-left: 2px solid #f0f0f0;\n padding-left: 8px;\n}\n.qb-group-head {\n display: flex;\n gap: 8px;\n margin-bottom: 8px;\n}\n.qb-group-body {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.qb-rule {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.qb-subgroup {\n padding: 6px 0 6px 8px;\n border-left: 2px dashed #f0f0f0;\n}\n.conditions-scroll {\n max-height: 200px;\n overflow-y: auto;\n padding-right: 8px;\n}\n.time-range-row {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 8px;\n}\n.field-label {\n font-weight: 500;\n color: rgba(0, 0, 0, 0.85);\n}\n</style>\n","<template>\n <a-modal\n title=\"原始数据 (仅返回50条数据)\"\n width=\"1300px\"\n v-model:open=\"open\"\n :maskClosable=\"false\"\n centered\n @cancel=\"handleCancel\"\n >\n <div>\n <a-card\n size=\"small\"\n :bordered=\"false\"\n style=\"margin-bottom: 12px\"\n >\n <a-space\n direction=\"vertical\"\n :size=\"8\"\n style=\"width: 100%\"\n >\n <div style=\"display: flex; justify-content: space-between; align-items: center\">\n <div style=\"font-weight: 600\">查询条件</div>\n <div>\n <a-button\n size=\"small\"\n @click=\"resetConditions\"\n >\n 重置\n </a-button>\n <a-button\n type=\"primary\"\n size=\"small\"\n style=\"margin-left: 8px\"\n @click=\"applySearch\"\n >\n 查询\n </a-button>\n </div>\n </div>\n <div class=\"conditions-scroll\">\n <ConditionGroup\n :group=\"conditionsRoot\"\n :columns=\"column\"\n :is-root=\"true\"\n />\n </div>\n <div class=\"time-range-row\">\n <span class=\"field-label\">时间范围:</span>\n <a-range-picker\n v-model:value=\"timeRange\"\n :show-time=\"{ format: 'HH:mm:ss' }\"\n format=\"YYYY-MM-DD HH:mm:ss\"\n value-format=\"YYYY-MM-DD HH:mm:ss\"\n :placeholder=\"['开始时间', '结束时间']\"\n style=\"width: 440px\"\n />\n </div>\n </a-space>\n </a-card>\n <a-spin :spinning=\"loading\">\n <s-table\n v-if=\"datasource?.length\"\n :columns=\"column\"\n :dataSource=\"datasource\"\n rowKey=\"_id\"\n :pagination=\"false\"\n :scroll=\"{ y: height, x: width }\"\n @change=\"handleTableChange\"\n >\n <template #headerCell=\"{ title, column }\">\n <a-tag\n color=\"blue\"\n v-if=\"column.isMatch\"\n >\n {{ title }}\n </a-tag>\n <span v-if=\"!column.isMatch\">\n {{ title }}\n </span>\n </template>\n </s-table>\n </a-spin>\n </div>\n <template #footer>\n <a-button\n key=\"back\"\n @click=\"handleCancel\"\n >\n 关闭\n </a-button>\n <a-button\n key=\"ok\"\n type=\"primary\"\n @click=\"handleCancel\"\n >\n 确定\n </a-button>\n </template>\n </a-modal>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, onUnmounted, ref, onMounted, computed } from 'vue';\nimport { debounce } from 'lodash';\nimport ConditionGroup from './syncConditionGroup.vue';\nimport { getApiData } from 'liyu-pc-base/api/api-model';\nimport dayjs from 'dayjs';\n\nexport default defineComponent({\n name: 'ExportRawData',\n components: { ConditionGroup },\n props: {\n title: {\n type: String,\n required: false,\n default: () => '',\n },\n record: {\n type: Object,\n required: false,\n default: () => ({}),\n },\n },\n setup(props, { emit }) {\n const height = ref(window.innerHeight - 550);\n const onresize: any = debounce(() => {\n height.value = window.innerHeight - 550;\n }, 200);\n const loading = ref(false);\n\n const datasource = ref<any[]>([]);\n const open = ref(true);\n const titleMap = ref({});\n\n const column = computed(() => {\n const res = [];\n if (datasource.value?.length) {\n for (const key of Object.keys(datasource.value[0])) {\n if (key != '_id') {\n const title = titleMap.value[key] || key;\n res.push({\n title: title,\n dataIndex: key,\n key: key,\n isMatch: !!titleMap.value[key],\n });\n }\n }\n }\n return res;\n });\n\n const get_column_config = async () => {\n const res: any = await getApiData(\n {\n export_task_id: (props as any).record?.id,\n },\n `${props.record.model_app}/query_sync_model_field_list`,\n );\n if (res?.data?.length) {\n for (const item of res.data) {\n titleMap.value[item.field_name] = item.name;\n titleMap.value[item.name] = item.name;\n }\n }\n };\n const current = ref<number>(1);\n const pageSize = ref<number>(10);\n const total = ref<number>(0);\n const width = computed(() => {\n let w = column.value.length * 200;\n return w < 1000 ? 1000 : w;\n });\n const pageOptions = ref<string[]>([\n '10',\n '20',\n '50',\n '100',\n '500',\n '1000',\n '2000',\n '5000',\n '10000',\n ]);\n\n // 条件构建器根\n const conditionsRoot = ref<any>({ type: 'and', conditions: [] });\n\n // 时间范围([start, end] 字符串,格式 YYYY-MM-DD HH:mm:ss;用于后端 start_time/end_time)\n const timeRange = ref<any>(null);\n\n // 排序状态 - 支持多字段排序\n const sortFields = ref<Array<{ field: string; order: string }>>([]);\n\n const handleCancel = () => {\n emit('cancel');\n };\n\n const get_datasource = async () => {\n loading.value = true;\n try {\n const params: any = {\n export_task_id: (props as any).record?.id,\n current: current.value,\n pageSize: pageSize.value,\n conditions: conditionsRoot.value,\n sort_fields: sortFields.value.length > 0 ? sortFields.value : undefined,\n };\n // 时间范围:前端传则带上(供后端作为 start_time/end_time 渲染 SQL),不传则回退默认区间\n if (\n timeRange.value &&\n timeRange.value.length === 2 &&\n timeRange.value[0] &&\n timeRange.value[1]\n ) {\n params.time_range = timeRange.value;\n }\n const res: any = await getApiData(params, `${props.record.model_app}/query_raw_data`);\n\n const tmpData = res?.data || [];\n let index = 0;\n for (const item of tmpData) {\n item._id = index;\n index++;\n }\n datasource.value = tmpData;\n current.value = res?.current ?? current.value;\n pageSize.value = res?.pageSize ?? pageSize.value;\n total.value = res?.total ?? 0;\n } finally {\n loading.value = false;\n }\n };\n\n const handlePageChange = (page: number, size?: number) => {\n current.value = page;\n if (size && size !== pageSize.value) pageSize.value = size;\n get_datasource();\n };\n\n const handleTableChange = (pagination: any, filters: any, sorter: any, { action }: any) => {\n if (action === 'sort') {\n if (sorter?.field) {\n const field = sorter.field;\n const order = sorter.order === 'ascend' ? 'asc' : 'desc';\n sortFields.value = [{ field: field, order: order }];\n } else {\n // 清除所有排序\n sortFields.value = [];\n }\n\n // 重置到第一页并重新查询\n current.value = 1;\n get_datasource();\n }\n };\n\n const resetConditions = () => {\n conditionsRoot.value = { type: 'and', conditions: [] };\n timeRange.value = null;\n };\n const applySearch = () => {\n current.value = 1;\n get_datasource();\n };\n\n onMounted(() => {\n window.addEventListener('resize', onresize);\n onresize();\n get_column_config();\n let start_time = '';\n if (props.record?.data_start_date) {\n start_time = props.record?.data_start_date + ' 00:00:00';\n } else {\n start_time = dayjs().subtract(1, 'day').format('YYYY-MM-DD HH:mm:ss');\n }\n timeRange.value = [start_time, dayjs().format('YYYY-MM-DD HH:mm:ss')];\n });\n onUnmounted(() => {\n window.removeEventListener('resize', onresize);\n });\n return {\n height,\n handleCancel,\n open,\n column,\n width,\n datasource,\n current,\n pageSize,\n total,\n handlePageChange,\n pageOptions,\n // 条件\n conditionsRoot,\n resetConditions,\n applySearch,\n loading,\n handleTableChange,\n // 时间范围\n timeRange,\n };\n },\n});\n</script>\n<style lang=\"less\" scoped>\n.qb-group {\n border-left: 2px solid #f0f0f0;\n padding-left: 8px;\n}\n.qb-group-head {\n display: flex;\n gap: 8px;\n margin-bottom: 8px;\n}\n.qb-group-body {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.qb-rule {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.qb-subgroup {\n padding: 6px 0 6px 8px;\n border-left: 2px dashed #f0f0f0;\n}\n.conditions-scroll {\n max-height: 200px;\n overflow-y: auto;\n padding-right: 8px;\n}\n.time-range-row {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 8px;\n}\n.field-label {\n font-weight: 500;\n color: rgba(0, 0, 0, 0.85);\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"syncModelTypeConfig-B2vV_kKb.js","names":[],"sources":["../src/components/lysync/syncModelTypeConfig.vue","../src/components/lysync/syncModelTypeConfig.vue"],"sourcesContent":["<template>\n <a-modal\n :title=\"title\"\n width=\"1040px\"\n :open=\"open\"\n :bodyStyle=\"{ maxHeight: '80vh', overflowY: 'auto', padding: '0 24px' }\"\n :confirm-loading=\"loading\"\n cancel-text=\"取消\"\n ok-text=\"确定\"\n centered\n destroy-on-close\n @cancel=\"handleCancel\"\n @ok=\"handleOk\"\n >\n <template #footer>\n <div class=\"modal-footer\">\n <a-alert\n v-if=\"fieldValidationMsg\"\n class=\"footer-error\"\n type=\"error\"\n show-icon\n :message=\"fieldValidationMsg\"\n />\n <div class=\"footer-buttons\">\n <a-button @click=\"handleCancel\">取消</a-button>\n <a-button\n type=\"primary\"\n :loading=\"loading\"\n :disabled=\"!!fieldValidationMsg\"\n @click=\"handleOk\"\n >\n 确定\n </a-button>\n </div>\n </div>\n </template>\n <a-form\n ref=\"formRef\"\n :model=\"modelType\"\n layout=\"vertical\"\n size=\"small\"\n >\n <a-divider orientation=\"left\">基本信息</a-divider>\n <a-row :gutter=\"16\">\n <a-col :span=\"8\">\n <a-form-item\n label=\"数据模型(app_label.model)\"\n name=\"app_label\"\n :rules=\"rules.app_label\"\n >\n <a-select\n v-model:value=\"selectedModel\"\n placeholder=\"请选择数据模型\"\n :options=\"contentTypes\"\n option-filter-prop=\"label\"\n show-search\n allow-clear\n :loading=\"ctLoading\"\n @change=\"onModelChange\"\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"8\">\n <a-form-item\n label=\"模型名称\"\n name=\"name\"\n :rules=\"rules.name\"\n >\n <a-input\n v-model:value=\"modelType.name\"\n placeholder=\"请填写模型名称\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"8\">\n <a-form-item\n label=\"排序\"\n name=\"sorted\"\n >\n <a-input-number\n v-model:value=\"modelType.sorted\"\n style=\"width: 100%\"\n :min=\"0\"\n :precision=\"0\"\n />\n </a-form-item>\n </a-col>\n </a-row>\n\n <!-- 数据集级处理函数列表(SyncModelType.process_list,对应 DATASET scope) -->\n <a-divider orientation=\"left\">数据处理(数据集级)</a-divider>\n <div\n v-for=\"(item, idx) in modelType.process_list\"\n :key=\"idx\"\n class=\"pl-item\"\n >\n <a-select\n v-model:value=\"item.func\"\n :options=\"processFuncOptions.dataset\"\n placeholder=\"选择处理函数\"\n option-filter-prop=\"label\"\n show-search\n allow-clear\n style=\"width: 340px\"\n />\n <a-textarea\n v-model:value=\"item.kwargs_str\"\n placeholder='可选,JSON 对象,如 {\"key\":\"val\"}'\n :auto-size=\"{ minRows: 3, maxRows: 8 }\"\n style=\"width: 340px\"\n allow-clear\n @update:value=\"val => onKwargsChange(item, val)\"\n />\n <a-button\n danger\n type=\"link\"\n @click=\"removeProcessItem(modelType.process_list, idx)\"\n >\n 删除\n </a-button>\n </div>\n <a-button\n type=\"dashed\"\n block\n @click=\"addProcessItem(modelType.process_list)\"\n >\n + 添加数据集级处理函数\n </a-button>\n\n <!-- 字段配置区(SyncModelField) -->\n <a-divider orientation=\"left\">字段配置</a-divider>\n <div class=\"field-hint\">\n 选择数据模型后自动加载其表字段;可增删字段行,并配置每个字段的同步属性与处理函数。\n 提交前需满足:至少选择一个「唯一字段」和一个「时间索引」字段,且两者都必须是「导入字段」。\n </div>\n <div\n v-if=\"fieldRows.length > 0\"\n class=\"import-col-toolbar\"\n >\n <span class=\"import-col-label\">导入列:</span>\n <a-button\n size=\"small\"\n @click=\"setAllImportColumn(true)\"\n >\n 全选\n </a-button>\n <a-button\n size=\"small\"\n @click=\"setAllImportColumn(false)\"\n >\n 全不选\n </a-button>\n <a-button\n size=\"small\"\n @click=\"invertImportColumn\"\n >\n 反选\n </a-button>\n </div>\n <a-empty\n v-if=\"fieldRows.length === 0\"\n description=\"暂无字段,请先选择数据模型或点击新增字段\"\n />\n <a-card\n v-for=\"(row, fidx) in fieldRows\"\n :key=\"row.id || 'new_' + fidx\"\n size=\"small\"\n class=\"field-card\"\n >\n <template #title>字段 {{ row.field_name }}</template>\n <!-- <template #extra>\n <a-button\n danger\n type=\"link\"\n size=\"small\"\n @click=\"removeField(fidx)\"\n >\n 删除字段\n </a-button>\n </template> -->\n <a-row :gutter=\"16\">\n <a-col :span=\"8\">\n <a-form-item label=\"字段名\">\n <span class=\"field-name\">{{ row.field_name }}</span>\n <!-- <a-input\n v-model:value=\"row.field_name\"\n placeholder=\"字段名\"\n allow-clear\n readonly\n /> -->\n </a-form-item>\n </a-col>\n <a-col :span=\"8\">\n <a-form-item label=\"显示名\">\n <a-input\n v-model:value=\"row.name\"\n placeholder=\"字段显示名\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"8\">\n <a-form-item>\n <a-checkbox v-model:checked=\"row.is_unique\">唯一字段</a-checkbox>\n <a-checkbox\n v-model:checked=\"row.is_datetime_index\"\n @change=\"onDatetimeIndexChange($event, row)\"\n >\n 时间索引\n </a-checkbox>\n <a-checkbox v-model:checked=\"row.is_import_column\">导入列</a-checkbox>\n </a-form-item>\n </a-col>\n </a-row>\n <!-- 字段级处理函数列表(SyncModelField.process_list,对应 FIELD scope) -->\n <div class=\"pl-subtitle\">字段级处理函数</div>\n <div\n v-for=\"(item, idx) in row.process_list\"\n :key=\"idx\"\n class=\"pl-item\"\n >\n <a-select\n v-model:value=\"item.func\"\n :options=\"processFuncOptions.field\"\n placeholder=\"选择处理函数\"\n option-filter-prop=\"label\"\n show-search\n allow-clear\n style=\"width: 340px\"\n />\n <a-textarea\n v-model:value=\"item.kwargs_str\"\n placeholder='可选,JSON 对象,如 {\"key\":\"val\"}'\n :auto-size=\"{ minRows: 1, maxRows: 3 }\"\n style=\"width: 340px\"\n allow-clear\n @update:value=\"val => onKwargsChange(item, val)\"\n />\n <a-button\n danger\n type=\"link\"\n @click=\"removeProcessItem(row.process_list, idx)\"\n >\n 删除\n </a-button>\n </div>\n <a-button\n type=\"dashed\"\n block\n size=\"small\"\n @click=\"addProcessItem(row.process_list)\"\n >\n + 添加字段级处理函数\n </a-button>\n </a-card>\n <!-- <a-button\n type=\"primary\"\n ghost\n block\n style=\"margin-top: 12px\"\n :disabled=\"form_type === 'create' && !modelType.app_label\"\n @click=\"addField\"\n >\n + 新增字段\n </a-button> -->\n </a-form>\n </a-modal>\n</template>\n\n<script setup lang=\"ts\">\nimport { message } from 'ant-design-vue';\nimport { computed, onMounted, reactive, ref } from 'vue';\nimport { event_obj } from 'liyu-pc-base/init-plugin';\nimport { getApiData } from 'liyu-pc-base/api/api-model';\nimport { useUserStore } from 'liyu-pc-base/init-plugin';\n\ninterface Props {\n title: string;\n record: any;\n}\nconst props = defineProps<Props>();\nconst emit = defineEmits(['cancel', 'ok']);\n\nconst formRef = ref<any>(null);\nconst loading = ref(false);\nconst ctLoading = ref(false);\nconst open = ref(true);\nconst form_type = ref('create');\nconst userStore = useUserStore();\nconst current_org = userStore.currentOrg;\n\n/* 选项数据 */\nconst contentTypes = ref<any[]>([]); // ContentType 列表 [{value, label}]\nconst modelFields = ref<any[]>([]); // 当前数据模型的字段名列表 [{value, label}]\nconst processFuncOptions = ref<{ dataset: any[]; field: any[] }>({ dataset: [], field: [] });\n\n/* 基本信息(SyncModelType) */\nconst modelType = ref<any>({\n id: undefined,\n name: undefined,\n app_label: undefined, // 应用标签(字符串)\n model: undefined, // 模型名(字符串)\n sorted: 0,\n process_list: [] as any[], // [{func, kwargs_str}]\n});\n\n/* 选中的内容类型(下拉值,用于回显;实际保存用 app_label + model 字符串) */\nconst selectedModel = ref<any>(undefined);\n\n/* 字段行(SyncModelField) */\nconst fieldRows = ref<any[]>([]);\n\nconst rules = reactive<any>({\n name: [{ required: true, message: '请填写模型名称' }],\n app_label: [{ required: true, message: '请选择数据模型' }],\n});\n\n/* 字段约束校验(响应式):\n * 1. 必须至少有一个「唯一字段」;\n * 2. 必须至少有一个「时间索引」字段;\n * 3. 被选为「唯一字段」与「时间索引」的字段,必须是「导入字段」(导入列已勾选)。\n * 不达标时返回提示文案,否则返回 null。 */\nconst fieldValidationMsg = computed<string | null>(() => {\n const rows: any[] = fieldRows.value;\n if (rows.length === 0) return null; // 尚未加载字段(如未选数据模型),交给其它校验处理\n const uniqueFields = rows.filter(f => f.is_unique);\n const datetimeFields = rows.filter(f => f.is_datetime_index);\n if (uniqueFields.length === 0) return '请至少选择一个「唯一字段」';\n if (uniqueFields.some(f => !f.is_import_column))\n return '「唯一字段」必须是导入字段,请勾选该字段的「导入列」';\n if (datetimeFields.length === 0) return '请至少选择一个「时间索引」字段';\n if (datetimeFields.some(f => !f.is_import_column))\n return '「时间索引」字段必须是导入字段,请勾选该字段的「导入列」';\n return null;\n});\n\n/* 兼容 getApiData 可能的返回结构(直接数组 或 {data:[...]}) */\nconst extractData = (res: any) => {\n if (res && typeof res === 'object' && !Array.isArray(res) && res.data !== undefined)\n return res.data;\n return res;\n};\nconst toList = (res: any): any[] => {\n const d = extractData(res);\n return Array.isArray(d) ? d : [];\n};\n\n/* 后端 process_list 是 JSONField,返回 [{func, kwargs}](kwargs 为对象)。\n 直接保留 kwargs 对象作为数据源,仅用 kwargs_str 承载多行文本框的展示文本。 */\nconst mapProcessList = (pl: any): any[] => {\n if (!Array.isArray(pl)) return [];\n return pl.map((item: any) => {\n const kwargs = item?.kwargs ?? null;\n return {\n func: item?.func || undefined,\n kwargs,\n kwargs_str: kwargs ? JSON.stringify(kwargs, null, 2) : '',\n };\n });\n};\n\n/* 将 JSON 文本解析为对象;空串返回 {};解析失败抛出错误(仅作为兜底校验) */\nconst parseKwargs = (str: string, label: string): any => {\n if (str === undefined || str === null || String(str).trim() === '') return {};\n try {\n return JSON.parse(str);\n } catch (e) {\n throw new Error(`${label} 的 kwargs 不是合法 JSON: ${(e as Error).message}`);\n }\n};\n\n/* 文本框输入时实时把 JSON 文本解析为对象,提交时直接复用,避免提交时再 JSON.parse。\n 解析失败(输入中 JSON 尚不完整)则保留上次合法值,由提交时的兜底解析负责校验。 */\nconst onKwargsChange = (item: any, val: string) => {\n item.kwargs_str = val;\n const text = (val || '').trim();\n if (!text) {\n item.kwargs = null;\n return;\n }\n try {\n item.kwargs = JSON.parse(text);\n } catch {\n /* 暂不完整,等待用户补全 */\n }\n};\n\n/* 构建提交用的 process_list:优先复用已解析的 kwargs 对象(@update:value 实时解析),\n 否则兜底解析 kwargs_str;空则 {}。常规路径不再做 JSON.parse。 */\nconst buildProcessList = (list: any[]): any[] => {\n const out: any[] = [];\n for (const item of list || []) {\n if (!item.func) continue;\n let kwargs: any;\n if (item.kwargs && typeof item.kwargs === 'object') {\n kwargs = item.kwargs;\n } else if (item.kwargs_str && String(item.kwargs_str).trim()) {\n kwargs = parseKwargs(item.kwargs_str, item.func);\n } else {\n kwargs = {};\n }\n out.push({ func: item.func, kwargs });\n }\n return out;\n};\n\n/* 读取内容类型(数据模型类)列表 */\nconst loadContentTypes = async () => {\n ctLoading.value = true;\n try {\n const res = await getApiData(\n { org_id: current_org?.id },\n `${props.record.model_app}/get_content_types`,\n );\n contentTypes.value = toList(res);\n } catch (e) {\n contentTypes.value = [];\n } finally {\n ctLoading.value = false;\n }\n};\n\n/* 读取处理函数列表(按 scope 分组) */\nconst loadProcessFuncs = async () => {\n try {\n const res = await getApiData(\n { org_id: current_org?.id },\n `${props.record.model_app}/get_process_funcs`,\n );\n const data = extractData(res) || {};\n processFuncOptions.value = { dataset: data.dataset || [], field: data.field || [] };\n } catch (e) {\n processFuncOptions.value = { dataset: [], field: [] };\n }\n};\n\n/* 反射指定 (app_label, model) 对应模型的字段名 */\nconst loadModelFields = async (app_label: any, model: any) => {\n try {\n const res = await getApiData(\n { app_label, model, org_id: current_org?.id },\n `${props.record.model_app}/get_model_fields`,\n );\n modelFields.value = toList(res);\n } catch (e) {\n modelFields.value = [];\n }\n};\n\n/* 编辑:调用公共项目提供的详情接口(<app_flag>/get_<model_name>,即 get_basemodel)\n 获取完整的 SyncModelType 记录。不依赖列表行数据 props.record,因为它可能缺失\n process_list 等字段。get_basemodel 返回单条 dict(非列表),按 id 查询(参数名\n 为 <model_name>_id,即 sync_model_type_id)。 */\nconst loadModelTypeDetail = async (id: any) => {\n const res = await getApiData(\n { sync_model_type_id: id, org_id: current_org?.id },\n `${props.record.model_app}/get_sync_model_type`,\n );\n return extractData(res) || {};\n};\n\n/* 编辑:加载已保存的字段配置(SyncModelField,公共项目通用列表接口按 model_type_id 过滤),\n 返回列表(不直接改 fieldRows,后续由 mergeFieldsWithSaved 与最新模型字段合并) */\nconst loadSavedFields = async (modelTypeId: any): Promise<any[]> => {\n try {\n const res = await getApiData(\n { model_type_id: modelTypeId, page: 1, page_size: 999, org_id: current_org?.id },\n `${props.record.model_app}/query_sync_model_field_list`,\n );\n return toList(res);\n } catch (e) {\n return [];\n }\n};\n\n/* 编辑:以最新模型字段为准(不能多、不能少),按 field_name 与已保存配置合并。\n - 遍历最新模型字段(modelFields),每行对应一个最新字段;\n - 若已保存配置中存在相同 field_name:沿用其配置标志与 process_list,并保留 id(便于后端按更新处理);\n - 若不存在:使用默认值(id 为空,保存时按新建处理)。\n - 已保存但最新模型中已不存在的字段:丢弃(实现「不能多」)。 */\nconst mergeFieldsWithSaved = (saved: any[]) => {\n const savedByFieldName = new Map<any, any>();\n (saved || []).forEach((f: any) => {\n if (f.field_name != null) savedByFieldName.set(f.field_name, f);\n });\n fieldRows.value = modelFields.value.map((mf: any) => {\n const fn = mf.value;\n const s = savedByFieldName.get(fn);\n return {\n id: s?.id,\n name: mf.verbose_name || mf.value,\n field_name: fn,\n is_unique: s ? !!s.is_unique : false,\n is_datetime_index: s ? !!s.is_datetime_index : false,\n is_import_column: s ? s.is_import_column !== false : true,\n process_list: s ? mapProcessList(s.process_list) : [],\n };\n });\n normalizeDatetimeIndex();\n};\n\n/* 选择数据模型后,加载字段名并按新模型的字段初始化字段行。\n 无论是新建还是编辑(编辑时切换了数据模型),都应重建字段行——\n 原配置字段属于旧模型,切换后不再适用,需用新模型字段重新生成。 */\nconst onModelChange = async (val: any) => {\n const opt = contentTypes.value.find((o: any) => o.value === val);\n modelType.value.app_label = opt?.app_label;\n modelType.value.model = opt?.model;\n modelFields.value = [];\n fieldRows.value = [];\n if (!opt?.app_label || !opt?.model) return;\n await loadModelFields(opt.app_label, opt.model);\n fieldRows.value = modelFields.value.map(f => {\n let is_import_column = true;\n if (['id', 'is_active', 'create_time', 'update_time'].includes(f.value)) {\n is_import_column = false;\n }\n return {\n name: f.verbose_name || f.value,\n field_name: f.value,\n is_unique: false,\n is_datetime_index: false,\n is_import_column,\n process_list: [],\n };\n });\n};\n\nconst addProcessItem = (list: any[]) =>\n list.push({ func: undefined, kwargs: null, kwargs_str: '' });\nconst removeProcessItem = (list: any[], idx: number) => list.splice(idx, 1);\n\n/* 批量控制「是否为导入列」:全选 / 全不选 / 反选 */\nconst setAllImportColumn = (val: boolean) => {\n fieldRows.value.forEach((f: any) => {\n f.is_import_column = val;\n });\n};\nconst invertImportColumn = () => {\n fieldRows.value.forEach((f: any) => {\n f.is_import_column = !f.is_import_column;\n });\n};\n\n/* 时间索引字段互斥单选:勾选某行时,其余行自动取消勾选 */\nconst onDatetimeIndexChange = (e: any, row: any) => {\n if (e?.target?.checked) {\n fieldRows.value.forEach((f: any) => {\n if (f !== row) f.is_datetime_index = false;\n });\n }\n};\n\n/* 编辑加载后规范化:若有多条时间索引为 true(脏数据),仅保留第一条 */\nconst normalizeDatetimeIndex = () => {\n let found = false;\n fieldRows.value.forEach((f: any) => {\n if (f.is_datetime_index) {\n if (found) f.is_datetime_index = false;\n else found = true;\n }\n });\n};\n\n/* 编辑初始化:先调用详情接口拿完整数据,避免依赖可能缺字段的列表行 */\nconst initForEdit = async () => {\n const id = props.record?.id;\n if (!id) return;\n const d = await loadModelTypeDetail(id);\n modelType.value = {\n id: d.id,\n name: d.name || '',\n app_label: d.app_label || undefined,\n model: d.model || undefined,\n sorted: d.sorted ?? 0,\n process_list: mapProcessList(d.process_list),\n };\n // 下拉回显:根据 app_label + model 匹配选项\n const matched = contentTypes.value.find(\n (o: any) => o.app_label === modelType.value.app_label && o.model === modelType.value.model,\n );\n selectedModel.value = matched?.value ?? undefined;\n if (modelType.value.app_label && modelType.value.model) {\n // 1) 先查最新模型字段(get_model_fields),保证字段是最新的、与模型一致\n await loadModelFields(modelType.value.app_label, modelType.value.model);\n // 2) 再查已保存的字段配置(SyncModelField),按 field_name 合并进最新字段\n const saved = await loadSavedFields(id);\n mergeFieldsWithSaved(saved);\n }\n};\n\n/* 构建字段提交数据 */\nconst buildFieldPayload = (row: any) => ({\n name: row.name,\n field_name: row.field_name,\n is_unique: !!row.is_unique,\n is_datetime_index: !!row.is_datetime_index,\n is_import_column: !!row.is_import_column,\n process_list: buildProcessList(row.process_list),\n});\n\nconst handleOk = async () => {\n try {\n await formRef.value.validate();\n if (!modelType.value.name) {\n message.error('请填写模型名称');\n return;\n }\n if (form_type.value === 'create' && (!modelType.value.app_label || !modelType.value.model)) {\n message.error('请选择数据模型');\n return;\n }\n // 字段约束:唯一字段 / 时间索引 必选且须为导入字段\n const fieldMsg = fieldValidationMsg.value;\n if (fieldMsg) {\n message.error(fieldMsg);\n return;\n }\n // 校验数据集级 process_list 的 JSON\n let mtProcessList: any[];\n try {\n mtProcessList = buildProcessList(modelType.value.process_list);\n } catch (e: any) {\n message.error(e.message);\n return;\n }\n const app = props.record.model_app;\n const org_id = current_org?.id;\n loading.value = true;\n\n // 一站式保存:有 id 走更新,无 id 走创建;字段的增/改/删由后端统一处理\n const field_list: any[] = [];\n for (const row of fieldRows.value) {\n let fp: any;\n try {\n fp = buildFieldPayload(row);\n } catch (e: any) {\n message.error(e.message);\n return;\n }\n if (row.id) fp.id = row.id;\n field_list.push(fp);\n }\n const mtPayload: any = {\n name: modelType.value.name,\n app_label: modelType.value.app_label,\n model: modelType.value.model,\n sorted: Number(modelType.value.sorted) || 0,\n process_list: mtProcessList,\n field_list,\n org_id,\n };\n if (form_type.value === 'edit') mtPayload.id = modelType.value.id;\n await getApiData({ ...mtPayload }, `${app}/save_sync_model_type`);\n message.success('操作成功');\n event_obj()?.emit('refresh');\n emit('ok');\n } catch (error) {\n // 校验失败或接口异常:保持弹窗打开\n } finally {\n loading.value = false;\n }\n};\nconst handleCancel = () => emit('cancel');\n\nonMounted(async () => {\n if (props.record?.form_type) form_type.value = props.record.form_type;\n await Promise.all([loadContentTypes(), loadProcessFuncs()]);\n if (form_type.value === 'edit') await initForEdit();\n});\n</script>\n\n<style scoped lang=\"less\">\n.field-hint {\n color: #999;\n font-size: 12px;\n margin-bottom: 8px;\n}\n.pl-item {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n margin-bottom: 8px;\n}\n.pl-subtitle {\n color: rgba(0, 0, 0, 0.85);\n font-size: 13px;\n font-weight: 500;\n margin: 4px 0 8px;\n}\n.field-card {\n margin-bottom: 12px;\n}\n.import-col-toolbar {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n.import-col-label {\n color: rgba(0, 0, 0, 0.85);\n font-size: 13px;\n}\n.modal-footer {\n display: flex;\n flex-direction: column;\n}\n.footer-error {\n margin-bottom: 8px;\n}\n.footer-buttons {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.field-name {\n font-weight: 500;\n color: #1890ff;\n}\n</style>\n","<template>\n <a-modal\n :title=\"title\"\n width=\"1040px\"\n :open=\"open\"\n :bodyStyle=\"{ maxHeight: '80vh', overflowY: 'auto', padding: '0 24px' }\"\n :confirm-loading=\"loading\"\n cancel-text=\"取消\"\n ok-text=\"确定\"\n centered\n destroy-on-close\n @cancel=\"handleCancel\"\n @ok=\"handleOk\"\n >\n <template #footer>\n <div class=\"modal-footer\">\n <a-alert\n v-if=\"fieldValidationMsg\"\n class=\"footer-error\"\n type=\"error\"\n show-icon\n :message=\"fieldValidationMsg\"\n />\n <div class=\"footer-buttons\">\n <a-button @click=\"handleCancel\">取消</a-button>\n <a-button\n type=\"primary\"\n :loading=\"loading\"\n :disabled=\"!!fieldValidationMsg\"\n @click=\"handleOk\"\n >\n 确定\n </a-button>\n </div>\n </div>\n </template>\n <a-form\n ref=\"formRef\"\n :model=\"modelType\"\n layout=\"vertical\"\n size=\"small\"\n >\n <a-divider orientation=\"left\">基本信息</a-divider>\n <a-row :gutter=\"16\">\n <a-col :span=\"8\">\n <a-form-item\n label=\"数据模型(app_label.model)\"\n name=\"app_label\"\n :rules=\"rules.app_label\"\n >\n <a-select\n v-model:value=\"selectedModel\"\n placeholder=\"请选择数据模型\"\n :options=\"contentTypes\"\n option-filter-prop=\"label\"\n show-search\n allow-clear\n :loading=\"ctLoading\"\n @change=\"onModelChange\"\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"8\">\n <a-form-item\n label=\"模型名称\"\n name=\"name\"\n :rules=\"rules.name\"\n >\n <a-input\n v-model:value=\"modelType.name\"\n placeholder=\"请填写模型名称\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"8\">\n <a-form-item\n label=\"排序\"\n name=\"sorted\"\n >\n <a-input-number\n v-model:value=\"modelType.sorted\"\n style=\"width: 100%\"\n :min=\"0\"\n :precision=\"0\"\n />\n </a-form-item>\n </a-col>\n </a-row>\n\n <!-- 数据集级处理函数列表(SyncModelType.process_list,对应 DATASET scope) -->\n <a-divider orientation=\"left\">数据处理(数据集级)</a-divider>\n <div\n v-for=\"(item, idx) in modelType.process_list\"\n :key=\"idx\"\n class=\"pl-item\"\n >\n <a-select\n v-model:value=\"item.func\"\n :options=\"processFuncOptions.dataset\"\n placeholder=\"选择处理函数\"\n option-filter-prop=\"label\"\n show-search\n allow-clear\n style=\"width: 340px\"\n />\n <a-textarea\n v-model:value=\"item.kwargs_str\"\n placeholder='可选,JSON 对象,如 {\"key\":\"val\"}'\n :auto-size=\"{ minRows: 3, maxRows: 8 }\"\n style=\"width: 340px\"\n allow-clear\n @update:value=\"val => onKwargsChange(item, val)\"\n />\n <a-button\n danger\n type=\"link\"\n @click=\"removeProcessItem(modelType.process_list, idx)\"\n >\n 删除\n </a-button>\n </div>\n <a-button\n type=\"dashed\"\n block\n @click=\"addProcessItem(modelType.process_list)\"\n >\n + 添加数据集级处理函数\n </a-button>\n\n <!-- 字段配置区(SyncModelField) -->\n <a-divider orientation=\"left\">字段配置</a-divider>\n <div class=\"field-hint\">\n 选择数据模型后自动加载其表字段;可增删字段行,并配置每个字段的同步属性与处理函数。\n 提交前需满足:至少选择一个「唯一字段」和一个「时间索引」字段,且两者都必须是「导入字段」。\n </div>\n <div\n v-if=\"fieldRows.length > 0\"\n class=\"import-col-toolbar\"\n >\n <span class=\"import-col-label\">导入列:</span>\n <a-button\n size=\"small\"\n @click=\"setAllImportColumn(true)\"\n >\n 全选\n </a-button>\n <a-button\n size=\"small\"\n @click=\"setAllImportColumn(false)\"\n >\n 全不选\n </a-button>\n <a-button\n size=\"small\"\n @click=\"invertImportColumn\"\n >\n 反选\n </a-button>\n </div>\n <a-empty\n v-if=\"fieldRows.length === 0\"\n description=\"暂无字段,请先选择数据模型或点击新增字段\"\n />\n <a-card\n v-for=\"(row, fidx) in fieldRows\"\n :key=\"row.id || 'new_' + fidx\"\n size=\"small\"\n class=\"field-card\"\n >\n <template #title>字段 {{ row.field_name }}</template>\n <!-- <template #extra>\n <a-button\n danger\n type=\"link\"\n size=\"small\"\n @click=\"removeField(fidx)\"\n >\n 删除字段\n </a-button>\n </template> -->\n <a-row :gutter=\"16\">\n <a-col :span=\"8\">\n <a-form-item label=\"字段名\">\n <span class=\"field-name\">{{ row.field_name }}</span>\n <!-- <a-input\n v-model:value=\"row.field_name\"\n placeholder=\"字段名\"\n allow-clear\n readonly\n /> -->\n </a-form-item>\n </a-col>\n <a-col :span=\"8\">\n <a-form-item label=\"显示名\">\n <a-input\n v-model:value=\"row.name\"\n placeholder=\"字段显示名\"\n allow-clear\n />\n </a-form-item>\n </a-col>\n <a-col :span=\"8\">\n <a-form-item>\n <a-checkbox v-model:checked=\"row.is_unique\">唯一字段</a-checkbox>\n <a-checkbox\n v-model:checked=\"row.is_datetime_index\"\n @change=\"onDatetimeIndexChange($event, row)\"\n >\n 时间索引\n </a-checkbox>\n <a-checkbox v-model:checked=\"row.is_import_column\">导入列</a-checkbox>\n </a-form-item>\n </a-col>\n </a-row>\n <!-- 字段级处理函数列表(SyncModelField.process_list,对应 FIELD scope) -->\n <div class=\"pl-subtitle\">字段级处理函数</div>\n <div\n v-for=\"(item, idx) in row.process_list\"\n :key=\"idx\"\n class=\"pl-item\"\n >\n <a-select\n v-model:value=\"item.func\"\n :options=\"processFuncOptions.field\"\n placeholder=\"选择处理函数\"\n option-filter-prop=\"label\"\n show-search\n allow-clear\n style=\"width: 340px\"\n />\n <a-textarea\n v-model:value=\"item.kwargs_str\"\n placeholder='可选,JSON 对象,如 {\"key\":\"val\"}'\n :auto-size=\"{ minRows: 1, maxRows: 3 }\"\n style=\"width: 340px\"\n allow-clear\n @update:value=\"val => onKwargsChange(item, val)\"\n />\n <a-button\n danger\n type=\"link\"\n @click=\"removeProcessItem(row.process_list, idx)\"\n >\n 删除\n </a-button>\n </div>\n <a-button\n type=\"dashed\"\n block\n size=\"small\"\n @click=\"addProcessItem(row.process_list)\"\n >\n + 添加字段级处理函数\n </a-button>\n </a-card>\n <!-- <a-button\n type=\"primary\"\n ghost\n block\n style=\"margin-top: 12px\"\n :disabled=\"form_type === 'create' && !modelType.app_label\"\n @click=\"addField\"\n >\n + 新增字段\n </a-button> -->\n </a-form>\n </a-modal>\n</template>\n\n<script setup lang=\"ts\">\nimport { message } from 'ant-design-vue';\nimport { computed, onMounted, reactive, ref } from 'vue';\nimport { event_obj } from 'liyu-pc-base/init-plugin';\nimport { getApiData } from 'liyu-pc-base/api/api-model';\nimport { useUserStore } from 'liyu-pc-base/init-plugin';\n\ninterface Props {\n title: string;\n record: any;\n}\nconst props = defineProps<Props>();\nconst emit = defineEmits(['cancel', 'ok']);\n\nconst formRef = ref<any>(null);\nconst loading = ref(false);\nconst ctLoading = ref(false);\nconst open = ref(true);\nconst form_type = ref('create');\nconst userStore = useUserStore();\nconst current_org = userStore.currentOrg;\n\n/* 选项数据 */\nconst contentTypes = ref<any[]>([]); // ContentType 列表 [{value, label}]\nconst modelFields = ref<any[]>([]); // 当前数据模型的字段名列表 [{value, label}]\nconst processFuncOptions = ref<{ dataset: any[]; field: any[] }>({ dataset: [], field: [] });\n\n/* 基本信息(SyncModelType) */\nconst modelType = ref<any>({\n id: undefined,\n name: undefined,\n app_label: undefined, // 应用标签(字符串)\n model: undefined, // 模型名(字符串)\n sorted: 0,\n process_list: [] as any[], // [{func, kwargs_str}]\n});\n\n/* 选中的内容类型(下拉值,用于回显;实际保存用 app_label + model 字符串) */\nconst selectedModel = ref<any>(undefined);\n\n/* 字段行(SyncModelField) */\nconst fieldRows = ref<any[]>([]);\n\nconst rules = reactive<any>({\n name: [{ required: true, message: '请填写模型名称' }],\n app_label: [{ required: true, message: '请选择数据模型' }],\n});\n\n/* 字段约束校验(响应式):\n * 1. 必须至少有一个「唯一字段」;\n * 2. 必须至少有一个「时间索引」字段;\n * 3. 被选为「唯一字段」与「时间索引」的字段,必须是「导入字段」(导入列已勾选)。\n * 不达标时返回提示文案,否则返回 null。 */\nconst fieldValidationMsg = computed<string | null>(() => {\n const rows: any[] = fieldRows.value;\n if (rows.length === 0) return null; // 尚未加载字段(如未选数据模型),交给其它校验处理\n const uniqueFields = rows.filter(f => f.is_unique);\n const datetimeFields = rows.filter(f => f.is_datetime_index);\n if (uniqueFields.length === 0) return '请至少选择一个「唯一字段」';\n if (uniqueFields.some(f => !f.is_import_column))\n return '「唯一字段」必须是导入字段,请勾选该字段的「导入列」';\n if (datetimeFields.length === 0) return '请至少选择一个「时间索引」字段';\n if (datetimeFields.some(f => !f.is_import_column))\n return '「时间索引」字段必须是导入字段,请勾选该字段的「导入列」';\n return null;\n});\n\n/* 兼容 getApiData 可能的返回结构(直接数组 或 {data:[...]}) */\nconst extractData = (res: any) => {\n if (res && typeof res === 'object' && !Array.isArray(res) && res.data !== undefined)\n return res.data;\n return res;\n};\nconst toList = (res: any): any[] => {\n const d = extractData(res);\n return Array.isArray(d) ? d : [];\n};\n\n/* 后端 process_list 是 JSONField,返回 [{func, kwargs}](kwargs 为对象)。\n 直接保留 kwargs 对象作为数据源,仅用 kwargs_str 承载多行文本框的展示文本。 */\nconst mapProcessList = (pl: any): any[] => {\n if (!Array.isArray(pl)) return [];\n return pl.map((item: any) => {\n const kwargs = item?.kwargs ?? null;\n return {\n func: item?.func || undefined,\n kwargs,\n kwargs_str: kwargs ? JSON.stringify(kwargs, null, 2) : '',\n };\n });\n};\n\n/* 将 JSON 文本解析为对象;空串返回 {};解析失败抛出错误(仅作为兜底校验) */\nconst parseKwargs = (str: string, label: string): any => {\n if (str === undefined || str === null || String(str).trim() === '') return {};\n try {\n return JSON.parse(str);\n } catch (e) {\n throw new Error(`${label} 的 kwargs 不是合法 JSON: ${(e as Error).message}`);\n }\n};\n\n/* 文本框输入时实时把 JSON 文本解析为对象,提交时直接复用,避免提交时再 JSON.parse。\n 解析失败(输入中 JSON 尚不完整)则保留上次合法值,由提交时的兜底解析负责校验。 */\nconst onKwargsChange = (item: any, val: string) => {\n item.kwargs_str = val;\n const text = (val || '').trim();\n if (!text) {\n item.kwargs = null;\n return;\n }\n try {\n item.kwargs = JSON.parse(text);\n } catch {\n /* 暂不完整,等待用户补全 */\n }\n};\n\n/* 构建提交用的 process_list:优先复用已解析的 kwargs 对象(@update:value 实时解析),\n 否则兜底解析 kwargs_str;空则 {}。常规路径不再做 JSON.parse。 */\nconst buildProcessList = (list: any[]): any[] => {\n const out: any[] = [];\n for (const item of list || []) {\n if (!item.func) continue;\n let kwargs: any;\n if (item.kwargs && typeof item.kwargs === 'object') {\n kwargs = item.kwargs;\n } else if (item.kwargs_str && String(item.kwargs_str).trim()) {\n kwargs = parseKwargs(item.kwargs_str, item.func);\n } else {\n kwargs = {};\n }\n out.push({ func: item.func, kwargs });\n }\n return out;\n};\n\n/* 读取内容类型(数据模型类)列表 */\nconst loadContentTypes = async () => {\n ctLoading.value = true;\n try {\n const res = await getApiData(\n { org_id: current_org?.id },\n `${props.record.model_app}/get_content_types`,\n );\n contentTypes.value = toList(res);\n } catch (e) {\n contentTypes.value = [];\n } finally {\n ctLoading.value = false;\n }\n};\n\n/* 读取处理函数列表(按 scope 分组) */\nconst loadProcessFuncs = async () => {\n try {\n const res = await getApiData(\n { org_id: current_org?.id },\n `${props.record.model_app}/get_process_funcs`,\n );\n const data = extractData(res) || {};\n processFuncOptions.value = { dataset: data.dataset || [], field: data.field || [] };\n } catch (e) {\n processFuncOptions.value = { dataset: [], field: [] };\n }\n};\n\n/* 反射指定 (app_label, model) 对应模型的字段名 */\nconst loadModelFields = async (app_label: any, model: any) => {\n try {\n const res = await getApiData(\n { app_label, model, org_id: current_org?.id },\n `${props.record.model_app}/get_model_fields`,\n );\n modelFields.value = toList(res);\n } catch (e) {\n modelFields.value = [];\n }\n};\n\n/* 编辑:调用公共项目提供的详情接口(<app_flag>/get_<model_name>,即 get_basemodel)\n 获取完整的 SyncModelType 记录。不依赖列表行数据 props.record,因为它可能缺失\n process_list 等字段。get_basemodel 返回单条 dict(非列表),按 id 查询(参数名\n 为 <model_name>_id,即 sync_model_type_id)。 */\nconst loadModelTypeDetail = async (id: any) => {\n const res = await getApiData(\n { sync_model_type_id: id, org_id: current_org?.id },\n `${props.record.model_app}/get_sync_model_type`,\n );\n return extractData(res) || {};\n};\n\n/* 编辑:加载已保存的字段配置(SyncModelField,公共项目通用列表接口按 model_type_id 过滤),\n 返回列表(不直接改 fieldRows,后续由 mergeFieldsWithSaved 与最新模型字段合并) */\nconst loadSavedFields = async (modelTypeId: any): Promise<any[]> => {\n try {\n const res = await getApiData(\n { model_type_id: modelTypeId, page: 1, page_size: 999, org_id: current_org?.id },\n `${props.record.model_app}/query_sync_model_field_list`,\n );\n return toList(res);\n } catch (e) {\n return [];\n }\n};\n\n/* 编辑:以最新模型字段为准(不能多、不能少),按 field_name 与已保存配置合并。\n - 遍历最新模型字段(modelFields),每行对应一个最新字段;\n - 若已保存配置中存在相同 field_name:沿用其配置标志与 process_list,并保留 id(便于后端按更新处理);\n - 若不存在:使用默认值(id 为空,保存时按新建处理)。\n - 已保存但最新模型中已不存在的字段:丢弃(实现「不能多」)。 */\nconst mergeFieldsWithSaved = (saved: any[]) => {\n const savedByFieldName = new Map<any, any>();\n (saved || []).forEach((f: any) => {\n if (f.field_name != null) savedByFieldName.set(f.field_name, f);\n });\n fieldRows.value = modelFields.value.map((mf: any) => {\n const fn = mf.value;\n const s = savedByFieldName.get(fn);\n return {\n id: s?.id,\n name: mf.verbose_name || mf.value,\n field_name: fn,\n is_unique: s ? !!s.is_unique : false,\n is_datetime_index: s ? !!s.is_datetime_index : false,\n is_import_column: s ? s.is_import_column !== false : true,\n process_list: s ? mapProcessList(s.process_list) : [],\n };\n });\n normalizeDatetimeIndex();\n};\n\n/* 选择数据模型后,加载字段名并按新模型的字段初始化字段行。\n 无论是新建还是编辑(编辑时切换了数据模型),都应重建字段行——\n 原配置字段属于旧模型,切换后不再适用,需用新模型字段重新生成。 */\nconst onModelChange = async (val: any) => {\n const opt = contentTypes.value.find((o: any) => o.value === val);\n modelType.value.app_label = opt?.app_label;\n modelType.value.model = opt?.model;\n modelFields.value = [];\n fieldRows.value = [];\n if (!opt?.app_label || !opt?.model) return;\n await loadModelFields(opt.app_label, opt.model);\n fieldRows.value = modelFields.value.map(f => {\n let is_import_column = true;\n if (['id', 'is_active', 'create_time', 'update_time'].includes(f.value)) {\n is_import_column = false;\n }\n return {\n name: f.verbose_name || f.value,\n field_name: f.value,\n is_unique: false,\n is_datetime_index: false,\n is_import_column,\n process_list: [],\n };\n });\n};\n\nconst addProcessItem = (list: any[]) =>\n list.push({ func: undefined, kwargs: null, kwargs_str: '' });\nconst removeProcessItem = (list: any[], idx: number) => list.splice(idx, 1);\n\n/* 批量控制「是否为导入列」:全选 / 全不选 / 反选 */\nconst setAllImportColumn = (val: boolean) => {\n fieldRows.value.forEach((f: any) => {\n f.is_import_column = val;\n });\n};\nconst invertImportColumn = () => {\n fieldRows.value.forEach((f: any) => {\n f.is_import_column = !f.is_import_column;\n });\n};\n\n/* 时间索引字段互斥单选:勾选某行时,其余行自动取消勾选 */\nconst onDatetimeIndexChange = (e: any, row: any) => {\n if (e?.target?.checked) {\n fieldRows.value.forEach((f: any) => {\n if (f !== row) f.is_datetime_index = false;\n });\n }\n};\n\n/* 编辑加载后规范化:若有多条时间索引为 true(脏数据),仅保留第一条 */\nconst normalizeDatetimeIndex = () => {\n let found = false;\n fieldRows.value.forEach((f: any) => {\n if (f.is_datetime_index) {\n if (found) f.is_datetime_index = false;\n else found = true;\n }\n });\n};\n\n/* 编辑初始化:先调用详情接口拿完整数据,避免依赖可能缺字段的列表行 */\nconst initForEdit = async () => {\n const id = props.record?.id;\n if (!id) return;\n const d = await loadModelTypeDetail(id);\n modelType.value = {\n id: d.id,\n name: d.name || '',\n app_label: d.app_label || undefined,\n model: d.model || undefined,\n sorted: d.sorted ?? 0,\n process_list: mapProcessList(d.process_list),\n };\n // 下拉回显:根据 app_label + model 匹配选项\n const matched = contentTypes.value.find(\n (o: any) => o.app_label === modelType.value.app_label && o.model === modelType.value.model,\n );\n selectedModel.value = matched?.value ?? undefined;\n if (modelType.value.app_label && modelType.value.model) {\n // 1) 先查最新模型字段(get_model_fields),保证字段是最新的、与模型一致\n await loadModelFields(modelType.value.app_label, modelType.value.model);\n // 2) 再查已保存的字段配置(SyncModelField),按 field_name 合并进最新字段\n const saved = await loadSavedFields(id);\n mergeFieldsWithSaved(saved);\n }\n};\n\n/* 构建字段提交数据 */\nconst buildFieldPayload = (row: any) => ({\n name: row.name,\n field_name: row.field_name,\n is_unique: !!row.is_unique,\n is_datetime_index: !!row.is_datetime_index,\n is_import_column: !!row.is_import_column,\n process_list: buildProcessList(row.process_list),\n});\n\nconst handleOk = async () => {\n try {\n await formRef.value.validate();\n if (!modelType.value.name) {\n message.error('请填写模型名称');\n return;\n }\n if (form_type.value === 'create' && (!modelType.value.app_label || !modelType.value.model)) {\n message.error('请选择数据模型');\n return;\n }\n // 字段约束:唯一字段 / 时间索引 必选且须为导入字段\n const fieldMsg = fieldValidationMsg.value;\n if (fieldMsg) {\n message.error(fieldMsg);\n return;\n }\n // 校验数据集级 process_list 的 JSON\n let mtProcessList: any[];\n try {\n mtProcessList = buildProcessList(modelType.value.process_list);\n } catch (e: any) {\n message.error(e.message);\n return;\n }\n const app = props.record.model_app;\n const org_id = current_org?.id;\n loading.value = true;\n\n // 一站式保存:有 id 走更新,无 id 走创建;字段的增/改/删由后端统一处理\n const field_list: any[] = [];\n for (const row of fieldRows.value) {\n let fp: any;\n try {\n fp = buildFieldPayload(row);\n } catch (e: any) {\n message.error(e.message);\n return;\n }\n if (row.id) fp.id = row.id;\n field_list.push(fp);\n }\n const mtPayload: any = {\n name: modelType.value.name,\n app_label: modelType.value.app_label,\n model: modelType.value.model,\n sorted: Number(modelType.value.sorted) || 0,\n process_list: mtProcessList,\n field_list,\n org_id,\n };\n if (form_type.value === 'edit') mtPayload.id = modelType.value.id;\n await getApiData({ ...mtPayload }, `${app}/save_sync_model_type`);\n message.success('操作成功');\n event_obj()?.emit('refresh');\n emit('ok');\n } catch (error) {\n // 校验失败或接口异常:保持弹窗打开\n } finally {\n loading.value = false;\n }\n};\nconst handleCancel = () => emit('cancel');\n\nonMounted(async () => {\n if (props.record?.form_type) form_type.value = props.record.form_type;\n await Promise.all([loadContentTypes(), loadProcessFuncs()]);\n if (form_type.value === 'edit') await initForEdit();\n});\n</script>\n\n<style scoped lang=\"less\">\n.field-hint {\n color: #999;\n font-size: 12px;\n margin-bottom: 8px;\n}\n.pl-item {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n margin-bottom: 8px;\n}\n.pl-subtitle {\n color: rgba(0, 0, 0, 0.85);\n font-size: 13px;\n font-weight: 500;\n margin: 4px 0 8px;\n}\n.field-card {\n margin-bottom: 12px;\n}\n.import-col-toolbar {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n}\n.import-col-label {\n color: rgba(0, 0, 0, 0.85);\n font-size: 13px;\n}\n.modal-footer {\n display: flex;\n flex-direction: column;\n}\n.footer-error {\n margin-bottom: 8px;\n}\n.footer-buttons {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.field-name {\n font-weight: 500;\n color: #1890ff;\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tableModal-DSKuV-hn.js","names":[],"sources":["../src/components/FormRender/RemoteSelectPro/tableModal.vue","../src/components/FormRender/RemoteSelectPro/tableModal.vue"],"sourcesContent":["<template>\n <a-modal\n :open=\"true\"\n title=\"请选择\"\n width=\"80%\"\n :destroyOnClose=\"true\"\n @ok=\"onOk\"\n >\n <div\n class=\"table-modal\"\n :style=\"{ height: `${height}px` }\"\n >\n <div class=\"header\">\n 当前选择:\n <a-tag\n v-for=\"item in innerValue\"\n :closable=\"true\"\n :key=\"item.value\"\n @close=\"handlerClose(item)\"\n >\n {{ item.label }}\n </a-tag>\n </div>\n <component\n :is=\"asyncTable\"\n :config=\"tableConfig\"\n :initSelectValue=\"initSelectValue\"\n :initValueKey=\"valueKey\"\n @selectChange=\"handlerSelectChange\"\n ></component>\n </div>\n </a-modal>\n</template>\n<script setup lang=\"ts\">\nimport { computed, defineAsyncComponent, onMounted, onUnmounted, ref } from 'vue';\nconst emit = defineEmits(['ok']);\nconst props = defineProps({\n tableConfig: {\n type: Object,\n required: true,\n },\n isMultiple: {\n type: Boolean,\n default: true,\n },\n value: {\n type: Object as () => any,\n default: () => null,\n },\n labelKey: {\n type: String,\n default: 'name',\n },\n labelKeyList: {\n type: Array,\n default: () => [],\n },\n labelKeyType: {\n type: Number,\n default: 0,\n },\n labelSpace: {\n type: String,\n default: '',\n },\n valueKey: {\n type: String,\n default: 'id',\n },\n});\nconst asyncTable = defineAsyncComponent(\n () => import('liyu-pc-base/components/tablePro/tablePro.vue'),\n);\nconst height = ref(500);\nconst innerValue = ref([]);\n\nconst resize = () => {\n height.value = window.innerHeight - 300;\n};\nconst initSelectValue = computed(() => {\n if (innerValue.value) {\n return innerValue.value.map(item => {\n return item.value;\n });\n } else {\n return [];\n }\n});\nonMounted(() => {\n resize();\n window.addEventListener('resize', resize);\n innerValue.value = props.value;\n});\nonUnmounted(() => {\n window.removeEventListener('resize', resize);\n});\nconst handlerClose = item => {\n innerValue.value = innerValue.value.filter(i => i.value !== item.value);\n};\nconst getLable = item => {\n let label;\n if (props.labelKeyList && props.labelKeyList.length > 0 && props?.labelKeyType !== 1) {\n const labelList = [];\n for (const key of props.labelKeyList) {\n labelList.push(item[key as string]);\n }\n label = labelList.join(props.labelSpace);\n } else {\n label = item[props.labelKey];\n }\n return label;\n};\nconst handlerSelectChange = (selectdRecord, unSelectdRecord) => {\n if (props.isMultiple) {\n const tmpValue = [...(innerValue.value || [])];\n for (const item of unSelectdRecord) {\n const value = item[props.valueKey];\n if (!tmpValue.some(i => i.value === value)) {\n continue;\n }\n tmpValue.splice(\n tmpValue.findIndex(i => i.value === value),\n 1,\n );\n }\n for (const item of selectdRecord) {\n const value = item[props.valueKey];\n const label = getLable(item);\n if (!tmpValue.some(i => i.value === value)) {\n tmpValue.push({\n value: value,\n label: label,\n });\n }\n }\n\n innerValue.value = tmpValue;\n } else {\n if (selectdRecord?.length > 0) {\n const item = selectdRecord[0];\n const value = item[props.valueKey];\n const label = getLable(item);\n innerValue.value = [\n {\n value: value,\n label: label,\n },\n ];\n } else {\n innerValue.value = [];\n }\n }\n};\nconst onOk = () => {\n emit('ok', innerValue.value);\n};\n</script>\n<style scoped lang=\"less\">\n.table-modal {\n display: flex;\n flex-direction: column;\n gap: 8px;\n .header {\n padding: 4px;\n }\n}\n</style>\n","<template>\n <a-modal\n :open=\"true\"\n title=\"请选择\"\n width=\"80%\"\n :destroyOnClose=\"true\"\n @ok=\"onOk\"\n >\n <div\n class=\"table-modal\"\n :style=\"{ height: `${height}px` }\"\n >\n <div class=\"header\">\n 当前选择:\n <a-tag\n v-for=\"item in innerValue\"\n :closable=\"true\"\n :key=\"item.value\"\n @close=\"handlerClose(item)\"\n >\n {{ item.label }}\n </a-tag>\n </div>\n <component\n :is=\"asyncTable\"\n :config=\"tableConfig\"\n :initSelectValue=\"initSelectValue\"\n :initValueKey=\"valueKey\"\n @selectChange=\"handlerSelectChange\"\n ></component>\n </div>\n </a-modal>\n</template>\n<script setup lang=\"ts\">\nimport { computed, defineAsyncComponent, onMounted, onUnmounted, ref } from 'vue';\nconst emit = defineEmits(['ok']);\nconst props = defineProps({\n tableConfig: {\n type: Object,\n required: true,\n },\n isMultiple: {\n type: Boolean,\n default: true,\n },\n value: {\n type: Object as () => any,\n default: () => null,\n },\n labelKey: {\n type: String,\n default: 'name',\n },\n labelKeyList: {\n type: Array,\n default: () => [],\n },\n labelKeyType: {\n type: Number,\n default: 0,\n },\n labelSpace: {\n type: String,\n default: '',\n },\n valueKey: {\n type: String,\n default: 'id',\n },\n});\nconst asyncTable = defineAsyncComponent(\n () => import('liyu-pc-base/components/tablePro/tablePro.vue'),\n);\nconst height = ref(500);\nconst innerValue = ref([]);\n\nconst resize = () => {\n height.value = window.innerHeight - 300;\n};\nconst initSelectValue = computed(() => {\n if (innerValue.value) {\n return innerValue.value.map(item => {\n return item.value;\n });\n } else {\n return [];\n }\n});\nonMounted(() => {\n resize();\n window.addEventListener('resize', resize);\n innerValue.value = props.value;\n});\nonUnmounted(() => {\n window.removeEventListener('resize', resize);\n});\nconst handlerClose = item => {\n innerValue.value = innerValue.value.filter(i => i.value !== item.value);\n};\nconst getLable = item => {\n let label;\n if (props.labelKeyList && props.labelKeyList.length > 0 && props?.labelKeyType !== 1) {\n const labelList = [];\n for (const key of props.labelKeyList) {\n labelList.push(item[key as string]);\n }\n label = labelList.join(props.labelSpace);\n } else {\n label = item[props.labelKey];\n }\n return label;\n};\nconst handlerSelectChange = (selectdRecord, unSelectdRecord) => {\n if (props.isMultiple) {\n const tmpValue = [...(innerValue.value || [])];\n for (const item of unSelectdRecord) {\n const value = item[props.valueKey];\n if (!tmpValue.some(i => i.value === value)) {\n continue;\n }\n tmpValue.splice(\n tmpValue.findIndex(i => i.value === value),\n 1,\n );\n }\n for (const item of selectdRecord) {\n const value = item[props.valueKey];\n const label = getLable(item);\n if (!tmpValue.some(i => i.value === value)) {\n tmpValue.push({\n value: value,\n label: label,\n });\n }\n }\n\n innerValue.value = tmpValue;\n } else {\n if (selectdRecord?.length > 0) {\n const item = selectdRecord[0];\n const value = item[props.valueKey];\n const label = getLable(item);\n innerValue.value = [\n {\n value: value,\n label: label,\n },\n ];\n } else {\n innerValue.value = [];\n }\n }\n};\nconst onOk = () => {\n emit('ok', innerValue.value);\n};\n</script>\n<style scoped lang=\"less\">\n.table-modal {\n display: flex;\n flex-direction: column;\n gap: 8px;\n .header {\n padding: 4px;\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmCA,IAAM,IAAO,GACP,IAAQ,GAkCR,IAAa,QACX,OAAO,yBACf,CAAA,MAAA,MAAA,EAAA,CAAA,CAAA,GACM,IAAS,EAAI,GAAG,GAChB,IAAa,EAAI,CAAC,CAAC,GAEnB,UAAe;GACnB,EAAO,QAAQ,OAAO,cAAc;EACtC,GACM,IAAkB,QAClB,EAAW,QACN,EAAW,MAAM,KAAI,MACnB,EAAK,KACb,IAEM,CAAC,CAEX;EAMD,AALA,QAAgB;GAGd,AAFA,EAAO,GACP,OAAO,iBAAiB,UAAU,CAAM,GACxC,EAAW,QAAQ,EAAM;EAC3B,CAAC,GACD,QAAkB;GAChB,OAAO,oBAAoB,UAAU,CAAM;EAC7C,CAAC;EACD,IAAM,KAAe,MAAQ;GAC3B,EAAW,QAAQ,EAAW,MAAM,QAAO,MAAK,EAAE,UAAU,EAAK,KAAK;EACxE,GACM,KAAW,MAAQ;GACvB,IAAI;GACJ,IAAI,EAAM,gBAAgB,EAAM,aAAa,SAAS,KAAK,GAAO,iBAAiB,GAAG;IACpF,IAAM,IAAY,CAAC;IACnB,KAAK,IAAM,KAAO,EAAM,cACtB,EAAU,KAAK,EAAK,EAAc;IAEpC,IAAQ,EAAU,KAAK,EAAM,UAAU;GACzC,OACE,IAAQ,EAAK,EAAM;GAErB,OAAO;EACT,GACM,KAAuB,GAAe,MAAoB;GAC9D,IAAI,EAAM,YAAY;IACpB,IAAM,IAAW,CAAC,GAAI,EAAW,SAAS,CAAC,CAAE;IAC7C,KAAK,IAAM,KAAQ,GAAiB;KAClC,IAAM,IAAQ,EAAK,EAAM;KACpB,EAAS,MAAK,MAAK,EAAE,UAAU,CAAK,KAGzC,EAAS,OACP,EAAS,WAAU,MAAK,EAAE,UAAU,CAAK,GACzC,CACF;IACF;IACA,KAAK,IAAM,KAAQ,GAAe;KAChC,IAAM,IAAQ,EAAK,EAAM,WACnB,IAAQ,EAAS,CAAI;KAC3B,AAAK,EAAS,MAAK,MAAK,EAAE,UAAU,CAAK,KACvC,EAAS,KAAK;MACL;MACA;KACT,CAAC;IAEL;IAEA,EAAW,QAAQ;GACrB,OACE,IAAI,GAAe,SAAS,GAAG;IAC7B,IAAM,IAAO,EAAc;IAG3B,EAAW,QAAQ,CACjB;KACS,OAJG,EAAK,EAAM;KAKd,OAJG,EAAS,CAIZ;IACT,CACF;GACF,OACE,EAAW,QAAQ,CAAC;EAG1B,GACM,UAAa;GACjB,EAAK,MAAM,EAAW,KAAK;EAC7B"}
|