Files
userbase/src/views/app/AppSort.vue
T
2026-08-18 23:31:27 +08:00

219 lines
5.1 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>
<Modal v-model="modalVisible" :title="'应用排序 - ' + categoryName" width="560" :mask-closable="false"
@on-visible-change="onVisibleChange">
<Alert show-icon>拖动左侧手柄进行排序,排序结果会实时记录</Alert>
<div class="sort-loading" v-if="loading">
<span>加载中...</span>
</div>
<draggable v-else class="sort-list" v-model="sortList" item-key="id" handle=".drag-handle" @change="onDragChange">
<template #item="{ element, index }">
<div class="sort-item">
<span class="drag-handle">⠿</span>
<span class="sort-index">{{ index + 1 }}</span>
<span class="sort-name">{{ element.name || element.id }}</span>
<span class="sort-no">排序号: {{ element.sortNo ?? '-' }}</span>
</div>
</template>
</draggable>
<div class="sort-empty" v-if="!loading && sortList.length === 0">
暂无应用数据
</div>
<template #footer>
<Button type="primary" :loading="saving" @click="handleSave" :disabled="sortList.length === 0">
保存排序
</Button>
<Button @click="modalVisible = false">取消</Button>
</template>
</Modal>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import draggable from "vuedraggable";
const props = withDefaults(
defineProps<{
modelValue: boolean;
categoryId: number | null;
categoryName: string;
}>(),
{
categoryName: "",
}
);
const emit = defineEmits<{
(e: "update:modelValue", val: boolean): void;
(e: "saved"): void;
}>();
const modalVisible = ref(false);
const loading = ref(false);
const saving = ref(false);
const sortList = ref<{ id: number; name?: string; sortNo?: number }[]>([]);
const hasChanged = ref(false);
watch(
() => props.modelValue,
(val) => {
modalVisible.value = val;
if (val) {
fetchSortList();
}
}
);
watch(modalVisible, (val) => {
emit("update:modelValue", val);
});
function onVisibleChange(visible: boolean) {
if (!visible) {
hasChanged.value = false;
}
}
async function fetchSortList() {
if (!props.categoryId) return;
loading.value = true;
try {
const res = await Apis.AppSettings.appSortList({
params: { categoryId: props.categoryId },
cacheFor: null,
});
sortList.value = (res ?? []).slice().sort((a: any, b: any) => (a.sortNo ?? 0) - (b.sortNo ?? 0));
hasChanged.value = false;
} catch (e: any) {
console.warn(e?.message || "获取排序列表失败");
} finally {
loading.value = false;
}
}
function onDragChange() {
hasChanged.value = true;
}
async function handleSave() {
if (!hasChanged.value) {
Message.info("排序未发生变化");
modalVisible.value = false;
return;
}
saving.value = true;
try {
const ids = sortList.value.map((item) => item.id);
await Apis.AppSettings.appSort({ data: ids });
Message.success("排序保存成功!");
hasChanged.value = false;
modalVisible.value = false;
emit("saved");
} catch (e: any) {
console.warn(e?.message || "排序保存失败");
} finally {
saving.value = false;
}
}
</script>
<style lang="scss" scoped>
.sort-list {
max-height: 400px;
overflow-y: auto;
}
.sort-item {
display: flex;
align-items: center;
padding: 10px 12px;
margin-bottom: 6px;
background: #fafafa;
border: 1px solid #e8e8e8;
border-radius: 4px;
transition: background 0.2s;
&:hover {
background: #f0f4ff;
border-color: #c0d0ff;
}
}
/* 拖拽中:当前被拖拽的元素高亮(明显橙色,不放大避免横向滚动条) */
.sort-list :deep(.sort-item.sortable-chosen),
.sort-list :deep(.sort-item.sortable-drag) {
background: #fff7e6;
border-color: #fa8c16;
box-shadow: 0 6px 16px rgba(250, 140, 22, 0.4);
cursor: grabbing;
.drag-handle {
color: #fa8c16;
}
}
/* 拖拽中:落点占位位置(虚线提示) */
.sort-list :deep(.sort-item.sortable-ghost) {
background: #fffbe6;
border: 1px dashed #fa8c16;
opacity: 0.6;
}
.drag-handle {
cursor: grab;
display: flex;
align-items: center;
padding: 0 8px;
color: #999;
flex-shrink: 0;
&:active {
cursor: grabbing;
}
}
.sort-index {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 50%;
background: #577afb;
color: #fff;
font-size: 13px;
font-weight: 500;
flex-shrink: 0;
margin-right: 12px;
}
.sort-name {
flex: 1;
font-size: 14px;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sort-no {
flex-shrink: 0;
font-size: 12px;
color: #999;
margin-left: 12px;
}
.sort-loading,
.sort-empty {
padding: 40px 0;
text-align: center;
color: #999;
font-size: 14px;
}
</style>