Commit cfc4c803 authored by Data Governance Dev's avatar Data Governance Dev

feat(web3): 字段配置加 ↑↓ 调整展示顺序

字段配置(FieldConfigDrawer + TaskModal 字段表)新增 ↑↓ 排序按钮,
调整字段在结果表里的展示列顺序:

- FieldConfigDrawer:每项右侧加 ↑↓ 按钮组,icon 左边
  - 把原来的 localChecked dict 改成 localItems 数组(同时管顺序 + 勾选)
  - 保存时 emit 完整字段顺序 + 仅勾选字段
- TaskModal:字段表去掉最左「排序」列,只在「默认展示」checkbox
  左边留 ↑↓(统一入口,列宽 130 → 170px 给按钮腾空间)
  - 空数据行 colspan 同步 6 → 5
- ResultTable:allColumns computed 在 visibleFields 非空时按其顺序排
  (FieldConfigDrawer ↑↓ 调整的结果),无 visibleFields 时回退原序
- 样式:.ord-btn / .ord-cell / .ord-group 抽到 global.css
  初始上下排版,用户反馈丑 → 改为左右排版(flex-direction: row)
parent 8450d6f3
...@@ -7,10 +7,15 @@ ...@@ -7,10 +7,15 @@
<button class="drawer-close" @click="$emit('update:open', false)">×</button> <button class="drawer-close" @click="$emit('update:open', false)">×</button>
</div> </div>
<div class="drawer-body"> <div class="drawer-body">
<div class="drawer-hint">勾选要在结果表中展示的字段。</div> <div class="drawer-hint">勾选要在结果表中展示的字段;右侧 ↑↓ 可调整展示顺序。</div>
<div class="field-check-list"> <div class="field-check-list">
<label v-for="f in businessFields" :key="f.key" class="field-check-item"> <!-- 2026-08-24:每项加 ↑↓ 按钮(icon 左边) —— 调整字段在结果表里的列展示顺序 -->
<input type="checkbox" class="checkbox" v-model="localChecked[f.key]" /> <label
v-for="(f, idx) in localItems"
:key="f.key"
class="field-check-item"
>
<input type="checkbox" class="checkbox" v-model="f.checked" />
<span class="cn">{{ f.cn }}</span> <span class="cn">{{ f.cn }}</span>
<span class="en">{{ f.en }}</span> <span class="en">{{ f.en }}</span>
<!-- 2026-08-21:字段规则数 —— 跟结果表表头的「⚠ N规则」徽章一致, <!-- 2026-08-21:字段规则数 —— 跟结果表表头的「⚠ N规则」徽章一致,
...@@ -23,6 +28,21 @@ ...@@ -23,6 +28,21 @@
> >
<span class="rule-mark">⚠ {{ f.rules }}规则</span> <span class="rule-mark">⚠ {{ f.rules }}规则</span>
</el-tooltip> </el-tooltip>
<!-- 2026-08-24:排序 ↑↓ —— 在 ⓘ icon 左边;首行禁用上移,末行禁用下移 -->
<span class="ord-group">
<button
class="ord-btn"
:disabled="idx === 0"
title="上移"
@click.prevent="moveItem(idx, -1)"
>↑</button>
<button
class="ord-btn"
:disabled="idx === localItems.length - 1"
title="下移"
@click.prevent="moveItem(idx, 1)"
>↓</button>
</span>
<!-- 字段注释:有 comment 时在右侧放个 ⓘ 图标,hover 出 tooltip --> <!-- 字段注释:有 comment 时在右侧放个 ⓘ 图标,hover 出 tooltip -->
<el-tooltip <el-tooltip
v-if="f.comment" v-if="f.comment"
...@@ -44,7 +64,7 @@ ...@@ -44,7 +64,7 @@
</template> </template>
<script setup> <script setup>
import { computed, reactive, watch } from 'vue' import { computed, ref, watch } from 'vue'
const props = defineProps({ const props = defineProps({
open: { type: Boolean, default: false }, open: { type: Boolean, default: false },
...@@ -56,19 +76,48 @@ const emit = defineEmits(['update:open', 'save']) ...@@ -56,19 +76,48 @@ const emit = defineEmits(['update:open', 'save'])
// 业务字段(排除内部 __reason) // 业务字段(排除内部 __reason)
const businessFields = computed(() => props.fields.filter((f) => f.key !== '__reason')) const businessFields = computed(() => props.fields.filter((f) => f.key !== '__reason'))
const localChecked = reactive({}) // 2026-08-24:把原来的 localChecked dict 改成 localItems 数组 —— 同时管顺序 + 勾选
// 每个 item = { key, cn, en, comment, rules, checked },数组顺序就是用户在结果表里的展示顺序
const localItems = ref([])
watch( watch(
() => [businessFields.value, props.visibleFields], () => [businessFields.value, props.visibleFields],
() => { () => {
businessFields.value.forEach((f) => { // 初始化顺序:先按 props.visibleFields 的顺序,再把未勾选字段按 props.fields 顺序追加
localChecked[f.key] = props.visibleFields.includes(f.key) const visibleSet = new Set(props.visibleFields)
}) const byKey = new Map(businessFields.value.map((f) => [f.key, f]))
const out = []
// 1) 已勾选字段按 visibleFields 顺序排
for (const k of props.visibleFields) {
const f = byKey.get(k)
if (f) out.push({ ...f, checked: true })
}
// 2) 未勾选字段按原始顺序追加
for (const f of businessFields.value) {
if (!visibleSet.has(f.key)) {
out.push({ ...f, checked: false })
}
}
localItems.value = out
}, },
{ immediate: true, deep: true } { immediate: true, deep: true }
) )
// 调整顺序(dir=-1 上移,dir=+1 下移)
function moveItem(idx, dir) {
const target = idx + dir
if (target < 0 || target >= localItems.value.length) return
const arr = localItems.value
const tmp = arr[idx]
arr[idx] = arr[target]
arr[target] = tmp
}
function onSave() { function onSave() {
const list = businessFields.value.filter((f) => localChecked[f.key]).map((f) => f.key) // 按用户调整后的顺序 + 仅勾选字段,emit 给父组件
const list = localItems.value
.filter((f) => f.checked)
.map((f) => f.key)
emit('save', list) emit('save', list)
} }
...@@ -93,4 +142,35 @@ function ruleHint(f) { ...@@ -93,4 +142,35 @@ function ruleHint(f) {
font-size: 12px; font-size: 12px;
cursor: help; cursor: help;
} }
/* 2026-08-24:↑↓ 排序按钮组 —— 复用 TaskModal 的 .ord-btn 视觉但 scoped 化
(避免跨组件污染;样式一致但独立维护)*/
.ord-group {
display: inline-flex;
flex-direction: row; /* 2026-08-24:左右排版 ↑↓ —— 上下太丑 */
gap: 4px;
margin-left: auto; /* 推到右侧,跟 ⓘ icon 相邻 */
}
.ord-btn {
width: 22px;
height: 18px;
padding: 0;
border: 1px solid #dcdfe6;
background: #fff;
color: #606266;
border-radius: 3px;
cursor: pointer;
font-size: 11px;
line-height: 16px;
font-family: inherit;
transition: all 0.15s;
}
.ord-btn:hover:not(:disabled) {
border-color: #409eff;
color: #409eff;
background: #ecf5ff;
}
.ord-btn:disabled {
opacity: 0.35;
cursor: not-allowed;
}
</style> </style>
\ No newline at end of file
...@@ -228,15 +228,29 @@ onBeforeUnmount(() => { ...@@ -228,15 +228,29 @@ onBeforeUnmount(() => {
// 1. props.visibleFields 非空 → 严格按它过滤(FieldConfigDrawer 出来的用户选择) // 1. props.visibleFields 非空 → 严格按它过滤(FieldConfigDrawer 出来的用户选择)
// 2. props.visibleFields 为空/null → 回退到「按 column.showDefault 过滤」(向后兼容) // 2. props.visibleFields 为空/null → 回退到「按 column.showDefault 过滤」(向后兼容)
// 兜底:`showDefault !== false` —— 未传 showDefault 的列(mock / 老调用方)全部展示 // 兜底:`showDefault !== false` —— 未传 showDefault 的列(mock / 老调用方)全部展示
// 2026-08-24:列展示顺序 —— 有 visibleFields 时按 visibleFields 顺序排(FieldConfigDrawer
// 的 ↑↓ 调整结果),否则按 props.columns 原始顺序(也就是 task.field_list ord)
// rows 为空时(无任务 / 查询 0 行)也不追加备注列 —— 没数据时备注列只会显示「—」占位,没意义 // rows 为空时(无任务 / 查询 0 行)也不追加备注列 —— 没数据时备注列只会显示「—」占位,没意义
const allColumns = computed(() => { const allColumns = computed(() => {
let cols = props.columns.filter((c) => c.key !== '__reason') let cols = props.columns.filter((c) => c.key !== '__reason')
let sortByVisibleFields = false
if (Array.isArray(props.visibleFields) && props.visibleFields.length > 0) { if (Array.isArray(props.visibleFields) && props.visibleFields.length > 0) {
const visibleSet = new Set(props.visibleFields) const visibleSet = new Set(props.visibleFields)
cols = cols.filter((c) => visibleSet.has(c.key)) cols = cols.filter((c) => visibleSet.has(c.key))
sortByVisibleFields = true
} else { } else {
cols = cols.filter((c) => c.showDefault !== false) cols = cols.filter((c) => c.showDefault !== false)
} }
if (sortByVisibleFields) {
// 按 visibleFields 顺序排序(FieldConfigDrawer 的 ↑↓ 顺序)
// visibleFields 里的 key 没在 props.columns 里(罕见,列被删除)→ 排到末尾
const orderMap = new Map(props.visibleFields.map((k, i) => [k, i]))
cols.sort((a, b) => {
const oa = orderMap.has(a.key) ? orderMap.get(a.key) : 999999
const ob = orderMap.has(b.key) ? orderMap.get(b.key) : 999999
return oa - ob
})
}
if (props.rows.length > 0) { if (props.rows.length > 0) {
cols.push({ key: '__reason', cn: '备注' }) cols.push({ key: '__reason', cn: '备注' })
} }
......
...@@ -191,11 +191,11 @@ ...@@ -191,11 +191,11 @@
<th style="min-width: 120px;">字段注释</th> <th style="min-width: 120px;">字段注释</th>
<th>数据类型</th> <th>数据类型</th>
<th style="width: 120px;">规则设置</th> <th style="width: 120px;">规则设置</th>
<th style="width: 80px;">默认展示</th> <th style="width: 170px;">默认展示</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="f in fieldList" :key="f.key"> <tr v-for="(f, idx) in fieldList" :key="f.key">
<td class="field-name-cell">{{ f.en }}</td> <td class="field-name-cell">{{ f.en }}</td>
<td>{{ f.cn || '—' }}</td> <td>{{ f.cn || '—' }}</td>
<td>{{ f.type }}</td> <td>{{ f.type }}</td>
...@@ -206,8 +206,23 @@ ...@@ -206,8 +206,23 @@
@click="openRule(f)" @click="openRule(f)"
>规则设置<span class="num">{{ f.rules?.length || 0 }}</span></button> >规则设置<span class="num">{{ f.rules?.length || 0 }}</span></button>
</td> </td>
<td style="text-align: center;"> <td>
<div class="show-default-cell">
<!-- 2026-08-24:「默认展示」checkbox 左边也加 ↑↓,用户明确要求两处都能调 -->
<button
class="ord-btn"
:disabled="idx === 0"
title="上移"
@click="moveField(idx, -1)"
>↑</button>
<button
class="ord-btn"
:disabled="idx === fieldList.length - 1"
title="下移"
@click="moveField(idx, 1)"
>↓</button>
<input type="checkbox" class="checkbox" v-model="f.showDefault" /> <input type="checkbox" class="checkbox" v-model="f.showDefault" />
</div>
</td> </td>
</tr> </tr>
<tr v-if="!loadingColumns && fieldList.length === 0 && form.table"> <tr v-if="!loadingColumns && fieldList.length === 0 && form.table">
...@@ -702,6 +717,18 @@ function openRule(f) { ...@@ -702,6 +717,18 @@ function openRule(f) {
ruleModalOpen.value = true ruleModalOpen.value = true
} }
// 2026-08-24:调整字段顺序(↑↓ 按钮)—— dir=-1 上移,dir=+1 下移
// fieldList 是 ref([]),直接 splice 即可触发响应式刷新
function moveField(idx, dir) {
const arr = fieldList.value
const target = idx + dir
if (target < 0 || target >= arr.length) return
// 经典数组交换
const tmp = arr[idx]
arr[idx] = arr[target]
arr[target] = tmp
}
// 规则保存回写:RuleModal.emit('save', {fieldKey, rules}) // 规则保存回写:RuleModal.emit('save', {fieldKey, rules})
// 把 rules 写回对应字段的 field.rules,让「规则设置 N」徽章实时刷新 // 把 rules 写回对应字段的 field.rules,让「规则设置 N」徽章实时刷新
function onRuleSave({ fieldKey, rules }) { function onRuleSave({ fieldKey, rules }) {
......
...@@ -506,6 +506,46 @@ table.dq-table tbody tr:last-child td { border-bottom: none; } ...@@ -506,6 +506,46 @@ table.dq-table tbody tr:last-child td { border-bottom: none; }
.rule-set-btn .num { background: #409eff; color: #fff; border-radius: 9px; padding: 0 6px; line-height: 16px; font-size: 11px; } .rule-set-btn .num { background: #409eff; color: #fff; border-radius: 9px; padding: 0 6px; line-height: 16px; font-size: 11px; }
.rule-set-btn.empty .num { background: #c0c4cc; } .rule-set-btn.empty .num { background: #c0c4cc; }
/* 2026-08-24:字段配置表里的排序 ↑↓ 按钮(左侧 + 「默认展示」列内)*/
.ord-cell {
display: flex;
flex-direction: row; /* 2026-08-24:左右排版 ↑↓ —— 上下太丑 */
align-items: center;
justify-content: center;
gap: 4px;
vertical-align: middle;
}
/* 「默认展示」列里:↑↓ 在左,checkbox 在右 */
.show-default-cell {
display: flex;
align-items: center;
gap: 6px;
justify-content: center;
}
.ord-btn {
width: 22px;
height: 20px;
padding: 0;
border: 1px solid #dcdfe6;
background: #fff;
color: #606266;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
line-height: 18px;
font-family: inherit;
transition: all 0.15s;
}
.ord-btn:hover:not(:disabled) {
border-color: #409eff;
color: #409eff;
background: #ecf5ff;
}
.ord-btn:disabled {
opacity: 0.35;
cursor: not-allowed;
}
/* ===== 规则弹窗内的单条规则 ===== */ /* ===== 规则弹窗内的单条规则 ===== */
.rule-item { .rule-item {
border: 1px solid #ebeef5; border: 1px solid #ebeef5;
......
web3/tests/filterable_schema_search.png

57.5 KB | W: | H:

web3/tests/filterable_schema_search.png

60.5 KB | W: | H:

web3/tests/filterable_schema_search.png
web3/tests/filterable_schema_search.png
web3/tests/filterable_schema_search.png
web3/tests/filterable_schema_search.png
  • 2-up
  • Swipe
  • Onion skin
web3/tests/filterable_with_data.png

6.87 KB | W: | H:

web3/tests/filterable_with_data.png

5.75 KB | W: | H:

web3/tests/filterable_with_data.png
web3/tests/filterable_with_data.png
web3/tests/filterable_with_data.png
web3/tests/filterable_with_data.png
  • 2-up
  • Swipe
  • Onion skin
web3/tests/modal_conn_section.png

37.2 KB | W: | H:

web3/tests/modal_conn_section.png

30.9 KB | W: | H:

web3/tests/modal_conn_section.png
web3/tests/modal_conn_section.png
web3/tests/modal_conn_section.png
web3/tests/modal_conn_section.png
  • 2-up
  • Swipe
  • Onion skin
web3/tests/rule_tab_with_columns.png

92.6 KB | W: | H:

web3/tests/rule_tab_with_columns.png

62.7 KB | W: | H:

web3/tests/rule_tab_with_columns.png
web3/tests/rule_tab_with_columns.png
web3/tests/rule_tab_with_columns.png
web3/tests/rule_tab_with_columns.png
  • 2-up
  • Swipe
  • Onion skin
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment