224 lines
4.4 KiB
Vue
224 lines
4.4 KiB
Vue
<template>
|
|
<div v-can="permissionCodes.store.update" class="store-sort">
|
|
<div class="tip" v-text="'按住右侧拖动手柄调整门店顺序,保存后生效'" />
|
|
<div class="items">
|
|
<t-cell-group v-if="storeList.length" theme="card">
|
|
<vue-draggable-next
|
|
v-model="storeList"
|
|
class="draggable"
|
|
item-key="id"
|
|
handle=".handle"
|
|
ghost-class="ghost"
|
|
chosen-class="chosen"
|
|
drag-class="dragging"
|
|
:animation="180"
|
|
:disabled="isLoading || isSaving"
|
|
@change="handleSortChange"
|
|
>
|
|
<t-cell
|
|
v-for="element in storeList"
|
|
:key="element.id"
|
|
class="cell"
|
|
:title="element.name || '未命名门店'"
|
|
:description="`编码:${element.code || '-'}`"
|
|
>
|
|
<template #note>
|
|
<t-tag
|
|
:theme="getStatusTheme(element.status)"
|
|
variant="outline"
|
|
:content="getStatusText(element.status)"
|
|
/>
|
|
</template>
|
|
<template #rightIcon>
|
|
<t-icon
|
|
class="handle"
|
|
role="button"
|
|
name="move"
|
|
aria-label="拖动排序"
|
|
/>
|
|
</template>
|
|
</t-cell>
|
|
</vue-draggable-next>
|
|
</t-cell-group>
|
|
<div v-else class="empty-block" v-text="isLoading ? '正在加载门店' : '暂无门店'" />
|
|
</div>
|
|
<t-button
|
|
class="save"
|
|
theme="primary"
|
|
:loading="isSaving"
|
|
:disabled="!sortDirty || isSaving || isLoading"
|
|
@click="saveSort"
|
|
>
|
|
保存排序
|
|
</t-button>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { Toast, type TagProps } from "tdesign-mobile-vue";
|
|
import { VueDraggableNext } from "vue-draggable-next";
|
|
import StoreAPI from "@/api/StoreAPI";
|
|
import { useNavBarStore } from "@/store/navBarStore";
|
|
import type { Store, StoreStatus } from "@/types/Store";
|
|
import { permissionCodes } from "@/utils/permission";
|
|
|
|
defineOptions({
|
|
name: "StoreSort"
|
|
});
|
|
|
|
const navBarStore = useNavBarStore();
|
|
const router = useRouter();
|
|
|
|
// ---------- 列表数据 ----------
|
|
|
|
const storeList = ref<Store[]>([]);
|
|
const isLoading = ref(false);
|
|
const isSaving = ref(false);
|
|
const sortDirty = ref(false);
|
|
|
|
// ---------- 展示文案 ----------
|
|
|
|
function getStatusText(status?: StoreStatus): string {
|
|
return status === "ACTIVE" ? "启用" : "停用";
|
|
}
|
|
|
|
function getStatusTheme(status?: StoreStatus): TagProps["theme"] {
|
|
return status === "ACTIVE" ? "primary" : "default";
|
|
}
|
|
|
|
// ---------- 拖拽排序 ----------
|
|
|
|
function handleSortChange(): void {
|
|
sortDirty.value = true;
|
|
}
|
|
|
|
// ---------- 保存 ----------
|
|
|
|
async function saveSort(): Promise<void> {
|
|
if (!sortDirty.value || isSaving.value) {
|
|
return;
|
|
}
|
|
|
|
isSaving.value = true;
|
|
try {
|
|
const idList = storeList.value
|
|
.map(item => item.id)
|
|
.filter((id): id is string => Boolean(id));
|
|
await StoreAPI.sort(idList);
|
|
storeList.value.forEach((item, index) => {
|
|
item.sort = index;
|
|
});
|
|
sortDirty.value = false;
|
|
Toast.success("门店排序已保存");
|
|
await router.back();
|
|
} catch {
|
|
Toast.error("门店排序保存失败");
|
|
} finally {
|
|
isSaving.value = false;
|
|
}
|
|
}
|
|
|
|
// ---------- 页面数据 ----------
|
|
|
|
async function loadData(): Promise<void> {
|
|
isLoading.value = true;
|
|
try {
|
|
const result = await StoreAPI.list({
|
|
index: 0,
|
|
size: 100,
|
|
orderMap: {
|
|
sort: "ASC",
|
|
createdAt: "DESC"
|
|
}
|
|
});
|
|
storeList.value = result.list || [];
|
|
sortDirty.value = false;
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
navBarStore.clearRight();
|
|
try {
|
|
await loadData();
|
|
} catch {
|
|
Toast.error("门店加载失败");
|
|
}
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
navBarStore.clearRight();
|
|
});
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
.store-sort {
|
|
padding: calc(var(--app-nav-height) + 1rem) 0 6rem;
|
|
|
|
.tip {
|
|
color: #52607a;
|
|
padding: 0 1rem .75rem;
|
|
font-size: .8125rem;
|
|
}
|
|
|
|
.items {
|
|
.draggable {
|
|
.cell {
|
|
position: relative;
|
|
|
|
.handle {
|
|
color: #52607a;
|
|
cursor: grab;
|
|
touch-action: none;
|
|
}
|
|
}
|
|
|
|
.ghost {
|
|
opacity: .4;
|
|
}
|
|
|
|
.chosen,
|
|
.dragging {
|
|
z-index: 1;
|
|
box-shadow: 0 .5rem 1rem rgba(15, 23, 42, .16);
|
|
}
|
|
}
|
|
}
|
|
|
|
.empty-block {
|
|
color: #52607a;
|
|
padding: 2rem 1rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.save {
|
|
left: 1rem;
|
|
right: 1rem;
|
|
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
|
position: fixed;
|
|
}
|
|
}
|
|
|
|
:global(html.theme-dark) {
|
|
.store-sort {
|
|
.tip,
|
|
.empty-block,
|
|
.handle {
|
|
color: #94a3b8;
|
|
}
|
|
|
|
.draggable {
|
|
.cell {
|
|
background: rgba(15, 23, 42, .88);
|
|
box-shadow: 0 .5rem 1rem rgba(2, 6, 23, .28);
|
|
}
|
|
|
|
.ghost {
|
|
background: rgba(15, 23, 42, .6);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|