20260723225202

This commit is contained in:
oneao committed 2026-07-23 22:52:02 +08:00
1 parent 5f73b54802
commit 4d118a0037
22 files changed
+1403 -259

No files matched your search

@@ -0,0 +1,322 @@
<script setup>
import { computed, h, onMounted, ref } from "vue";
import { message, Switch, Button, Popconfirm, Tag } from "antdv-next";
import { Plus } from "@lucide/vue";
import FmsTable from "@/components/fms-table/FmsTable.vue";
import { loadDataApi, saveObjectApi, nextIdApi } from "@/services/api";
import { usePermissionStore } from "@/stores/permissions";
const permissionStore = usePermissionStore();
const data = ref([]);
const loading = ref(false);
const saving = ref(false);
const canCreate = computed(() => permissionStore.canPower("m_i18n_type", "create"));
const canUpdate = computed(() => permissionStore.canPower("m_i18n_type", "update"));
const canDelete = computed(() => permissionStore.canPower("m_i18n_type", "delete"));
async function loadData() {
loading.value = true;
try {
const res = await loadDataApi("b_i18n_type", "", "b_xh ASC, b_id ASC");
data.value = res.data || [];
} catch (e) {
message.error("加载多语言类型失败:" + (e?.message || e));
} finally {
loading.value = false;
}
}
onMounted(loadData);
/* ---------- 行内单元格渲染 ---------- */
function createDefaultCell() {
return (props) =>
props.row.b_default === 1
? h(Tag, { color: "blue" }, "默认")
: h("span", { class: "muted" }, "—");
}
function createCanuseCell() {
return (props) => {
const row = props.row;
return h(Switch, {
checked: row.b_canuse === 1,
size: "small",
disabled: !canUpdate.value,
"onUpdate:checked": (val) => {
const next = val ? 1 : 0;
if (row.b_canuse === next) return;
row.b_canuse = next;
saveRow(row, { b_canuse: next });
},
});
};
}
function createActionsCell() {
return (props) => {
const row = props.row;
const isDefault = row.b_default === 1;
const children = [];
if (canUpdate.value) {
children.push(
h(
Button,
{ type: "link", size: "small", onClick: (e) => { e.stopPropagation(); openEdit(row); } },
"编辑",
),
);
}
if (canDelete.value && !isDefault) {
children.push(
h(
Popconfirm,
{
title: "确定删除该语言?",
okText: "删除",
okButtonProps: { danger: true },
onConfirm: () => removeRow(row),
},
{
default: () =>
h(
Button,
{ type: "link", size: "small", danger: true, onClick: (e) => e.stopPropagation() },
"删除",
),
},
),
);
}
return h("span", { class: "actions-cell" }, children);
};
}
const columns = [
{ dataIndex: "b_code", title: "简称 / 编码", width: 150 },
{ dataIndex: "b_name", title: "名称", width: 160 },
{ dataIndex: "b_default", title: "默认", width: 80, align: "center", customCell: createDefaultCell() },
{ dataIndex: "b_canuse", title: "是否启用", width: 100, align: "center", customCell: createCanuseCell() },
{ dataIndex: "b_xh", title: "排序", width: 80, align: "center" },
{ dataIndex: "actions", title: "操作", width: 130, align: "center", customCell: createActionsCell() },
];
/* ---------- 行内开关即时保存 ---------- */
async function saveRow(row, patch) {
try {
await saveObjectApi([
{ table: "b_i18n_type", key_field: "b_id", updates: [{ b_id: row.b_id, ...patch }] },
]);
message.success("已更新");
} catch (e) {
message.error("更新失败:" + (e?.message || e));
await loadData();
}
}
async function removeRow(row) {
if (row.b_default === 1) return;
saving.value = true;
try {
await saveObjectApi([
{ table: "b_i18n_type", key_field: "b_id", deletes: [{ b_id: row.b_id }] },
]);
message.success("删除成功");
await loadData();
} catch (e) {
message.error("删除失败:" + (e?.message || e));
} finally {
saving.value = false;
}
}
/* ---------- 新增 / 编辑弹窗 ---------- */
const modalVisible = ref(false);
const modalMode = ref("create");
const editingId = ref(null);
const editingDefault = ref(false);
const form = ref({
b_code: "",
b_name: "",
b_xh: 0,
defaultOn: false,
canuseOn: true,
});
function openCreate() {
modalMode.value = "create";
editingId.value = null;
editingDefault.value = false;
form.value = { b_code: "", b_name: "", b_xh: 0, defaultOn: false, canuseOn: true };
modalVisible.value = true;
}
function openEdit(row) {
modalMode.value = "edit";
editingId.value = row.b_id;
editingDefault.value = row.b_default === 1;
form.value = {
b_code: row.b_code,
b_name: row.b_name,
b_xh: row.b_xh ?? 0,
defaultOn: row.b_default === 1,
canuseOn: row.b_canuse === 1,
};
modalVisible.value = true;
}
async function saveForm() {
const f = form.value;
if (!f.b_code || !f.b_code.trim()) {
message.warning("请填写语言编码");
return;
}
if (!f.b_name || !f.b_name.trim()) {
message.warning("请填写语言名称");
return;
}
saving.value = true;
try {
const isNew = modalMode.value === "create" || !editingId.value;
const b_id = isNew ? (await nextIdApi(1)).data : editingId.value;
const row = {
b_id,
b_code: f.b_code.trim(),
b_name: f.b_name.trim(),
b_canuse: f.canuseOn ? 1 : 0,
b_default: f.defaultOn ? 1 : 0,
b_xh: f.b_xh ?? 0,
};
const req = { table: "b_i18n_type", key_field: "b_id", inserts: [], updates: [], deletes: [] };
if (isNew) req.inserts.push(row);
else req.updates.push(row);
// 保证仅一个默认语言:若本次设为默认,则将其它语言取消默认
if (row.b_default === 1) {
for (const r of data.value) {
if (r.b_id !== b_id && r.b_default === 1) {
req.updates.push({ b_id: r.b_id, b_default: 0 });
}
}
}
await saveObjectApi([req]);
message.success("保存成功");
modalVisible.value = false;
await loadData();
} catch (e) {
message.error("保存失败:" + (e?.message || e));
} finally {
saving.value = false;
}
}
</script>
<template>
<div class="i18n-type-page">
<div class="panel-card">
<div class="panel-toolbar">
<a-button v-if="canCreate" type="primary" @click="openCreate">
<Plus :size="16" /> 新增语言
</a-button>
</div>
<div class="panel-body">
<FmsTable
:columns="columns"
:data-source="data"
row-key="b_id"
:loading="loading"
:pagination="false"
/>
</div>
</div>
<a-modal
v-model:open="modalVisible"
:title="modalMode === 'create' ? '新增语言' : '编辑语言'"
:confirm-loading="saving"
@ok="saveForm"
@cancel="modalVisible = false"
>
<a-form layout="vertical">
<a-form-item label="语言编码(简称)" required>
<a-input
v-model:value="form.b_code"
:disabled="editingDefault"
/>
</a-form-item>
<a-form-item label="语言名称" required>
<a-input v-model:value="form.b_name" />
</a-form-item>
<a-form-item label="是否默认">
<a-switch v-model:checked="form.defaultOn" :disabled="editingDefault" />
<span class="form-tip">设为默认后,其它语言将自动取消默认</span>
</a-form-item>
<a-form-item label="是否启用">
<a-switch v-model:checked="form.canuseOn" />
</a-form-item>
<a-form-item label="排序">
<a-input-number v-model:value="form.b_xh" :min="0" style="width: 100%" />
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<style scoped lang="scss">
.i18n-type-page {
display: flex;
flex-direction: column;
height: 100%;
gap: 12px;
}
.panel-card {
display: flex;
flex: 1;
min-height: 0;
flex-direction: column;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 1px 4px rgb(0 0 0 / 6%);
overflow: hidden;
}
.panel-toolbar {
display: flex;
flex: none;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
&__title {
font-size: 14px;
font-weight: 500;
color: #1f2329;
}
}
.panel-body {
flex: 1;
min-height: 0;
padding: 12px 16px;
}
.actions-cell {
display: inline-flex;
gap: 4px;
align-items: center;
}
.muted {
color: #c0c4cc;
}
.form-tip {
margin-left: 8px;
font-size: 12px;
color: #646a73;
}
</style>