fix(web): 修复编辑模板名称输入组件的标签嵌套问题

- 移除 el-input 标签的多余闭合标签
- 确保模板名称输入框结构符合规范
- 改善表单项的渲染正确性和稳定性
This commit is contained in:
zhouyonggao 2025-12-17 09:28:11 +08:00
parent c0578b1d24
commit 18e2e372a0
3 changed files with 94 additions and 28 deletions

9
.qoder/rules/index.md Normal file
View File

@ -0,0 +1,9 @@
---
trigger: always_on
---
# 项目规则
## 需求文档
@docs/需求文档.md

View File

@ -175,7 +175,7 @@
</el-dialog> </el-dialog>
<el-dialog v-model="editVisible" title="编辑模板" :width="editWidth"> <el-dialog v-model="editVisible" title="编辑模板" :width="editWidth">
<el-form ref="editFormRef" :model="edit" :rules="editRules" label-width="110px"> <el-form ref="editFormRef" :model="edit" :rules="editRules" label-width="110px">
<el-form-item label="模板名称" required show-message prop="name"><el-input v-model="edit.name" /></el-input></el-form-item> <el-form-item label="模板名称" required show-message prop="name"><el-input v-model="edit.name" /></el-form-item>
<el-form-item label="数据源"> <el-form-item label="数据源">
<el-select v-model="edit.datasource" placeholder="选择" :teleported="false" style="width:160px"> <el-select v-model="edit.datasource" placeholder="选择" :teleported="false" style="width:160px">
<el-option v-for="opt in datasourceOptions" :key="opt.value" :label="opt.label" :value="opt.value" /> <el-option v-for="opt in datasourceOptions" :key="opt.value" :label="opt.label" :value="opt.value" />

View File

@ -1,5 +1,23 @@
const { createApp, reactive } = Vue; const { createApp, reactive } = Vue;
// ==================== 常量定义 ====================
const CONSTANTS = {
// 轮询间隔(毫秒)
JOBS_POLL_INTERVAL: 1000,
// 树形选择器渲染延迟(毫秒)
TREE_RENDER_DELAY: 100,
// 编辑模式树渲染延迟(毫秒)
TREE_EDIT_RENDER_DELAY: 300,
// 对话框最小宽度
DIALOG_MIN_WIDTH: 500,
// 对话框最大宽度
DIALOG_MAX_WIDTH: 1400,
// 默认对话框宽度
DIALOG_DEFAULT_WIDTH: 900,
// 默认编辑对话框宽度
DIALOG_EDIT_DEFAULT_WIDTH: 900
};
const app = createApp({ const app = createApp({
setup() { setup() {
// ==================== 配置和工具函数 ==================== // ==================== 配置和工具函数 ====================
@ -70,8 +88,8 @@ const app = createApp({
editVisible: false, editVisible: false,
exportVisible: false, exportVisible: false,
exportSubmitting: false, exportSubmitting: false,
createWidth: localStorage.getItem('tplDialogWidth') || '900px', createWidth: localStorage.getItem('tplDialogWidth') || CONSTANTS.DIALOG_DEFAULT_WIDTH + 'px',
editWidth: localStorage.getItem('tplEditDialogWidth') || '900px', editWidth: localStorage.getItem('tplEditDialogWidth') || CONSTANTS.DIALOG_EDIT_DEFAULT_WIDTH + 'px',
edit: { edit: {
id: null, id: null,
name: '', name: '',
@ -120,10 +138,21 @@ const app = createApp({
const metaTableLabels = Vue.ref({}); const metaTableLabels = Vue.ref({});
const recommendedMeta = Vue.ref([]); const recommendedMeta = Vue.ref([]);
const loadFieldsMeta = async (ds, type) => { /**
* 加载字段元数据和推荐字段合并为单次 API 调用
* @param {string} ds - 数据源
* @param {number} type - 订单类型
* @returns {Promise<string[]>} 推荐字段列表
*/
const loadFieldsMetaAndRecommended = async (ds, type) => {
try { try {
const res = await fetch(API_BASE + '/api/metadata/fields?datasource=' + encodeURIComponent(ds) + '&order_type=' + encodeURIComponent(String(type || 0))); const res = await fetch(API_BASE + '/api/metadata/fields?datasource=' + encodeURIComponent(ds) + '&order_type=' + encodeURIComponent(String(type || 0)));
if (!res.ok) {
throw new Error(`加载字段元数据失败: ${res.status}`);
}
const data = await res.json(); const data = await res.json();
// 处理表字段映射
const tables = Array.isArray(data?.data?.tables) ? data.data.tables : (Array.isArray(data?.tables) ? data.tables : []); const tables = Array.isArray(data?.data?.tables) ? data.data.tables : (Array.isArray(data?.tables) ? data.tables : []);
const m = {}; const m = {};
const tblLabels = {}; const tblLabels = {};
@ -135,25 +164,24 @@ const app = createApp({
}); });
metaFM.value = m; metaFM.value = m;
metaTableLabels.value = tblLabels; metaTableLabels.value = tblLabels;
} catch (_e) {
metaFM.value = {}; // 处理推荐字段
metaTableLabels.value = {};
}
};
const loadRecommendedFields = async (ds, orderType) => {
try {
const res = await fetch(API_BASE + '/api/metadata/fields?datasource=' + encodeURIComponent(ds) + '&order_type=' + encodeURIComponent(String(orderType || 0)));
const data = await res.json();
const rec = Array.isArray(data?.data?.recommended) ? data.data.recommended : (Array.isArray(data?.recommended) ? data.recommended : []); const rec = Array.isArray(data?.data?.recommended) ? data.data.recommended : (Array.isArray(data?.recommended) ? data.recommended : []);
recommendedMeta.value = rec; recommendedMeta.value = rec;
return rec; return rec;
} catch (_e) { } catch (e) {
console.error('加载字段元数据失败:', e);
metaFM.value = {};
metaTableLabels.value = {};
recommendedMeta.value = []; recommendedMeta.value = [];
return []; return [];
} }
}; };
// 兼容旧接口
const loadFieldsMeta = (ds, type) => loadFieldsMetaAndRecommended(ds, type);
const loadRecommendedFields = (ds, orderType) => loadFieldsMetaAndRecommended(ds, orderType);
const getFieldsMap = (ds) => metaFM.value || {}; const getFieldsMap = (ds) => metaFM.value || {};
// ==================== 表标签映射 ==================== // ==================== 表标签映射 ====================
@ -340,8 +368,15 @@ const app = createApp({
const createFieldsTree = Vue.ref(null); const createFieldsTree = Vue.ref(null);
const editFieldsTree = Vue.ref(null); const editFieldsTree = Vue.ref(null);
// 树形选择器复选框变化处理 /**
const onTreeCheck = (kind, data) => { * 树形选择器复选框变化处理
* @param {string} kind - 操作类型: 'create' | 'edit'
*/
const onTreeCheck = (kind) => {
if (kind !== 'create' && kind !== 'edit') {
console.warn('onTreeCheck: 无效的 kind 参数', kind);
return;
}
const tree = kind === 'create' ? createFieldsTree.value : editFieldsTree.value; const tree = kind === 'create' ? createFieldsTree.value : editFieldsTree.value;
if (!tree) return; if (!tree) return;
@ -455,7 +490,7 @@ const app = createApp({
} catch (e) { } catch (e) {
console.warn('设置树形选择器选中状态失败:', e); console.warn('设置树形选择器选中状态失败:', e);
} }
}, 100); }, CONSTANTS.TREE_RENDER_DELAY);
}); });
}; };
@ -476,9 +511,7 @@ const app = createApp({
} }
}); });
// 兼容级联选择器的函数(已废弃,保留以避免错误) // 废弃的函数已移除,如需调用请检查代码
const onCascaderVisible = () => {};
const onFieldsSelChange = () => {};
// ==================== 表单验证规则 ==================== // ==================== 表单验证规则 ====================
const createRules = { const createRules = {
@ -545,10 +578,14 @@ const app = createApp({
const loadCreators = async () => { const loadCreators = async () => {
try { try {
const res = await fetch(API_BASE + '/api/creators'); const res = await fetch(API_BASE + '/api/creators');
if (!res.ok) {
throw new Error(`加载创建者列表失败: ${res.status}`);
}
const data = await res.json(); const data = await res.json();
const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []); const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []);
creatorOptions.value = arr.map(it => ({ label: it.name || String(it.id), value: Number(it.id) })); creatorOptions.value = arr.map(it => ({ label: it.name || String(it.id), value: Number(it.id) }));
} catch (_e) { } catch (e) {
console.error('加载创建者列表失败:', e);
creatorOptions.value = []; creatorOptions.value = [];
} }
}; };
@ -558,10 +595,14 @@ const app = createApp({
const uid = getUserId(); const uid = getUserId();
const url = API_BASE + '/api/ymt/users?limit=2000' + (uid ? ('&q=' + encodeURIComponent(uid)) : ''); const url = API_BASE + '/api/ymt/users?limit=2000' + (uid ? ('&q=' + encodeURIComponent(uid)) : '');
const res = await fetch(url); const res = await fetch(url);
if (!res.ok) {
throw new Error(`加载易码通用户列表失败: ${res.status}`);
}
const data = await res.json(); const data = await res.json();
const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []); const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []);
ymtCreatorOptions.value = arr.map(it => ({ label: it.name || String(it.id), value: Number(it.id) })); ymtCreatorOptions.value = arr.map(it => ({ label: it.name || String(it.id), value: Number(it.id) }));
} catch (_e) { } catch (e) {
console.error('加载易码通用户列表失败:', e);
ymtCreatorOptions.value = []; ymtCreatorOptions.value = [];
} }
}; };
@ -574,10 +615,14 @@ const app = createApp({
} }
try { try {
const res = await fetch(API_BASE + '/api/resellers?creator=' + ids.join(',')); const res = await fetch(API_BASE + '/api/resellers?creator=' + ids.join(','));
if (!res.ok) {
throw new Error(`加载分销商列表失败: ${res.status}`);
}
const data = await res.json(); const data = await res.json();
const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []); const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []);
resellerOptions.value = arr.map(it => ({ label: (it.name || '') + (it.name ? '' : ''), value: Number(it.id) })); resellerOptions.value = arr.map(it => ({ label: (it.name || '') + (it.name ? '' : ''), value: Number(it.id) }));
} catch (_e) { } catch (e) {
console.error('加载分销商列表失败:', e);
resellerOptions.value = []; resellerOptions.value = [];
} }
}; };
@ -593,10 +638,14 @@ const app = createApp({
qs.set('user_id', String(uid)); qs.set('user_id', String(uid));
qs.set('limit', '2000'); qs.set('limit', '2000');
const res = await fetch(API_BASE + '/api/ymt/merchants?' + qs.toString()); const res = await fetch(API_BASE + '/api/ymt/merchants?' + qs.toString());
if (!res.ok) {
throw new Error(`加载客户列表失败: ${res.status}`);
}
const data = await res.json(); const data = await res.json();
const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []); const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []);
ymtMerchantOptions.value = arr.map(it => ({ label: `${it.id} - ${it.name || ''}`, value: Number(it.id) })); ymtMerchantOptions.value = arr.map(it => ({ label: `${it.id} - ${it.name || ''}`, value: Number(it.id) }));
} catch (_e) { } catch (e) {
console.error('加载客户列表失败:', e);
ymtMerchantOptions.value = []; ymtMerchantOptions.value = [];
} }
}; };
@ -612,10 +661,14 @@ const app = createApp({
qs.set('merchant_id', String(mid)); qs.set('merchant_id', String(mid));
qs.set('limit', '2000'); qs.set('limit', '2000');
const res = await fetch(API_BASE + '/api/ymt/activities?' + qs.toString()); const res = await fetch(API_BASE + '/api/ymt/activities?' + qs.toString());
if (!res.ok) {
throw new Error(`加载活动列表失败: ${res.status}`);
}
const data = await res.json(); const data = await res.json();
const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []); const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []);
ymtActivityOptions.value = arr.map(it => ({ label: `${it.id} - ${it.name || ''}`, value: Number(it.id) })); ymtActivityOptions.value = arr.map(it => ({ label: `${it.id} - ${it.name || ''}`, value: Number(it.id) }));
} catch (_e) { } catch (e) {
console.error('加载活动列表失败:', e);
ymtActivityOptions.value = []; ymtActivityOptions.value = [];
} }
}; };
@ -630,10 +683,14 @@ const app = createApp({
const qs = new URLSearchParams(); const qs = new URLSearchParams();
qs.set('reseller', String(rid)); qs.set('reseller', String(rid));
const res = await fetch(API_BASE + '/api/plans?' + qs.toString()); const res = await fetch(API_BASE + '/api/plans?' + qs.toString());
if (!res.ok) {
throw new Error(`加载计划列表失败: ${res.status}`);
}
const data = await res.json(); const data = await res.json();
const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []); const arr = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []);
planOptions.value = arr.map(it => ({ label: `${it.id} - ${it.title || ''}`, value: Number(it.id) })); planOptions.value = arr.map(it => ({ label: `${it.id} - ${it.title || ''}`, value: Number(it.id) }));
} catch (_e) { } catch (e) {
console.error('加载计划列表失败:', e);
planOptions.value = []; planOptions.value = [];
} }
}; };
@ -925,7 +982,7 @@ const app = createApp({
await Vue.nextTick(); await Vue.nextTick();
setTimeout(() => { setTimeout(() => {
setTreeChecked('edit', paths); setTreeChecked('edit', paths);
}, 300); }, CONSTANTS.TREE_EDIT_RENDER_DELAY);
} catch (_e) { } catch (_e) {
state.edit.name = row.name; state.edit.name = row.name;
state.edit.datasource = row.datasource || 'marketing'; state.edit.datasource = row.datasource || 'marketing';