init: UserBaseFront (frontend)
This commit is contained in:
@@ -0,0 +1,304 @@
|
||||
<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" />
|
||||
</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 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 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, '删除', '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) {
|
||||
Message.error(e?.message || '删除失败');
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------------- 导航 ---------------- */
|
||||
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>
|
||||
Reference in New Issue
Block a user