243 lines
6.3 KiB
Vue
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>
|