Files
UserbaseFront/src/views/app/AppCategory.vue
T
2026-09-07 09:32:38 +08:00

284 lines
8.4 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.
<template>
<div class="main-wrapper">
<div class="main-container">
<Alert show-icon>设置说明:拖动排序,被使用的分类不能删除</Alert>
<!-- 多校管理员:学校切换(改名/排序仅对所选学校生效) -->
<Select v-if="multiSchool" v-model="currentSchoolId" class="mb-5 school-select" transfer
placeholder="全部学校(并集)" @on-change="fetchTypeList">
<Option v-for="s in schoolList" :key="s.id" :value="s.id">{{ s.name }}</Option>
</Select>
<Button v-if="!isSchoolMode" class="mb-5" :class="{ 'ml-8': multiSchool }" type="primary"
@click="addModuleShow">新增应用类型</Button>
<draggable class="type_set-wrap fix" tag="ul" v-model="typeList" :group="'appType'" @change="typeSortSave"
item-key="id">
<template #item="{ element }">
<li class="fl">
<div class="type_set-box">
<div class="type_name">
<span class="content">{{ element.name }}</span>
</div>
<div class="type_action flex">
<div class="action_edit" @click="showSetting(element)">
<span>编辑</span>
</div>
<div class="action_delete" v-if="!isSchoolMode" @click="removeSave(element)">
<span>删除</span>
</div>
</div>
<div class="divider"></div>
</div>
</li>
</template>
</draggable>
</div>
<div class="modal-wrap">
<Modal v-model="editModal" :mask-closable="false" width="600"
:title="currentId == null ? '新增应用类型' : '编辑应用类型'">
<Form ref="formData" class="mt-8 mr-4 mb-12" :model="form" :rules="ruleValidate" :label-width="100">
<FormItem label="类型名称:" prop="name">
<Input placeholder="请输入应用类型名称" v-model="form.name" />
</FormItem>
</Form>
<template #footer>
<Button type="primary" :loading="submitting" @click="onEditSave">提交</Button>
<Button @click="onReset">取消</Button>
</template>
</Modal>
</div>
</div>
</template>
<script lang="ts" setup>
import draggable from 'vuedraggable'
import { debounce } from "lodash";
import MyConfirm from "@/components/MyConfirm";
import { useAppSchoolScope } from "@/utils/hook/useAppSchoolScope";
// 学校管理员上下文:仅改名/排序(写学校覆盖表);新增/删除类型仅超管(按钮隐藏 + 接口 requireSuper 双挡)
const { isSchoolMode, multiSchool, schoolList, currentSchoolId, requireSchool } = useAppSchoolScope();
const typeList = ref<any[]>([])
async function fetchTypeList() {
try {
const res: any = await Apis.AppSettings.appTypeList({
params: { schoolId: currentSchoolId.value ?? undefined },
cacheFor: null,
});
typeList.value = (res ?? []).slice().sort((a: any, b: any) => (a.sortNo ?? 0) - (b.sortNo ?? 0));
} catch (e: any) {
console.warn(e?.message || "获取应用类型失败");
}
}
fetchTypeList()
const refFormData = useTemplateRef('formData');
const editModal = ref(false);
const currentId = ref<number | null>(null);
const submitting = ref(false);
const form = ref<{ id: number | null; name: string }>({
id: null,
name: '',
});
const ruleValidate = ref({
name: [{ required: true, message: "请输入应用类型名称", trigger: "blur" }],
});
async function addModuleShow() {
currentId.value = null;
form.value = { id: null, name: '' };
await refFormData.value?.resetFields();
editModal.value = true;
}
//拖动排序:提交当前顺序的 id 数组(SCHOOL 模式写学校覆盖表快照;多校须先选定学校)
async function typeSortSave() {
if (!requireSchool()) {
// 未选学校:拒绝提交并回拉列表,撤销拖拽造成的本地顺序变化
await fetchTypeList();
return;
}
const ids = (typeList.value ?? []).map((s: any) => s.id);
await Apis.AppSettings.appTypeSort({
data: ids,
params: currentSchoolId.value != null ? { schoolId: currentSchoolId.value } : undefined,
});
}
async function showSetting(element: any) {
if (!requireSchool()) return;
currentId.value = element.id;
form.value = {
id: element.id,
name: element.name,
};
editModal.value = true;
}
/**
* removeSave
*/
async function removeSave(element: any) {
await MyConfirm({
msg: "确定删除该应用类型吗?",
info: "删除后不可恢复!",
async onOk() {
try {
await Apis.AppSettings.deleteType({ pathParams: { id: element.id } });
Message.success("操作成功!");
await fetchTypeList();
} catch (error) {
console.error(error);
}
},
});
}
const onEditSave = debounce(onRawEditSave, 500)
async function onRawEditSave() {
try {
const validate = await refFormData.value?.validate();
if (!validate) return false;
} catch {
return false
}
submitting.value = true;
try {
if (currentId.value == null) {
await Apis.AppSettings.addType({
data: {
name: form.value.name,
},
});
} else {
await Apis.AppSettings.editType({
pathParams: { id: currentId.value },
data: {
id: currentId.value,
name: form.value.name,
},
// SCHOOL 模式:改名写学校覆盖表(仅该校生效)
params: currentSchoolId.value != null ? { schoolId: currentSchoolId.value } : undefined,
});
}
Message.success("操作成功!");
editModal.value = false;
await fetchTypeList();
} catch (error) {
console.error(error);
} finally {
submitting.value = false;
}
}
function onReset() {
refFormData.value?.resetFields();
currentId.value = null;
editModal.value = false;
}
</script>
<style lang="scss" scoped>
.flex {
display: flex;
}
.school-select {
width: 180px;
}
.type_set-wrap {
margin: 12px 0 16px;
}
.type_set-box {
width: 222px;
background: #ffffff;
margin-right: 15px;
margin-bottom: 15px;
.type_name {
height: 66px;
border: 1px solid #e9e9e9;
border-bottom: none;
padding-left: 15px;
padding-right: 15px;
line-height: 66px;
display: flex;
justify-content: center;
align-items: center;
cursor: move;
span.content {
height: 24px;
font-size: 15px;
font-family: PingFangSC-Semibold, PingFang SC;
font-weight: 500;
color: #272829;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 222px;
display: inline-block;
flex: 1 1 0;
}
.iconfont {
font-size: 26px;
}
}
.type_action {
flex-wrap: wrap;
justify-content: center;
align-items: center;
width: 222px;
height: 30px;
line-height: 30px;
background: #f7f7f7;
border-radius: 0px 0px 2px 2px;
border-top: none;
border: 1px solid #e9e9e9;
span {
font-size: 12px;
}
div {
cursor: pointer;
flex: 1;
text-align: center;
}
div.disabled {
color: #ccc;
cursor: not-allowed;
&:hover {
color: #ccc;
}
}
div:hover {
color: #6c91f0;
}
}
.divider {
height: 14px;
width: 1px;
background: #e8e8e8;
float: left;
margin-left: 111px;
margin-top: -22px;
}
}
</style>