🧹 清理重复配置文件
- 删除根目录中重复的 NestJS 配置文件 - 删除 tsconfig.json, tsconfig.build.json, eslint.config.mjs, .prettierrc - 保留 wwjcloud-nest/ 目录中的完整配置 - 避免配置冲突,确保项目结构清晰
This commit is contained in:
557
admin-vben/apps/web-antd/src/views/site/group.vue
Normal file
557
admin-vben/apps/web-antd/src/views/site/group.vue
Normal file
@@ -0,0 +1,557 @@
|
||||
<template>
|
||||
<div class="site-group-container">
|
||||
<Card :bordered="false" class="search-card">
|
||||
<Form
|
||||
:model="searchForm"
|
||||
layout="inline"
|
||||
@finish="handleSearch"
|
||||
class="search-form"
|
||||
>
|
||||
<FormItem name="keywords">
|
||||
<Input
|
||||
v-model:value="searchForm.keywords"
|
||||
placeholder="请输入分组名称"
|
||||
allow-clear
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Button type="primary" html-type="submit" :loading="loading">
|
||||
搜索
|
||||
</Button>
|
||||
<Button @click="handleReset" style="margin-left: 8px">
|
||||
重置
|
||||
</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<div class="action-bar">
|
||||
<Button type="primary" @click="handleAdd">
|
||||
添加站点分组
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card :bordered="false" class="table-card">
|
||||
<Table
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@change="handleTableChange"
|
||||
row-key="group_id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'app_name'">
|
||||
<div v-if="record.app_list && record.app_list.length > 0" class="app-list">
|
||||
<Tooltip
|
||||
placement="top-start"
|
||||
:title="getAppTooltipContent(record.app_list)"
|
||||
>
|
||||
<div class="app-icons">
|
||||
<div
|
||||
v-for="(app, index) in record.app_list.slice(0, 4)"
|
||||
:key="index"
|
||||
class="app-icon"
|
||||
>
|
||||
<Avatar
|
||||
:src="app.icon"
|
||||
:size="54"
|
||||
shape="square"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="record.app_list.length > 4"
|
||||
class="app-more"
|
||||
>
|
||||
<span>...</span>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div v-else class="empty-apps">
|
||||
暂无应用
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'addon_name'">
|
||||
<div v-if="record.addon_list && record.addon_list.length > 0" class="addon-list">
|
||||
<Tooltip
|
||||
placement="top-start"
|
||||
:title="getAddonTooltipContent(record.addon_list)"
|
||||
>
|
||||
<div class="addon-icons">
|
||||
<div
|
||||
v-for="(addon, index) in record.addon_list.slice(0, 4)"
|
||||
:key="index"
|
||||
class="addon-icon"
|
||||
>
|
||||
<Avatar
|
||||
:src="addon.icon"
|
||||
:size="54"
|
||||
shape="square"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="record.addon_list.length > 4"
|
||||
class="addon-more"
|
||||
>
|
||||
<span>...</span>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div v-else class="empty-addons">
|
||||
暂无插件
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<Space>
|
||||
<Button type="link" @click="handleEdit(record)">
|
||||
编辑
|
||||
</Button>
|
||||
<Button type="link" @click="handleDelete(record)" danger>
|
||||
删除
|
||||
</Button>
|
||||
</Space>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</Card>
|
||||
|
||||
<!-- 添加/编辑分组对话框 -->
|
||||
<Modal
|
||||
v-model:open="groupModalVisible"
|
||||
:title="isEdit ? '编辑站点分组' : '添加站点分组'"
|
||||
@ok="handleSubmit"
|
||||
:confirm-loading="submitLoading"
|
||||
width="600px"
|
||||
>
|
||||
<Form
|
||||
ref="groupFormRef"
|
||||
:model="groupForm"
|
||||
:rules="groupRules"
|
||||
layout="vertical"
|
||||
>
|
||||
<FormItem label="分组名称" name="group_name" required>
|
||||
<Input
|
||||
v-model:value="groupForm.group_name"
|
||||
placeholder="请输入分组名称"
|
||||
maxlength="50"
|
||||
show-count
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="分组描述" name="group_desc">
|
||||
<TextArea
|
||||
v-model:value="groupForm.group_desc"
|
||||
placeholder="请输入分组描述"
|
||||
:rows="3"
|
||||
maxlength="255"
|
||||
show-count
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="应用" name="app">
|
||||
<Select
|
||||
v-model:value="groupForm.app"
|
||||
mode="multiple"
|
||||
placeholder="请选择应用"
|
||||
:options="appOptions"
|
||||
:filter-option="filterAppOption"
|
||||
show-search
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="插件" name="addon">
|
||||
<Select
|
||||
v-model:value="groupForm.addon"
|
||||
mode="multiple"
|
||||
placeholder="请选择插件"
|
||||
:options="addonOptions"
|
||||
:filter-option="filterAddonOption"
|
||||
show-search
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<!-- 删除确认对话框 -->
|
||||
<Modal
|
||||
v-model:open="deleteModalVisible"
|
||||
title="确认删除"
|
||||
@ok="confirmDelete"
|
||||
:confirm-loading="deleteLoading"
|
||||
>
|
||||
<p>确定要删除该站点分组吗?删除后无法恢复!</p>
|
||||
<p v-if="currentRecord?.site_count > 0" class="text-red-500">
|
||||
注意:该分组下还有 {{ currentRecord.site_count }} 个站点,删除分组可能会影响这些站点!
|
||||
</p>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
Card,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
Button,
|
||||
Table,
|
||||
Space,
|
||||
Modal,
|
||||
Avatar,
|
||||
Tooltip,
|
||||
Select,
|
||||
TextArea
|
||||
} from 'ant-design-vue'
|
||||
import { useSiteApi } from '@/api/site'
|
||||
|
||||
// API
|
||||
const siteApi = useSiteApi()
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const deleteLoading = ref(false)
|
||||
const groupModalVisible = ref(false)
|
||||
const deleteModalVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const currentRecord = ref<any>(null)
|
||||
const groupFormRef = ref()
|
||||
|
||||
// 表单数据
|
||||
const searchForm = reactive({
|
||||
keywords: ''
|
||||
})
|
||||
|
||||
const groupForm = reactive({
|
||||
group_id: '',
|
||||
group_name: '',
|
||||
group_desc: '',
|
||||
app: [],
|
||||
addon: []
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref([])
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total: number) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
// 选项数据
|
||||
const appOptions = ref([])
|
||||
const addonOptions = ref([])
|
||||
|
||||
// 表单验证规则
|
||||
const groupRules = {
|
||||
group_name: [
|
||||
{ required: true, message: '请输入分组名称', trigger: 'blur' },
|
||||
{ max: 50, message: '分组名称不能超过50个字符', trigger: 'blur' }
|
||||
],
|
||||
group_desc: [
|
||||
{ max: 255, message: '分组描述不能超过255个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '分组名称',
|
||||
dataIndex: 'group_name',
|
||||
key: 'group_name',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '应用',
|
||||
key: 'app_name',
|
||||
width: 250
|
||||
},
|
||||
{
|
||||
title: '插件',
|
||||
key: 'addon_name',
|
||||
width: 250
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'create_time',
|
||||
key: 'create_time',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 120,
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
// 方法
|
||||
const loadGroupList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.current,
|
||||
limit: pagination.pageSize,
|
||||
...searchForm
|
||||
}
|
||||
|
||||
const response = await siteApi.getSiteGroupList(params)
|
||||
tableData.value = response.data.list || []
|
||||
pagination.total = response.data.total || 0
|
||||
} catch (error) {
|
||||
message.error('加载分组列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.current = 1
|
||||
loadGroupList()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
searchForm.keywords = ''
|
||||
pagination.current = 1
|
||||
loadGroupList()
|
||||
}
|
||||
|
||||
const handleTableChange = (pag: any) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
loadGroupList()
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
isEdit.value = false
|
||||
resetGroupForm()
|
||||
groupModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleEdit = (record: any) => {
|
||||
isEdit.value = true
|
||||
currentRecord.value = record
|
||||
Object.assign(groupForm, {
|
||||
group_id: record.group_id,
|
||||
group_name: record.group_name,
|
||||
group_desc: record.group_desc,
|
||||
app: record.app ? record.app.split(',').filter(Boolean) : [],
|
||||
addon: record.addon ? record.addon.split(',').filter(Boolean) : []
|
||||
})
|
||||
groupModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleDelete = (record: any) => {
|
||||
currentRecord.value = record
|
||||
deleteModalVisible.value = true
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await groupFormRef.value.validate()
|
||||
|
||||
submitLoading.value = true
|
||||
const params = {
|
||||
...groupForm,
|
||||
app: groupForm.app.join(','),
|
||||
addon: groupForm.addon.join(',')
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
await siteApi.editSiteGroup(params)
|
||||
message.success('编辑成功')
|
||||
} else {
|
||||
await siteApi.addSiteGroup(params)
|
||||
message.success('添加成功')
|
||||
}
|
||||
|
||||
groupModalVisible.value = false
|
||||
loadGroupList()
|
||||
} catch (error) {
|
||||
if (error.errorFields) {
|
||||
message.error('请检查表单填写')
|
||||
} else {
|
||||
message.error(isEdit.value ? '编辑失败' : '添加失败')
|
||||
}
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const confirmDelete = async () => {
|
||||
deleteLoading.value = true
|
||||
try {
|
||||
await siteApi.deleteSiteGroup(currentRecord.value.group_id)
|
||||
message.success('删除成功')
|
||||
deleteModalVisible.value = false
|
||||
loadGroupList()
|
||||
} catch (error) {
|
||||
message.error('删除失败')
|
||||
} finally {
|
||||
deleteLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const resetGroupForm = () => {
|
||||
Object.assign(groupForm, {
|
||||
group_id: '',
|
||||
group_name: '',
|
||||
group_desc: '',
|
||||
app: [],
|
||||
addon: []
|
||||
})
|
||||
}
|
||||
|
||||
const getAppTooltipContent = (appList: any[]) => {
|
||||
return (
|
||||
<div class="app-tooltip">
|
||||
{appList.map((app, index) => (
|
||||
<div key={index} class="app-tooltip-item">
|
||||
<img src={app.icon} class="app-tooltip-icon" />
|
||||
<span>{app.title}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const getAddonTooltipContent = (addonList: any[]) => {
|
||||
return (
|
||||
<div class="addon-tooltip">
|
||||
{addonList.map((addon, index) => (
|
||||
<div key={index} class="addon-tooltip-item">
|
||||
<img src={addon.icon} class="addon-tooltip-icon" />
|
||||
<span>{addon.title}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const filterAppOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
|
||||
const filterAddonOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
|
||||
const loadOptions = async () => {
|
||||
try {
|
||||
const [appResponse, addonResponse] = await Promise.all([
|
||||
siteApi.getShowApp(),
|
||||
siteApi.getShowMarketing()
|
||||
])
|
||||
|
||||
appOptions.value = (appResponse.data || []).map((item: any) => ({
|
||||
label: item.title,
|
||||
value: item.key
|
||||
}))
|
||||
|
||||
addonOptions.value = (addonResponse.data || []).map((item: any) => ({
|
||||
label: item.title,
|
||||
value: item.key
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('加载选项数据失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
loadGroupList(),
|
||||
loadOptions()
|
||||
])
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.site-group-container {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.app-list,
|
||||
.addon-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.app-icons,
|
||||
.addon-icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-icon,
|
||||
.addon-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.app-more,
|
||||
.addon-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 54px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.empty-apps,
|
||||
.empty-addons {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.app-tooltip,
|
||||
.addon-tooltip {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
max-width: 315px;
|
||||
}
|
||||
|
||||
.app-tooltip-item,
|
||||
.addon-tooltip-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.app-tooltip-icon,
|
||||
.addon-tooltip-icon {
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
16
admin-vben/apps/web-antd/src/views/site/list.vue
Normal file
16
admin-vben/apps/web-antd/src/views/site/list.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
// 简单的测试页面
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>站点管理</h1>
|
||||
<p>这是一个测试页面</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user