abpcoder 1 неделя назад
Родитель
Сommit
d39d48b1ac

+ 1 - 0
package.json

@@ -66,6 +66,7 @@
     "mp-html": "^2.5.1",
     "pinia": "2",
     "pinia-plugin-persistedstate": "^4.3.0",
+    "sortablejs": "^1.15.6",
     "uqrcodejs": "^4.0.7",
     "vue": "^3.4.21",
     "vue-i18n": "^9.1.9"

+ 1 - 2
src/pages.json

@@ -132,8 +132,7 @@
         {
           "path": "pages/university/picker/picker",
           "style": {
-            "navigationBarTitleText": "",
-            "enablePullDownRefresh": true
+            "navigationBarTitleText": ""
           }
         }
       ]

+ 1 - 1
src/pagesOther/pages/university/index/components/plus/college-item.vue

@@ -1,6 +1,6 @@
 <template>
     <view class="p-20 bg-white flex justify-between items-start gap-20" @click="emits('click')">
-        <ie-image v-if="!hiddenLogo&&!reverse" :src="item.logo" mode="aspectFit" custom-class="w-128 h-128"/>
+        <ie-image v-if="!hiddenLogo&&!reverse" :src="item.logo" mode="aspectFit" custom-class="w-120 h-120"/>
         <view class="flex-1 flex flex-col gap-10">
             <view v-if="showName||showStar" class="flex justify-between items-center gap-20">
                 <uv-text v-if="showName" type="main" bold :text="item.name"/>

+ 5 - 1
src/pagesOther/pages/voluntary/index/index.vue

@@ -8,7 +8,7 @@
         <view class="mx-30 -mt-180 z-1 bg-white rounded-xl p-35">
             <view class="flex justify-between items-center">
                 <view class="text-lg text-fore-title">报考院校专业</view>
-                <view class="text-base text-fore-placeholder flex items-center">
+                <view class="text-base text-fore-placeholder flex items-center" @click="handleSelect">
                     <text>更多</text>
                     <uv-icon name="arrow-right" color="info"/>
                 </view>
@@ -40,6 +40,10 @@ const data = ref<SelectedCollegeMajorWithRules>({} as SelectedCollegeMajorWithRu
 const model = ref<VoluntaryModel>({})
 const {transferTo} = useTransferPage<any, VoluntaryDto>()
 
+const handleSelect = async () => {
+    transferTo()
+}
+
 const handleSubmit = async () => {
     await form.value?.validate()
     // make request

+ 40 - 0
src/pagesOther/pages/voluntary/list/components/voluntary-item.vue

@@ -0,0 +1,40 @@
+<template>
+    <view class="bg-white rounded-xl p-28 flex justify-between items-center gap-20 relative">
+        <view v-if="showIndex" class="absolute left-0 top-10 px-20 py-12 rounded-r-full text-24 font-bold"
+              :class="showIndex.clazz">
+            {{ showIndex.text }}
+        </view>
+        <view class="flex-1" :class="{'mt-50': showIndex}">
+            <view class="flex justify-between items-center gap-20">
+                <ie-image :src="data.universityLogo" mode="aspectFit" custom-class="w-48 h-48"/>
+                <view class="text-30 font-bold text-fore-title flex-1">{{ data.universityName }}</view>
+                <uv-icon name="more-dot-fill" size="20" @click="$emit('more')"/>
+            </view>
+            <voluntary-majors-draggable v-model:majors="data.majors" />
+        </view>
+    </view>
+</template>
+
+<script lang="ts" setup>
+import {VoluntaryRecord} from "@/types/voluntary";
+import VoluntaryMajorsDraggable from "@/pagesOther/pages/voluntary/list/components/voluntary-majors-draggable.vue";
+
+const props = defineProps<{
+    index: number;
+    data: VoluntaryRecord;
+}>()
+const emits = defineEmits(['more'])
+
+const indexOptions = [{
+    text: '第一志愿',
+    clazz: 'bg-warning-light text-warning-dark'
+}, {
+    text: '第二志愿',
+    clazz: 'bg-primary-100 text-primary'
+}]
+const showIndex = computed(() => indexOptions[props.index])
+</script>
+
+<style scoped>
+
+</style>

+ 209 - 0
src/pagesOther/pages/voluntary/list/components/voluntary-majors-draggable.vue

@@ -0,0 +1,209 @@
+<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;
+}>();
+
+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);
+        },
+    });
+});
+
+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;
+    emits("update:majors", arr);
+}
+</script>
+
+<style scoped>
+.drag-handle {
+    touch-action: none;
+    -webkit-user-select: none;
+    user-select: none;
+}
+</style>

+ 151 - 7
src/pagesOther/pages/voluntary/list/list.vue

@@ -1,23 +1,167 @@
 <template>
     <ie-page>
-        <z-paging ref="paging" v-model="list" safe-area-inset-bottom @query="handleQuery">
+        <z-paging ref="paging" v-model="list" bg-color="#F6F8FA" safe-area-inset-bottom :scrollable="!isSorting"
+                  :refresher-enabled="!isSorting" @query="handleQuery">
             <template #top>
                 <ie-navbar title="志愿表"/>
-                <view class="bg-warning-light p-28 text-20 text-fore-tip">
-                    <text class="font-bold text-fore-title">说明</text>
-                    排序前两个即为第一、二志愿,可以通过修改排序重新选择第一、二志愿
-                </view>
             </template>
