<template>
|
<div class="page">
|
<div v-if="isSubMode" class="sub-hdr">
|
<el-button link type="primary" @click="goRoot">← 返回一级区域</el-button>
|
<span class="sub-title">子区域管理:{{ parentTitle }}</span>
|
</div>
|
|
<el-form :inline="true" class="search-form" @submit.prevent="doSearch">
|
<el-form-item label="区域名称">
|
<el-input v-model="filters.areaName" placeholder="请输入区域名称" clearable style="width: 160px" />
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" native-type="submit">搜索</el-button>
|
<el-button @click="resetSearch">重置</el-button>
|
</el-form-item>
|
<el-form-item class="push-right">
|
<el-button type="primary" @click="openCreate">添加区域</el-button>
|
</el-form-item>
|
</el-form>
|
|
<div class="table-wrap">
|
<el-table :data="rows" v-loading="loading" stripe border height="100%">
|
<el-table-column label="序号" width="64" type="index" :index="indexMethod" />
|
<el-table-column prop="areaName" label="区域名称" min-width="140" show-overflow-tooltip />
|
<el-table-column prop="areaDesc" label="区域说明" min-width="180" show-overflow-tooltip />
|
<el-table-column label="创建时间" width="170">
|
<template #default="{ row }">{{ formatTime(row.createTime) }}</template>
|
</el-table-column>
|
<el-table-column label="操作" width="220" fixed="right">
|
<template #default="{ row }">
|
<div class="table-ops">
|
<el-button type="primary" link @click="openEdit(row)">编辑</el-button>
|
<el-button v-if="!isSubMode" type="primary" link @click="goSub(row)">子区域管理</el-button>
|
<el-button type="primary" link @click="confirmDelete(row)">删除</el-button>
|
</div>
|
</template>
|
</el-table-column>
|
</el-table>
|
</div>
|
|
<el-pagination
|
class="main-pager"
|
background
|
layout="sizes, prev, pager, next"
|
:total="total"
|
:page-size="size"
|
:page-sizes="[10, 20, 50, 100]"
|
v-model:current-page="page"
|
@current-change="load"
|
@size-change="onSizeChange"
|
/>
|
|
<el-dialog v-model="dlg" :title="dlgTitle" width="520px" destroy-on-close @closed="onDlgClosed">
|
<el-form :model="form" label-width="100px">
|
<el-form-item v-if="showParentField" label="上级区域" required>
|
<el-input
|
v-if="isCreate && isSubMode"
|
:model-value="parentTitle"
|
disabled
|
class="fullw"
|
/>
|
<el-select
|
v-else
|
v-model="form.pid"
|
placeholder="请选择一级区域"
|
class="fullw"
|
filterable
|
:clearable="false"
|
>
|
<el-option
|
v-for="p in parentSelectOptions"
|
:key="p.id"
|
:label="p.areaName"
|
:value="p.id"
|
/>
|
</el-select>
|
</el-form-item>
|
<el-form-item label="区域名称" required>
|
<el-input v-model="form.areaName" maxlength="128" placeholder="区域名称请与原系统保持一致" />
|
</el-form-item>
|
<el-form-item label="区域说明">
|
<el-input v-model="form.areaDesc" type="textarea" :rows="3" maxlength="2000" placeholder="请输入区域说明(选填)" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<el-button @click="dlg = false">取消</el-button>
|
<el-button type="primary" :loading="saving" @click="submitForm">保存</el-button>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { computed, onMounted, reactive, ref, watch } from "vue";
|
import { useRoute, useRouter } from "vue-router";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
import http from "../api/http";
|
import { normalizeFormData } from "../utils/formHelper";
|
|
type Row = {
|
id: number;
|
uuid: string;
|
pid: number;
|
areaName: string;
|
areaDesc?: string;
|
createTime: string;
|
};
|
|
const route = useRoute();
|
const router = useRouter();
|
|
const loading = ref(false);
|
const saving = ref(false);
|
const rows = ref<Row[]>([]);
|
const page = ref(1);
|
const size = ref(10);
|
const total = ref(0);
|
const rootOptions = ref<{ id: number; areaName: string }[]>([]);
|
/** 编辑子区域时,若一级列表尚未含当前上级,用语义名补一条便于下拉回显 */
|
const parentLabelFallback = ref("");
|
|
const filters = reactive({ areaName: "" });
|
|
const listPid = computed(() => {
|
const p = route.query.pid;
|
if (p === undefined || p === null || p === "") return 0;
|
const n = Number(p);
|
return Number.isFinite(n) && n > 0 ? n : 0;
|
});
|
|
const isSubMode = computed(() => listPid.value > 0);
|
|
const parentTitle = computed(() => {
|
const n = route.query.parentName;
|
return typeof n === "string" && n ? n : "—";
|
});
|
|
const showParentField = computed(
|
() => (isCreate.value && isSubMode.value) || (!isCreate.value && form.pid > 0)
|
);
|
|
const parentSelectOptions = computed(() => {
|
const base = rootOptions.value.map((o) => ({ id: Number(o.id), areaName: o.areaName }));
|
if (!isCreate.value && form.pid > 0) {
|
const pid = Number(form.pid);
|
if (pid > 0 && !base.some((o) => o.id === pid) && parentLabelFallback.value) {
|
return [{ id: pid, areaName: parentLabelFallback.value }, ...base];
|
}
|
}
|
return base;
|
});
|
|
function indexMethod(i: number) {
|
return (page.value - 1) * size.value + i + 1;
|
}
|
|
function formatTime(s: string) {
|
if (!s) return "";
|
return s.replace("T", " ").slice(0, 19);
|
}
|
|
function buildParams() {
|
return {
|
page: page.value,
|
size: size.value,
|
pid: listPid.value,
|
areaName: filters.areaName?.trim() || undefined,
|
};
|
}
|
|
async function load() {
|
loading.value = true;
|
try {
|
const { data } = await http.get("/sys/pass-access-areas", { params: buildParams() });
|
rows.value = (data.result || []) as Row[];
|
total.value = Number(data.total) || 0;
|
} catch (e: unknown) {
|
ElMessage.error(e instanceof Error ? e.message : "加载失败");
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
async function loadRootOptions() {
|
try {
|
const { data } = await http.get("/sys/pass-access-areas", { params: { page: 1, size: 500, pid: 0 } });
|
const list = (data.result || []) as Row[];
|
rootOptions.value = list.map((r) => ({ id: Number(r.id), areaName: r.areaName }));
|
} catch {
|
rootOptions.value = [];
|
}
|
}
|
|
function doSearch() {
|
page.value = 1;
|
load();
|
}
|
|
function onSizeChange(nextSize: number) {
|
size.value = nextSize;
|
page.value = 1;
|
load();
|
}
|
|
function resetSearch() {
|
filters.areaName = "";
|
page.value = 1;
|
load();
|
}
|
|
function goRoot() {
|
router.push({ path: "/region/pass-areas" });
|
}
|
|
function goSub(row: Row) {
|
router.push({
|
path: "/region/pass-areas",
|
query: { pid: String(row.id), parentUuid: row.uuid, parentName: row.areaName },
|
});
|
}
|
|
const dlg = ref(false);
|
const dlgTitle = ref("添加区域");
|
const isCreate = ref(true);
|
const editingId = ref<number | null>(null);
|
|
const form = reactive({
|
uuid: "",
|
pid: 0 as number,
|
areaName: "",
|
areaDesc: "",
|
});
|
|
function openCreate() {
|
isCreate.value = true;
|
editingId.value = null;
|
parentLabelFallback.value = "";
|
dlgTitle.value = "添加区域";
|
form.uuid = "";
|
form.pid = listPid.value > 0 ? listPid.value : 0;
|
form.areaName = "";
|
form.areaDesc = "";
|
dlg.value = true;
|
}
|
|
async function openEdit(row: Row) {
|
isCreate.value = false;
|
editingId.value = row.id;
|
parentLabelFallback.value = "";
|
dlgTitle.value = "编辑区域";
|
await loadRootOptions();
|
try {
|
const { data } = await http.get(`/sys/pass-access-areas/${row.id}`, { params: { uuid: row.uuid } });
|
const d = data.result as {
|
uuid: string;
|
pid: number;
|
areaName: string;
|
areaDesc?: string;
|
parentAreaName?: string;
|
};
|
form.uuid = d.uuid;
|
form.pid = Number(d.pid) || 0;
|
form.areaName = d.areaName || "";
|
form.areaDesc = d.areaDesc || "";
|
parentLabelFallback.value = (d.parentAreaName && String(d.parentAreaName).trim()) || "";
|
dlg.value = true;
|
} catch (e: unknown) {
|
ElMessage.error(e instanceof Error ? e.message : "加载失败");
|
}
|
}
|
|
function onDlgClosed() {
|
editingId.value = null;
|
}
|
|
async function submitForm() {
|
if (!form.areaName.trim()) {
|
ElMessage.warning("请填写区域名称");
|
return;
|
}
|
if (showParentField.value) {
|
const p = Number(form.pid);
|
if (!Number.isFinite(p) || p <= 0) {
|
ElMessage.warning("请选择上级区域");
|
return;
|
}
|
}
|
if (isCreate.value && isSubMode.value && (!listPid.value || listPid.value <= 0)) {
|
ElMessage.warning("缺少上级区域,请从一级区域的「子区域管理」进入");
|
return;
|
}
|
saving.value = true;
|
try {
|
if (isCreate.value) {
|
const body = normalizeFormData({
|
pid: listPid.value > 0 ? listPid.value : 0,
|
areaName: form.areaName.trim(),
|
areaDesc: form.areaDesc.trim(),
|
});
|
await http.post("/sys/pass-access-areas", body);
|
ElMessage.success("已添加");
|
} else if (editingId.value != null) {
|
const body = normalizeFormData({
|
uuid: form.uuid,
|
pid: form.pid > 0 ? form.pid : 0,
|
areaName: form.areaName.trim(),
|
areaDesc: form.areaDesc.trim(),
|
});
|
await http.put(`/sys/pass-access-areas/${editingId.value}`, body);
|
ElMessage.success("已保存");
|
}
|
dlg.value = false;
|
load();
|
} catch (e: unknown) {
|
ElMessage.error(e instanceof Error ? e.message : "保存失败");
|
} finally {
|
saving.value = false;
|
}
|
}
|
|
function confirmDelete(row: Row) {
|
ElMessageBox.confirm(`确认删除区域「${row.areaName}」?`, "删除确认", { type: "warning" })
|
.then(async () => {
|
try {
|
await http.delete(`/sys/pass-access-areas/${row.id}`, { params: { uuid: row.uuid } });
|
ElMessage.success("已删除");
|
load();
|
} catch (e: unknown) {
|
ElMessage.error(e instanceof Error ? e.message : "删除失败");
|
}
|
})
|
.catch(() => {});
|
}
|
|
watch(
|
() => [route.query.pid, route.query.parentUuid],
|
() => {
|
page.value = 1;
|
load();
|
}
|
);
|
|
onMounted(async () => {
|
await loadRootOptions();
|
load();
|
});
|
</script>
|
|
<style scoped>
|
.page {
|
padding: 0;
|
display: flex;
|
flex-direction: column;
|
height: calc(100vh - 190px);
|
min-height: 420px;
|
}
|
.sub-hdr {
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
margin-bottom: 12px;
|
}
|
.sub-title {
|
font-weight: 600;
|
color: #303133;
|
}
|
.search-form {
|
margin-bottom: 12px;
|
flex-wrap: wrap;
|
flex: none;
|
}
|
.push-right {
|
margin-left: auto;
|
}
|
.table-wrap {
|
flex: 1;
|
min-height: 0;
|
}
|
.main-pager {
|
margin-top: 12px;
|
justify-content: flex-end;
|
flex: none;
|
}
|
.fullw {
|
width: 100%;
|
}
|
</style>
|