Files
workspace/code/g3soft-erp/web/src/views/module/module-management/ModuleBasicPanel.vue
T
2026-10-14 17:25:47 +08:00

254 lines
9.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
/**
* 模块基础信息面板(s_module)。
*
* 数据契约:v-model:module 绑定父级持有的「编辑草稿」(选中模块的 s_module 行副本),
* 面板就地修改字段(与原型一致);保存编排在父级,本面板只管 UI 与校验。
* 编码(b_id 即主键):仅未落库草稿可改,改动时同步派生多语言键 module.xxx;
* 类型切换会清掉不适用字段(module 无数据来源;virtual 无保存表)。
*
* 校验规则(随输入实时提示;保存前由父级调 validate() 统一跑一遍):
* - 编码:必填、2-50 位、小写字母开头、只含小写字母/数字/下划线、禁止负数临时编码;
* - 名称 / 类型必填;data 的查询来源(表或 SQL)、主键字段、保存表必填。
*/
import { computed, ref } from 'vue'
import { G3Form, G3FormGroup, G3FormItem, G3Input, G3Select, G3Switch } from '@g3soft/ui'
import type { FormRule } from '@g3soft/ui'
import { buildModuleI18nKey, type ModuleFieldRow, type ModuleNode } from './utils'
const moduleModel = defineModel<ModuleNode | null>('module', { default: null })
const props = withDefaults(
defineProps<{
/** 本模块的字段清单(s_field 最小投影):「数据范围字段」下拉的候选 */
fields?: ModuleFieldRow[]
/** 编码只读:已保存模块主键即编码,创建后不可修改 */
codeReadonly?: boolean
}>(),
{ fields: () => [], codeReadonly: false },
)
const typeOptions = [
{ label: '模块', value: 'module' },
{ label: '数据模块', value: 'data' },
{ label: '虚拟模块', value: 'virtual' },
]
const isData = computed(() => moduleModel.value?.b_module_type === 'data')
const isVirtual = computed(() => moduleModel.value?.b_module_type === 'virtual')
/** data / virtual 拥有查询来源与字段定义 */
const hasDataSource = computed(() => isData.value || isVirtual.value)
/** 数据范围字段候选(本模块字段),空 b_field 的行直接剔除 */
const scopeFieldOptions = computed(() =>
props.fields
.filter((field) => field.b_field != null && String(field.b_field) !== '')
.map((field) => ({
label: String(field.b_name || field.b_field),
value: String(field.b_field),
})),
)
const formRules = computed<Record<string, FormRule[]>>(() => ({
b_id: [
{ required: true, message: '请填写模块编码' },
{
validator: (value) => {
const code = String(value ?? '').trim()
if (!code) return '编码不能为空'
if (/^-\d+$/.test(code)) return '新建模块编码不能以负数临时值保存'
if (code.length < 2 || code.length > 50) return '编码长度需为 2-50 个字符'
return /^[a-z][a-z0-9_]*$/.test(code)
? true
: '编码须以小写字母开头,且只能包含小写字母、数字和下划线'
},
},
],
b_name: [{ required: true, message: '请填写模块名称' }],
b_module_type: [{ required: true, message: '请选择模块类型' }],
b_view_table: isData.value
? [
{
validator: (value) => {
if (String(value ?? '').trim()) return true
if (String(moduleModel.value?.b_query_sql ?? '').trim()) return true
return '请填写查询表/视图,或配置查询 SQL'
},
},
]
: [],
b_key_field: isData.value ? [{ required: true, message: '请填写主键字段' }] : [],
b_save_table: isData.value ? [{ required: true, message: '请填写保存表' }] : [],
}))
const formRef = ref<InstanceType<typeof G3Form> | null>(null)
/** 校验全表:返回 [{ name, errors }],空数组即通过(供保存前调用) */
async function validate() {
return (await formRef.value?.validateAll()) ?? []
}
defineExpose({ validate })
/** 就地写草稿字段:空字符串统一收敛成 null(数据库可空列的约定) */
function setField(field: string, value: unknown) {
const draft = moduleModel.value as Record<string, unknown> | null
if (!draft) return
draft[field] = value === '' ? null : value
}
/** 编码变化:始终以完整编码重新派生多语言键 */
function onCodeInput(value: string | number) {
setField('b_id', value)
setField('b_i18n', buildModuleI18nKey(String(value ?? '')))
}
/** 切换类型:清掉不适用字段(module 无数据来源;virtual 无保存表),并刷新多语言键 */
function onModuleTypeChange(value: unknown) {
const type = String(value ?? '')
setField('b_module_type', type)
if (type === 'module') {
setField('b_view_table', null)
setField('b_save_table', null)
setField('b_key_field', null)
setField('b_scope_field', null)
setField('b_order_sql', null)
setField('b_query_sql', null)
} else if (type === 'virtual') {
setField('b_save_table', null)
}
setField('b_i18n', buildModuleI18nKey(String(moduleModel.value?.b_id ?? '')))
}
</script>
<template>
<div v-if="moduleModel" class="mm-basic">
<G3Form
ref="formRef"
:model="moduleModel"
:rules="formRules"
:columns="3"
max-width="720px"
label-width="96px"
>
<G3FormGroup title="标识与归属">
<G3FormItem label="模块编码" name="b_id" required>
<G3Input
:model-value="moduleModel.b_id ?? ''"
:disabled="codeReadonly"
@update:model-value="onCodeInput"
/>
</G3FormItem>
<G3FormItem label="模块名称" name="b_name" required>
<G3Input
:model-value="moduleModel.b_name ?? ''"
@update:model-value="(value) => setField('b_name', value)"
/>
</G3FormItem>
<G3FormItem label="模块类型" name="b_module_type" required>
<G3Select
:model-value="moduleModel.b_module_type ?? ''"
:options="typeOptions"
@update:model-value="onModuleTypeChange"
/>
</G3FormItem>
<G3FormItem label="多语言键" name="b_i18n" :span="2">
<G3Input :model-value="moduleModel.b_i18n ?? ''" readonly />
</G3FormItem>
</G3FormGroup>
<G3FormGroup v-if="hasDataSource" title="数据来源">
<G3FormItem label="查询表/视图" name="b_view_table" :required="isData">
<G3Input
:model-value="moduleModel.b_view_table ?? ''"
@update:model-value="(value) => setField('b_view_table', value)"
/>
</G3FormItem>
<G3FormItem label="主键字段" name="b_key_field" :required="isData">
<G3Input
:model-value="moduleModel.b_key_field ?? ''"
@update:model-value="(value) => setField('b_key_field', value)"
/>
</G3FormItem>
<!-- 数据范围字段:记录归属人字段(如录入人)。
用户数据范围面板只选「看到哪一档」,用哪个字段判在这里定 ——
同一个模块的所有用户必须用同一字段,否则同一份数据对不同用户含义不同 -->
<G3FormItem label="数据范围字段" name="b_scope_field">
<G3Select
:model-value="moduleModel.b_scope_field ?? ''"
:options="scopeFieldOptions"
clearable
title="记录归属人字段:「仅本人」按它比对,「本部门 / 本部门及下级」由该用户反查部门;留空表示本模块不按字段判范围"
@update:model-value="(value) => setField('b_scope_field', value)"
/>
</G3FormItem>
<G3FormItem v-if="isData" label="保存表" name="b_save_table" required>
<G3Input
:model-value="moduleModel.b_save_table ?? ''"
@update:model-value="(value) => setField('b_save_table', value)"
/>
</G3FormItem>
<G3FormItem label="默认排序 SQL" name="b_order_sql" :span="isData ? 1 : 3">
<G3Input
:model-value="moduleModel.b_order_sql ?? ''"
@update:model-value="(value) => setField('b_order_sql', value)"
/>
</G3FormItem>
</G3FormGroup>
<G3FormGroup v-if="hasDataSource" title="查询与扩展">
<G3FormItem label="查询 SQL" name="b_query_sql" :span="3">
<G3Input
type="textarea"
:rows="3"
:model-value="moduleModel.b_query_sql ?? ''"
@update:model-value="(value) => setField('b_query_sql', value)"
/>
</G3FormItem>
<G3FormItem label="扩展配置 JSON" name="b_config_json" :span="3">
<G3Input
type="textarea"
:rows="2"
:model-value="moduleModel.b_config_json ?? ''"
@update:model-value="(value) => setField('b_config_json', value)"
/>
</G3FormItem>
</G3FormGroup>
<G3FormGroup title="行为设置">
<G3FormItem label="排序号" name="b_xh">
<G3Input
type="number"
:model-value="moduleModel.b_xh ?? ''"
@update:model-value="(value) => setField('b_xh', value)"
/>
</G3FormItem>
<G3FormItem label="启用" name="b_canuse">
<G3Switch
:model-value="Number(moduleModel.b_canuse) === 1"
@update:model-value="(value) => setField('b_canuse', value ? 1 : 0)"
/>
</G3FormItem>
</G3FormGroup>
<G3FormGroup title="更多">
<G3FormItem label="描述" name="b_bz" :span="3">
<G3Input
type="textarea"
:rows="2"
:model-value="moduleModel.b_bz ?? ''"
@update:model-value="(value) => setField('b_bz', value)"
/>
</G3FormItem>
</G3FormGroup>
</G3Form>
</div>
</template>
<style lang="scss" scoped>
/* 面板内容自带内边距(骨架的 pane 只负责滚动,不设 padding) */
.mm-basic {
padding: 4px 0 16px;
}
</style>