refactor event
This commit is contained in:
@@ -0,0 +1,354 @@
|
||||
<template>
|
||||
<div v-can="permissionCodes.event.update" class="registration-event-sort">
|
||||
<div class="tip" v-text="'按住右侧拖动手柄调整事件顺序,保存后生效'" />
|
||||
<div ref="sortListRef" class="items">
|
||||
<t-cell-group v-if="eventList.length" theme="card">
|
||||
<t-cell
|
||||
class="cell"
|
||||
:title="item.name || '未命名事件'"
|
||||
v-for="(item, index) in eventList"
|
||||
:key="item.id"
|
||||
:class="{ dragging: draggingIndex === index }"
|
||||
>
|
||||
<template #leftIcon>
|
||||
<div class="color" :style="{ background: item.color || defaultColor }" />
|
||||
</template>
|
||||
<template #note>
|
||||
<t-tag
|
||||
:theme="getStatusTheme(item.status)"
|
||||
variant="outline"
|
||||
:content="getStatusText(item.status)"
|
||||
/>
|
||||
</template>
|
||||
<template #rightIcon>
|
||||
<t-icon
|
||||
class="handle"
|
||||
role="button"
|
||||
name="move"
|
||||
aria-label="拖动排序"
|
||||
@pointerdown.stop="startDrag($event, index)"
|
||||
@pointermove.stop="moveDrag"
|
||||
@pointerup.stop="finishDrag"
|
||||
@pointercancel.stop="finishDrag"
|
||||
@lostpointercapture.stop="finishDrag"
|
||||
@touchstart.stop="startTouchDrag($event, index)"
|
||||
@touchmove.stop="moveTouchDrag"
|
||||
@touchend.stop="finishTouchDrag"
|
||||
@touchcancel.stop="finishTouchDrag"
|
||||
/>
|
||||
</template>
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
<div v-else class="empty-block" v-text="loading ? '正在加载登记事件' : '暂无登记事件'" />
|
||||
</div>
|
||||
<t-button
|
||||
class="save"
|
||||
theme="primary"
|
||||
:loading="saving"
|
||||
:disabled="!sortDirty || saving || loading"
|
||||
@click="saveSort"
|
||||
>
|
||||
保存排序
|
||||
</t-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Toast, type TagProps } from "tdesign-mobile-vue";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { EventItem, EventStatus } from "@/types/EventItem";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
|
||||
defineOptions({
|
||||
name: "EventSort"
|
||||
});
|
||||
|
||||
const navBarStore = useNavBarStore();
|
||||
const defaultColor = "#2f88ff";
|
||||
const eventList = ref<EventItem[]>([]);
|
||||
const sortListRef = ref<HTMLElement>();
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const sortDirty = ref(false);
|
||||
const draggingIndex = ref(-1);
|
||||
let pointerId: number | undefined;
|
||||
let pointerStartY = 0;
|
||||
let pointerMoved = false;
|
||||
let pointerTarget: HTMLElement | undefined;
|
||||
let dragInputType: "pointer" | "touch" | undefined;
|
||||
|
||||
function getStatusText(status?: EventStatus): string {
|
||||
if (status === "DRAFT") {
|
||||
return "草稿";
|
||||
}
|
||||
if (status === "INACTIVE") {
|
||||
return "不可登记";
|
||||
}
|
||||
return "可登记";
|
||||
}
|
||||
|
||||
function getStatusTheme(status?: EventStatus): TagProps["theme"] {
|
||||
if (status === "DRAFT") {
|
||||
return "warning";
|
||||
}
|
||||
if (status === "INACTIVE") {
|
||||
return "default";
|
||||
}
|
||||
return "primary";
|
||||
}
|
||||
|
||||
function resolveDropIndex(clientY: number): number {
|
||||
const elements = sortListRef.value?.querySelectorAll<HTMLElement>(".item");
|
||||
if (!elements?.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (let index = 0; index < elements.length; index++) {
|
||||
const rect = elements[index].getBoundingClientRect();
|
||||
if (clientY < rect.top + rect.height / 2) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return elements.length - 1;
|
||||
}
|
||||
|
||||
function beginDrag(index: number, clientY: number, target: EventTarget | null): void {
|
||||
pointerStartY = clientY;
|
||||
pointerMoved = false;
|
||||
draggingIndex.value = index;
|
||||
pointerTarget = target instanceof HTMLElement ? target : undefined;
|
||||
}
|
||||
|
||||
function moveDragAt(clientY: number): void {
|
||||
if (0 > draggingIndex.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pointerMoved && 8 > Math.abs(clientY - pointerStartY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pointerMoved = true;
|
||||
const targetIndex = resolveDropIndex(clientY);
|
||||
if (0 > targetIndex || targetIndex === draggingIndex.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [item] = eventList.value.splice(draggingIndex.value, 1);
|
||||
eventList.value.splice(targetIndex, 0, item);
|
||||
draggingIndex.value = targetIndex;
|
||||
sortDirty.value = true;
|
||||
}
|
||||
|
||||
function finishDragState(): void {
|
||||
if (pointerMoved) {
|
||||
sortDirty.value = true;
|
||||
}
|
||||
pointerId = undefined;
|
||||
pointerTarget = undefined;
|
||||
dragInputType = undefined;
|
||||
draggingIndex.value = -1;
|
||||
}
|
||||
|
||||
function startDrag(event: PointerEvent, index: number): void {
|
||||
if (saving.value || loading.value || dragInputType || !event.isPrimary || event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
pointerId = event.pointerId;
|
||||
dragInputType = "pointer";
|
||||
beginDrag(index, event.clientY, event.currentTarget);
|
||||
pointerTarget?.setPointerCapture(event.pointerId);
|
||||
}
|
||||
|
||||
function moveDrag(event: PointerEvent): void {
|
||||
if (dragInputType !== "pointer" || pointerId !== event.pointerId || 0 > draggingIndex.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
moveDragAt(event.clientY);
|
||||
}
|
||||
|
||||
function finishDrag(event: PointerEvent): void {
|
||||
if (dragInputType !== "pointer" || pointerId !== event.pointerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pointerTarget?.hasPointerCapture(event.pointerId)) {
|
||||
pointerTarget.releasePointerCapture(event.pointerId);
|
||||
}
|
||||
finishDragState();
|
||||
}
|
||||
|
||||
function startTouchDrag(event: TouchEvent, index: number): void {
|
||||
if (saving.value || loading.value || dragInputType || event.touches.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
const touch = event.touches[0];
|
||||
pointerId = touch.identifier;
|
||||
dragInputType = "touch";
|
||||
beginDrag(index, touch.clientY, event.currentTarget);
|
||||
}
|
||||
|
||||
function moveTouchDrag(event: TouchEvent): void {
|
||||
if (dragInputType !== "touch" || pointerId === undefined || 0 > draggingIndex.value || event.touches.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
moveDragAt(event.touches[0].clientY);
|
||||
}
|
||||
|
||||
function finishTouchDrag(event: TouchEvent): void {
|
||||
if (dragInputType !== "touch" || pointerId === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const touch = event.changedTouches[0];
|
||||
if (touch && touch.identifier !== pointerId) {
|
||||
return;
|
||||
}
|
||||
finishDragState();
|
||||
}
|
||||
|
||||
async function saveSort(): Promise<void> {
|
||||
if (!sortDirty.value || saving.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
saving.value = true;
|
||||
try {
|
||||
const idList = eventList.value
|
||||
.map(item => item.id)
|
||||
.filter((id): id is string => Boolean(id));
|
||||
await EventAPI.sort(idList);
|
||||
eventList.value.forEach((item, index) => {
|
||||
item.sort = index;
|
||||
});
|
||||
sortDirty.value = false;
|
||||
showToast("登记事件排序已保存");
|
||||
} catch {
|
||||
showToast("登记事件排序保存失败", "error");
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(message: string, theme: "success" | "error" = "success"): void {
|
||||
Toast({
|
||||
theme,
|
||||
direction: "row",
|
||||
message
|
||||
});
|
||||
}
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await EventAPI.list({
|
||||
index: 0,
|
||||
size: 100
|
||||
});
|
||||
eventList.value = result.list || [];
|
||||
sortDirty.value = false;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
navBarStore.clearRight();
|
||||
try {
|
||||
await loadData();
|
||||
} catch {
|
||||
showToast("登记事件加载失败", "error");
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
navBarStore.clearRight();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.registration-event-sort {
|
||||
padding: calc(var(--app-nav-height) + 1rem) 0 6rem;
|
||||
|
||||
.tip {
|
||||
color: #52607a;
|
||||
padding: 0 1rem .75rem;
|
||||
font-size: .8125rem;
|
||||
}
|
||||
|
||||
.items {
|
||||
|
||||
.cell {
|
||||
position: relative;
|
||||
|
||||
&.dragging {
|
||||
z-index: 1;
|
||||
box-shadow: 0 .5rem 1rem rgba(15, 23, 42, .16);
|
||||
}
|
||||
|
||||
.color {
|
||||
flex: 0 0 auto;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: .4rem;
|
||||
}
|
||||
|
||||
.name {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.handle {
|
||||
color: #52607a;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.cell.dragging,
|
||||
.cell.dragging * {
|
||||
touch-action: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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) {
|
||||
.registration-event-sort {
|
||||
.tip,
|
||||
.empty-block,
|
||||
.handle {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.item {
|
||||
background: rgba(15, 23, 42, .88);
|
||||
box-shadow: 0 .5rem 1rem rgba(2, 6, 23, .28);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user