284 lines
8.4 KiB
Vue
284 lines
8.4 KiB
Vue
<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>
|