Files
crawler-plugin/admin-frontend-vue/src/pages/account/GroupsPage.vue
T
huangzd1997 4c88964473 task-283(后台迁移收尾/发布): admin-vue 后台工程入库 + Flask 后台整体退役
- 新后台 admin-frontend-vue 整工程入库(base=/admin-vue/、History 路由、Nginx 静态托管方案与 verify-dist 校验)
- Java AdminConsoleController 改为入口重定向: /admin|/admin.html、/login|/login.html -> /admin-vue/; 删除 classpath:static 旧 admin.html/login.html 单页与 admin.js 副本
- Flask 后台整体退役: 删除 admin_api/auth/main 蓝图、web_source 页面、static 脚本与 admin 相关测试; app.py 收敛为仅注册 version_bp(/api/version、/api/version/latest, 供桌面端更新检查)
- AdminApiGuardFilterTest 豁免样例路径 /admin.html -> /admin-vue/
2026-09-06 10:41:47 +08:00

171 lines
5.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { formatDateTime } from '@/utils/datetime'
import { computed, onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useAdminSessionStore } from '@/stores/admin-session'
import { openAdminConfirm } from '@/components/admin-confirm'
import { deleteShopGroup, fetchShopGroups } from './shop-group-api'
import { shopGroupSummary, type ShopGroupItem } from './shop-group-model'
import GroupEditorDialog from './GroupEditorDialog.vue'
const session = useAdminSessionStore()
const loading = ref(false)
const rows = ref<ShopGroupItem[]>([])
const editorVisible = ref(false)
const editingGroup = ref<ShopGroupItem | null>(null)
const summary = computed(() => shopGroupSummary(rows.value))
const isSuperAdmin = computed(() => session.isSuperAdmin)
const currentUserId = computed(() => session.user?.id ?? null)
// 客户端分页:全量拉取后按页切片展示。
const page = ref(1)
const pageSize = ref(10)
const total = computed(() => rows.value.length)
const pagedRows = computed(() => rows.value.slice((page.value - 1) * pageSize.value, page.value * pageSize.value))
function changePage(p: number) {
page.value = p
}
function changeSize(size: number) {
pageSize.value = size
page.value = 1
}
function canEditOf(group: ShopGroupItem): boolean {
if (isSuperAdmin.value) return true
return currentUserId.value != null && group.leaderUserId != null && currentUserId.value === group.leaderUserId
}
async function loadGroups() {
loading.value = true
try {
rows.value = await fetchShopGroups()
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '数据权限分组加载失败')
} finally {
loading.value = false
}
}
function openCreate() {
editingGroup.value = null
editorVisible.value = true
}
function openEdit(group: ShopGroupItem) {
if (!canEditOf(group)) return
editingGroup.value = group
editorVisible.value = true
}
async function removeGroup(group: ShopGroupItem) {
const ok = await openAdminConfirm({ message: `确定删除分组“${group.name}”吗?`, danger: true })
if (!ok) return
try {
await deleteShopGroup(group.id)
ElMessage.success('删除成功')
await loadGroups()
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '删除失败')
}
}
onMounted(loadGroups)
</script>
<template>
<div class="page-stack">
<div class="page-heading">
<div>
<h2>数据权限分组</h2>
<p>管理店铺数据访问分组和组员范围组长不可更改组员为普通账号</p>
</div>
</div>
<el-row :gutter="12" style="margin-bottom: 12px">
<el-col :span="8">
<el-card shadow="never">
<div class="stat-block">
<span class="stat-label">分组总数</span>
<span class="stat-value">{{ summary.groupCount }}</span>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="never">
<div class="stat-block">
<span class="stat-label">组员总数</span>
<span class="stat-value">{{ summary.memberTotal }}</span>
</div>
</el-card>
</el-col>
</el-row>
<el-card shadow="never">
<div class="table-toolbar">
<el-button type="primary" @click="openCreate">新建分组</el-button>
</div>
<el-table v-loading="loading" :data="pagedRows" stripe>
<el-table-column prop="name" label="分组名称" min-width="180" />
<el-table-column prop="leaderUsername" label="组长" min-width="170" />
<el-table-column prop="memberCount" label="组员数量" min-width="100" />
<el-table-column label="组员" min-width="240">
<template #default="{ row }">
<template v-if="row.memberUsernames.length">
<el-tag v-for="name in row.memberUsernames" :key="name" size="small" class="member-tag">{{ name }}</el-tag>
</template>
<span v-else class="member-none"></span>
</template>
</el-table-column>
<el-table-column prop="createdAt" label="创建时间" min-width="170">
<template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template>
</el-table-column>
<el-table-column prop="updatedAt" label="修改时间" min-width="170">
<template #default="{ row }">{{ formatDateTime(row.updatedAt || row.createdAt) }}</template>
</el-table-column>
<el-table-column label="操作" min-width="140" fixed="right">
<template #default="{ row }">
<el-button link type="primary" :disabled="!canEditOf(row)" @click="openEdit(row)">编辑</el-button>
<el-button link type="danger" :disabled="!canEditOf(row)" @click="removeGroup(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="table-footer">
<el-pagination background layout="sizes, prev, pager, next, jumper" :total="total" :page-size="pageSize" :page-sizes="[10, 20, 50, 100]" :current-page="page" @current-change="changePage" @size-change="changeSize" />
</div>
</el-card>
<GroupEditorDialog
v-model="editorVisible"
:group="editingGroup"
:operator-super="isSuperAdmin"
@saved="loadGroups"
/>
</div>
</template>
<style scoped>
.stat-block {
display: flex;
flex-direction: column;
gap: 4px;
}
.stat-label {
color: var(--el-text-color-secondary);
font-size: 13px;
}
.stat-value {
font-size: 24px;
font-weight: 600;
line-height: 1.2;
}
.member-tag {
margin: 2px 4px 2px 0;
}
.member-more {
font-size: 12px;
color: var(--admin-muted);
}
.member-none {
color: var(--admin-muted);
font-size: 13px;
}
</style>