| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- <template>
- <!-- H5:SortableJS -->
- <!-- #ifdef H5 -->
- <view ref="h5ListRef" class="mt-30 flex flex-col">
- <view
- v-for="(m, i) in innerMajors"
- :key="getKey(m, i)"
- class="bg-back-light rounded-lg p-20 flex justify-between items-center gap-20 mb-20 min-w-0"
- :data-index="i"
- >
- <view class="text-32 text-fore-placeholder font-bold">{{ toFixedLen(i) }}</view>
- <view class="flex-1 w-0 text-28 text-fore-title font-bold truncate">{{ m.majorName }}</view>
- <uv-icon name="trash" size="18" color="error"/>
- <!-- 拖拽手柄:Sortable handle -->
- <view class="drag-handle">
- <uv-icon name="list-dot" size="18" color="primary"/>
- </view>
- </view>
- </view>
- <!-- #endif -->
- <!-- #ifndef H5 -->
- <movable-area
- class="mt-30 w-full"
- :style="{ height: areaHeight + 'px' }"
- :catchtouchmove="dragging"
- @touchmove.stop.prevent="noop"
- >
- <movable-view
- v-for="(m, i) in innerMajors"
- :key="getKey(m, i)"
- class="w-full"
- direction="vertical"
- :y="getY(i)"
- :disabled="activeIndex !== i"
- :inertia="false"
- :damping="80"
- :animation="true"
- @change="(e) => onDragChange(e, i)"
- @touchend="onDragEnd"
- >
- <view class="bg-back-light rounded-lg p-20 flex justify-between items-center gap-20">
- <!-- 主内容:阻断触摸,确保只能手柄拖 -->
- <view class="flex-1 flex items-center gap-20" @touchstart.stop @touchmove.stop>
- <view class="text-32 text-fore-placeholder font-bold">{{ toFixedLen(i) }}</view>
- <view class="flex-1 w-0 text-28 text-fore-title font-bold truncate">{{ m.majorName }}</view>
- <uv-icon name="trash" size="18" color="error" />
- </view>
- <!-- 手柄:不 stop / prevent -->
- <view class="ml-10" @touchstart="onHandleDown(i)" @touchend="onHandleUp">
- <uv-icon name="list-dot" size="18" color="primary" />
- </view>
- </view>
- </movable-view>
- </movable-area>
- <!-- #endif -->
- </template>
- <script setup lang="ts">
- import {VoluntaryMajorItem} from "@/types/voluntary";
- import {VOLUNTARY_SORTING} from "@/types/injectionSymbols";
- // #ifdef H5
- import Sortable from "sortablejs";
- // #endif
- const props = defineProps<{
- majors: VoluntaryMajorItem[];
- }>();
- const emits = defineEmits<{
- (e: "update:majors", majors: VoluntaryMajorItem[]): void;
- (e: "change", majors: VoluntaryMajorItem[]): void;
- }>();
- const isSorting = inject(VOLUNTARY_SORTING) || ref(false)
- const innerMajors = ref<VoluntaryMajorItem[]>([]);
- watch(
- () => props.majors,
- (v) => (innerMajors.value = v ? [...v] : []),
- {immediate: true, deep: true}
- );
- function toFixedLen(i: number, len: number = 2) {
- return String(i + 1).padStart(len, "0");
- }
- function getKey(m: any, i: number) {
- // 你的示例 majorId 可能重复,所以拼 i
- return m.majorId
- }
- // ========== H5:SortableJS ==========
- const h5ListRef = ref<any>(null);
- // #ifdef H5
- let sortableIns: Sortable | null = null;
- onMounted(async () => {
- await nextTick();
- const el = h5ListRef.value?.$el || h5ListRef.value; // 兼容 view ref
- if (!el) return;
- sortableIns = Sortable.create(el, {
- animation: 150,
- handle: ".drag-handle", // 只允许手柄拖
- forceFallback: true,
- onStart: () => {
- isSorting.value = true
- console.log('sortableIns onStart', true)
- },
- onEnd: (evt) => {
- isSorting.value = false;
- console.log('sortableIns onEnd', false)
- // sort logic
- if (evt.oldIndex == null || evt.newIndex == null) return;
- const arr = [...innerMajors.value];
- const [moved] = arr.splice(evt.oldIndex, 1);
- arr.splice(evt.newIndex, 0, moved);
- innerMajors.value = arr;
- emits("update:majors", arr);
- emits('change', arr)
- },
- });
- });
- onBeforeUnmount(() => {
- sortableIns?.destroy();
- sortableIns = null;
- });
- // #endif
- // ========== 小程序:movable-view(重写版) ==========
- const dragging = ref(false);
- const activeIndex = ref(-1);
- const activeY = ref(0);
- const rowGapPx = uni.upx2px(20);
- const rowHeightPx = uni.upx2px(92) + rowGapPx;
- const areaHeight = computed(() => innerMajors.value.length * rowHeightPx);
- const noop = () => {}; // 关键:给 @touchmove 一个“函数”,不要给 boolean
- function getY(i: number) {
- return i === activeIndex.value ? activeY.value : i * rowHeightPx;
- }
- function clampY(y: number) {
- const maxY = Math.max(0, (innerMajors.value.length - 1) * rowHeightPx);
- return Math.min(Math.max(y, 0), maxY);
- }
- function swap(arr: any[], a: number, b: number) {
- const t = arr[a];
- arr[a] = arr[b];
- arr[b] = t;
- }
- function onHandleDown(i: number) {
- console.log("onHandleDown", i);
- dragging.value = true;
- activeIndex.value = i;
- activeY.value = i * rowHeightPx;
- }
- function onHandleUp() {
- console.log("onHandleUp");
- // 不要在这里结束;结束交给 movable-view 的 touchend
- }
- function onDragChange(e: any, i: number) {
- // 有了上面的报错修复后,这里应该能打出来
- console.log("onDragChange", e?.detail, i);
- if (!dragging.value) return;
- if (i !== activeIndex.value) return;
- if (e?.detail?.source !== "touch") return;
- const y = clampY(Number(e.detail.y || 0));
- activeY.value = y;
- const target = Math.round(y / rowHeightPx);
- if (target === i) return;
- swap(innerMajors.value as any[], i, target);
- activeIndex.value = target;
- }
- function onDragEnd() {
- console.log("onDragEnd");
- if (!dragging.value) return;
- dragging.value = false;
- if (activeIndex.value === -1) return;
- activeY.value = activeIndex.value * rowHeightPx;
- const arr = [...innerMajors.value];
- activeIndex.value = -1;
- console.log('emits update:majors', arr)
- emits("update:majors", arr);
- emits('change', arr);
- }
- </script>
- <style scoped>
- .drag-handle {
- touch-action: none;
- -webkit-user-select: none;
- user-select: none;
- }
- </style>
|