Files
workspace/code/fms/fms-vue/src/views/module-management/components/ModulePowersPanel.vue
T
2026-07-26 22:52:21 +08:00

243 lines
6.3 KiB
Vue

<script setup>
import { computed, h, nextTick, ref, watch } from "vue";
import { message, Modal } from "antdv-next";
import { Languages } from "@lucide/vue";
import FmsTable from "@/components/fms-table/FmsTable.vue";
import { createTempId } from "@/utils/tempId";
import I18nQuickSetModal from "./I18nQuickSetModal.vue";
const props = defineProps({
moduleId: { type: String, required: true },
moduleCode: { type: String, default: "" },
powers: { type: Array, default: () => [] },
loading: { type: Boolean, default: false },
});
const emit = defineEmits(["update"]);
const localPowers = ref([]);
const activeKey = ref(null);
const tableRef = ref(null);
// 多语言键按 power.<模块代码>.<操作编码> 自动生成,无需手动填写。
watch(
() => [props.powers, props.moduleCode],
([val, moduleCode], previous = []) => {
if (val === localPowers.value && moduleCode === previous[1]) return;
localPowers.value = (val || []).map((r) => ({
...r,
b_i18n:
r.b_code && props.moduleCode ? `power.${props.moduleCode}.${r.b_code}` : (r.b_i18n ?? null),
}));
},
{ immediate: true },
);
function emitUpdate() {
emit("update", localPowers.value);
}
async function addRow() {
if (props.loading) return;
const newId = createTempId();
localPowers.value.push({
b_id: newId,
b_module_id: props.moduleId,
b_code: "",
b_name: "",
b_i18n: null,
b_xh: localPowers.value.length * 10,
b_canuse: 1,
});
emitUpdate();
activeKey.value = String(newId);
await nextTick();
tableRef.value?.scrollToRow(String(newId));
}
function deleteRow(record) {
if (props.loading) return;
localPowers.value = localPowers.value.filter((r) => r.b_id !== record.b_id);
// 同步清理选择状态,避免选中项被删后 keys 残留
const k = String(record.b_id);
if (selectedRowKeys.value.includes(k)) {
selectedRowKeys.value = selectedRowKeys.value.filter((x) => x !== k);
}
emitUpdate();
}
// 行多选:selectedRowKeys 由 FmsTable 的选择列(createSelectionCellRenderers 提供)
// 通过 onChange 回写;FmsTable 在传了 rowSelection 时自动在最前列插入复选框列。
const selectedRowKeys = ref([]);
const rowSelection = computed(() => ({
selectedRowKeys: selectedRowKeys.value,
onChange: (keys) => {
selectedRowKeys.value = keys;
},
}));
// 批量删除选中行
function batchDelete() {
if (props.loading) return;
const keys = selectedRowKeys.value;
if (!keys.length) return;
Modal.confirm({
title: `确定删除选中的 ${keys.length} 条记录?`,
okText: "删除",
okType: "danger",
cancelText: "取消",
onOk: () => {
const delSet = new Set(keys);
localPowers.value = localPowers.value.filter((r) => !delSet.has(r.b_id));
selectedRowKeys.value = [];
if (activeKey.value && delSet.has(activeKey.value)) activeKey.value = "";
emitUpdate();
},
});
}
// 编辑器已直接更新当前行,这里只向父级同步草稿。
function onCellChange() {
emitUpdate();
}
// ── 快速设置多语言 ──
const i18nModalRef = ref(null);
function openI18n(row) {
const key =
row.b_code && props.moduleCode ? `power.${props.moduleCode}.${row.b_code}` : row.b_i18n;
if (!key) {
message.warning("请先填写操作编码");
return;
}
i18nModalRef.value?.open(key, row.b_name || "");
}
// 多语言键单元格:值 + 右侧翻译 icon 按钮(原生 button,避免 antdv Button 组件开销)
const I18nKeyCell = ({ row, cellValue }) =>
h(
"span",
{
style: {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: "4px",
width: "100%",
},
},
[
h(
"span",
{ style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } },
cellValue ?? "",
),
h(
"button",
{
type: "button",
title: "设置多语言",
style: {
flex: "none",
border: "none",
background: "transparent",
cursor: "pointer",
padding: "2px",
display: "inline-flex",
alignItems: "center",
},
onClick: (e) => {
e.stopPropagation();
openI18n(row);
},
},
[h(Languages, { size: 14 })],
),
],
);
const columns = ref([
{ dataIndex: "b_code", title: "操作编码", width: 160, editor: { type: "input" } },
{ dataIndex: "b_name", title: "操作名称", width: 160, editor: { type: "input" } },
{ dataIndex: "b_i18n", title: "多语言键", width: 160, customCell: I18nKeyCell },
{ dataIndex: "b_xh", title: "排序", width: 80, editor: { type: "input" } },
{
dataIndex: "b_canuse",
title: "启用",
width: 70,
editor: { type: "checkbox" },
},
{
dataIndex: "actions",
title: "操作",
width: 60,
actions: [
{
type: "delete",
label: "删除",
confirm: "确定删除该记录?",
onClick: (row) => deleteRow(row),
},
],
},
]);
</script>
<template>
<div class="powers-panel">
<div class="panel-toolbar">
<a-space>
<a-button type="primary" :disabled="loading" @click="addRow">新增权限</a-button>
<a-button danger :disabled="loading || !selectedRowKeys.length" @click="batchDelete">
批量删除{{ selectedRowKeys.length ? `(${selectedRowKeys.length})` : "" }}
</a-button>
</a-space>
</div>
<div class="panel-body">
<FmsTable
ref="tableRef"
v-model:columns="columns"
:data-source="localPowers"
:editable="!loading"
row-key="b_id"
:active-row-key="activeKey"
:row-selection="rowSelection"
@row-click="(_, row) => (activeKey = String(row.b_id))"
@change="onCellChange"
/>
</div>
<I18nQuickSetModal ref="i18nModalRef" />
</div>
</template>
<style scoped lang="scss">
.powers-panel {
display: flex;
flex-direction: column;
height: 100%;
}
.panel-toolbar {
display: flex;
flex: none;
gap: 8px;
padding: 12px 20px 0;
}
.panel-body {
flex: 1;
min-height: 0;
padding: 12px 20px 16px;
display: flex;
flex-direction: column;
}
.panel-body > :deep(.fms-table) {
flex: 1;
min-height: 0;
width: 100%;
}
</style>