Files
workspace/code/container-ocr/ocr-vue/src/views/system/field/index.vue
T
2026-03-19 22:15:36 +08:00

715 lines
16 KiB
Vue

<script lang="js" setup>
import {
checkColumnsApi,
loadDataApi,
loadAllColumnsApi,
loadDataBySqlApi,
saveObjectApi,
} from "@/request/api";
import { ElMessage } from "element-plus";
import PreviewSql from "./preview-sql.vue";
import { diffObject } from "@/utils/diff";
const tableRef = ref();
const loading = ref(false);
const leftSize = ref(200);
const isCollapse = ref(false);
const showSql = ref(false);
const treeData = [{ b_id: "root", mc_b: "全部分类", isLeaf: false, children: [] }];
const treeProps = {
children: "children",
label: "mc_b",
};
const currentNode = ref(null);
const currentModule = ref(null);
const isSelectedNode = computed(() => !!currentNode.value);
const tableData = ref([]);
const currentRow = ref({});
const isSelectedRow = computed(() => !!currentRow.value?.col_FieldName);
let oldRowData = {};
const columns = [
{
prop: "col_field_name",
label: "字段",
width: 140,
fixed: "left",
},
{
prop: "col_caption",
label: "标题",
width: 100,
fixed: "left",
type: "input",
},
{
prop: "col_field_name_v",
label: "视图字段",
width: 100,
type: "input",
},
{
prop: "col_group_index",
label: "分组",
width: 80,
type: "input",
},
{
prop: "col_visible",
label: "显示",
width: 80,
type: "checkbox",
},
{
prop: "col_allow_readonly",
label: "只读",
width: 80,
type: "checkbox",
},
{
prop: "col_allow_null",
label: "可不输",
width: 80,
type: "checkbox",
},
{
prop: "col_allow_repeat",
label: "可重复",
width: 80,
type: "checkbox",
},
{
prop: "col_allow_reserve",
label: "可保留",
width: 80,
type: "checkbox",
},
{
prop: "col_search",
label: "可查找",
width: 80,
type: "checkbox",
},
{
prop: "col_search_order",
label: "查找顺序",
width: 100,
type: "input",
},
{
prop: "col_search_group_index",
label: "查找分组",
width: 100,
type: "input",
},
{
prop: "col_type",
label: "字段类型",
width: 100,
type: "option",
},
{
prop: "col_default_value",
label: "默认值",
width: 80,
type: "input",
},
{
prop: "col_width",
label: "宽度",
width: 80,
type: "input",
},
{
prop: "col_position",
label: "位置",
width: 80,
type: "input",
},
{
prop: "col_show_summary",
label: "合计",
width: 80,
type: "checkbox",
},
{
prop: "col_display_format",
label: "显示格式",
width: 100,
type: "input",
},
{
prop: "col_edit",
label: "编辑",
width: 80,
type: "checkbox",
},
{
prop: "col_edit_position",
label: "编辑位置",
width: 100,
type: "input",
},
{
prop: "col_edit_width",
label: "编辑宽度",
width: 100,
type: "input",
},
{
prop: "col_edit_height",
label: "编辑高度",
width: 100,
type: "input",
},
{
prop: "col_edit_source_data",
label: "编辑数据",
width: 160,
type: "input",
},
{
prop: "col_edit_source_field",
label: "下拉表显示字段",
width: 100,
type: "input",
},
{
prop: "col_edit_group_index",
label: "编辑分组",
width: 100,
type: "input",
},
{
prop: "col_edit_batch",
label: "批量编辑",
width: 100,
type: "checkbox",
},
{
prop: "col_show_property",
label: "属性",
width: 80,
type: "checkbox",
},
];
const editTypeOptions = [
{
value: "input",
label: "input",
},
{
value: "textarea",
label: "textarea",
},
{
value: "date",
label: "date",
},
{
value: "datetime",
label: "datetime",
},
{
value: "singleId",
label: "singleId",
},
{
value: "singleName",
label: "singleName",
},
{
value: "option",
label: "option",
},
{
value: "checkbox",
label: "checkbox",
},
{
value: "radio",
label: "radio",
},
];
function getOptions(field) {
if (field === "col_type") {
return editTypeOptions;
}
}
function updateCollapse() {
if (isCollapse.value) {
isCollapse.value = false;
leftSize.value = 200;
} else {
isCollapse.value = true;
leftSize.value = 0;
}
}
async function handleTreeLoad(node, resolve) {
try {
const { level: nodeLevel, data: nodeData } = node;
if (nodeLevel === 0) {
resolve([
{
b_id: "root",
mc_b: "全部分类",
children: [],
isLeaf: false,
},
]);
return;
}
const currentLevel = nodeLevel - 1;
let sql = "";
if (currentLevel === 0) {
sql = `SELECT * FROM s_module_bmfl WHERE js='1' AND b_canuse='1' ORDER BY b_xh`;
} else {
if (!nodeData) {
resolve([]);
return;
}
const parentField = `bm_${currentLevel}_b`;
const parentCondition = ` AND ${parentField}='${nodeData.b_id}'`;
const childLevel = currentLevel + 1;
sql = `
SELECT s_module_bmfl.*, s_module.b_id AS b_moduleid
FROM s_module_bmfl
LEFT JOIN s_module ON s_module.b_cate_id = s_module_bmfl.b_id
WHERE s_module_bmfl.b_canuse='1'
AND js='${childLevel}'
${parentCondition}
AND s_module_bmfl.b_canuse='1'
ORDER BY b_xh
`;
}
const res = await loadDataBySqlApi(sql);
if (res.success) {
resolve(res.data);
} else {
resolve([]);
}
} catch (err) {
console.error(err);
resolve([]);
}
}
async function handleTreeNodeClick(nodeData) {
if (!nodeData || nodeData.b_id === "root" || !nodeData.b_moduleid) return;
currentNode.value = nodeData;
loading.value = true;
try {
const res = await loadDataApi("s_module", `b_id = '${nodeData.b_moduleid}'`);
if (res.success) {
currentModule.value = res.data[0] || null;
if (currentModule.value?.b_ViewTable) {
const viewTable = currentModule.value?.b_ViewTable;
await loadDatas(viewTable);
}
}
} catch (err) {
console.error("加载模块或列失败", err);
} finally {
loading.value = false;
}
}
// 加载数据
async function loadDatas(viewName) {
if (!viewName) {
return;
}
const res = await loadAllColumnsApi(viewName);
if (res.success) {
tableData.value = res.data;
}
}
// 编辑关闭事件
async function handleEditClosed({ row, rowIndex, column, columnIndex }) {
const $table = tableRef.value;
if ($table) {
await handleSaveRow(row);
}
}
// 编辑激活事件
function handleEditActivated({ row }) {
const $table = tableRef.value;
if ($table) {
oldRowData = JSON.parse(JSON.stringify(row));
}
}
// 选中行事件
function handleCurrentRowChange({ newValue }) {
currentRow.value = newValue;
}
// 保存
function handleSaveRow(row) {
if (!row?.b_id) return;
const diffRowResult = diffObject(row, oldRowData, ["b_id"], []);
if (!diffRowResult.isChanged) {
return;
}
const saveData = [
{
tableName: "s_columnLib",
keyField: "b_id",
inserts: [],
updates: [diffRowResult.result],
deletes: [],
log: "0",
user: "0",
},
];
saveObjectApi(saveData).then((res) => {
if (res.success) {
ElMessage.success("保存成功");
}
});
}
// 检查
function handleCheckColumns() {
if (!currentModule.value) {
return;
}
const viewName = currentModule.value.b_ViewTable || "";
if (!viewName) {
return;
}
loading.value = true;
checkColumnsApi(viewName)
.then(async (res) => {
if (res.success) {
ElMessage.success("检查成功");
await loadDatas(viewName);
}
})
.finally(() => {
loading.value = false;
});
}
</script>
<template>
<div class="g3-wrapper">
<el-splitter>
<el-splitter-panel :size="leftSize" :min="0" :max="260">
<div class="g3-wrapper-left">
<div class="tree-header">
<el-text>模块名</el-text>
<div class="tree-options"></div>
</div>
<div class="tree-container">
<el-scrollbar style="flex: 1">
<el-tree
lazy
highlight-current
node-key="b_id"
:data="treeData"
:props="treeProps"
:load="handleTreeLoad"
:default-expanded-keys="['root']"
@node-click="handleTreeNodeClick"
/>
</el-scrollbar>
</div>
</div>
</el-splitter-panel>
<el-splitter-panel style="margin-left: 8px">
<div class="g3-wrapper-right">
<div class="content">
<div class="content-head">
<div class="content-head-left">
<el-button type="primary" :disabled="!isSelectedNode" @click="handleCheckColumns"
>检查</el-button
>
<el-button type="primary" :disabled="!isSelectedNode">浏览栏位预览</el-button>
<el-button type="primary" :disabled="!isSelectedNode">编辑栏位预览</el-button>
<el-button type="primary" :disabled="!isSelectedNode">属性预览</el-button>
<el-button type="primary">修改标签</el-button>
<el-button type="primary" :disabled="!isSelectedNode" @click="showSql = !showSql">
SQL语句
</el-button>
</div>
<div class="content-head-right">
<el-input placeholder="请输入关键词搜索" />
<el-button type="primary">搜索</el-button>
</div>
</div>
<div class="content-table">
<vxe-table
ref="tableRef"
height="100%"
:data="tableData"
:loading="loading"
:row-config="{
useKey: true,
keyField: 'col_FieldName',
isCurrent: true,
}"
:column-config="{
resizable: true,
useKey: true,
}"
:edit-config="{
trigger: 'click',
mode: 'row',
autoClear: false,
showStatus: false,
showIcon: false,
}"
:scroll-x="{
gt: 0,
enabled: true,
}"
:scroll-y="{
gt: 0,
enabled: true,
}"
show-overflow
show-header-overflow
@current-row-change="handleCurrentRowChange"
@edit-closed="handleEditClosed"
@edit-activated="handleEditActivated"
>
<vxe-column type="seq" width="50" fixed="left" />
<vxe-column
v-for="column in columns"
:key="column.prop"
:field="column.prop"
:title="column.label"
:width="column.width"
align="center"
sortable
:edit-render="{ autoFocus: true }"
:fixed="column.fixed"
>
<template #default="{ row }">
<ElCheckbox
v-if="column.type === 'checkbox'"
true-value="1"
false-value="0"
:checked="row[column.prop] === '1'"
/>
<template v-else>
{{ row[column.prop] }}
</template>
</template>
<template #edit="{ row }">
<ElCheckbox
v-if="column.type === 'checkbox'"
v-model="row[column.prop]"
true-value="1"
false-value="0"
/>
<el-select
v-else-if="column.type === 'option'"
v-model="row[column.prop]"
size="small"
placeholder=" "
>
<el-option
v-for="item in getOptions(column.prop)"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<ElInput
v-else-if="column.type === 'input'"
v-model="row[column.prop]"
size="small"
/>
<template v-else>
{{ row[column.prop] }}
</template>
</template>
</vxe-column>
</vxe-table>
</div>
</div>
</div>
</el-splitter-panel>
</el-splitter>
<PreviewSql v-model="showSql" :data="tableData" :viewtable="currentModule?.b_ViewTable" />
</div>
</template>
<style lang="scss" scoped>
@use "@/styles/mixins.scss" as *;
.g3-wrapper {
width: 100%;
height: 100%;
display: flex;
align-items: center;
:deep(.el-splitter-bar__dragger-horizontal::after),
:deep(.el-splitter-bar__dragger-horizontal::before) {
width: 1px;
}
:deep(.el-splitter-bar__dragger::after),
:deep(.el-splitter-bar__dragger::before) {
background-color: #f2f6fc;
}
}
.g3-wrapper-left {
height: 100%;
width: 100%;
background-color: #fff;
.tree-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
padding: 0 10px;
border-bottom: 1px solid #f2f6fc;
}
.tree-options {
display: flex;
align-items: center;
.item {
display: flex;
justify-content: center;
align-items: center;
list-style: none;
margin-left: 5px;
cursor: pointer;
border-radius: 6px;
font-size: 16px;
height: 26px;
width: 26px;
color: #333;
transition: background-color 0.3s ease;
&:hover {
background-color: #f5f7fa;
}
}
}
.tree-container {
height: calc(100% - 40px);
padding: 10px;
display: flex;
flex-direction: column;
}
}
.g3-wrapper-right {
flex: 1;
transition: width 0.3s;
height: 100%;
display: flex;
flex-direction: column;
background-color: #fff;
.head {
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
padding: 0 10px;
border-bottom: 1px solid #f2f6fc;
flex-shrink: 0;
.options {
display: flex;
align-items: center;
.item {
display: flex;
justify-content: center;
align-items: center;
list-style: none;
margin-left: 5px;
border-radius: 6px;
font-size: 16px;
height: 26px;
width: 26px;
color: #333;
transition: background-color 0.3s ease;
}
.item-bg {
cursor: pointer;
background-color: #f5f7fa;
}
}
}
.content {
padding: 10px;
display: flex;
flex-direction: column;
min-width: 0;
height: 100%;
.content-head {
display: flex;
align-items: center;
justify-content: space-between;
.content-head-left {
display: flex;
@include flexGapWrap(4px, 8px);
}
.content-head-right {
display: flex;
@include flexGapWrap(4px, 8px);
flex-wrap: nowrap;
min-width: 200px;
}
}
.content-table {
margin-top: 8px;
flex: 1;
}
}
}
</style>