init: UserBaseFront (frontend)

This commit is contained in:
chenxing
2026-08-10 14:41:13 +08:00
commit eea881b616
174 changed files with 32833 additions and 0 deletions
+218
View File
@@ -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>