init: UserBaseFront (frontend)
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
<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) {
|
||||
Message.error(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) {
|
||||
Message.error(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>
|
||||
Reference in New Issue
Block a user