init: UserBaseFront (frontend)

This commit is contained in:
chenxing
2026-08-10 14:41:13 +08:00
commit eea881b616
174 changed files with 32833 additions and 0 deletions
+304
View File
@@ -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>