Commit 34a1d93a by 周海峰

应用分组管理

parent d553149b
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
"vue-i18n": "^8.1.0", "vue-i18n": "^8.1.0",
"vue-template-compiler": "^2.7.16", "vue-template-compiler": "^2.7.16",
"vue-ueditor-wrap": "^2.4.1", "vue-ueditor-wrap": "^2.4.1",
"vuedraggable": "^2.24.3",
"vuelidate": "^0.6.2", "vuelidate": "^0.6.2",
"xlsx": "^0.15.1" "xlsx": "^0.15.1"
}, },
......
...@@ -117,7 +117,7 @@ module.exports = function (ctx) { ...@@ -117,7 +117,7 @@ module.exports = function (ctx) {
proxy: { proxy: {
// 将所有以/api开头的请求代理到jsonplaceholder // 将所有以/api开头的请求代理到jsonplaceholder
'/manager': { '/manager': {
target: 'http://192.168.1.186:8086', target: 'http://localhost:8082',
changeOrigin: true, changeOrigin: true,
pathRewrite: { pathRewrite: {
'^/manager': '' '^/manager': ''
......
...@@ -36,7 +36,11 @@ service.interceptors.response.use( ...@@ -36,7 +36,11 @@ service.interceptors.response.use(
response => { response => {
loading.hide(response.config) loading.hide(response.config)
const res = response.data; const res = response.data;
if (res.code !== 200) { if (res.code == 200) {
return response
}
if (res.code == 401) {
Notify.create({ Notify.create({
message: "当前用户已在其它地点登录,页面跳转登录页!" message: "当前用户已在其它地点登录,页面跳转登录页!"
}) })
...@@ -50,7 +54,10 @@ service.interceptors.response.use( ...@@ -50,7 +54,10 @@ service.interceptors.response.use(
} }
return Promise.reject('error'); return Promise.reject('error');
} else { } else {
return response; Notify.create({
message: res.data || '系统错误'
})
return Promise.reject('error');
} }
}, },
error => { error => {
......
...@@ -20,6 +20,10 @@ ...@@ -20,6 +20,10 @@
<template slot="top-selection" slot-scope="props"> <template slot="top-selection" slot-scope="props">
<q-btn color="negative" flat round delete icon="delete" @click="delRoles" /> <q-btn color="negative" flat round delete icon="delete" @click="delRoles" />
</template> </template>
<q-td slot="body-cell-href" slot-scope="props" :props="props">
<span>电脑入口:{{ props.row.href ? props.row.href: '未维护'}}</span><br/>
<span>企微入口:{{ props.row.wechatUrl ? props.row.wechatUrl: '未维护' }}</span>
</q-td>
<q-td slot="body-cell-id" slot-scope="props" :props="props" style="width:100px"> <q-td slot="body-cell-id" slot-scope="props" :props="props" style="width:100px">
<!-- <q-btn small round push glossy dense icon="edit" color="primary" @click="editapp(props.value)"></q-btn> <!-- <q-btn small round push glossy dense icon="edit" color="primary" @click="editapp(props.value)"></q-btn>
<q-btn small round push glossy dense icon="delete" color="red" @click="delapp(props.value)"></q-btn> --> <q-btn small round push glossy dense icon="delete" color="red" @click="delapp(props.value)"></q-btn> -->
...@@ -77,7 +81,10 @@ ...@@ -77,7 +81,10 @@
</div> </div>
</q-field> </q-field>
<q-field :count="255"> <q-field :count="255">
<q-input v-model="temp.href" float-label="连接" /> <q-input v-model="temp.href" float-label="电脑入口" />
</q-field>
<q-field :count="255">
<q-input v-model="temp.wechatUrl" float-label="企微入口" />
</q-field> </q-field>
<q-field :count="64"> <q-field :count="64">
<q-input v-model="temp.appid" float-label="应用唯一标识" @blur="getsecret()" :disable=appidflag /> <q-input v-model="temp.appid" float-label="应用唯一标识" @blur="getsecret()" :disable=appidflag />
...@@ -127,7 +134,7 @@ ...@@ -127,7 +134,7 @@
<script> <script>
import { getPlatformApplicationsPagedList,saveapp,delapp } from "@/service/permission/platformapplications"; import { getPlatformApplicationsPagedList,saveapp,delapp } from "@/service/permission/platformapplications";
import { getApplicationsGroupList,addApplicationsGroup,updateApplicationsGroup,delApplicationsGroup } from "@/service/permission/platformapplicationsGroup"; import { getApplicationsGroupList,addApplicationsGroup } from "@/service/permission/platformapplicationsGroup";
const uploadpath="http://nw.sy-metro.com:6104/uploadfileadmin/manager"; const uploadpath="http://nw.sy-metro.com:6104/uploadfileadmin/manager";
export default { export default {
data() { data() {
......
<template>
<div style="padding:10px" >
<q-card inline class="fit shadow-6">
<q-card-main>
<q-table class="fixed-height-table" ref="table" color="primary" :data="serverData" :columns="columns" separator="cell" row-key="id" :pagination.sync="pagination"
@request="request" :loading="loading" :rows-per-page-options="[10,20,30,40,50,60,200,500]" :rows-per-page-label="$t('Rows per page')" :no-data-label="$t('No data')">
<template slot="top-left" slot-scope="props">
<q-input v-model="filter.groupName" type="text" prefix="名称:" />&nbsp;&nbsp;
<q-btn push dense color="primary" icon="search" @click="search">{{$t('Search')}}</q-btn>&nbsp;&nbsp;
<q-btn push dense color="primary" icon="add" @click="openAddApplicationsGroup">{{$t('Add')}}</q-btn>
</template>
<template slot="top-right" slot-scope="props">
<q-btn flat round dense :icon="props.inFullscreen ? 'fullscreen_exit' : 'fullscreen'" @click="props.toggleFullscreen" />
</template>
<q-td slot="body-cell-id" slot-scope="props" :props="props" style="width:100px">
<q-btn-dropdown color="secondary" label="操作" no-wrap>
<q-list link>
<q-item v-close-overlay @click.native="editApplicationsGroup(props.row)">
<q-item-main>
<q-item-tile sublabel>修改</q-item-tile>
</q-item-main>
</q-item>
<q-item v-close-overlay @click.native="delApplicationsGroup(props.value)">
<q-item-main>
<q-item-tile sublabel >删除</q-item-tile>
</q-item-main>
</q-item>
<q-item v-close-overlay>
<q-item-main>
<q-item-tile sublabel>关闭弹窗</q-item-tile>
</q-item-main>
</q-item>
</q-list>
</q-btn-dropdown>
</q-td>
</q-table>
</q-card-main>
</q-card>
<!-- 编辑弹层:在一个弹层中同时修改 groupName 和 sortIndex -->
<q-modal v-model="editDialogVisible" maximized >
<q-modal-layout>
<q-toolbar slot="header">
<q-btn flat round dense @click="closeEditDialog" icon="reply" />
<q-toolbar-title>
{{$t('Edit')}}
</q-toolbar-title>
</q-toolbar>
<q-toolbar slot="footer">
<q-toolbar-title>
</q-toolbar-title>
<q-btn round color="red" @click="saveEditGroup" >{{$t('Save')}}</q-btn>
<q-btn round @click="closeEditDialog">{{$t('Cancel')}}</q-btn>
</q-toolbar>
<div class="layout-padding">
<q-field :count="255">
<q-input v-model="editDialogModel.groupName" float-label="名称" />
</q-field>
<q-field :count="255">
<q-input v-model.number="editDialogModel.sortIndex" type="number" float-label="排序" />
</q-field>
</div>
</q-modal-layout>
</q-modal>
</div>
</template>
<script>
import { getApplicationsGroupList,addApplicationsGroup,updateApplicationsGroup,delApplicationsGroup } from "@/service/permission/platformapplicationsGroup";
export default {
data() {
return {
serverData: [],
pagination: {
page: 1,
rowsPerPage: 10 // 默认每页显示条数
},
columns: [
{
name: "groupName",
required: true,
label: '名称',
align: "left",
field: "groupName",
sortable: true
},
{
name: "groupCode",
required: true,
label: '编码',
align: "left",
field: "groupCode",
sortable: true
},
{
name: "sortIndex",
required: true,
label: '排序',
align: "left",
field: "sortIndex",
sortable: true
},
{
name: "id",
required: true,
label: '操作',
align: "left",
field: "id",
sortable: true
}
],
filter: {
groupName: "",
},
selected: [],
loading: false,
// 编辑弹层状态
editDialogVisible: false,
editDialogModel: {
id: null,
groupName: '',
sortIndex: 0
},
}
},
mounted() {
this.request({
filter: this.filter
});
},
methods: {
async request() {
this.loading = true;
let query = {
groupName: this.filter.groupName
};
let dataRes = await getApplicationsGroupList(query);
this.serverData = dataRes.data.data;
setTimeout(() => {
this.loading = false;
}, 500);
},
search() {
this.request({
filter: this.filter
});
},
async delApplicationsGroup(id) {
try {
await this.$q.dialog({
title: this.$t("Delete"),
message: this.$t("Confirm the deletion?"),
position: "right",
ok: {
push: true,
label: this.$t("Delete")
},
cancel: {
push: true,
color: "negative",
label: this.$t("Cancel")
}
});
await delApplicationsGroup({ id: id });
this.$q.notify({
type: "positive",
message: this.$t("Successfully deleted"),
position: "bottom-right"
});
this.search();
} catch (e) {}
},
// 点击添加应用分组按钮
async openAddApplicationsGroup() {
try {
const name = await this.$q.dialog({
title: '添加应用分组',
prompt: {
model: '',
type: 'text',
isValid: val => val && val.trim().length > 0
},
ok: { label: '添加', push: true },
cancel: { label: '取消', push: true }
});
console.log(name);
// `name` contains the input string in this Quasar version
if (name) {
const groupName = name; // be defensive across versions
await addApplicationsGroup({ groupName: groupName });
this.$q.notify({ type: 'positive', message: '添加成功', position: 'bottom-right' });
// refresh options
this.search();
}
} catch (e) {
// user cancelled or error
}
},
editApplicationsGroup(row) {
// fill model and open dialog
this.editDialogModel.id = row.id;
this.editDialogModel.groupName = row.groupName;
this.editDialogModel.sortIndex = row.sortIndex != null ? row.sortIndex : 0;
this.editDialogVisible = true;
},
closeEditDialog() {
this.editDialogVisible = false;
// reset model
this.editDialogModel = { id: null, groupName: '', sortIndex: 0 };
},
async saveEditGroup() {
// validation
if (!this.editDialogModel.groupName || !this.editDialogModel.groupName.trim()) {
this.$q.notify({ type: 'negative', message: '名称不能为空', position: 'bottom-right' });
return;
}
const sid = this.editDialogModel.id;
try {
await updateApplicationsGroup({ id: sid, groupName: this.editDialogModel.groupName, sortIndex: this.editDialogModel.sortIndex });
this.$q.notify({ type: 'positive', message: '修改成功', position: 'bottom-right' });
this.editDialogVisible = false;
this.search();
} catch (e) {
console.error(e);
this.$q.notify({ type: 'negative', message: '修改失败', position: 'bottom-right' });
}
},
},
}
</script>
<style scoped>
.fixed-height-table {
.q-table-middle {
/* 设置一个你认为合适的高度 */
height: 500px;
overflow: hidden;
/* 滚动 */
overflow-y: auto;
/* 固定table头 */
thead tr:first-child th {
position: sticky;
top: 0;
z-index: 1;
background-color: #fff; /* 确保表头有背景色,否则滚动时内容会透上来 */
}
}
}
</style>
\ No newline at end of file
<template>
<div style="padding: 10px">
<q-card inline class="fit shadow-6">
<q-card-main>
<div class="row fit">
<!-- 左侧:应用分组列表 -->
<div class="col-4" style="border-right: 1px solid #e0e0e0; padding-right: 10px">
<div class="row items-center justify-between q-mb-md">
<div class="text-h6">应用分组</div>
<q-btn color="primary" icon="add" size="sm" @click="openAddGroupDialog">
添加分组
</q-btn>
</div>
<q-list bordered separator class="group-list-container">
<draggable
v-model="applicationsGroups"
@end="onGroupDragEnd"
animation="200"
handle=".drag-handle"
>
<q-item
v-for="group in applicationsGroups"
:key="group.id"
clickable
:active="selectedGroup && selectedGroup.id === group.id"
class="group-item"
:class="{
'selected-group': selectedGroup && selectedGroup.id === group.id,
}"
>
<q-item-main>
<div class="row items-center justify-between">
<div class="col">
<div class="text-left text-weight-medium">
{{ group.groupName }}
</div>
<div class="text-left text-caption text-grey-7">
{{ group.groupCode }}
</div>
</div>
<div class="col-auto">
<q-icon
name="drag_indicator"
class="drag-handle cursor-move text-grey-6 drag-icon-visible"
size="18px"
></q-icon>
</div>
</div>
</q-item-main>
<q-item-side right>
<q-btn
flat
round
dense
icon="apps"
size="sm"
color="primary"
@click.stop="selectGroup(group)"
/>
<q-btn
flat
round
dense
icon="edit"
size="sm"
@click.stop="editGroup(group)"
/>
<q-btn
flat
round
dense
icon="delete"
size="sm"
color="negative"
@click.stop="deleteGroup(group)"
/>
</q-item-side>
</q-item>
</draggable>
</q-list>
</div>
<!-- 右侧:应用管理 -->
<div class="col-8" style="padding-left: 10px">
<div v-if="selectedGroup">
<div class="row items-center justify-between q-mb-md">
<div class="text-h6">{{ selectedGroup.groupName }} - 应用管理</div>
<q-btn color="primary" icon="add" @click="openAddApplicationDialog">
添加应用
</q-btn>
</div>
<q-table
:data="groupApplications"
:columns="applicationColumns"
row-key="id"
:loading="loading"
:no-data-label="$t('No data')"
separator="cell"
>
<template slot="body-cell-title" slot-scope="props">
<q-td :props="props">
<div class="row items-center">
<q-icon :name="'apps'" size="20px" class="q-mr-sm" />
{{ props.value }}
</div>
</q-td>
</template>
<template slot="body-cell-href" slot-scope="props">
<q-td :props="props">
<div v-if="props.row.href">
<q-icon name="computer" size="16px" />
{{ props.row.href }}
</div>
<div v-if="props.row.wechatUrl">
<q-icon name="chat" size="16px" />
{{ props.row.wechatUrl }}
</div>
<div v-if="!props.row.href && !props.row.wechatUrl">
<span class="text-grey-7 text-caption">无入口</span>
</div>
</q-td>
</template>
<template slot="body-cell-status" slot-scope="props">
<q-td :props="props">
<span
class="text-caption"
:class="
props.row.qyflag === '1' ? 'text-positive' : 'text-negative'
"
>
{{ props.row.qyflag === "1" ? "启用" : "停用" }}
</span>
</q-td>
</template>
<template slot="body-cell-actions" slot-scope="props">
<q-td :props="props" style="width: 100px">
<q-btn-dropdown color="secondary" label="操作" no-wrap>
<q-list link>
<q-item
v-close-overlay
@click.native="editApplication(props.row)"
>
<q-item-main>
<q-item-tile sublabel>修改</q-item-tile>
</q-item-main>
</q-item>
<q-item
v-close-overlay
@click.native="removeApplicationFromGroup(props.row)"
>
<q-item-main>
<q-item-tile sublabel>从分组移除</q-item-tile>
</q-item-main>
</q-item>
</q-list>
</q-btn-dropdown>
</q-td>
</template>
</q-table>
</div>
<div v-else class="flex flex-center" style="height: 400px">
<div class="text-center">
<q-icon name="info" size="48px" color="grey" />
<div class="text-h6 q-mt-md">请选择一个应用分组</div>
<div class="text-grey">点击左侧分组查看和管理应用</div>
</div>
</div>
</div>
</div>
</q-card-main>
</q-card>
<!-- 添加应用对话框 -->
<q-modal v-model="addApplicationDialog" maximized>
<q-modal-layout>
<q-toolbar slot="header">
<q-btn flat round dense @click="addApplicationDialog = false" icon="reply" />
<q-toolbar-title> 添加应用到分组 </q-toolbar-title>
</q-toolbar>
<q-toolbar slot="footer">
<q-toolbar-title> </q-toolbar-title>
<q-btn
round
color="red"
:disabled="selectedApplications.length === 0"
@click="addSelectedApplications"
>添加</q-btn
>
<q-btn round @click="addApplicationDialog = false">{{ $t("Cancel") }}</q-btn>
</q-toolbar>
<div class="layout-padding">
<div class="row q-mb-md">
<q-input
v-model="applicationFilter"
type="text"
placeholder="搜索应用名称"
class="col-4"
clearable
>
<template v-slot:prepend>
<q-icon name="search" />
</template>
</q-input>
</div>
<q-table
:data="filteredApplications"
:columns="availableApplicationColumns"
row-key="id"
selection="multiple"
:selected.sync="selectedApplications"
:loading="loadingApplications"
:no-data-label="$t('No data')"
separator="cell"
>
<template slot="body-cell-title" slot-scope="props">
<q-td :props="props">
<div class="row items-center">
<q-icon :name="'apps'" size="20px" class="q-mr-sm" />
{{ props.value }}
</div>
</q-td>
</template>
<template slot="body-cell-status" slot-scope="props">
<q-td :props="props">
<span
class="text-caption"
:class="props.row.qyflag === '1' ? 'text-positive' : 'text-negative'"
>
{{ props.row.qyflag === "1" ? "启用" : "停用" }}
</span>
</q-td>
</template>
</q-table>
</div>
</q-modal-layout>
</q-modal>
</div>
</template>
<script>
import draggable from "vuedraggable";
import {
getApplicationsGroupList,
addApplicationsGroup,
updateApplicationsGroup,
delApplicationsGroup,
updateApplicationsGroupSortIndex,
} from "@/service/permission/platformapplicationsGroup";
import {
getPlatformApplicationsPagedList,
saveapp,
} from "@/service/permission/platformapplications";
export default {
name: "PlatformApplicationsGroupManager",
components: {
draggable,
},
data() {
return {
applicationsGroups: [],
selectedGroup: null,
groupApplications: [],
allApplications: [],
loading: false,
loadingApplications: false,
addApplicationDialog: false,
applicationFilter: "",
selectedApplications: [],
applicationColumns: [
{
name: "title",
required: true,
label: "应用名称",
align: "left",
field: "title",
sortable: true,
},
{
name: "href",
required: true,
label: "入口地址",
align: "left",
field: "href",
sortable: true,
},
{
name: "status",
required: true,
label: "状态",
align: "left",
field: "qyflag",
sortable: true,
},
{
name: "addtime",
required: true,
label: "添加时间",
align: "left",
field: "addtime",
sortable: true,
},
{
name: "actions",
required: true,
label: "操作",
align: "left",
field: "id",
sortable: false,
},
],
availableApplicationColumns: [
{
name: "title",
required: true,
label: "应用名称",
align: "left",
field: "title",
sortable: true,
},
{
name: "appid",
required: true,
label: "应用标识",
align: "left",
field: "appid",
sortable: true,
},
{
name: "status",
required: true,
label: "状态",
align: "left",
field: "qyflag",
sortable: true,
},
{
name: "addtime",
required: true,
label: "添加时间",
align: "left",
field: "addtime",
sortable: true,
},
],
};
},
computed: {
filteredApplications() {
if (!this.applicationFilter) {
return this.allApplications.filter(
(app) => !this.groupApplications.some((groupApp) => groupApp.id === app.id)
);
}
return this.allApplications.filter(
(app) =>
app.title.toLowerCase().includes(this.applicationFilter.toLowerCase()) &&
!this.groupApplications.some((groupApp) => groupApp.id === app.id)
);
},
},
mounted() {
this.loadApplicationsGroups();
this.loadAllApplications();
},
methods: {
async loadApplicationsGroups() {
try {
const response = await getApplicationsGroupList({});
this.applicationsGroups = response.data.data || [];
} catch (error) {
console.error("Failed to load applications groups:", error);
this.$q.notify({
type: "negative",
message: "加载应用分组失败",
position: "bottom-right",
});
}
},
async loadAllApplications() {
try {
this.loadingApplications = true;
const response = await getPlatformApplicationsPagedList({
pageNum: 1,
pageSize: 1000,
title: "",
});
this.allApplications = response.data.data.list || [];
} catch (error) {
console.error("Failed to load applications:", error);
this.$q.notify({
type: "negative",
message: "加载应用列表失败",
position: "bottom-right",
});
} finally {
this.loadingApplications = false;
}
},
async selectGroup(group) {
console.log(group, "======selectGroup=====");
this.selectedGroup = group;
await this.loadGroupApplications();
},
async loadGroupApplications() {
if (!this.selectedGroup) return;
try {
this.loading = true;
// 使用现有的API,通过groupCode过滤应用
const response = await getPlatformApplicationsPagedList({
pageNum: 1,
pageSize: 1000,
title: "",
});
// 过滤出当前分组的应用
this.groupApplications = (response.data.data.list || []).filter(
(app) => app.groupCode === this.selectedGroup.groupCode
);
} catch (error) {
console.error("Failed to load group applications:", error);
this.$q.notify({
type: "negative",
message: "加载分组应用失败",
position: "bottom-right",
});
} finally {
this.loading = false;
}
},
async onGroupDragEnd() {
// 更新排序索引
this.applicationsGroups.forEach((group, index) => {
group.sortIndex = index + 1;
});
console.log(this.applicationsGroups, "=========onGroupDragEnd=========");
try {
// 批量更新排序(这里需要后端支持批量更新)
await updateApplicationsGroupSortIndex(this.applicationsGroups);
this.$q.notify({
type: "positive",
message: "分组排序已更新",
position: "bottom-right",
});
} catch (error) {
console.error("Failed to update group sort:", error);
this.$q.notify({
type: "negative",
message: "更新分组排序失败",
position: "bottom-right",
});
}
},
async openAddGroupDialog() {
try {
const name = await this.$q.dialog({
title: "添加应用分组",
prompt: {
model: "",
type: "text",
isValid: (val) => val && val.trim().length > 0,
},
ok: { label: "添加", push: true },
cancel: { label: "取消", push: true },
});
if (name) {
await addApplicationsGroup({ groupName: name });
this.$q.notify({
type: "positive",
message: "添加成功",
position: "bottom-right",
});
this.loadApplicationsGroups();
}
} catch (e) {
// user cancelled
}
},
editGroup(group) {
// 实现编辑分组功能
this.$q
.dialog({
title: "编辑应用分组",
prompt: {
model: group.groupName,
type: "text",
isValid: (val) => val && val.trim().length > 0,
},
ok: { label: "保存", push: true },
cancel: { label: "取消", push: true },
})
.then(async (name) => {
try {
await updateApplicationsGroup({
id: group.id,
groupName: name,
sortIndex: group.sortIndex,
});
this.$q.notify({
type: "positive",
message: "修改成功",
position: "bottom-right",
});
this.loadApplicationsGroups();
} catch (error) {
this.$q.notify({
type: "negative",
message: "修改失败",
position: "bottom-right",
});
}
});
},
async deleteGroup(group) {
try {
await this.$q.dialog({
title: "删除确认",
message: `确定要删除分组"${group.groupName}"吗?`,
ok: { label: "删除", push: true, color: "negative" },
cancel: { label: "取消", push: true },
});
await delApplicationsGroup({ id: group.id });
this.$q.notify({
type: "positive",
message: "删除成功",
position: "bottom-right",
});
if (this.selectedGroup && this.selectedGroup.id === group.id) {
this.selectedGroup = null;
this.groupApplications = [];
}
this.loadApplicationsGroups();
} catch (error) {
// user cancelled or error
}
},
openAddApplicationDialog() {
this.selectedApplications = [];
this.addApplicationDialog = true;
},
async addSelectedApplications() {
if (!this.selectedGroup || this.selectedApplications.length === 0) return;
try {
// 批量更新应用的分组
for (let application of this.selectedApplications) {
await saveapp({
...application,
groupCode: this.selectedGroup.groupCode,
});
}
this.$q.notify({
type: "positive",
message: `成功添加 ${this.selectedApplications.length} 个应用到分组`,
position: "bottom-right",
});
this.addApplicationDialog = false;
this.loadGroupApplications();
} catch (error) {
console.error("Failed to add applications to group:", error);
this.$q.notify({
type: "negative",
message: "添加应用到分组失败",
position: "bottom-right",
});
}
},
editApplication(application) {
// 跳转到应用编辑页面
this.$router.push({
path: "/permission/platformapplications",
query: { editId: application.id },
});
},
async removeApplicationFromGroup(application) {
try {
await this.$q.dialog({
title: "移除确认",
message: `确定要从分组中移除应用"${application.title}"吗?`,
ok: { label: "移除", push: true, color: "negative" },
cancel: { label: "取消", push: true },
});
// 将应用的分组设置为空
await saveapp({
...application,
groupCode: null,
});
this.$q.notify({
type: "positive",
message: "移除成功",
position: "bottom-right",
});
this.loadGroupApplications();
} catch (error) {
// user cancelled or error
}
},
},
};
</script>
<style scoped>
.group-list-container {
min-height: 500px;
max-height: 600px;
overflow-y: auto;
}
.group-item {
transition: all 0.3s ease;
border-left: 3px solid transparent;
}
.group-item:hover {
background-color: #f5f5f5;
}
.group-item.sortable-ghost {
opacity: 0.5;
background-color: #e3f2fd;
}
.selected-group {
background-color: #e3f2fd !important;
border-left: 3px solid #2196f3;
}
.drag-handle {
cursor: move;
color: #999;
padding: 4px;
border-radius: 4px;
}
.drag-handle:hover {
color: #333;
background-color: rgba(0, 0, 0, 0.05);
}
.fixed-height-table {
height: 600px;
}
.q-list {
max-height: 600px;
overflow-y: auto;
}
</style>
# 应用分组管理功能需求文档
# 应用分组管理功能需求文档
## 1. 项目背景
基于现有的应用分组(`platformapplicationsGroup`)和应用管理(`platformapplications`)功能,需要开发一个新的管理页面,实现应用分组的拖拽排序以及向分组中添加应用的可视化操作。
## 2. 功能需求
### 2.1 核心功能
1. **应用分组拖拽排序**
- 支持应用分组列表的拖拽重新排序
- 拖拽后自动更新排序索引(sortIndex)
- 实时保存排序结果到后端
2. **应用分组内应用管理**
- 左侧显示应用分组列表(可拖拽排序)
- 右侧显示当前选中分组内的应用列表
- 支持从所有应用池中选择应用添加到分组
- 支持从分组中移除应用
### 2.2 界面布局
```
+----------------------------------+
| 应用分组管理 |
+----------------------------------+
| 左侧:分组列表 | 右侧:应用管理 |
| (可拖拽排序) | |
| | 当前分组应用 |
| - 分组1 | +-----------+ |
| - 分组2 | | 应用1 | |
| - 分组3 | | 应用2 | |
| | +-----------+ |
| [添加分组] | [添加应用] |
+-------------------+----------------+
```
## 3. 技术实现
### 3.1 数据结构
基于现有数据结构:
- **应用分组**:包含`id`, `groupName`, `groupCode`, `sortIndex`
- **应用**:包含`id`, `title`, `groupCode`, 以及其他应用相关字段
### 3.2 新增API需求
需要后端提供以下API(假设):
```javascript
// 更新分组排序
updateApplicationsGroupSort(data) {
return request({
url: '/applicationsGroup/updateSort',
method: 'post',
data: data
})
}
// 获取分组中的应用列表
getApplicationsByGroup(groupCode) {
return request({
url: '/platformapplications/byGroup',
method: 'get',
params: { groupCode }
})
}
// 将应用添加到分组
addApplicationToGroup(data) {
return request({
url: '/platformapplications/addToGroup',
method: 'post',
data: data
})
}
// 从分组中移除应用
removeApplicationFromGroup(data) {
return request({
url: '/platformapplications/removeFromGroup',
method: 'post',
data: data
})
}
```
### 3.3 前端组件设计
1. **分组列表组件**
- 使用Quasar的`q-list`和拖拽功能
- 支持拖拽排序
- 点击选中分组
2. **应用管理组件**
- 当前分组应用列表
- 添加应用对话框(从所有应用中选择)
- 移除应用确认
3. **拖拽排序实现**
- 使用HTML5拖拽API或第三方拖拽库
- 实时更新排序索引
- 批量保存排序结果
## 4. 路由配置
在现有路由基础上添加新页面:
```javascript
{
path: 'platformapplicationsGroupManager',
name: 'platformapplicationsGroupManager',
component: () => import('pages/platformapplicationsGroup/platformapplicationsGroupManager.vue')
}
```
## 5. 菜单配置
在权限管理菜单下添加新菜单项:
```javascript
{
path: "platformapplicationsGroupManager",
icon: "settings",
title: "应用分组管理",
name: "platformapplicationsGroupManager",
leftmemu: true
}
```
## 6. 用户体验要求
1. **拖拽体验**
- 拖拽时有视觉反馈
- 拖拽结束后自动保存
- 保存成功/失败有提示
2. **操作便捷性**
- 点击分组即可查看和管理内部应用
- 添加应用支持搜索和筛选
- 移除应用需要确认提示
3. **响应式设计**
- 适配不同屏幕尺寸
- 移动端友好的触控操作
## 7. 技术栈
- **前端框架**:Vue.js + Quasar Framework
- **拖拽功能**:原生HTML5拖拽API或SortableJS
- **状态管理**:Vuex(如需)
- **HTTP请求**:基于现有request封装
## 8. 开发计划
1. 创建页面组件和路由配置
2. 实现分组列表拖拽排序功能
3. 实现应用管理功能(添加/移除)
4. 集成API接口
5. 优化用户体验和界面
6. 测试和调试
请确认这个需求文档是否符合您的预期,如有需要调整的地方请指出,确认后我将开始实现该功能。
\ No newline at end of file
...@@ -156,9 +156,9 @@ const appRouter = [{ ...@@ -156,9 +156,9 @@ const appRouter = [{
component: () => import('pages/platformapplications/platformapplications.vue') component: () => import('pages/platformapplications/platformapplications.vue')
}, },
{ {
path: 'platformapplicationsGroup', path: 'platformapplicationsGroupManager',
name: 'platformapplicationsGroup', name: 'groupManager',
component: () => import('pages/platformapplicationsGroup/platformapplicationsGroup.vue') component: () => import('pages/platformapplicationsGroup/platformapplicationsGroupManager.vue')
}, },
] ]
}, },
......
...@@ -26,6 +26,16 @@ export function updateApplicationsGroup(data) { ...@@ -26,6 +26,16 @@ export function updateApplicationsGroup(data) {
}) })
} }
export function updateApplicationsGroupSortIndex(data) {
return request({
url: '/applicationsGroup/updateSortIndex',
method: 'post',
data: data,
loading:"hourglass"
})
}
export function delApplicationsGroup(data) { export function delApplicationsGroup(data) {
return request({ return request({
url: '/applicationsGroup/delete', url: '/applicationsGroup/delete',
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论