浏览代码

添加考试分析和定向刷题页面

shmily1213 1 月之前
父节点
当前提交
4a0bc4269d
共有 27 个文件被更改,包括 1491 次插入30 次删除
  1. 1 1
      package.json
  2. 13 1
      src/pages.json
  3. 2 2
      src/pagesMain/pages/volunteer/volunteer.vue
  4. 7 1
      src/pagesOther/pages/college-library/picker/picker.vue
  5. 5 0
      src/pagesStudy/components/ie-echart/ie-echart.vue
  6. 80 0
      src/pagesStudy/pages/homework/homework.vue
  7. 10 3
      src/pagesStudy/pages/index/compoentns/index-menu.vue
  8. 12 3
      src/pagesStudy/pages/index/index.vue
  9. 74 0
      src/pagesStudy/pages/simulation-analysis/components/exam-stat.vue
  10. 173 0
      src/pagesStudy/pages/simulation-analysis/components/rate-chart.vue
  11. 23 0
      src/pagesStudy/pages/simulation-analysis/components/score-stat.vue
  12. 39 17
      src/pagesStudy/pages/simulation-analysis/simulation-analysis.vue
  13. 1 1
      src/pagesStudy/pages/simulation-start/simulation-start.vue
  14. 0 0
      src/pagesStudy/pages/targeted-practice/targeted-practice.vue
  15. 72 0
      src/pagesStudy/pages/targeted-setting/targeted-setting.vue
  16. 二进制
      src/pagesStudy/static/image/icon-check-white.png
  17. 二进制
      src/pagesStudy/static/image/icon-edit-pen.png
  18. 二进制
      src/pagesStudy/static/image/icon-empty.png
  19. 33 0
      src/uni_modules/lime-drag/changelog.md
  20. 87 0
      src/uni_modules/lime-drag/components/l-drag/index.scss
  21. 524 0
      src/uni_modules/lime-drag/components/l-drag/l-drag.vue
  22. 47 0
      src/uni_modules/lime-drag/components/l-drag/props.ts
  23. 21 0
      src/uni_modules/lime-drag/components/l-drag/type.ts
  24. 9 0
      src/uni_modules/lime-drag/components/l-drag/vue.ts
  25. 87 0
      src/uni_modules/lime-drag/package.json
  26. 170 0
      src/uni_modules/lime-drag/readme.md
  27. 1 1
      tailwind.config.js

+ 1 - 1
package.json

@@ -17,7 +17,7 @@
     "@dcloudio/uni-h5": "3.0.0-4070620250821001",
     "@dcloudio/uni-mp-weixin": "3.0.0-4070620250821001",
     "@vueuse/core": "^11.2.0",
-    "echarts": "^5.5.1",
+    "echarts": "^6.0.0",
     "flyio": "^0.6.14",
     "lodash": "^4.17.21",
     "pinia": "2",

+ 13 - 1
src/pages.json

@@ -530,7 +530,7 @@
           }
         },
         {
-          "path": "pages/practice-targeted/practice-targeted",
+          "path": "pages/targeted-practice/targeted-practice",
           "style": {
             "navigationBarTitleText": ""
           }
@@ -564,6 +564,18 @@
           "style": {
             "navigationBarTitleText": ""
           }
+        },
+        {
+          "path": "pages/targeted-setting/targeted-setting",
+          "style": {
+            "navigationBarTitleText": ""
+          }
+        },
+        {
+          "path": "pages/homework/homework",
+          "style": {
+            "navigationBarTitleText": ""
+          }
         }
       ]
     }

+ 2 - 2
src/pagesMain/pages/volunteer/volunteer.vue

@@ -7,7 +7,7 @@
     </view>
     <view class="relative">
       <ie-image :is-oss="true" src="/volunteer/volunteer-bg.png" custom-class="w-full h-220" mode="scaleToFill" />
-      <view class="text-28 text-white absolute top-42 left-60">我的信息</view>
+      <view class="text-28 text-white absolute top-40 left-60">我的信息</view>
       <view
         class="px-46 h-1/2 absolute left-0 right-0 top-1/2 -translate-y-1/2 flex items-center justify-between box-border pt-50 text-24 text-primary">
         <text>姓名:{{ userStore.nickName }}</text>
@@ -18,7 +18,7 @@
     <view class="mx-26">
       <view class="mb-20 shadow-card px-30 py-20 bg-white rounded-8 flex items-center justify-between gap-x-30"
         v-for="item in menu" :key="item.title" @click="handleClick(item)">
-        <ie-image :is-oss="true" :src="item.icon" custom-class="w-96 h-96 rounded-full" />
+        <ie-image :is-oss="true" :src="item.icon" custom-class="w-96 h-96 rounded-full" mode="aspectFill" />
         <view class="flex-1">
           <view class="text-28 text-fore-title">{{ item.title }}</view>
           <view class="mt-8 text-22 text-fore-light">{{ item.desc }}</view>

+ 7 - 1
src/pagesOther/pages/college-library/picker/picker.vue

@@ -17,6 +17,7 @@ import {ref, computed, onMounted} from 'vue';
 import CollegeList from "@/pagesOther/pages/college-library/components/college-list.vue";
 import {useCacheStore} from "@/hooks/useCacheStore";
 import {useTransfer} from "@/hooks/useTransfer";
+import useTransferPage from '@/hooks/useTransferPage';
 import {cacheActions} from "@/hooks/defineCacheActions";
 import {findTreeNode} from "@/utils/tree-helper";
 import {toast} from "@/uni_modules/uv-ui-tools/libs/function";
@@ -24,6 +25,7 @@ import {array} from "@/uni_modules/uv-ui-tools/libs/function/test";
 
 const {dispatchCache} = useCacheStore()
 const {prevData, callbackEventData, transferBack} = useTransfer()
+const { transferBack: transferBackPage } = useTransferPage();
 const majorTree = ref([])
 const categoryNode = computed(() => findTreeNode(majorTree.value, m => m.code == prevData.value.majorCategory))
 const extraFilter = computed(() => {
@@ -38,7 +40,11 @@ const handleItemClick = (college) => {
             return toast('您已经选择过该院校了')
     }
     callbackEventData.value = college
-    transferBack()
+    if (prevData.value.transferType === 'v2') {
+        transferBackPage(college)
+    } else {
+        transferBack()
+    }
 }
 
 onMounted(async () => majorTree.value = await dispatchCache(cacheActions.getMajorTree))

+ 5 - 0
src/pagesStudy/components/ie-echart/ie-echart.vue

@@ -6,7 +6,12 @@
 
 <script setup>
 import echart from './echart.vue';
+// #ifdef MP-WEIXIN
 const echarts = require('../../static/echarts.min.js')
+// #endif
+// #ifdef H5
+import * as echarts from 'echarts'
+// #endif
 const props = defineProps({
   option: {
     type: Object,

+ 80 - 0
src/pagesStudy/pages/homework/homework.vue

@@ -0,0 +1,80 @@
+<template>
+  <ie-page bg-color="#F6F8FA" :fix-height="true">
+    <ie-navbar title="组卷作业" />
+    <view class="bg-white">
+      <uv-tabs :list="list" :activeStyle="activeStyle" :inactiveStyle="inactiveStyle" :scrollable="false" @change="handleTabChange"></uv-tabs>
+    </view>
+    <view class="flex-1 min-h-1 relative">
+      <view class="absolute inset-0">
+        <z-paging ref="paging" :fixed="false" v-model="dataList" :safe-area-inset-bottom="true"
+          :use-safe-area-placeholder="true" :auto="false" empty-view-text="暂无发布组卷作业~"
+          empty-view-img="/src/pagesStudy/static/image/icon-empty.png" :empty-view-style="emptyViewStyle"
+          :empty-view-img-style="emptyViewImgStyle" :empty-view-title-style="emptyViewTextStyle" @query="handleQuery">
+          <view v-if="dataList.length > 0" class="pt-10">
+            <view class="card-item" v-for="item in dataList" :key="item.id">
+              <activity-item :data="item" />
+            </view>
+          </view>
+        </z-paging>
+      </view>
+    </view>
+  </ie-page>
+</template>
+
+<script lang="ts" setup>
+const activeStyle = {
+  color: '#1A1A1A'
+}
+const inactiveStyle = {
+  color: '#999999'
+}
+const emptyViewStyle = {
+  marginTop: '-200rpx'
+}
+const emptyViewImgStyle = {
+  width: '364rpx',
+  height: '252rpx'
+}
+const emptyViewTextStyle = {
+  color: '#B3B3B3',
+  fontSize: '30rpx',
+  marginTop: '40rpx'
+}
+const list = [
+  {
+    name: '全部(0)',
+    value: 'all'
+  },
+  {
+    name: '未完成(0)',
+    value: 'special'
+  },
+  {
+    name: '已完成(0)',
+    value: 'special'
+  }
+]
+const dataList = ref<any[]>([]);
+const paging = ref<any>(null);
+const handleTabChange = (item: any) => {
+  console.log(item);
+  paging.value.reload();
+}
+const handleQuery = (page: number, pageSize: number) => {
+  console.log(page, pageSize);
+  uni.$ie.showLoading();
+  setTimeout(() => {
+    uni.$ie.hideLoading();
+    paging.value.complete([]);
+  }, 1000);
+}
+const click = (item: any) => {
+  console.log(item);
+}
+
+onMounted(() => {
+  paging.value.reload();
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 10 - 3
src/pagesStudy/pages/index/compoentns/index-menu.vue

@@ -1,12 +1,15 @@
 <template>
   <view class="menu-container">
-    <view class="menu-item" v-for="menu in menus" :key="menu.label">
+    <view class="menu-item" v-for="menu in menus" :key="menu.label" @click="navigateTo(menu.pageUrl)">
       <ie-image :is-oss="true" :src="menu.icon" custom-class="w-98 h-110" />
       <view class="text-26 text-fore-title">{{ menu.label }}</view>
     </view>
   </view>
 </template>
 <script lang="ts" setup>
+import useTransferPage from '@/hooks/useTransferPage';
+const { transferTo } = useTransferPage();
+
 const menus = [
   {
     label: '课程学习',
@@ -16,12 +19,12 @@ const menus = [
   {
     label: '组卷作业',
     icon: '/menu/menu-exam.png',
-    pageUrl: '/pagesStudy/exam/index'
+    pageUrl: '/pagesStudy/pages/homework/homework'
   },
   {
     label: '收藏夹',
     icon: '/menu/menu-favorite.png',
-    pageUrl: '/pagesStudy/favorite/index'
+    pageUrl: '/pagesOther/pages/personal-center/my-concerned/my-concerned'
   },
   {
     label: '错题本',
@@ -34,11 +37,15 @@ const menus = [
     pageUrl: '/pagesStudy/record/index'
   }
 ]
+const navigateTo = (pageUrl: string) => {
+  transferTo(pageUrl);
+}
 </script>
 <style lang="scss" scoped>
 .menu-container {
   @apply mx-48 mt-32 flex items-center gap-x-20;
 }
+
 .menu-item {
   @apply flex-1 flex flex-col items-center justify-center;
 }

+ 12 - 3
src/pagesStudy/pages/index/index.vue

@@ -19,7 +19,7 @@
             <view class="mt-8 text-24 text-fore-tip">根据知识点系统练习</view>
             <view
               class="mt-32 w-200 h-56 flex items-center justify-center rounded-full text-26 text-white bg-gradient-to-r from-[#91E0FE] to-[#16AFF5]"
-              @click="navigateTo('/pagesStudy/practice-targeted/practice-targeted')">
+              @click="navigateTo('/pagesStudy/targeted-practice/targeted-practice')">
               开始练习
             </view>
           </view>
@@ -27,13 +27,18 @@
             mode="heightFix" />
         </view>
         <view class="bg-[#FFFBEB] border-2 border-[#FEF6DA] flex-1 rounded-15 relative overflow-hidden">
-          <view class="h-70 z-1 relative"></view>
+          <view class="mx-30 h-70 z-1 relative flex items-center gap-x-6" @click="handleSetting">
+            <view class="w-fit ellipsis-1 text-[#F59E0B]">
+              <text class="text-24 ">长沙民政职业技术学院长沙民政职业技术学院</text>
+            </view>
+            <ie-image src="/src/pagesStudy/static/image/icon-edit-pen.png" custom-class="w-28 h-28" mode="aspectFill" />
+          </view>
           <view class="ml-30 mb-32 z-1 relative">
             <view class="text-28 text-fore-title font-bold">专项刷题</view>
             <view class="mt-8 text-24 text-fore-tip">结合考纲考点精准练习</view>
             <view
               class="mt-32 w-200 h-56 flex items-center justify-center rounded-full text-26 text-white bg-gradient-to-r from-[#FED448] to-[#F9942F]"
-              @click="navigateTo('/pagesStudy/pages/practice-targeted/practice-targeted')">
+              @click="navigateTo('/pagesStudy/pages/targeted-practice/targeted-practice')">
               开始练习
             </view>
           </view>
@@ -61,6 +66,10 @@ const { transferTo } = useTransferPage();
 const navigateTo = (pageUrl: string) => {
   transferTo(pageUrl);
 }
+
+const handleSetting = () => {
+  transferTo('/pagesStudy/pages/targeted-setting/targeted-setting');
+}
 </script>
 
 <style></style>

+ 74 - 0
src/pagesStudy/pages/simulation-analysis/components/exam-stat.vue

@@ -0,0 +1,74 @@
+<template>
+  <view class="px-16 py-20 bg-white rounded-15 mt-20">
+    <view class="text-30 text-fore-title font-bold">答题情况</view>
+    <view class="mt-20 flex items-center">
+      <view class="flex-1 text-center">
+        <view class="text-40 text-fore-title font-bold">100</view>
+        <view class="mt-5 text-28 text-fore-light">总题量</view>
+      </view>
+      <view class="flex-1 text-center">
+        <view class="text-40 text-fore-title font-bold">68</view>
+        <view class="mt-5 text-28 text-fore-light">答对</view>
+      </view>
+      <view class="flex-1 text-center">
+        <view class="text-40 text-fore-title font-bold">68</view>
+        <view class="mt-5 text-28 text-fore-light">答对</view>
+      </view>
+      <view class="flex-1 text-center">
+        <view class="text-40 text-fore-title font-bold">68</view>
+        <view class="mt-5 text-28 text-fore-light">答对</view>
+      </view>
+    </view>
+    <view class="h-1 bg-[#E6E6E6] my-20"></view>
+    <view class="mt-40 flex items-center justify-end gap-x-10">
+      <view class="w-18 h-18 rounded-full unanswered"></view>
+      <view class="text-20 text-fore-subtitle">未答</view>
+      <view class="ml-10 w-18 h-18 rounded-full bg-[#2CC6A0]"></view>
+      <view class="text-20 text-fore-subtitle">答对</view>
+      <view class="ml-10 w-18 h-18 rounded-full bg-[#FF5B5C]"></view>
+      <view class="text-20 text-fore-subtitle">答错</view>
+    </view>
+    <view class="mt-20 grid grid-cols-5 gap-x-10 gap-y-30 place-items-center">
+      <view class="question-item" v-for="(item, index) in list" :key="item.id"
+        :class="item.type === 0 ? 'unanswered' : item.type === 1 ? 'correct' : 'incorrect'">
+        <view class="text-36 font-bold">{{ index + 1 }}</view>
+      </view>
+    </view>
+  </view>
+</template>
+<script lang="ts" setup>
+type QuestionItem = {
+  id: number;
+  type: number;
+  isCorrect: boolean;
+}
+const list = ref<QuestionItem[]>([]);
+// 生成100条测试数据
+for (let i = 0; i < 100; i++) {
+  list.value.push({
+    id: i,
+    type: i % 3,
+    isCorrect: i % 2 === 0
+  });
+}
+</script>
+<style lang="scss" scoped>
+.question-item {
+  @apply w-80 h-80 rounded-full overflow-hidden flex items-center justify-center text-20 text-fore-subtitle;
+}
+
+.unanswered {
+  background: #EEF4FA;
+  color: #B3B3B3;
+}
+
+.correct {
+  background: #E7FCF8;
+  color: #2CC6A0;
+}
+
+.incorrect {
+  background: #FEEDE9;
+  color: #FF5B5C;
+}
+</style>

+ 173 - 0
src/pagesStudy/pages/simulation-analysis/components/rate-chart.vue

@@ -0,0 +1,173 @@
+<template>
+  <view class="w-full h-[164px]">
+    <ie-echart :option="options" />
+  </view>
+</template>
+<script lang="ts" setup>
+import ieEchart from '@/pagesStudy/components/ie-echart/ie-echart.vue';
+const props = defineProps({
+  value: {
+    type: Number,
+    default: 0
+  }
+});
+const options = computed(() => {
+  return {
+    series: [
+      {
+        type: 'gauge',
+        startAngle: 180,
+        endAngle: 0,
+        radius: '130%',
+        center: ['50%', '82%'],
+        z: 0,
+        progress: {
+          show: false
+        },
+        axisLine: {
+          show: true,
+          roundCap: true,
+          lineStyle: {
+            width: 12,
+            color: [[1, '#EBF9FF']]
+          }
+        },
+        splitLine: {
+          show: false
+        },
+        axisTick: {
+          show: false
+        },
+        axisLabel: {
+          show: false
+        },
+        pointer: {
+          show: false
+        },
+        data: []
+      },
+      {
+        name: 'Pressure',
+        type: 'gauge',
+        startAngle: 180,
+        endAngle: 0,
+        radius: '135%',
+        center: ['50%', '82%'],
+        z: 1,
+        progress: {
+          show: true,
+          overlap: false,
+          roundCap: true,
+          clip: false,
+          itemStyle: {
+            color: {
+              type: 'linear',
+              x: 0,
+              y: 0,
+              x2: 0,
+              y2: 1,
+              colorStops: [
+                { offset: 0, color: '#70C8FD' },
+                { offset: 1, color: '#31A0FC' }
+              ]
+            }
+          }
+        },
+        axisLine: {
+          show: false,
+          lineStyle: {
+            width: 22
+          }
+        },
+        splitLine: {
+          show: false
+        },
+        axisTick: {
+          show: false
+        },
+        axisLabel: {
+          show: false
+        },
+        pointer: {
+          show: false
+        },
+        title: {
+          show: false
+        },
+        detail: {
+          show: false
+        },
+        data: [
+          {
+            value: props.value,
+            name: '正确率'
+          }
+        ]
+      },
+      {
+        type: 'gauge',
+        startAngle: 180,
+        endAngle: 0,
+        radius: '130%',
+        center: ['50%', '82%'],
+        z: 2,
+        progress: {
+          show: false
+        },
+        axisLine: {
+          show: false,
+          lineStyle: {
+            color: [[1, '#FFFFFF']],
+            width: 1,
+            shadowColor: 'rgba(0, 0, 0, 0.08)',
+            shadowBlur: 4,
+            shadowOffsetY: -20,
+            shadowOffsetX: 0
+          }
+        },
+        splitLine: {
+          show: false
+        },
+        axisTick: {
+          show: false
+        },
+        axisLabel: {
+          show: false
+        },
+        pointer: {
+          show: true,
+          icon: 'circle',
+          length: '190%',
+          width: 20,
+          itemStyle: {
+            color: '#31A0FC',
+            borderWidth: 6,
+            borderColor: '#FFFFFF'
+          }
+        },
+        title: {
+          color: '#B3B3B3',
+          fontSize: 14,
+          offsetCenter: [0, -46]
+        },
+        detail: {
+          formatter: '{value}%',
+          fontSize: 32,
+          fontWeight: 'bold',
+          offsetCenter: [0, -10],
+          textBorderColor: '#333333',
+          textBorderWidth: 4,
+        },
+        data: [
+          {
+            value: props.value,
+            name: '正确率'
+          }
+        ]
+      }
+    ]
+  };
+
+});
+</script>
+<style lang="scss" scoped></style>

+ 23 - 0
src/pagesStudy/pages/simulation-analysis/components/score-stat.vue

@@ -0,0 +1,23 @@
+<template>
+  <view class="px-16 py-20 bg-white rounded-15 mt-20">
+    <view class="text-30 text-fore-title font-bold">得分分布</view>
+    <view class="mt-20 mx-10 flex items-center bg-back rounded-10 py-30">
+      <view class="flex-1 text-center">
+        <view class="text-40 text-fore-title font-bold">276</view>
+        <view class="mt-5 text-28 text-fore-light">最高分</view>
+      </view>
+      <view class="flex-1 text-center">
+        <view class="text-40 text-fore-title font-bold">147</view>
+        <view class="mt-5 text-28 text-fore-light">平均分</view>
+      </view>
+      <view class="flex-1 text-center">
+        <view class="text-40 text-fore-title font-bold">36%</view>
+        <view class="mt-5 text-28 text-fore-light">击败考生</view>
+      </view>
+    </view>
+  </view>
+</template>
+<script lang="ts" setup>
+
+</script>
+<style lang="scss" scoped></style>

+ 39 - 17
src/pagesStudy/pages/simulation-analysis/simulation-analysis.vue

@@ -1,22 +1,44 @@
 <template>
-  <view>
-    
-  </view>
+  <ie-page bg-color="#F6F8FA">
+    <ie-navbar title="考试分析" transparent bg-color="#FFFFFF" title-color="black" :keep-title-color="true" />
+    <view class="relative">
+      <ie-image :is-oss="true" src="/study-bg11.png" custom-class="w-full h-[545rpx] absolute top-0 left-0 z-0" />
+      <ie-image :is-oss="true" src="/study-bg12.png" custom-class="w-308 h-302 absolute top-57 right-14 z-1" />
+      <ie-image :is-oss="true" src="/study-title4.png" custom-class="w-282 h-64 absolute top-126 left-72 z-2" />
+      <view class="relative z-3 pt-244 pb-20 mx-30">
+        <view class="bg-white rounded-15 px-20 pb-1">
+          <rate-chart :value="50" />
+          <view class="h-1 bg-[#E6E6E6] my-20"></view>
+          <view>
+            <view class="my-20 flex items-center justify-between text-24">
+              <ie-image src="/src/pagesStudy/static/image/icon-house.png" custom-class="w-24 h-24" mode="aspectFill" />
+              <text class="ml-10 text-fore-light flex-1">考试院校</text>
+              <text class="text-fore-title">长沙民政职业技术学院-大数据与会计</text>
+            </view>
+            <view class="my-20 flex items-center justify-between text-24">
+              <ie-image src="/src/pagesStudy/static/image/icon-group.png" custom-class="w-24 h-24" mode="aspectFill" />
+              <text class="ml-10 text-fore-light flex-1">测试类型</text>
+              <text class="text-fore-title">职业技能模拟考试</text>
+            </view>
+            <view class="my-20 flex items-center justify-between text-24">
+              <ie-image src="/src/pagesStudy/static/image/icon-clock.png" custom-class="w-24 h-24" mode="aspectFill" />
+              <text class="ml-10 text-fore-light flex-1">测试时间</text>
+              <text class="text-fore-title">2025.09.25 10:00</text>
+            </view>
+          </view>
+        </view>
+        <exam-stat />
+        <score-stat />
+      </view>
+    </view>
+  </ie-page>
 </template>
 
-<script>
-  export default {
-    data() {
-      return {
-        
-      }
-    },
-    methods: {
-      
-    }
-  }
+<script lang="ts" setup>
+import RateChart from './components/rate-chart.vue';
+import ExamStat from './components/exam-stat.vue';
+import ScoreStat from './components/score-stat.vue';
+onPageScroll(() => { })
 </script>
 
-<style>
-
-</style>
+<style lang="scss" scoped></style>

+ 1 - 1
src/pagesStudy/pages/simulation-start/simulation-start.vue

@@ -56,7 +56,7 @@ import useTransferPage from '@/hooks/useTransferPage';
 const { transferTo } = useTransferPage();
 
 const handleStartTest = () => {
-  console.log('开始考试')
+  transferTo('/pagesStudy/pages/simulation-analysis/simulation-analysis')
 }
 </script>
 

+ 0 - 0
src/pagesStudy/pages/practice-targeted/practice-targeted.vue → src/pagesStudy/pages/targeted-practice/targeted-practice.vue


+ 72 - 0
src/pagesStudy/pages/targeted-setting/targeted-setting.vue

@@ -0,0 +1,72 @@
+<template>
+  <ie-page bg-color="#F6F8FA" :fix-height="true">
+    <ie-navbar title="定向刷题" />
+    <view class="p-32 bg-white flex items-center justify-between">
+      <view class="">
+        <text class="text-32 text-fore-title font-bold">添加定向院校</text>
+        <text class="ml-10 text-32 text-fore-light">({{ collegeList.length }}/3)</text>
+      </view>
+      <view class="text-24 text-primary">添加后无法更改</view>
+    </view>
+    <view class="px-48 pt-52 pb-32 flex items-center justify-between">
+      <view class="text-32 text-fore-title font-bold">我的定向院校</view>
+      <view v-if="hasSetting" class="flex items-center gap-x-4">
+        <text class="text-28 text-[#F59E0B]">设置</text>
+        <ie-image src="/src/pagesStudy/static/image/icon-edit-pen.png" custom-class="w-24 h-24" mode="aspectFill" />
+      </view>
+    </view>
+    <view v-if="hasSetting" class="px-30">
+      <view class="bg-white rounded-10 py-28 pl-32 pr-20 relative flex items-center gap-x-20 mb-20"
+        v-for="(item, index) in collegeList" :key="item.code">
+        <view class="text-64 text-primary font-bold absolute top-6 left-14 italic leading-[1] opacity-30">
+          {{ index + 1 }}
+        </view>
+        <view class="w-40 h-40 bg-back"></view>
+        <view class="flex-1 min-w-1 flex items-center gap-x-20">
+          <ie-image :src="item.logo" custom-class="w-96 h-96" mode="aspectFill" />
+          <view class="flex-1 min-w-1">
+            <view class="text-28 text-fore-title font-bold">{{ item.name }}</view>
+            <view class="mt-8 w-fit text-20 text-primary border border-solid border-primary rounded-4 px-10 py-4">大数据与会计
+            </view>
+            <view class="flex items-center justify-between text-20">
+              <view class="mt-8  text-fore-light">财经商贸大类>财务会计类</view>
+              <view
+                class="flex items-center gap-x-4 text-white bg-gradient-to-r from-[#FED448] to-[#F9942F] rounded-full px-12 py-4">
+                <ie-image src="/src/pagesStudy/static/image/icon-check-white.png" custom-class="w-24 h-24" />
+                <text>定向学习中</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view v-else class="flex-1 flex flex-col items-center justify-center gap-y-50">
+      <ie-image :is-oss="true" src="/src/pagesStudy/static/image/icon-empty.png" custom-class="w-364 h-252 mx-auto" mode="aspectFill" />
+      <text class="text-30 text-fore-light text-center">目前暂无定向院校~</text>
+    </view>
+    <ie-safe-toolbar v-if="collegeList.length < 3" :height="84" :shadow="false">
+      <view class="px-46 pt-24">
+        <ie-button type="primary" @click="handleAdd">添加</ie-button>
+      </view>
+    </ie-safe-toolbar>
+  </ie-page>
+</template>
+
+<script lang="ts" setup>
+import useTransferPage from '@/hooks/useTransferPage';
+const { transferTo } = useTransferPage();
+const hasSetting = ref(false);
+const collegeList = ref<any[]>([]);
+const handleAdd = () => {
+  transferTo('/pagesOther/pages/college-library/picker/picker', {
+    data: {
+      transferType: 'v2'
+    }
+  }).then(res => {
+    collegeList.value.push(res);
+    hasSetting.value = true;
+  });
+}
+</script>
+
+<style lang="scss" scoped></style>

二进制
src/pagesStudy/static/image/icon-check-white.png


二进制
src/pagesStudy/static/image/icon-edit-pen.png


二进制
src/pagesStudy/static/image/icon-empty.png


+ 33 - 0
src/uni_modules/lime-drag/changelog.md

@@ -0,0 +1,33 @@
+## 0.1.3(2023-08-19)
+-  fix: 修复使用remove导致样式错乱
+## 0.1.2(2023-08-09)
+-  fix: 修复nvue没有获取节点的问题
+-  fix: 修复因延时导致卡在中途
+-  fix: 修复change事件有时失效的问题
+## 0.1.1(2023-07-03)
+-  chore: 更新文档
+## 0.1.0(2023-07-03)
+-  fix: 外面的事件冒泡导致点击调动内部移动方法错乱
+## 0.0.9(2023-05-30)
+-  fix: 修复因手机事件为`onLongpress`导致,在手机上无法长按
+-  fix: 无法因css导致滚动
+## 0.0.8(2023-04-23)
+-  feat: 更新文档
+## 0.0.7(2023-04-23)
+-  feat: 由于删除是一个危险的动作,故把方法暴露出来,而不在内部处理。如果之前有使用删除的,需要注意
+-  feat: 原来的`add`变更为`push`,增加`unshift`
+## 0.0.6(2023-04-12)
+-  fix: 修复`handle`不生效问题
+-  feat: 增加 `to`方法
+## 0.0.5(2023-04-11)
+-  chore: `grid` 插槽增加 `nindex`、`oindex`
+## 0.0.4(2023-04-04)
+- chore: 去掉 script-setup 语法糖
+- chore: 文档增加 vue2 使用方法
+## 0.0.3(2023-03-30)
+- feat: 重要说明 更新 list 只会再次初始化
+- feat: 更新文档
+## 0.0.2(2023-03-29)
+- 修改文档
+## 0.0.1(2023-03-29)
+- 初次提交

+ 87 - 0
src/uni_modules/lime-drag/components/l-drag/index.scss

@@ -0,0 +1,87 @@
+$drag-handle-size: var(--l-drag-handle-size, 50rpx);
+$drag-delete-size: var(--l-drag-delete-size, 32rpx);
+.l-drag {
+	min-height: 100rpx;
+	overflow: hidden;
+	/* #ifdef APP-NVUE */
+	flex: 1;
+	/*  #endif */
+	/* #ifndef APP-NVUE */
+	width: 100%;
+	/*  #endif */
+}
+.l-drag__inner {
+	/* #ifdef APP-NVUE */
+	flex: 1;
+	/*  #endif */
+	/* #ifndef APP-NVUE */
+	width: 100%;
+	/*  #endif */
+	min-height: 100rpx;
+}
+.l-drag__view {
+	// touch-action: none;
+	// user-select: none;
+	// -webkit-user-select: auto; 
+	z-index: 2;
+	transition: opacity 300ms ease; 
+	.mask {
+		position: absolute;
+		inset: 0;
+		background-color: transparent;
+		z-index: 9;
+	}
+	/* #ifndef APP-NVUE */
+	> view {
+		&:last-child {
+			width: 100%;
+			height: 100%;
+		}
+	}
+	box-sizing: border-box;
+	/*  #endif */
+	
+}
+.l-drag-enter {
+	opacity: 0;
+}
+.l-drag__ghost {
+	/* #ifndef APP-NVUE */
+	> view {
+		&:last-child {
+			width: 100%;
+			height: 100%;
+		}
+	}
+	box-sizing: border-box;
+	/*  #endif */
+}
+.l-is-active {
+	z-index: 3;
+}
+.l-is-hidden {
+	opacity: 0;
+}
+.l-drag__delete {
+	position: absolute;
+	z-index: 10;
+	width: $drag-delete-size; 
+	height: $drag-delete-size;
+}
+.l-drag__handle {
+	position: absolute;
+	z-index: 10;
+	width: $drag-handle-size;
+	height: $drag-handle-size;
+}
+/* #ifndef APP-NVUE */
+.l-drag__delete::before,.l-drag__handle::before {
+	content: '';
+	position: absolute;
+	width: 100%;
+	height: 100%;
+	left: 0;
+	top: 0;
+	z-index: 10;
+}
+/*  #endif */

+ 524 - 0
src/uni_modules/lime-drag/components/l-drag/l-drag.vue

@@ -0,0 +1,524 @@
+<template>
+	<view class="l-drag l-class" :style="[areaStyles]" ref="dragRef" @touchstart="setDisabled">
+		<movable-area class="l-drag__inner" v-if="isReset" :style="[innerStyles]">
+			<slot></slot>
+			<movable-view class="l-drag__ghost" v-if="isDrag && props.ghost" :animation="true" :style="[viewStyles]" direction="all" :x="ghostEl.x" :y="ghostEl.y" key="l-drag-clone">
+				<slot name="ghost"></slot>
+			</movable-view>
+			<movable-view v-if="props.before" class="l-drag__before" disabled :animation="false" :style="[viewStyles]" :x="beforeEl.x" :y="beforeEl.y">
+				<slot name="before"></slot>
+			</movable-view>
+			<movable-view
+				v-for="(item, oindex) in cloneList" :key="item.id"
+				direction="all" 
+				:data-oindex="oindex" 
+				:style="[viewStyles]"
+				class="l-drag__view"
+				:class="[{'l-is-active': oindex == active, 'l-is-hidden': !item.show}, item.class]"
+				:x="item.x"
+				:y="item.y"
+				:friction="friction"
+				:damping="damping"
+				:animation="animation"
+				:disabled="isDisabled || props.disabled"
+				@touchstart="touchStart" 
+				@change="touchMove" 
+				@touchend="touchEnd"
+				@touchcancel="touchEnd"
+				@longpress="setDisabled"
+				>
+				<!-- <view v-if="props.remove" class="l-drag__remove" :style="removeStyle" data-remove="true">
+					<slot name="remove" :oindex="oindex" data-remove="true" />
+				</view> -->
+				<!-- <view v-if="props.handle" class="l-drag__handle" :style="handleStyle" data-handle="true">
+					<slot name="handle" :oindex="oindex" :active="!isDisabled && !isDisabled && oindex == active" />
+				</view> -->
+				<slot name="grid" :oindex="oindex" :index="item.index" :oldindex="item.oldindex" :content="item.content" :active="!isDisabled && !isDisabled && oindex == active" />
+				<view class="mask" v-if="!(isDisabled || props.disabled) && props.longpress"></view>
+			</movable-view>
+			
+			
+			<movable-view v-if="props.after" class="l-drag__after" disabled :animation="true"  direction="all" :style="[viewStyles]" :x="afterEl.x" :y="afterEl.y">
+				<slot name="after"></slot>
+			</movable-view>
+		</movable-area>
+	</view>
+</template>
+<script lang="ts">
+	// @ts-nocheck
+	import { computed, onMounted, ref, getCurrentInstance, watch, nextTick, reactive , triggerRef, onUnmounted, defineComponent} from "./vue";
+	import DragProps from './props';
+	import type {GridRect, Grid, Position} from  './type'
+	// #ifdef APP-NVUE
+	const dom = weex.requireModule('dom')
+	// #endif
+	
+	export default defineComponent({
+		name: 'l-drag',
+		externalClasses: ['l-class'],
+		options: {
+			addGlobalClass: true,
+			virtualHost: true,
+		},
+		props: DragProps,
+		emits: ['change'],
+		setup(props, {emit, expose}) {
+			// #ifdef APP-NVUE
+			const dragRef = ref(null)
+			// #endif
+			const app = getCurrentInstance()
+			const isDrag = ref(false)
+			const isInit = ref(false)
+			const isReset = ref(true)
+			const colmunId = ref(-1)
+			/** 选中项原始下标 */
+			const active = ref(-1)
+			const maxIndex = ref(-1)
+			const animation = ref(true)
+			const isDisabled = ref(props.handle || props.longpress ? true: false)
+			
+			const dragEl  = reactive({
+				content: null,
+				/** 当前视图下标*/
+				index: 0,
+				/** 旧视图下标 */
+				oldindex: -1,
+				/** 上次原始下标 */
+				lastindex: -1
+			})
+			
+			const ghostEl = reactive({
+				content: null,
+				x: 0,
+				y: 0
+			})
+			const beforeEl = reactive({
+				x: 0,
+				y: 0
+			})
+			const afterEl = reactive({
+				x: 0,
+				y: 0
+			})
+			
+			let gridRects = [] //ref<GridRect[]>([])
+			const areaWidth = ref(0)
+			const cloneList = ref<Grid[]>([])
+			// 删除项时可能会减少行数影响到删除过渡动画,故增加此值在删除时保持高度不变,等动画完成后再归零
+			const leaveRow = ref(0)
+			const extra = computed(() => (props.before ? 1 :0) + (props.after ? 1 : 0))
+			const rows = computed(() => Math.ceil( ((isInit.value ? cloneList.value.length : props.list.length) + extra.value) / props.column ))
+			const gridHeight = computed(() => props.aspectRatio ? girdWidth.value / props.aspectRatio : (/rpx$/.test(`${props.gridHeight}`) ? uni.upx2px(parseInt(`${props.gridHeight}`)) : parseInt(`${props.gridHeight}`)))
+			const girdWidth = computed(() => areaWidth.value / props.column)
+			const viewStyles = computed(() => ({width: girdWidth.value + 'px',height: gridHeight.value + 'px'}))
+			const areaStyles = computed(() => ({height: (rows.value + leaveRow.value ) * gridHeight.value + 'px'}))
+			const innerStyles = computed(() => ({
+				// #ifdef APP-NVUE
+				width: areaWidth.value + 'px', 
+				// #endif
+				height: (rows.value + props.extraRow + leaveRow.value) * gridHeight.value + 'px'}))
+			
+			const sleep = (cb: Function, time = 1000/60) => setTimeout(cb, time)
+			const createGrid = (content: any, position?:Position|null): Grid => {
+				colmunId.value++
+				maxIndex.value++
+				const index = maxIndex.value
+				const colmun = gridRects[index]
+				
+				let x = 0
+				let y = 0
+				if(colmun) {
+					if(props.after) {
+						let nxet = gridRects[index + 1]
+						if(!nxet) {
+							nxet = createGridRect(gridRects.length + (props.before ? 1 : 0))
+							gridRects.push(nxet)
+						} 
+						setReset(() => setAfter(nxet))
+					} else {
+						setReset()
+					}
+					x = colmun.x
+					y = colmun.y
+				} else {
+					const nxet = createGridRect(gridRects.length + (props.before ? 1 : 0))
+					gridRects.push(nxet)
+					setReset()
+					x = nxet.x
+					y = nxet.y
+				}
+				if(position) {
+					x = position.x
+					y = position.y
+				}
+				return {id: `l-drag-item-${colmunId.value}`, index, oldindex: index, content, x, y, class: '', show: true}
+			}
+			const setReset = (cb?: any) => {
+				// const newRow = (cloneList.value.length + extra.value) % (props.column)
+				if(isInit.value) {
+					cb&&sleep(cb)
+				}
+			}
+			const setAfter = ({x, y} = {x: 0, y: 0}) => {
+				if(props.after) {
+					afterEl.x = x
+					afterEl.y = y
+				}
+			}
+			const setDisabled = (e: any, flag?: boolean= false) => {
+				 // e?.preventDefault() 
+				const type = `${e.type}`.toLowerCase()
+				const {handle = props.touchHandle} = e.target.dataset
+				if(props.handle && !handle) {
+					isDisabled.value = true
+				} else if(props.handle && handle && !props.longpress) {
+					isDisabled.value = flag
+				} else if(props.handle && handle && props.longpress && type.includes('longpress')) {
+					isDisabled.value = false
+				} else if(props.longpress && type.includes('longpress') && !props.handle) {
+					isDisabled.value = false
+				}
+				if(type.includes('touchend') && props.longpress) {
+					isDisabled.value = true
+				}
+			}
+			const createGridRect = (i: number, last?: GridRect): GridRect => {
+				let { row } = last || gridRects[gridRects.length - 1] || { row: 0 }
+				const col = i % (props.column)
+				const grid = (row: number, x: number, y: number):GridRect => {
+					return {row, x, y, x1: x +  girdWidth.value, y1: y + gridHeight.value}
+				}
+				if(col == 0 && i != 0) {row++} 
+				return grid(row, col * girdWidth.value, row * gridHeight.value)
+			}
+			const createGridRects = () => {
+				let rects: GridRect[] = []
+				const length = rows.value * props.column + extra.value
+				gridRects = []
+				for (var i = 0; i < length; i++) {
+					const item = createGridRect(i, rects[rects.length - 1])
+					rects.push(item)
+				}
+				if(props.before) {
+					const {x, y} = rects.shift()
+					beforeEl.x = x
+					beforeEl.y = y
+				}
+				setAfter(rects[props.list.length])
+				gridRects = rects as GridRect[]
+			}
+			const updateList = (v: any[]) => {
+				cloneList.value = v.map((content) => createGrid(content))
+			}
+			
+			const touchStart = (e: any) => {
+				if(e.target.dataset.remove) return
+				// 选中项原始下标
+				const {oindex} = e.currentTarget?.dataset || e.target?.dataset || {}
+				if(typeof oindex !== 'number') return
+				const target = cloneList.value[oindex]
+				isDrag.value = true
+				// 选中项原始下标
+				active.value = oindex 
+				// 选中项的当前下标
+				dragEl.index = dragEl.oldindex = target.index
+				ghostEl.x = target.x||0
+				ghostEl.y = target.y||0
+				dragEl.content = ghostEl.content = target.content
+			}
+			
+			const touchEnd = (e: any) => {
+				setTimeout(() => {
+					if(e.target.dataset.remove || active.value==-1) return
+					setDisabled(e, true)
+					isDrag.value = false
+					const isEmit = dragEl.index !== dragEl.oldindex && dragEl.oldindex > -1 // active.value !== dragEl.index
+					dragEl.lastindex = active.value
+					dragEl.oldindex = active.value = -1
+					const last = cloneList.value[dragEl.lastindex]
+					const position = gridRects[dragEl.index]
+					nextTick(() => {
+						last.x = position.x + 0.001
+						last.y = position.y + 0.001
+						sleep(() => {
+							last.x = position.x
+							last.y = position.y
+							isEmit && emitting()
+						})
+					})
+				},80)
+				
+			}
+			const emitting = () => {
+				const clone = [...cloneList.value].sort((a, b) => a.index - b.index)//.map(item => ref(item.content))
+				emit('change', clone)
+			}
+			
+			const touchMove = (e: any) => {
+				if(!isDrag.value) return
+				// #ifndef APP-NVUE
+				let {oindex} = e.currentTarget.dataset
+				// #endif
+				// #ifdef APP-NVUE
+				oindex = e.currentTarget.dataset['-oindex']
+				// #endif
+				if(oindex != active.value) return
+				const {x, y} = e.detail
+				const centerX = x + girdWidth.value / 2
+				const centerY = y + gridHeight.value / 2
+				for (let i = 0; i < cloneList.value.length; i++) {
+					const item = gridRects[i]
+					if(centerX > item.x && centerX < item.x1 && centerY > item.y && centerY < item.y1) {
+						ghostEl.x = item.x
+						ghostEl.y = item.y
+						if(dragEl.index != i) {
+							_move(active.value, i)
+						}
+						break;
+					}
+				}
+			}
+			const getDragEl = (oindex: number) => {
+				if(isDrag.value) {return dragEl}
+				return cloneList.value[oindex]
+			}
+			
+			/**
+			 * 把原始数据中排序为index的项 移动到 toIndex
+			 * @param oindex 原始数据的下标
+			 * @param toIndex 视图中的下标
+			 * @param position 指定坐标
+			 */
+			const _move = (oindex: number, toIndex: number, position?: Position|null, emit: boolean = true) => {
+				const length = cloneList.value.length - 1
+				if(toIndex > length || toIndex < 0) return
+				// 获取oIdnex在视图中的项目
+				const dragEl = getDragEl(oindex)
+				let speed = 0
+				let start = dragEl.index
+				// 比较开始index和终点index,设置方向
+				if(start < toIndex) {speed = 1} 
+				if(start > toIndex) {speed = -1}
+				if(!speed) return
+				// 距离
+				let distance = start - toIndex
+				// 找到区间所有的项
+				while(distance) {
+					distance += speed
+					// 目标
+					const target = isDrag.value ? (dragEl.index += speed)  : (start += speed) 
+					let targetOindex = cloneList.value.findIndex(item => item.index == target && item.content != dragEl.content)
+					if (targetOindex == oindex) return
+					if (targetOindex < 0) {targetOindex = cloneList.value.length - 1}
+					let targetEl = cloneList.value[targetOindex]
+					if(!targetEl) return;
+					// 上一个index
+					const lastIndex = target - speed
+					const activeEl = cloneList.value[oindex]
+					const rect = gridRects[lastIndex]
+					targetEl.x = rect.x
+					targetEl.y = rect.y
+					targetEl.oldindex = targetEl.index
+					targetEl.index = lastIndex
+					activeEl.oldindex = activeEl.index //oIndex
+					activeEl.index = toIndex
+					// 到达终点,如果是拖拽则不处理
+					if(!distance && !isDrag.value) {
+						const rect = gridRects[toIndex]
+						const {x, y} = position||rect
+						activeEl.x = dragEl.x = x
+						activeEl.y = dragEl.y = y
+						// triggerRef(cloneList)
+						if(emit) {
+							emitting()
+						}
+					}
+				}
+			}
+			/**
+			 * 为区分是主动调用还是内部方法
+			 */
+			const move = (oindex: number, toIndex: number) => {
+				active.value = -1
+				isDrag.value = false
+				_move(oindex, toIndex)
+			}
+			// 临时处理 待有空再完善
+			const REMOVE_TIME = 400
+			let removeTimer = null
+			const remove = (oindex: number) => {
+				active.value = -1
+				isDrag.value = false
+				
+				clearTimeout(removeTimer)
+				const item = cloneList.value[oindex]
+				if(props.disabled || !item) return
+				item.show = false
+				const after = cloneList.value.length - 1
+				_move(oindex, after, item, false)
+				setAfter(gridRects[after])
+				maxIndex.value--
+				const _remove = (_index = oindex) => {
+					// 小程序 删除会闪一下 所以先关闭动画再开启
+					// animation.value = false
+					const row = Math.ceil((cloneList.value.length - 1 + extra.value) / props.column)
+					if( row < rows.value) {
+						leaveRow.value = (rows.value - row)
+					}
+					cloneList.value.splice(_index, 1)[0]
+					emitting()
+					removeTimer = setTimeout(() => {
+						leaveRow.value = 0
+					},  REMOVE_TIME)
+				} 
+				_remove()
+			}
+			const push = (...args: any) => {
+				if(props.disabled) return
+				if(Array.isArray(args)) {
+					Promise.all(args.map(async item => await add(item, true))).then(emitting)
+				}
+			}
+			const add = (content: any, after: boolean) => {
+				return new Promise((resolve) => {
+					const item = createGrid(content, after ? null : {x: -100, y:0})
+					item.class = 'l-drag-enter'
+					cloneList.value.push(item)
+					const length = cloneList.value.length - 1
+					nextTick(() => {
+						sleep(() => {
+							item.class = 'l-drag-leave'
+							_move(length, (after ? length : 0), null, false)
+							triggerRef(cloneList)
+							resolve(true)
+						})
+					})
+					
+				})
+			}
+			const unshift = (...args: any) => {
+				if(props.disabled) return
+				if(Array.isArray(args)) {
+					Promise.all(args.map(async (item) => await add(item))).then(emitting)
+				}
+			}
+			
+			// 暂时先简单处理,待有空再完善
+			const shift = () => {
+				if(!cloneList.value.length) return
+				remove(cloneList.value.findIndex(item => item.index == 0) || 0)
+			}
+			const pop = () => {
+				const length = cloneList.value.length-1
+				if(length < 0 ) return
+				remove(cloneList.value.findIndex(item => item.index == length) || length)
+			}
+			// const splice = (start, count, ...context) => {
+			// 	// 暂未实现
+			// }
+			const clear = () => {
+				isInit.value = isDrag.value = false
+				maxIndex.value = colmunId.value = active.value = -1
+				cloneList.value = []
+				gridRects = []
+			}
+			const init = () => {
+				clear()
+				createGridRects()
+				nextTick(() => {
+					updateList(props.list)
+					isInit.value = true
+				})
+			}
+			let count = 0
+			const getRect = () => {
+				count++
+				// #ifndef APP-NVUE
+				uni.createSelectorQuery().in(app.proxy).select('.l-drag').boundingClientRect((res: UniNamespace.NodeInfo) => {
+					if(res) {
+						areaWidth.value = res.width || 0
+						// 小程序居然无法响应式?
+						init()
+					}
+				}).exec()
+				// #endif
+				// #ifdef APP-NVUE
+				sleep(() => {
+					nextTick(() => {
+						dom.getComponentRect(dragRef.value, (res) => {
+							if(!res.size.width && count < 5) {
+								getRect()
+							} else {
+								areaWidth.value = res.size.width || 0
+								init()
+							}
+						})
+					})
+				})
+				// #endif
+			}
+			onMounted(getRect)
+			onUnmounted(clear)
+			watch(() => props.list, init)
+			
+			// #ifdef VUE3
+			expose({
+				remove,
+				// add,
+				move,
+				push,
+				unshift,
+				shift,
+				pop
+			})
+			// #endif
+			return {
+				// #ifdef APP-NVUE
+				dragRef,
+				// #endif
+				cloneList,
+				
+				areaStyles,
+				innerStyles,
+				viewStyles,
+				
+				setDisabled,
+				isDisabled,
+				isReset,
+				isDrag,
+				
+				active,
+				animation,
+				
+				afterEl,
+				ghostEl,
+				beforeEl,
+				
+				touchStart,
+				touchMove,
+				touchEnd,
+				
+				remove,
+				// add,
+				move,
+				push,
+				unshift,
+				// shift,
+				// pop,
+				props
+				// isDelete: props.delete,
+				// ...toRefs(props)
+			}
+		}
+	})
+	
+	
+	
+	
+	
+	
+	
+</script>
+<style lang="scss">
+	@import './index';
+</style>

+ 47 - 0
src/uni_modules/lime-drag/components/l-drag/props.ts

@@ -0,0 +1,47 @@
+// @ts-nocheck
+export default {
+		list: {
+			 type: Array,
+			 default: []
+		},
+		column: {
+			 type: Number,
+			 default: 2
+		},
+		/**宽高比 填写这项, gridHeight 失效*/
+		aspectRatio: Number,
+		gridHeight: {
+			type: [Number, String],
+			default: '120rpx'
+		},
+		// removeStyle: String,
+		// handleStyle: String,
+		damping: {
+			type: Number,
+			default: 40
+		},
+		friction: {
+			type: Number,
+			default: 2
+		},
+		/**
+		 * 由于 movable-area 无法动态设置高度,故增加额外的行数。用于增加动态项时,高度不够无法正确显示
+		 */
+		extraRow: {
+			type: Number,
+			default: 0
+		},
+		/**
+		 * 由于 movable-area 无法动态设置高度,但vif 重染可以,另一种实现动态高度的方式, 这BUG uni官方好像修复了。
+		 */
+		// reset: Boolean,
+		// sort: Boolean,
+		// remove: Boolean,
+		ghost: Boolean,
+		handle: Boolean,
+		touchHandle: Boolean,
+		before: Boolean,
+		after: Boolean,
+		disabled: Boolean,
+		longpress: Boolean,
+	}

+ 21 - 0
src/uni_modules/lime-drag/components/l-drag/type.ts

@@ -0,0 +1,21 @@
+export interface Position {
+	x: number
+	y: number
+}
+export interface GridRect extends Position{
+	row : number
+	// x : number
+	// y : number
+	x1 : number
+	y1 : number
+}
+export interface Grid extends Position{
+	id : string
+	index : number
+	oldindex : number
+	content : any
+	// x : number
+	// y : number
+	class : string
+	show: boolean
+}

+ 9 - 0
src/uni_modules/lime-drag/components/l-drag/vue.ts

@@ -0,0 +1,9 @@
+// @ts-nocheck
+// export * from '@/uni_modules/lime-vue'
+
+// #ifdef VUE3
+export * from 'vue';
+// #endif
+// #ifndef VUE3
+export * from '@vue/composition-api';
+// #endif

+ 87 - 0
src/uni_modules/lime-drag/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "lime-drag",
+  "displayName": "拖拽排序-拖动排序-LimeUI",
+  "version": "0.1.3",
+  "description": "uniapp vue3 拖拽排序插件,用于图片或列表的拖动排序,可设置列数、增加删除等功能, vue2只要配置@vue/composition-api",
+  "keywords": [
+    "拖拽",
+    "拖拽排序",
+    "排序",
+    "拖动",
+    "拖动排序"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.7.12"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+		"lime-shared"
+	],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "Vue": {
+          "vue2": "n",
+          "vue3": "y"
+        },
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "u",
+          "Edge": "u",
+          "Firefox": "u",
+          "Safari": "u"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "u",
+          "百度": "u",
+          "字节跳动": "u",
+          "QQ": "u",
+          "钉钉": "u",
+          "快手": "u",
+          "飞书": "u",
+          "京东": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        }
+      }
+    }
+  }
+}

+ 170 - 0
src/uni_modules/lime-drag/readme.md

@@ -0,0 +1,170 @@
+# lime-drag 拖拽排序
+- 当前为初版 可能会有BUG
+- 基于uniapp vue3
+- Q群 1169785031
+
+
+### 安装
+- 在市场导入插件即可在任意页面使用,无须再`import`
+
+
+### 使用
+- 提供简单的使用示例,更多请查看下方的demo
+
+```html
+<l-drag :list="list" @change="change">
+	<!-- // 每一项的插槽 grid 的 content 您传入的数据 -->
+	<template #grid="{active, content}">
+		<!-- // grid.active 是否为当前拖拽项目 根据自己需要写样式 -->
+		<view class="inner" :class="{active: active}">
+			<text class="text" :class="{'text-active': active}">{{grid.content}}</text>
+		</view>
+	</template>
+</l-drag> 
+```
+
+```js
+const list = new Array(7).fill(0).map((v,i) => i);
+// 拖拽后新的数据
+const newList = ref([])
+const change = v => newList.value = v
+```
+#### 增删
+- 不要给list赋值,这样只会重新初始化
+- 增加数据 调用暴露的`push`
+- 删除某条数据调用暴露的`remove`方法,需要传入`oindex`
+
+```html
+<l-drag :list="list" @change="change" ref="dragRef" after remove>
+	<!-- 每一项插槽 grid 的 content 是您传入的数据 -->
+	<template #grid="{active, index, oldindex, oindex}">
+		<!-- active 是否为当前拖拽项目 根据自己需要写样式 -->
+		<!-- index 排序后列表下标 -->
+		<!-- oldindex 排序后列表旧下标 -->
+		<!-- oindex 列表原始下标,输入的数据排位不会因为排序而改变 -->
+		<view class="remove" @click="onRemove(oindex)"></view>
+		<view class="inner" :class="{active}">
+			<text class="text" :class="{'text-active': active}">{{content}}</text>
+		</view>
+	</template>
+	<template #after>
+		<view class="grid">
+			<view class="inner extra" @click="onAdd">
+				增加
+			</view>
+		</view>
+	</template>
+</l-drag> 
+```
+```js
+const dragRef = ref(null)
+const list = new Array(7).fill(0).map((v,i) => i);
+const onAdd = () => {
+	dragRef.value.push(Math.round(Math.random() * 1000))
+}
+const onRemove = (oindex) => {
+	if(dragRef.value && oindex >= 0) {
+		// 记得oindex为数组的原始index
+		dragRef.value.remove(oindex)
+	}
+}
+```
+
+
+#### 插槽
+```html
+<l-drag :list="list">
+	<!-- 每一项的插槽 -->
+	<template #grid="{active, index, oldindex, oindex, content}"></template>
+	<!-- 当前拖拽项幽灵插槽 设置`ghost`后使用 主要为实现拖拽时 有个影子跟着 -->
+	<template #ghots></template>
+	
+	<!-- 前后方插槽为固定在列表前方和后方,不能拖动 -->
+	<!-- 列表前方的插槽 设置`before`后使用 -->
+	<template #before></template>
+	<!-- 列表后方的插槽 设置`after`后使用 -->
+	<template #after></template>
+</l-drag> 
+```
+
+
+### 查看示例
+- 导入后直接使用这个标签查看演示效果
+
+```html
+<!-- // 代码位于 uni_modules/lime-drag/compoents/lime-drag -->
+<lime-drag />
+```
+
+
+### 插件标签
+- 默认 l-drag 为 component
+- 默认 lime-drag 为 demo
+
+### 关于vue2的使用方式
+- 插件使用了`composition-api`, 如果你希望在vue2中使用请按官方的教程[vue-composition-api](https://uniapp.dcloud.net.cn/tutorial/vue-composition-api.html)配置
+- 关键代码是: 在main.js中 在vue2部分加上这一段即可,官方是把它单独成了一个文件.
+```js
+// vue2
+import Vue from 'vue'
+import VueCompositionAPI from '@vue/composition-api'
+Vue.use(VueCompositionAPI)
+```
+
+- 另外插件也用到了TS,vue2可能会遇过官方的TS版本过低的问题,找到HX目录下的`compile-typescript`目录
+```cmd
+// \HBuilderX\plugins\compile-typescript
+yarn add typescript -D
+- or - 
+npm install typescript -D
+```
+
+- 小程序需要在`manifest.json`启用`slotMultipleInstance`
+```json
+"mp-weixin" : {
+    "slotMultipleInstance" : true
+}
+```
+
+
+## API
+
+### Props
+
+| 参数                       | 说明                                                         | 类型             | 默认值       |
+| --------------------------| ------------------------------------------------------------ | ---------------- | ------------ |
+| list                      | 列表数组,不可变化,变化后会重新初始化                                                      | <em>array</em>  | `[]`     |
+| column                    | 列数                  | <em>number</em>  | `2` |
+| gridHeight               | 行高,宫格高度                 								| <em>string</em>  | `120rpx` |
+| damping               	| 阻尼系数,用于控制x或y改变时的动画和过界回弹的动画,值越大移动越快         				| <em>string</em>  | `-` |
+| friction               	| 摩擦系数,用于控制惯性滑动的动画,值越大摩擦力越大,滑动越快停止;必须大于0,否则会被设置成默认值         				| <em>number</em>  | `2` |
+| extraRow               	| 额外行数        				| <em>number</em>  | `0` |
+| ghost               	| 开启幽灵插槽        				| <em>boolean</em>  | `false` |
+| before               	| 开启列前插槽        				| <em>boolean</em>  | `false` |
+| after               	| 开启列后插槽        				| <em>boolean</em>  | `false` |
+| disabled              | 是否禁用        				| <em>boolean</em>  | `false` |
+| longpress              | 是否长按        				| <em>boolean</em>  | `false` |
+
+### Events
+| 参数                       | 说明                                                         | 参数             | 
+| --------------------------| ------------------------------------------------------------ | ---------------- |
+| change              		| 返回新数据  | list | 
+
+### Expose
+| 参数                       | 说明                                                         | 参数             | 
+| --------------------------| ------------------------------------------------------------ | ---------------- |
+| remove              		| 删除, 传入`oindex`,即数据列表原始的index  |  | 
+| push             			| 向后增加,可以是数组或单数据  |  | 
+| unshift             	    | 向前增加,可以是数组或单数据  |  | 
+| move             			| 移动, 传入(`oindex`, `toindex`),将数据列表原始的index项移到视图中的目标位置  |  | 
+
+
+### TODO
+将来实现的功能
+- splice
+
+## 打赏
+
+如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。  
+![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/alipay.png)
+![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/wpay.png)

+ 1 - 1
tailwind.config.js

@@ -77,7 +77,7 @@ module.exports = {
         '5xl': '32px',
         '6xl': '36px',
         '7xl': '40px',
-        ...generateSize(50, 'rpx'),
+        ...generateSize(70, 'rpx'),
       },
       colors: {
         primary: genSimilarColorsName('primary'),