Archived
forked from admin/userbase
318 lines
9.2 KiB
Vue
318 lines
9.2 KiB
Vue
<template>
|
||
<div class="page-wrapper tree-wrapper">
|
||
<div class="tree-container app-type-tree">
|
||
<div class="tree-scroll">
|
||
<ul class="grade-ul">
|
||
<li :class="dataParams.categoryId == null ? 'active' : ''" @click="onCategoryClick(null)">
|
||
全部
|
||
</li>
|
||
<li v-for="item in appTypeList" :key="item.id"
|
||
:class="item.id == dataParams.categoryId ? 'active' : ''" @click="onCategoryClick(item.id)">
|
||
{{ item.name }}
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<div class="tree-footer">
|
||
<Button type="text" @click="goAppType">管理应用类型</Button>
|
||
</div>
|
||
</div>
|
||
<div class="main-container">
|
||
<div class="search-wrap fix">
|
||
<Button class="fl" type="primary" @click="handleAdd">新增应用</Button>
|
||
<Button class="fl ml-8" type="default" v-if="dataParams.categoryId != null"
|
||
@click="sortModalVisible = true">排序管理</Button>
|
||
<Input class="fr" v-model="keyword" placeholder="输入应用名称或appKey筛选" search clearable suffix="ios-search"
|
||
style="width:220px" @on-search="onSearch" @on-clear="onSearch" />
|
||
</div>
|
||
|
||
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columnsComp" :tableHeight="tableHeight" />
|
||
</div>
|
||
|
||
<!-- 容器组件:新增 / 编辑 -->
|
||
<AddAppContainer ref="addRef" />
|
||
<EditAppContainer ref="editRef" />
|
||
|
||
<!-- 排序管理 -->
|
||
<AppSort v-model="sortModalVisible" :categoryId="dataParams.categoryId" :categoryName="currentCategoryName"
|
||
@saved="onSortSaved" />
|
||
|
||
<!-- 推送设置 -->
|
||
<PushSettingModal v-model="pushSettingVisible" :appId="currentApp.id" :appName="currentApp.name" />
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { ref, computed } from "vue";
|
||
defineOptions({ name: "AppList" });
|
||
import { useRouter } from "vue-router";
|
||
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
|
||
import MyConfirm from "@/components/MyConfirm";
|
||
import AddAppContainer from "./AddAppContainer.vue";
|
||
import EditAppContainer from "./EditAppContainer.vue";
|
||
import PushSettingModal from "./PushSettingModal.vue";
|
||
import TablePageAlovaNew from '@/components/TablePageAlovaNew.vue';
|
||
import { OperatorEnum } from '@/types';
|
||
import AppSort from './AppSort.vue';
|
||
|
||
const router = useRouter();
|
||
const { $utils } = useCurrentInstance();
|
||
|
||
const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
||
|
||
const sortModalVisible = ref(false);
|
||
const pushSettingVisible = ref(false);
|
||
const currentApp = ref<{ id: number; name: string }>({ id: 0, name: "" });
|
||
const keyword = ref("");
|
||
|
||
const dataParams = ref<{
|
||
categoryId: number | null;
|
||
name?: string;
|
||
}>({
|
||
categoryId: null,
|
||
name: "",
|
||
});
|
||
|
||
|
||
/* ---------------- 左侧分类切换:立即刷新 ---------------- */
|
||
function onCategoryClick(id: number | null) {
|
||
dataParams.value.categoryId = id;
|
||
tableRef.value?.refreshData();
|
||
}
|
||
|
||
/* ---------------- 输入框搜索:仅在触发搜索时提交 ---------------- */
|
||
function onSearch() {
|
||
dataParams.value.name = keyword.value.trim() || undefined;
|
||
tableRef.value?.refreshData();
|
||
}
|
||
|
||
/* ---------------- 左侧应用类型 ---------------- */
|
||
const { data: appTypeList, send: fetchTypeList } = useRequest(
|
||
Apis.AppSettings.appTypeList({
|
||
transform: (res: any) => {
|
||
const list: any[] = res ?? [];
|
||
return list.slice().sort((a, b) => (a.sortNo ?? 0) - (b.sortNo ?? 0));
|
||
},
|
||
cacheFor: null,
|
||
}),
|
||
{ initialData: [] }
|
||
);
|
||
|
||
const tableApi = (params: { pageIndex: number; pageSize: number }) => {
|
||
return Apis.AppSettings.appList({
|
||
params: {
|
||
categoryId: dataParams.value.categoryId ?? undefined,
|
||
name: dataParams.value.name || undefined,
|
||
pageIndex: params.pageIndex,
|
||
pageSize: params.pageSize,
|
||
},
|
||
cacheFor: null,
|
||
});
|
||
};
|
||
|
||
|
||
const typeNameMap = computed(() => {
|
||
const map: Record<number, string> = {};
|
||
(appTypeList.value ?? []).forEach((t: any) => (map[t.id] = t.name));
|
||
return map;
|
||
});
|
||
|
||
const currentCategoryName = computed(() => {
|
||
if (dataParams.value.categoryId == null) return "";
|
||
return typeNameMap.value[dataParams.value.categoryId] ?? "";
|
||
});
|
||
|
||
/* ---------------- 排序保存后刷新列表 ---------------- */
|
||
function onSortSaved() {
|
||
tableRef.value?.reloadData(OperatorEnum.sel);
|
||
}
|
||
|
||
const tableHeight = ref(
|
||
$utils.useTableHeight({ isOperat: false, isPage: true, isSearch: true })
|
||
);
|
||
|
||
/* ---------------- 表格固定列(不含“应用类型”列) ---------------- */
|
||
const columns = [
|
||
{ type: "index", title: "序号", width: 70 },
|
||
|
||
{ title: "应用名称", key: "name", width: 160 },
|
||
{ title: "描述", key: "desc", ellipsis: true, tooltip: true, minWidth: 140 },
|
||
{
|
||
title: "前端",
|
||
key: "clients",
|
||
width: 180,
|
||
render: (h: any, { row }: any) => {
|
||
const map: Record<number, string> = { 1: "Pc浏览器", 2: "移动App" };
|
||
const arr: number[] = Array.isArray(row.clients) ? row.clients : [];
|
||
if (!arr.length) return h("span", "-");
|
||
return h("span", arr.map((v) => map[v] ?? v).join("、"));
|
||
},
|
||
},
|
||
{
|
||
title: "操作",
|
||
key: "action",
|
||
align: 'center',
|
||
width: 280,
|
||
render: (h: any, { row }: any) => {
|
||
return [
|
||
$utils.operatingTextHandle(h, '编辑', '', () => handleEdit(row)),
|
||
$utils.operatingTextHandle(h, '模块管理', '', () => goModuleManage(row)),
|
||
$utils.operatingTextHandle(h, '推送设置', '', () => openPushSetting(row)),
|
||
$utils.operatingTextHandle(h, '删除', 'text-error', () => handleDelete(row)),
|
||
];
|
||
}
|
||
},
|
||
];
|
||
|
||
// 条件列:仅在「未筛选应用类型」时插入到“应用名称”之后
|
||
const typeColumn = {
|
||
title: "应用类型",
|
||
key: "categoryId",
|
||
width: 110,
|
||
render: (h: any, { row }: any) => h("span", typeNameMap.value[row.categoryId] ?? "-"),
|
||
};
|
||
|
||
/* ---------------- 表格最终数据源(动态列) ---------------- */
|
||
const columnsComp = computed(() => {
|
||
const cols = [...columns];
|
||
// 未筛选应用类型时,展示“应用类型”列
|
||
if (!dataParams.value.categoryId) {
|
||
const idx = cols.findIndex((c) => c.key === "name");
|
||
cols.splice(idx + 1, 0, typeColumn);
|
||
}
|
||
return cols;
|
||
});
|
||
|
||
/* ---------------- 新增应用 ---------------- */
|
||
const addRef = ref();
|
||
|
||
function handleAdd() {
|
||
addRef.value?.open(appTypeList.value, dataParams.value.categoryId, () => {
|
||
tableRef.value?.reloadData(OperatorEnum.add);
|
||
});
|
||
}
|
||
|
||
/* ---------------- 编辑应用 ---------------- */
|
||
const editRef = ref();
|
||
|
||
function handleEdit(row: any) {
|
||
editRef.value?.open(row.id, row, appTypeList.value, () => {
|
||
tableRef.value?.reloadData(OperatorEnum.edit);
|
||
});
|
||
}
|
||
|
||
/* ---------------- 删除应用 ---------------- */
|
||
async function handleDelete(row: any) {
|
||
await MyConfirm({
|
||
msg: `确定删除应用「${row.name || row.id}」吗?`,
|
||
info: "删除后不可恢复!",
|
||
async onOk() {
|
||
try {
|
||
await Apis.AppSettings.deleteApp({ pathParams: { id: row.id } });
|
||
Message.success('删除成功!');
|
||
tableRef.value?.reloadData(OperatorEnum.del);
|
||
} catch (e: any) {
|
||
console.warn(e?.message || '删除失败');
|
||
}
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ---------------- 推送设置 ---------------- */
|
||
function openPushSetting(row: any) {
|
||
currentApp.value = { id: row.id, name: row.name };
|
||
pushSettingVisible.value = true;
|
||
}
|
||
|
||
/* ---------------- 导航 ---------------- */
|
||
const goAppType = () => {
|
||
router.push("/app-manage/category");
|
||
};
|
||
|
||
const goModuleManage = (row: any) => {
|
||
router.push({
|
||
path: "/app-manage/module",
|
||
query: { id: row.id, name: row.name },
|
||
});
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.app-type-tree {
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
padding: 0;
|
||
width: 180px;
|
||
}
|
||
|
||
.tree-scroll {
|
||
flex: 1;
|
||
overflow: auto;
|
||
min-height: 0;
|
||
padding: 10px 12px;
|
||
}
|
||
|
||
.tree-footer {
|
||
flex-shrink: 0;
|
||
padding-top: 8px;
|
||
text-align: center;
|
||
background: #ffffff;
|
||
border-top: 1px solid #eeeeee;
|
||
|
||
.ivu-btn-text {
|
||
color: #577afb;
|
||
}
|
||
}
|
||
|
||
.grade-ul {
|
||
margin: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
|
||
li {
|
||
cursor: pointer;
|
||
background-color: #fafafa;
|
||
border: 1px solid #ebeef5;
|
||
border-radius: 4px;
|
||
height: 40px;
|
||
line-height: 40px;
|
||
text-align: center;
|
||
}
|
||
|
||
li.active {
|
||
color: #577afb;
|
||
border-color: #577afb;
|
||
}
|
||
}
|
||
|
||
.action-cell {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.action-link {
|
||
cursor: pointer;
|
||
color: #577afb;
|
||
font-size: 13px;
|
||
|
||
&:hover {
|
||
color: #3d5afe;
|
||
}
|
||
|
||
&.danger {
|
||
color: #ed4014;
|
||
|
||
&:hover {
|
||
color: #c51d04;
|
||
}
|
||
}
|
||
}
|
||
|
||
.action-divider {
|
||
margin: 0 8px;
|
||
color: #dcdee2;
|
||
}
|
||
</style>
|