+            <view class="mt-20 bg-warning-light p-28 text-23 leading-38 text-fore-title">
+                <text class="font-bold">说明:</text>
+                目前志愿计划为2025年,排序前两个为第一、二志愿,可通过修改排序重新选择第一、二志愿
+            </view>
+            <view class="p-28 flex flex-col gap-28">
+                <voluntary-item v-for="(item,i) in list" :key="i" :data="item" :index="i"/>
+            </view>
         </z-paging>
     </ie-page>
 </template>
 
 <script setup lang="ts">
-const list = ref([])
+import {VoluntaryRecord} from "@/types/voluntary";
+import VoluntaryItem from "@/pagesOther/pages/voluntary/list/components/voluntary-item.vue";
+import {sleep} from "@/uni_modules/uv-ui-tools/libs/function";
+import {ApiResponseList} from "@/types";
+import {VOLUNTARY_SORTING} from "@/types/injectionSymbols";
 
-const handleQuery = async () => {
+const list = ref<VoluntaryRecord>([])
+const paging = ref<ZPagingInstance>()
+const isSorting = ref<boolean>(false)
 
+const handleQuery = async () => {
+    await sleep()
+    const res: ApiResponseList<VoluntaryRecord> = {
+        code: 200,
+        msg: '',
+        total: 2,
+        rows: [{
+            id: 1,
+            universityId: "20949",
+            universityLogo: "https://mingxuejingbang.oss-cn-beijing.aliyuncs.com/ie/universityLog/23b6da550a584ea6b60886c6ae97b610.jpg",
+            universityName: "湖南铁道职业技术学院",
+            majors: [{
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68526",
+                majorName: "铁道交通运营管理1",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68527",
+                majorName: "铁道交通运营管理2",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68528",
+                majorName: "铁道交通运营管理3",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68529",
+                majorName: "铁道交通运营管理4",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68530",
+                majorName: "铁道交通运营管理5",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68531",
+                majorName: "铁道交通运营管理6",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68532",
+                majorName: "铁道交通运营管理7",
+                majorGroup: '专业组一',
+            }]
+        }, {
+            id: 2,
+            universityId: "20949",
+            universityLogo: "https://mingxuejingbang.oss-cn-beijing.aliyuncs.com/ie/universityLog/23b6da550a584ea6b60886c6ae97b610.jpg",
+            universityName: "湖南铁道职业技术学院",
+            majors: [{
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68526",
+                majorName: "铁道交通运营管理1",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68527",
+                majorName: "铁道交通运营管理2",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68528",
+                majorName: "铁道交通运营管理3",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68529",
+                majorName: "铁道交通运营管理4",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68530",
+                majorName: "铁道交通运营管理5",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68531",
+                majorName: "铁道交通运营管理6",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68532",
+                majorName: "铁道交通运营管理7",
+                majorGroup: '专业组一',
+            }]
+        }, {
+            id: 3,
+            universityId: "20949",
+            universityLogo: "https://mingxuejingbang.oss-cn-beijing.aliyuncs.com/ie/universityLog/23b6da550a584ea6b60886c6ae97b610.jpg",
+            universityName: "湖南铁道职业技术学院",
+            majors: [{
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68526",
+                majorName: "铁道交通运营管理1",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68527",
+                majorName: "铁道交通运营管理2",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68528",
+                majorName: "铁道交通运营管理3",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68529",
+                majorName: "铁道交通运营管理4",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68530",
+                majorName: "铁道交通运营管理5",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68531",
+                majorName: "铁道交通运营管理6",
+                majorGroup: '专业组一',
+            }, {
+                majorAncestors: "交通运输大类>铁道运输类",
+                majorId: "68532",
+                majorName: "铁道交通运营管理7",
+                majorGroup: '专业组一',
+            }]
+        }]
+    }
+    paging.value?.completeByTotal(res.rows, res.total)
 }
+
+provide(VOLUNTARY_SORTING, isSorting)
 </script>
 
 <style lang="scss">

+ 2 - 1
src/types/injectionSymbols.ts

@@ -49,4 +49,5 @@ export const VOLUNTARY_FORM = Symbol('VOLUNTARY_FORM') as InjectionKey<Ref<Volun
 export const VOLUNTARY_MODEL = Symbol('VOLUNTARY_MODEL') as InjectionKey<Ref<Voluntary.VoluntaryModel>>
 export const VOLUNTARY_RESULT = Symbol('VOLUNTARY_RESULT') as InjectionKey<Ref<Voluntary.VoluntaryResult>>
 
-export const UNIVERSITY_FILTER = Symbol('UNIVERSITY_FILTER') as InjectionKey<Ref<University.UniversityQueryDto>>
+export const UNIVERSITY_FILTER = Symbol('UNIVERSITY_FILTER') as InjectionKey<Ref<University.UniversityQueryDto>>
+export const VOLUNTARY_SORTING = Symbol('VOLUNTARY_SORTING') as InjectionKey<Ref<boolean>>

+ 3 - 0
src/types/transfer.ts

@@ -44,4 +44,7 @@ export interface SimulationAnalysisPageOptions {
 
 export interface UniversityPickerPageOptions {
   title?: string;
+  transferBack?: boolean;
+  selectedCollegeCode?: string;
+  selectedMajorCode?: string;
 }

+ 14 - 0
src/types/voluntary.ts

@@ -89,4 +89,18 @@ export interface VoluntaryScoreItem {
     total: number;
     labelWidth?: string;
     scoreWidth?: string;
+}
+
+export interface VoluntaryMajorItem {
+    majorId: string;
+    majorName: string;
+    majorAncestors: string;
+    majorGroup: string;
+}
+export interface VoluntaryRecord {
+    id: number;
+    universityLogo: string;
+    universityId: string;
+    universityName: string;
+    majors: VoluntaryMajorItem[]
 }