20260913231607
This commit is contained in:
1 parent
f3ad727f1c
commit
c31329d387
179 files changed
+10846
-2684
No files matched your search
@@ -323,27 +323,8 @@ function getNodeValueLabel(node) {
|
||||
const option = getFieldOptions(node.field).find((item) => item.value === node.value)
|
||||
return option?.label || node.value
|
||||
}
|
||||
const quickQueryAst = computed(() => {
|
||||
const children = []
|
||||
quickSearchFields.value.forEach((field) => {
|
||||
const value = field.type === 'numrange'
|
||||
? { min: searchForm[`${field.key}Min`], max: searchForm[`${field.key}Max`] }
|
||||
: searchForm[field.key]
|
||||
const hasValue = field.type === 'numrange' ? value.min !== '' || value.max !== '' : value !== '' && value !== null
|
||||
if (hasValue) children.push({ type: 'condition', field: field.path || field.key, operator: field.type === 'input' ? 'contains' : field.type === 'select' ? 'eq' : 'between', value })
|
||||
})
|
||||
return { type: 'group', logic: 'and', children }
|
||||
})
|
||||
const advancedQueryAst = computed(() => {
|
||||
const children = []
|
||||
const advanced = advancedNodes.filter((node) => hasNodeValue(node.value)).map((node) => ({ type: 'condition', field: fieldByKey.value[node.field]?.path || node.field, operator: node.operator, value: node.value }))
|
||||
if (advanced.length) children.push({ type: 'group', logic: advancedLogic.value, children: advanced })
|
||||
advancedGroups.forEach((group) => {
|
||||
const groupChildren = group.nodes.filter((node) => hasNodeValue(node.value)).map((node) => ({ type: 'condition', field: fieldByKey.value[node.field]?.path || node.field, operator: node.operator, value: node.value }))
|
||||
if (groupChildren.length) children.push({ type: 'group', logic: group.logic, children: groupChildren })
|
||||
})
|
||||
return { type: 'group', logic: 'and', children }
|
||||
})
|
||||
// 条件 AST 由 fms-module-common/queryUtils 统一构造(astToSql 生成 search_condition),
|
||||
// 本页是布局原型,不做查询执行,不重复维护一套 AST。
|
||||
const advancedConditionCount = computed(() => allAdvancedNodes.value.filter((node) => hasNodeValue(node.value)).length)
|
||||
const querySummary = computed(() => {
|
||||
const items = []
|
||||
|
||||
@@ -34,7 +34,7 @@ const isExternal = computed(() => maindata.value?.b_menu_type === 'external')
|
||||
/**
|
||||
* 路由地址候选:由静态路由表(@/router/routes)派生的页面路由,
|
||||
* Select 的默认筛选即对 label / value 做包含匹配,等价于 like '%关键词%',
|
||||
* 输入 "/module" 命中路径、输入 "模块" 命中标题。
|
||||
* 输入路径片段(如 "/demo")命中路径、输入标题关键词(如 "组件库")命中标题。
|
||||
* 库里已存但已不在路由表中的旧值(历史数据 / 路由已下线)补进首项,
|
||||
* 否则它会变成下拉里选不到的孤立值。
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { useHotkeys } from '@/composables/useHotkeys'
|
||||
import { FolderTree, Save, RefreshCw } from '@lucide/vue'
|
||||
import { Button, Splitter, SplitterPanel, Modal, Message } from '@/components/ui'
|
||||
import { Button, Splitter, SplitterPanel, Modal, Message, Spin } from '@/components/ui'
|
||||
import { loadDataApi, saveObjectApi } from '@/services/api'
|
||||
import {
|
||||
buildTree,
|
||||
@@ -859,7 +859,7 @@ loadModuleOptions()
|
||||
</Splitter>
|
||||
</div>
|
||||
<div v-else-if="configLoading" class="mm-config-state">
|
||||
<span class="mm-config-state__spinner"></span>
|
||||
<Spin />
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<div v-else class="mm-config-state">暂无配置数据</div>
|
||||
@@ -942,11 +942,11 @@ loadModuleOptions()
|
||||
}
|
||||
|
||||
/* ---- Splitter 面板 ----
|
||||
* 面板左右留 3px(白卡之间灰缝 3 + 分隔条 6 + 3 = 12px)。
|
||||
* 面板不再左右留白,两张白卡紧贴;灰缝由分隔条自身的触发区宽度
|
||||
* (--fms-splitter-trigger-size,默认 6px)承担。
|
||||
* 常显线平时透明,hover / 拖拽时显示整条 100% 高度的细线 + 手柄提示 */
|
||||
.mm-body-splitter :deep(> .fms-splitter-panel) {
|
||||
overflow: hidden;
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
.mm-body-splitter :deep(.fms-splitter-bar::before) {
|
||||
@@ -1064,19 +1064,4 @@ loadModuleOptions()
|
||||
color: var(--fms-text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mm-config-state__spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--fms-border);
|
||||
border-top-color: var(--fms-primary);
|
||||
border-radius: 50%;
|
||||
animation: mm-spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes mm-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -204,7 +204,6 @@ async function addStandardActions() {
|
||||
|
||||
function removeRow(row) {
|
||||
powers.value = powers.value.filter((item) => item !== row)
|
||||
Message.success('已删除权限点')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
+10
-2
@@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup>
|
||||
/**
|
||||
* 表单设计器(FormDesignEditor)——主表表单画布设计(s_field_edit)。
|
||||
* 纯编辑器逻辑,不与业务打交道:数据经 props 注入、变更经 emit 上抛。
|
||||
@@ -85,7 +85,15 @@ let syncedSchema = null
|
||||
watch(
|
||||
schemaModel,
|
||||
(val) => {
|
||||
if (val === syncedSchema) return
|
||||
// 变更若为「本编辑器回写后再经父级 round-trip」的同一内容,则不重复解析:
|
||||
// 仅凭引用相等(===)在 defineModel 子→父→子回流下会失效(回流交出结构相同的新引用),
|
||||
// 导致 rows/分组被重新 schemaToEditor、_rowKey 重新生成而丢失画布/属性面板选中态。
|
||||
if (
|
||||
val === syncedSchema ||
|
||||
(syncedSchema && stableStringify(val ?? {}) === stableStringify(syncedSchema ?? {}))
|
||||
) {
|
||||
return
|
||||
}
|
||||
const parsed = schemaToEditor('edit', val || createDefaultSchema('edit'), props.moduleId)
|
||||
rows.value = parsed.rows
|
||||
groups.value = parsed.groups
|
||||
|
||||
+9
-1
@@ -77,7 +77,15 @@ let syncedSchema = null
|
||||
watch(
|
||||
schemaModel,
|
||||
(val) => {
|
||||
if (val === syncedSchema) return
|
||||
// 变更若为「本编辑器回写后再经父级 round-trip」的同一内容,则不重复解析:
|
||||
// 仅凭引用相等(===)在 defineModel 子→父→子回流下会失效(回流交出结构相同的新引用),
|
||||
// 导致 rows/分组被重新 schemaToEditor、_rowKey 重新生成而丢失画布/属性面板选中态。
|
||||
if (
|
||||
val === syncedSchema ||
|
||||
(syncedSchema && stableStringify(val ?? {}) === stableStringify(syncedSchema ?? {}))
|
||||
) {
|
||||
return
|
||||
}
|
||||
const parsed = schemaToEditor(
|
||||
schemaType.value,
|
||||
val || createDefaultSchema(schemaType.value),
|
||||
|
||||
-42
@@ -34,7 +34,6 @@ const emit = defineEmits([
|
||||
'toggle',
|
||||
'colspan-change',
|
||||
'height-change',
|
||||
'placeholder-change',
|
||||
'remove-selected',
|
||||
])
|
||||
|
||||
@@ -47,9 +46,6 @@ function onColspanInput(value) {
|
||||
function onHeightChange(value) {
|
||||
emit('height-change', value)
|
||||
}
|
||||
function onPlaceholderChange(value) {
|
||||
emit('placeholder-change', value)
|
||||
}
|
||||
function removeSelected() {
|
||||
emit('remove-selected')
|
||||
}
|
||||
@@ -88,7 +84,6 @@ function removeSelected() {
|
||||
</span>
|
||||
</div>
|
||||
<template v-if="selectedRow">
|
||||
<div class="mm-fdesign__prop-group">基础信息</div>
|
||||
<div class="mm-fdesign__prop">
|
||||
<label>字段标题</label>
|
||||
<span class="mm-fdesign__prop-static">{{ fieldName(selectedRow) }}</span>
|
||||
@@ -97,19 +92,6 @@ function removeSelected() {
|
||||
<label>控件类型</label>
|
||||
<span class="mm-fdesign__prop-static">{{ controlTypeLabel(selectedRow) }}</span>
|
||||
</div>
|
||||
<div class="mm-fdesign__prop mm-fdesign__prop--full">
|
||||
<label for="mm-fdesign-placeholder">占位符</label>
|
||||
<Input
|
||||
id="mm-fdesign-placeholder"
|
||||
class="mm-fdesign__prop-input"
|
||||
maxlength="100"
|
||||
:model-value="selectedRow.b_placeholder || ''"
|
||||
:disabled="loading"
|
||||
placeholder="输入框内的浅色提示文字"
|
||||
@change="onPlaceholderChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="mm-fdesign__prop-group">布局</div>
|
||||
<div class="mm-fdesign__prop-row">
|
||||
<div
|
||||
class="mm-fdesign__prop"
|
||||
@@ -149,7 +131,6 @@ function removeSelected() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mm-fdesign__prop-group">状态</div>
|
||||
<div class="mm-fdesign__toggles">
|
||||
<div class="mm-fdesign__toggle-line">
|
||||
<span>必填</span>
|
||||
@@ -184,7 +165,6 @@ function removeSelected() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mm-fdesign__prop-group">操作</div>
|
||||
<Button
|
||||
type="danger"
|
||||
:disabled="loading"
|
||||
@@ -274,20 +254,6 @@ function removeSelected() {
|
||||
}
|
||||
}
|
||||
|
||||
.mm-fdesign__prop-group {
|
||||
margin-top: 14px;
|
||||
color: var(--fms-text);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
border-left: 3px solid var(--fms-primary);
|
||||
padding-left: 8px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mm-fdesign__prop {
|
||||
margin-top: 10px;
|
||||
|
||||
@@ -360,14 +326,6 @@ function removeSelected() {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* 属性面板常规输入:压缩 ui Input 高度 */
|
||||
.mm-fdesign__prop-input.input-wrapper {
|
||||
height: 26px;
|
||||
min-height: 0;
|
||||
padding: 0 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.mm-fdesign__toggles {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
+18
-53
@@ -6,15 +6,8 @@
|
||||
* 纯展示(mock 值),无编辑交互;未来运行时表单引擎可复用同一渲染结构。
|
||||
*/
|
||||
import { computed } from 'vue'
|
||||
import {
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Form,
|
||||
FormGroup,
|
||||
FormItem,
|
||||
Input,
|
||||
Select,
|
||||
} from '@/components/ui'
|
||||
import { Form, FormGroup, FormItem } from '@/components/ui'
|
||||
import FmsField from '@/components/fms-field/FmsField.vue'
|
||||
import { useFieldLookup } from '../common/useStructureEditing'
|
||||
import { spanOf } from './useFormGrid'
|
||||
|
||||
@@ -38,6 +31,19 @@ function fieldType(row) {
|
||||
return fieldOf(row)?.b_type || 'input'
|
||||
}
|
||||
|
||||
/* 预览为 mock 形态(不绑值):只喂给内部控件展示用的选项/行数/占位,
|
||||
日期与复选框无占位,与运行时形态一致 */
|
||||
function previewPropsOf(row) {
|
||||
const type = fieldType(row)
|
||||
const extra = {}
|
||||
if (type === 'select') extra.options = []
|
||||
if (type === 'textarea') extra.rows = 2
|
||||
if (row.b_placeholder && type !== 'checkbox' && type !== 'date' && type !== 'datetime') {
|
||||
extra.placeholder = row.b_placeholder
|
||||
}
|
||||
return extra
|
||||
}
|
||||
|
||||
/* ================= 分组结构推导 ================= */
|
||||
// 分组按 b_xh 排序;行按 b_group_id 归组(未分组行垫底,仅在非空时展示)。
|
||||
// 运行时形态分组占满整行(span 48),与设计画布 48 宫格一致。
|
||||
@@ -91,52 +97,11 @@ const hasAnyField = computed(() => (props.rows || []).length > 0)
|
||||
:required="Number(row.b_required) === 1"
|
||||
:span="spanOf(row)"
|
||||
>
|
||||
<Input
|
||||
v-if="fieldType(row) === 'input'"
|
||||
:placeholder="row.b_placeholder"
|
||||
:disabled="Number(row.b_disabled) === 1"
|
||||
:readonly="Number(row.b_readonly) === 1"
|
||||
/>
|
||||
<Input
|
||||
v-else-if="fieldType(row) === 'textarea'"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:placeholder="row.b_placeholder"
|
||||
:disabled="Number(row.b_disabled) === 1"
|
||||
:readonly="Number(row.b_readonly) === 1"
|
||||
/>
|
||||
<Select
|
||||
v-else-if="fieldType(row) === 'select'"
|
||||
:placeholder="row.b_placeholder"
|
||||
:options="[]"
|
||||
:disabled="Number(row.b_disabled) === 1"
|
||||
/>
|
||||
<Checkbox
|
||||
v-else-if="fieldType(row) === 'checkbox'"
|
||||
:disabled="Number(row.b_disabled) === 1"
|
||||
>{{ fieldOf(row)?.b_default_value || '选项' }}</Checkbox>
|
||||
<DatePicker
|
||||
v-else-if="fieldType(row) === 'date'"
|
||||
:disabled="Number(row.b_disabled) === 1"
|
||||
/>
|
||||
<DatePicker
|
||||
v-else-if="fieldType(row) === 'datetime'"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
:disabled="Number(row.b_disabled) === 1"
|
||||
/>
|
||||
<Input
|
||||
v-else-if="fieldType(row) === 'number' || fieldType(row) === 'money'"
|
||||
type="number"
|
||||
:placeholder="row.b_placeholder"
|
||||
:disabled="Number(row.b_disabled) === 1"
|
||||
:readonly="Number(row.b_readonly) === 1"
|
||||
/>
|
||||
<Input
|
||||
v-else
|
||||
type="text"
|
||||
:placeholder="row.b_placeholder"
|
||||
<FmsField
|
||||
:type="fieldType(row)"
|
||||
:disabled="Number(row.b_disabled) === 1"
|
||||
:readonly="Number(row.b_readonly) === 1"
|
||||
:control-props="previewPropsOf(row)"
|
||||
/>
|
||||
</FormItem>
|
||||
</FormGroup>
|
||||
|
||||
-19
@@ -104,7 +104,6 @@ function isOn(field) {
|
||||
|
||||
<!-- 列属性 -->
|
||||
<template v-if="selectedRow">
|
||||
<div class="mm-fdesign__prop-group">基础信息</div>
|
||||
<div class="mm-fdesign__prop">
|
||||
<label>列标题</label>
|
||||
<span class="mm-fdesign__prop-static">{{ fieldName(selectedRow) }}</span>
|
||||
@@ -125,7 +124,6 @@ function isOn(field) {
|
||||
@change="(v) => emit('width-commit', v)"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="statusChips.length" class="mm-fdesign__prop-group">状态</div>
|
||||
<div
|
||||
v-if="statusChips.length"
|
||||
class="mm-fdesign__status-chips"
|
||||
@@ -147,7 +145,6 @@ function isOn(field) {
|
||||
<component :is="chip.iconOff && !isOn(chip.flag) ? chip.iconOff : chip.icon" :size="12" />
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="showRowDelete" class="mm-fdesign__prop-group">操作</div>
|
||||
<Button
|
||||
v-if="showRowDelete"
|
||||
type="danger"
|
||||
@@ -161,7 +158,6 @@ function isOn(field) {
|
||||
|
||||
<!-- 分组带属性 -->
|
||||
<template v-else-if="selectedBand">
|
||||
<div class="mm-fdesign__prop-group">分组带</div>
|
||||
<div class="mm-fdesign__prop">
|
||||
<label for="mm-tdesign-band-title">带标题</label>
|
||||
<Input
|
||||
@@ -194,7 +190,6 @@ function isOn(field) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mm-fdesign__prop-group">操作</div>
|
||||
<Button
|
||||
type="outline"
|
||||
:disabled="loading"
|
||||
@@ -314,20 +309,6 @@ function isOn(field) {
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.mm-fdesign__prop-group {
|
||||
margin-top: 14px;
|
||||
color: var(--fms-text);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
border-left: 3px solid var(--fms-primary);
|
||||
padding-left: 8px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mm-fdesign__prop {
|
||||
margin-top: 10px;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
ShieldCheck,
|
||||
Table2,
|
||||
} from '@lucide/vue'
|
||||
import { Button, Splitter, SplitterPanel, Tabs, Modal, Message } from '@/components/ui'
|
||||
import { Button, Splitter, SplitterPanel, Tabs, Modal, Message, Spin } from '@/components/ui'
|
||||
import { loadDataApi, saveObjectApi } from '@/services/api'
|
||||
import {
|
||||
buildTree,
|
||||
@@ -763,8 +763,7 @@ async function loadModuleConfig(moduleId) {
|
||||
// 权限点引用的共用动作动词键(action.*)跨模块公用:按精确键并入查询,
|
||||
// 已存在的键进入基线后 diff 不会重复插入(模块使用权限键即模块名键,已在条件内)
|
||||
for (const power of powers_org.value || []) {
|
||||
const key =
|
||||
String(power.b_i18n || '').trim() || buildPowerI18nKey(power.b_action, id)
|
||||
const key = String(power.b_i18n || '').trim() || buildPowerI18nKey(power.b_action, id)
|
||||
if (key && key.startsWith('action.')) i18nKeys.push(key)
|
||||
}
|
||||
const i18nRes = await loadDataApi(
|
||||
@@ -1091,7 +1090,7 @@ loadModuleTree()
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="configLoading" class="mm-config-state">
|
||||
<span class="mm-config-state__spinner"></span>
|
||||
<Spin />
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<div v-else class="mm-config-state">暂无配置数据</div>
|
||||
@@ -1157,7 +1156,6 @@ loadModuleTree()
|
||||
|
||||
.mm-body-splitter :deep(> .fms-splitter-panel) {
|
||||
overflow: hidden;
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
.mm-body-splitter :deep(.fms-splitter-bar::before) {
|
||||
@@ -1274,19 +1272,4 @@ loadModuleTree()
|
||||
color: var(--fms-text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.mm-config-state__spinner {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid var(--fms-border);
|
||||
border-top-color: var(--fms-primary);
|
||||
border-radius: 50%;
|
||||
animation: mm-config-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes mm-config-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user