1. 提交代码(标签同步)

This commit is contained in:
2026-04-27 16:57:19 +08:00
parent 78ff26cb20
commit 9d7ac679a0
4 changed files with 127 additions and 26 deletions

View File

@@ -832,7 +832,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
component: () => import('@/views/ydoyun/report/lijun/reportpage6/detail.vue')
},
{
path: 'wbl/product-splb',
path: 'wangbuliao/product-splb',
name: 'ProductSplbReport',
meta: {
title: '商品列表报表',

View File

@@ -1,6 +1,6 @@
<template>
<Dialog :title="'编辑SQL - ' + (tag?.name || '')" v-model="dialogVisible" width="800px" max-height="85vh">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form :model="form" label-width="100px">
<el-form-item label="标签名称">
<el-input :model-value="tag?.name" readonly />
</el-form-item>
@@ -28,14 +28,26 @@
</div>
</div>
</el-form-item>
<el-form-item label="标签同步">
<div class="sync-switch-row">
<el-switch
:model-value="syncEnabled"
active-text="开启同步"
inactive-text="关闭同步"
@update:model-value="onSyncEnabledChange"
/>
<span class="form-item-hint sync-switch-hint">关闭后本标签不执行 SQL 同步确定保存后脚本将清空再次开启可继续编辑或恢复关前内容</span>
</div>
</el-form-item>
<el-form-item label="执行SQL脚本" prop="sqlScript">
<div class="param-ref-row">
<el-button size="small" type="success" plain @click="insertInsertTemplate">
<el-button size="small" type="success" plain :disabled="!syncEnabled" @click="insertInsertTemplate">
插入 {{ insertTableName }} 模板
</el-button>
<el-button
size="small"
class="btn-ref-color"
:disabled="!syncEnabled"
:style="{ backgroundColor: editableColor || '#409EFF', borderColor: editableColor || '#409EFF', color: '#fff' }"
@click="insertParamRef('颜色')"
>
@@ -49,6 +61,7 @@
size="small"
type="info"
plain
:disabled="!syncEnabled"
@click="insertParamRef(p)"
>
引用{{ p }}
@@ -60,12 +73,15 @@
v-model="form.sqlScript"
type="textarea"
:rows="10"
placeholder="请输入标签同步时执行的SQL脚本支持多行。可使用上方按钮插入参数引用格式为 ${参数1}、${参数2} 等"
:disabled="!syncEnabled"
placeholder="开启「标签同步」后可编辑。支持 ${参数1}、${颜色} 等占位符"
class="sql-script-area"
/>
<div class="form-item-hint">该SQL将用于标签计算与同步请确保语法正确修改后需点击底部保存按钮统一保存</div>
<div class="form-item-hint">
与上方标签同步开关配合=不同步=可填写脚本确定后请在配置页点保存统一提交
</div>
<!-- SQL 预览文本框下方参数代入并高亮 -->
<div class="sql-preview-block" v-if="form.sqlScript && paramList.length > 0">
<div class="sql-preview-block" v-if="syncEnabled && form.sqlScript && paramList.length > 0">
<div class="sql-preview-label">SQL 预览参数已代入</div>
<div class="sql-preview-content" v-html="previewHtml"></div>
</div>
@@ -80,6 +96,7 @@
size="small"
placeholder="输入值"
class="preview-param-input"
:disabled="!syncEnabled"
@input="updatePreview"
/>
</div>
@@ -119,21 +136,32 @@ const COLOR_OPTIONS = [
]
const form = ref({ sqlScript: '' })
const formRef = ref()
/** 关同步前暂存的脚本,仅再次开启同一次弹窗时恢复 */
const sqlStash = ref('')
/** 与 SQL 是否为空一致:有脚本=开启;关闭开关会清空编辑区并暂存到 sqlStash */
const syncEnabled = ref(true)
const sqlTextareaRef = ref()
const saving = ref(false)
const editableColor = ref('')
const previewParamValues = ref<Record<string, string>>({})
const previewHtml = ref('')
const rules = {
sqlScript: [{ required: true, message: '执行SQL脚本不能为空', trigger: 'blur' }]
}
/** 颜色以外的参数列表(颜色由上方标签颜色选择器编辑) */
const paramListWithoutColor = computed(() =>
paramList.value.filter((p) => p !== '颜色')
)
const onSyncEnabledChange = (val: boolean) => {
if (!val) {
sqlStash.value = form.value.sqlScript || ''
form.value.sqlScript = ''
} else {
form.value.sqlScript = sqlStash.value
}
syncEnabled.value = val
nextTick(updatePreview)
}
const setEditableColor = (color: string) => {
editableColor.value = color
previewParamValues.value = { ...previewParamValues.value, '颜色': color }
@@ -196,7 +224,10 @@ const updatePreview = () => {
watch(
() => tag.value,
(t) => {
form.value.sqlScript = t?.sqlScript || ''
const raw = t?.sqlScript || ''
form.value.sqlScript = raw
sqlStash.value = raw
syncEnabled.value = raw.trim() !== ''
const defaults = (t as any)?.paramDefaults ?? {}
const init: Record<string, string> = {}
paramList.value.forEach((p) => {
@@ -220,6 +251,7 @@ watch(
)
const insertAtCursor = (text: string) => {
if (!syncEnabled.value) return
const textarea = sqlTextareaRef.value?.$el?.querySelector('textarea')
if (textarea) {
const start = textarea.selectionStart
@@ -237,6 +269,7 @@ const insertAtCursor = (text: string) => {
}
const insertInsertTemplate = () => {
if (!syncEnabled.value) return
const table = insertTableName.value
const template = `INSERT INTO ${table} (code, name, color)
SELECT code, name, '\${颜色}' AS color FROM (
@@ -246,6 +279,7 @@ SELECT code, name, '\${颜色}' AS color FROM (
`
const oldScript = form.value.sqlScript || ''
form.value.sqlScript = template + (oldScript ? '\n' + oldScript : '')
sqlStash.value = form.value.sqlScript
// 颜色参数默认取标签当前颜色
const colorVal = tag.value?.color ?? '#409EFF'
editableColor.value = colorVal
@@ -261,6 +295,7 @@ SELECT code, name, '\${颜色}' AS color FROM (
}
const insertParamRef = (paramName: string) => {
if (!syncEnabled.value) return
insertAtCursor(`\${${paramName}}`)
}
@@ -269,17 +304,15 @@ const substituteParams = (sql: string, values: Record<string, string>) => {
}
const handleConfirm = async () => {
await formRef.value?.validate()
if (!tag.value?.id) return
saving.value = true
try {
const sqlScript = form.value.sqlScript
const sqlScript = syncEnabled.value ? (form.value.sqlScript?.trim() ?? '') : ''
const paramVals = { ...previewParamValues.value }
// 始终带上标签颜色,供父组件更新 tagColorValues 及保存
// 始终带上标签颜色,供父组件更新 tagColorValues 及保存(含仅含 ${颜色} 无其它参数的情况)
if (editableColor.value) paramVals['颜色'] = editableColor.value
const sqlScriptResolved = paramList.value.length > 0
? substituteParams(sqlScript, paramVals)
: sqlScript
// 只要脚本里出现 ${...} 占位符就替换,不依赖 paramList 长度(避免仅「颜色」或解析时序问题导致不替换)
const sqlScriptResolved = substituteParams(sqlScript, paramVals)
emit('confirm', tag.value.id, sqlScript, sqlScriptResolved, paramVals)
dialogVisible.value = false
} finally {
@@ -315,6 +348,18 @@ const handleConfirm = async () => {
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
font-size: 13px;
}
.sync-switch-row {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
}
.sync-switch-hint {
display: block;
margin-top: 0;
max-width: 100%;
line-height: 1.5;
}
.form-item-hint {
font-size: 12px;
color: var(--el-text-color-secondary);

View File

@@ -45,7 +45,7 @@
placeholder="请输入标签同步时执行的SQL脚本支持多行"
class="sql-script-area"
/>
<div class="form-item-hint">该SQL将用于标签计算与同步请确保语法正确</div>
<div class="form-item-hint">可为空留空表示关闭该标签同步有内容时请确保语法正确</div>
</el-form-item>
<el-form-item label="是否代入参数" prop="useParams">
<el-radio-group v-model="formData.useParams">
@@ -90,7 +90,6 @@ const formData = ref<Tag>({
const formRules = reactive({
name: [{ required: true, message: '标签名称不能为空', trigger: 'blur' }],
type: [{ required: true, message: '请选择标签类型', trigger: 'change' }],
sqlScript: [{ required: true, message: '执行SQL脚本不能为空', trigger: 'blur' }],
useParams: [{ required: true, message: '请选择是否代入参数', trigger: 'change' }],
})
const formRef = ref()

View File

@@ -1423,6 +1423,64 @@ const getTagParamListAndValues = (type: string, name: string) => {
return { paramList, values }
}
const SQL_PLACEHOLDER_NAMES = (sql: string): string[] => {
if (!sql) return []
const s = new Set<string>()
const re = /\$\{([^}]+)\}/g
let m: RegExpExecArray | null
while ((m = re.exec(sql)) !== null) s.add(m[1].trim())
return [...s]
}
/**
* 保存时构造「占位符名 → 值」:不依赖是否打开过编辑 SQL 弹窗。
* 顺序:预设参数默认值 < 标签接口 paramValues < 主页面已编辑的 tagParamValues颜色与 getTagColor 一致。
* 再按当前 sqlScript 中出现的 ${...} 补全仍为空的项目(如仅含 ${颜色}、首次进入未点过主页面输入框)。
*/
const collectTagParamValuesForSave = (type: string, name: string, t: Tag, sqlScript: string): Record<string, string> => {
const key = type && name ? `${type}:${name}` : ''
const preset = key ? PRESET_TAGS.find((p) => p.type === type && p.name === name) : undefined
const { values: presetVals } = getTagParamListAndValues(type, name)
let fromApi: Record<string, string> = {}
if (t.paramValues) {
try {
const v = JSON.parse(t.paramValues) as unknown
if (v && typeof v === 'object' && !Array.isArray(v)) fromApi = { ...v } as Record<string, string>
} catch {
/* ignore */
}
}
const fromPanel = key && tagParamValues[key] ? { ...tagParamValues[key] } : {}
const merged: Record<string, string> = { ...presetVals, ...fromApi, ...fromPanel }
const resolveColor = (): string => {
if (!key) return t.color || preset?.color || '#409EFF'
return (
tagColorValues[key] ||
fromPanel['颜色'] ||
fromApi['颜色'] ||
t.color ||
preset?.color ||
'#409EFF'
)
}
const names = SQL_PLACEHOLDER_NAMES(sqlScript)
for (const pname of names) {
if (pname === '颜色') {
merged['颜色'] = resolveColor()
continue
}
if (merged[pname] != null && String(merged[pname]) !== '') continue
const m = /^参数(\d+)$/.exec(pname)
if (m) {
const idx = parseInt(m[1], 10) - 1
if (idx >= 0) merged[pname] = getTagParamValue(type, name, idx)
}
}
return merged
}
const activeTabList = computed(() => {
const tab = tabList.find((t) => t.name === activeTab.value)
return tab ? [tab] : []
@@ -1583,16 +1641,15 @@ const saveConfig = async () => {
const paramDef = tagKey ? PRESET_TAG_PARAMS[tagKey] : undefined
const useParams = t.useParams ?? preset?.useParams ?? false
const params = (t.params || preset?.params || paramDef?.params || '').trim()
const { values: paramVals } = getTagParamListAndValues(t.type || '', t.name || '')
const sqlScript = edit?.sqlScript ?? t.sqlScript ?? ''
const sqlScriptResolved =
edit?.sqlScriptResolved ??
(paramVals && Object.keys(paramVals).length && sqlScript
? substituteParams(sqlScript, paramVals)
: t.sqlScriptResolved ?? sqlScript)
const mergedParamVals = collectTagParamValuesForSave(t.type || '', t.name || '', t, sqlScript)
// 保存时按当前 sql、接口/主页面参数与颜色重算(未打开过编辑 SQL 弹窗同样生效)
const sqlScriptResolved = sqlScript.trim()
? substituteParams(sqlScript, mergedParamVals)
: ''
const color = tagColorValues[tagKey] ?? t.color ?? preset?.color
const paramValuesStr =
paramVals && Object.keys(paramVals).length > 0 ? JSON.stringify(paramVals) : undefined
Object.keys(mergedParamVals).length > 0 ? JSON.stringify(mergedParamVals) : undefined
return {
...t,
sqlScript,