goodsSort.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807
  1. <template>
  2. <view class="app-content">
  3. <view class="list-wrap">
  4. <block v-if="!$util.isEmpty(list.data)">
  5. <block v-for="(item, index) in list.data" :key="item.id">
  6. <view
  7. class="list"
  8. :class="{
  9. dragging: draggedIndex === index,
  10. 'target-position': targetIndex === index && isDragging && targetIndex !== draggedIndex
  11. }"
  12. :style="{
  13. transform: transformStyle(index),
  14. transition: isDragging ? 'none' : 'transform 0.3s ease',
  15. zIndex: draggedIndex === index ? 999 : targetIndex === index && isDragging ? 888 : 1
  16. }"
  17. >
  18. <view class="list-img">
  19. <view v-if="item.stock <= 0" class="sold-out">
  20. <view class="sold-out-xj">已下架</view>
  21. </view>
  22. <image :src="item.miniCover" mode="aspectFit"></image>
  23. </view>
  24. <view class="list-det" style="position: absolute; top: 10upx; left: 125upx">
  25. <view class="app-size-28 app-color-0" style="font-size: 28upx; font-weight: bold">{{ item.name || item.goodsName }}</view>
  26. </view>
  27. <view class="list-det" style="position: absolute; bottom: 15upx; left: 122upx">
  28. <view v-if="item.priceType == 1" class="app-size-28 app-color-0" style="font-size:30upx;color:red;font-weight:bold;">¥{{ item.price?parseFloat(item.price):0 }}</view>
  29. <view v-else></view>
  30. </view>
  31. <view
  32. v-if="index > 0"
  33. style="right: 375upx; top: 87upx"
  34. class="move-btn move-btn-top"
  35. :class="{ disabled: isOperating }"
  36. @click.stop="topFn(item.id)"
  37. title="置顶"
  38. >
  39. <zui-svg-icon icon="general-top-line" color="#fff" :width="22" :height="22" style="margin-top: 10upx" />
  40. </view>
  41. <view
  42. v-if="index < list.data.length - 1"
  43. style="right: 290upx; top: 87upx"
  44. class="move-btn move-btn-bottom"
  45. :class="{ disabled: isOperating }"
  46. @click.stop="bottomFn(item.id)"
  47. title="置底"
  48. >
  49. <zui-svg-icon icon="general-bottom-line" color="#fff" :width="22" :height="22" style="margin-top: 10upx" />
  50. </view>
  51. <text style="right: 205upx; top: 87upx" class="move-btn move-btn-up" :class="{ disabled: isOperating }" @click.stop="upFn(item.id)" title="上移一位"
  52. >↑</text
  53. >
  54. <text
  55. style="right: 120upx; top: 87upx"
  56. class="move-btn move-btn-down"
  57. :class="{ disabled: isOperating }"
  58. @click.stop="downFn(item.id)"
  59. title="下移一位"
  60. >↓</text
  61. >
  62. <view
  63. class="drag-handle"
  64. :data-index="index"
  65. :data-id="item.id"
  66. @touchstart="dragStart"
  67. @touchmove="dragMove"
  68. @touchend="dragEnd"
  69. >
  70. <text class="drag-tip">按住</text>
  71. <text class="drag-tip">拖拽</text>
  72. </view>
  73. </view>
  74. </block>
  75. </block>
  76. <block v-else>
  77. <app-wrapper-empty title="暂无数据" :is-empty="$util.isEmpty(list.data)" />
  78. </block>
  79. </view>
  80. </view>
  81. </template>
  82. <script>
  83. import AppWrapperEmpty from '@/components/app-wrapper-empty';
  84. import { list } from '@/mixins';
  85. import { getListB, sort } from '@/api/goods';
  86. export default {
  87. name: 'goodsSort',
  88. components: {
  89. AppWrapperEmpty
  90. },
  91. mixins: [list],
  92. data() {
  93. return {
  94. draggedIndex: -1,
  95. draggedItem: null,
  96. dragOffsetY: 0,
  97. isDragging: false,
  98. itemHeight: 160, // 列表项高度,单位upx(与CSS中的height保持一致)
  99. startY: 0,
  100. scrollTop: 0, // 页面滚动高度
  101. originalOrder: [], // 原始顺序,用于拖拽失败时恢复
  102. targetIndex: -1, // 目标交换位置的索引
  103. isOperating: false, // 防止快速点击操作按钮
  104. totalNum: 0 // 总数量
  105. };
  106. },
  107. onPageScroll(e) {
  108. // 拖拽期间禁止更新滚动高度
  109. if (!this.isDragging) {
  110. this.scrollTop = e.scrollTop;
  111. }
  112. },
  113. onPullDownRefresh() {
  114. this.resetList();
  115. this.getGoodsList()
  116. .then((res) => {
  117. uni.stopPullDownRefresh();
  118. })
  119. .catch((err) => {
  120. console.error('下拉刷新失败:', err);
  121. uni.stopPullDownRefresh();
  122. });
  123. },
  124. onReachBottom() {
  125. if (!this.list.finished) {
  126. this.getGoodsList().then((res) => {
  127. uni.stopPullDownRefresh();
  128. });
  129. } else {
  130. uni.stopPullDownRefresh();
  131. }
  132. },
  133. methods: {
  134. async init() {
  135. // 确保list.data是数组
  136. if (!Array.isArray(this.list.data)) {
  137. this.list.data = [];
  138. }
  139. this.getGoodsList();
  140. if(this.option.name){
  141. uni.setNavigationBarTitle({ title: this.option.name+" 排序" })
  142. }
  143. },
  144. getGoodsList() {
  145. // 传递分类ID参数和分页参数到API
  146. const params = {
  147. cId: this.option.id,
  148. page: this.list.page,
  149. pageSize: this.list.pageSize
  150. };
  151. return getListB(params)
  152. .then((res) => {
  153. // 使用mixin提供的completes方法处理分页数据
  154. this.completes(res);
  155. this.totalNum = Number(res.data.total);
  156. })
  157. .catch((err) => {
  158. console.error('API请求失败:', err);
  159. this.list.loading = false;
  160. });
  161. },
  162. // 响应 touchmove 事件并实时更新 transform 样式
  163. transformStyle(index) {
  164. if (index === this.draggedIndex) {
  165. // #ifdef APP-PLUS
  166. // 在 App 端,为与小程序保持一致,拖拽时项目保持原位
  167. return 'translateY(0upx)';
  168. // #endif
  169. //return `translateY(${this.dragOffsetY}upx)`;
  170. return 'translateY(0upx)';
  171. }
  172. return 'translateY(0upx)';
  173. },
  174. dragStart(e) {
  175. // 防止重复触发
  176. if (this.isDragging) return;
  177. this.isDragging = true;
  178. const index = parseInt(e.currentTarget.dataset.index);
  179. if (isNaN(index) || index < 0 || index >= this.list.data.length) {
  180. this.isDragging = false;
  181. return;
  182. }
  183. this.draggedIndex = index;
  184. this.draggedItem = this.list.data[index];
  185. this.startY = e.touches[0].clientY;
  186. this.targetIndex = index; // 初始化目标位置为当前位置
  187. // 保存原始顺序,用于拖拽失败时恢复
  188. this.originalOrder = [...this.list.data];
  189. // 添加拖拽模式样式,防止页面滚动
  190. this.toggleDragMode(true);
  191. // 阻止默认行为,防止页面滚动(兼容微信端与H5)
  192. if (e.preventDefault) {
  193. e.preventDefault();
  194. }
  195. if (e.stopPropagation) {
  196. e.stopPropagation();
  197. }
  198. return false;
  199. },
  200. dragMove(e) {
  201. if (!this.isDragging || this.draggedIndex === -1) return;
  202. // 验证触摸点是否存在
  203. if (!e.touches || !e.touches[0]) return;
  204. // 阻止默认行为,防止页面滚动(兼容微信端与H5)
  205. if (e.preventDefault) {
  206. e.preventDefault();
  207. }
  208. if (e.stopPropagation) {
  209. e.stopPropagation();
  210. }
  211. const currentY = e.touches[0].clientY;
  212. const deltaY = currentY - this.startY;
  213. // 根据设备像素比例转换为upx单位,确保在不同设备上表现一致
  214. const systemInfo = uni.getSystemInfoSync();
  215. const pixelRatio = 750 / systemInfo.windowWidth;
  216. const deltaYupx = deltaY * pixelRatio;
  217. // 限制拖拽范围,防止超出列表边界
  218. const maxOffset = (this.list.data.length - 1 - this.draggedIndex) * this.itemHeight;
  219. const minOffset = -this.draggedIndex * this.itemHeight;
  220. const limitedOffset = Math.max(minOffset, Math.min(maxOffset, deltaYupx));
  221. this.dragOffsetY = limitedOffset;
  222. // 实时计算目标位置,让用户看到将要交换的元素
  223. const moveItems = Math.round(limitedOffset / this.itemHeight);
  224. this.targetIndex = Math.max(0, Math.min(this.list.data.length - 1, this.draggedIndex + moveItems));
  225. },
  226. dragEnd() {
  227. if (!this.isDragging) return;
  228. // 计算最终位置
  229. const moveItems = Math.round(this.dragOffsetY / this.itemHeight);
  230. const targetIndex = Math.max(0, Math.min(this.list.data.length - 1, this.draggedIndex + moveItems));
  231. // 如果位置有变化,则更新数组
  232. if (targetIndex !== this.draggedIndex) {
  233. const newData = [...this.list.data];
  234. const draggedItem = newData[this.draggedIndex];
  235. // 移除拖拽项
  236. newData.splice(this.draggedIndex, 1);
  237. // 插入到新位置
  238. newData.splice(targetIndex, 0, draggedItem);
  239. this.list.data = newData;
  240. // 更新排序值并发送到后端
  241. this.updateSortOrder();
  242. }
  243. // 移除拖拽模式样式
  244. this.toggleDragMode(false);
  245. // 重置拖拽状态
  246. this.draggedIndex = -1;
  247. this.draggedItem = null;
  248. this.dragOffsetY = 0;
  249. this.isDragging = false;
  250. this.startY = 0;
  251. this.originalOrder = [];
  252. this.targetIndex = -1; // 重置目标位置
  253. },
  254. updateSortOrder() {
  255. // 更新每个项目的inTurn值,从大到小排序(值越大越靠前)
  256. const sortData = this.list.data.map((item, index) => ({
  257. id: item.id,
  258. inTurn: this.totalNum - index // 反向索引,使第一个项目有最大值
  259. }));
  260. // console.log('排序数据:', sortData)
  261. // 调用API更新排序
  262. sort({ cId: this.option.id, items: sortData })
  263. .then((res) => {
  264. if (res.code === 1) {
  265. } else {
  266. this.$msg('排序更新失败');
  267. // 恢复原始顺序
  268. this.list.data = this.originalOrder;
  269. }
  270. })
  271. .catch((err) => {
  272. console.error('更新排序失败:', err);
  273. this.$msg('排序更新失败');
  274. // 恢复原始顺序
  275. this.list.data = this.originalOrder;
  276. });
  277. },
  278. // 切换拖拽模式,防止页面滚动
  279. toggleDragMode(isDragging) {
  280. // #ifdef H5
  281. // H5平台:直接操作body样式
  282. if (isDragging) {
  283. document.body.classList.add('dragging-mode');
  284. } else {
  285. document.body.classList.remove('dragging-mode');
  286. }
  287. // #endif
  288. // #ifdef MP-WEIXIN
  289. // 微信小程序:使用页面样式和滚动API
  290. if (typeof uni.setPageStyle !== 'function') {
  291. console.warn('uni.setPageStyle is not available on this platform.');
  292. return;
  293. }
  294. if (isDragging) {
  295. uni.setPageStyle({
  296. style: {
  297. overflow: 'hidden',
  298. position: 'fixed',
  299. width: '100%',
  300. top: `-${this.scrollTop}px`,
  301. left: '0',
  302. right: '0'
  303. }
  304. });
  305. } else {
  306. uni.setPageStyle({
  307. style: {
  308. overflow: '',
  309. position: '',
  310. width: '',
  311. top: '',
  312. left: '',
  313. right: ''
  314. }
  315. });
  316. uni.pageScrollTo({
  317. scrollTop: this.scrollTop,
  318. duration: 0
  319. });
  320. }
  321. // #endif
  322. // #ifdef APP-PLUS
  323. const systemInfo = uni.getSystemInfoSync();
  324. // --- iOS 与 Android App 平台替代方案 ---
  325. // 通过操作 webview 内的 body 样式来禁止滚动
  326. const webview = this.$scope.$getAppWebview();
  327. if (!webview) {
  328. console.warn('uni.setPageStyle 在当前环境不可用,且无法获取 webview。');
  329. return;
  330. }
  331. if (isDragging) {
  332. webview.evalJS(`
  333. var body = document.body;
  334. body.style.overflow = 'hidden';
  335. body.style.position = 'fixed';
  336. body.style.width = '100%';
  337. body.style.left = '0';
  338. body.style.right = '0';
  339. body.style.top = '-${this.scrollTop}px';
  340. `);
  341. } else {
  342. webview.evalJS(`
  343. var body = document.body;
  344. body.style.overflow = '';
  345. body.style.position = '';
  346. body.style.width = '';
  347. body.style.left = '';
  348. body.style.right = '';
  349. body.style.top = '';
  350. `);
  351. // 恢复滚动位置
  352. uni.pageScrollTo({
  353. scrollTop: this.scrollTop,
  354. duration: 0
  355. });
  356. }
  357. return; // iOS 平台处理完毕
  358. // #endif
  359. },
  360. // 原有的上移下移方法
  361. upFn(id) {
  362. if (this.isOperating) return;
  363. this.isOperating = true;
  364. const index = this.list.data.findIndex((item) => item.id === id);
  365. if (index > 0) {
  366. const newData = [...this.list.data];
  367. const temp = newData[index];
  368. newData[index] = newData[index - 1];
  369. newData[index - 1] = temp;
  370. this.list.data = newData;
  371. this.updateSortOrder();
  372. }
  373. // 防抖延迟
  374. setTimeout(() => {
  375. this.isOperating = false;
  376. }, 300);
  377. },
  378. downFn(id) {
  379. if (this.isOperating) return;
  380. this.isOperating = true;
  381. const index = this.list.data.findIndex((item) => item.id === id);
  382. if (index < this.list.data.length - 1) {
  383. const newData = [...this.list.data];
  384. const temp = newData[index];
  385. newData[index] = newData[index + 1];
  386. newData[index + 1] = temp;
  387. this.list.data = newData;
  388. this.updateSortOrder();
  389. }
  390. // 防抖延迟
  391. setTimeout(() => {
  392. this.isOperating = false;
  393. }, 300);
  394. },
  395. // 置顶
  396. topFn(id) {
  397. if (this.isOperating) return;
  398. this.isOperating = true;
  399. const index = this.list.data.findIndex((item) => item.id === id);
  400. if (index > 0) {
  401. const newData = [...this.list.data];
  402. const targetItem = newData[index];
  403. // 将目标项移到第一位
  404. newData.splice(index, 1);
  405. newData.unshift(targetItem);
  406. this.list.data = newData;
  407. this.updateSortOrder();
  408. }
  409. // 防抖延迟
  410. setTimeout(() => {
  411. this.isOperating = false;
  412. }, 300);
  413. },
  414. // 置底
  415. bottomFn(id) {
  416. if (this.isOperating) return;
  417. this.isOperating = true;
  418. const index = this.list.data.findIndex((item) => item.id === id);
  419. if (index < this.list.data.length - 1) {
  420. const newData = [...this.list.data];
  421. const targetItem = newData[index];
  422. // 将目标项移到最后一位
  423. newData.splice(index, 1);
  424. newData.push(targetItem);
  425. this.list.data = newData;
  426. this.updateSortOrder();
  427. }
  428. // 防抖延迟
  429. setTimeout(() => {
  430. this.isOperating = false;
  431. }, 300);
  432. }
  433. }
  434. };
  435. </script>
  436. <style lang="scss" scoped>
  437. .app-content {
  438. min-height: calc(100vh - 100upx);
  439. padding-bottom: 100upx;
  440. }
  441. /* 现代化标签样式 */
  442. .modern-tabs-container {
  443. padding: 0;
  444. background-color: #f8f9fa;
  445. width: 100%;
  446. }
  447. .modern-tabs {
  448. display: flex;
  449. background-color: #ffffff;
  450. padding: 6upx;
  451. box-shadow: 0 2upx 12upx rgba(0, 0, 0, 0.06);
  452. border: 2upx solid #f0f2f5;
  453. position: relative;
  454. z-index: 5;
  455. width: 100%;
  456. }
  457. .modern-tab {
  458. flex: 1;
  459. text-align: center;
  460. padding: 14upx 0;
  461. position: relative;
  462. transition: all 0.35s cubic-bezier(0.25, 1, 0.5, 1);
  463. border-radius: 12upx;
  464. margin: 0 2upx;
  465. background-color: #fafafa;
  466. border: 2upx solid #f2f4f7;
  467. box-shadow: none;
  468. .tab-content {
  469. display: flex;
  470. flex-direction: column;
  471. align-items: center;
  472. justify-content: center;
  473. }
  474. .tab-icon {
  475. font-size: 32upx;
  476. margin-bottom: 4upx;
  477. display: block;
  478. opacity: 0.8;
  479. }
  480. &.active .tab-icon {
  481. filter: brightness(1.15);
  482. opacity: 1;
  483. }
  484. .tab-text {
  485. font-size: 26upx;
  486. color: #666;
  487. font-weight: 600;
  488. }
  489. &.active {
  490. background: linear-gradient(135deg, #10b981 0%, #09C567 100%);
  491. box-shadow: 0 4upx 12upx rgba(16, 185, 129, 0.25);
  492. transform: none;
  493. border-color: rgba(16, 185, 129, 0.25);
  494. .tab-text {
  495. color: #ffffff;
  496. font-weight: 700;
  497. font-size: 28upx;
  498. }
  499. }
  500. /* 未选中态悬停(H5 生效,其它端无影响) */
  501. &:not(.active):hover {
  502. background-color: #f6f7f9;
  503. border-color: #e9ecef;
  504. }
  505. &:active {
  506. transform: scale(0.98);
  507. }
  508. }
  509. .list-wrap {
  510. position: relative;
  511. background-color: #fff;
  512. .list {
  513. height: 160upx;
  514. @include disFlex(center, flex-start);
  515. padding: 30upx 0;
  516. margin: 0 30upx;
  517. color: $fontColor3;
  518. border-bottom: 1px solid $borderColor;
  519. position: relative;
  520. .list-img {
  521. width: 130upx;
  522. height: 130upx;
  523. image {
  524. width: 130upx;
  525. height: 130upx;
  526. border-radius: 20upx;
  527. }
  528. .sold-out {
  529. z-index: 10;
  530. width: 130upx;
  531. height: 52upx; /* 130 * (2/5) = 52upx */
  532. background-color: black;
  533. position: absolute;
  534. top: 92upx;
  535. left: 0;
  536. opacity: 0.48;
  537. color: white;
  538. text-align: center;
  539. border-radius: 0 0 20upx 20upx;
  540. .sold-out-xj {
  541. line-height: 52upx;
  542. font-size: 24upx;
  543. font-weight: bold;
  544. }
  545. }
  546. }
  547. .list-det {
  548. margin-left: 20upx;
  549. & > div {
  550. margin-top: 14upx;
  551. &:first-child {
  552. margin-top: 0;
  553. }
  554. }
  555. }
  556. }
  557. }
  558. .move-btn {
  559. position: absolute;
  560. top: 88upx;
  561. display: inline-flex;
  562. align-items: center;
  563. justify-content: center;
  564. width: 60upx;
  565. height: 60upx;
  566. color: #fff;
  567. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  568. border: 2px solid rgba(255, 255, 255, 0.3);
  569. border-radius: 50%;
  570. font-size: 28upx; /* 增大20% */
  571. font-weight: bold;
  572. box-shadow: 0 4upx 12upx rgba(102, 126, 234, 0.3);
  573. transition: all 0.2s ease;
  574. z-index: 9999;
  575. cursor: pointer;
  576. /* 触摸反馈 */
  577. &:active {
  578. transform: scale(0.95);
  579. box-shadow: 0 2upx 8upx rgba(102, 126, 234, 0.5);
  580. }
  581. /* 悬停效果 */
  582. &:hover {
  583. transform: translateY(-2upx);
  584. box-shadow: 0 6upx 20upx rgba(102, 126, 234, 0.4);
  585. }
  586. }
  587. /* 置顶按钮 - 红色渐变 */
  588. .move-btn-top {
  589. // background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
  590. // box-shadow: 0 4upx 12upx rgba(255, 107, 107, 0.3);
  591. background: linear-gradient(135deg, #feca57 0%, #ff9ff3 100%);
  592. box-shadow: 0 4upx 12upx rgba(254, 202, 87, 0.3);
  593. &:hover {
  594. box-shadow: 0 6upx 20upx rgba(255, 107, 107, 0.4);
  595. }
  596. &:active {
  597. box-shadow: 0 2upx 8upx rgba(255, 107, 107, 0.5);
  598. }
  599. }
  600. /* 置底按钮 - 橙色渐变 */
  601. .move-btn-bottom {
  602. background: linear-gradient(135deg, #feca57 0%, #ff9ff3 100%);
  603. box-shadow: 0 4upx 12upx rgba(254, 202, 87, 0.3);
  604. &:hover {
  605. box-shadow: 0 6upx 20upx rgba(254, 202, 87, 0.4);
  606. }
  607. &:active {
  608. box-shadow: 0 2upx 8upx rgba(254, 202, 87, 0.5);
  609. }
  610. }
  611. /* 上移按钮 - 绿色渐变 */
  612. .move-btn-up {
  613. background: linear-gradient(135deg, #48dbfb 0%, #0abde3 100%);
  614. box-shadow: 0 4upx 12upx rgba(72, 219, 251, 0.3);
  615. &:hover {
  616. box-shadow: 0 6upx 20upx rgba(72, 219, 251, 0.4);
  617. }
  618. &:active {
  619. box-shadow: 0 2upx 8upx rgba(72, 219, 251, 0.5);
  620. }
  621. }
  622. /* 下移按钮 - 紫色渐变 */
  623. .move-btn-down {
  624. // background: linear-gradient(135deg, #a55eea 0%, #8854d0 100%);
  625. // box-shadow: 0 4upx 12upx rgba(165, 94, 234, 0.3);
  626. background: linear-gradient(135deg, #48dbfb 0%, #0abde3 100%);
  627. box-shadow: 0 4upx 12upx rgba(72, 219, 251, 0.3);
  628. &:hover {
  629. box-shadow: 0 6upx 20upx rgba(165, 94, 234, 0.4);
  630. }
  631. &:active {
  632. box-shadow: 0 2upx 8upx rgba(165, 94, 234, 0.5);
  633. }
  634. }
  635. /* 按钮禁用状态 */
  636. .move-btn.disabled {
  637. opacity: 0.5;
  638. transform: none !important;
  639. box-shadow: 0 2upx 6upx rgba(0, 0, 0, 0.1) !important;
  640. pointer-events: none;
  641. cursor: not-allowed;
  642. &:hover,
  643. &:active {
  644. transform: none !important;
  645. box-shadow: 0 2upx 6upx rgba(0, 0, 0, 0.1) !important;
  646. }
  647. }
  648. // 拖拽相关样式
  649. .list.dragging {
  650. border: 2px dashed #007aff !important;
  651. box-shadow: 0 8upx 40upx rgba(0, 122, 255, 0.25) !important;
  652. background-color: rgba(0, 122, 255, 0.08) !important;
  653. opacity: 0.9;
  654. border-radius: 16upx;
  655. transform: scale(1.02);
  656. transition: none !important; /* 拖拽时禁用过渡动画 */
  657. }
  658. .list.target-position {
  659. border: 3px solid #52c41a !important; /* 使用绿色边框区别于拖拽元素 */
  660. box-shadow: 0 6upx 30upx rgba(82, 196, 26, 0.3) !important;
  661. background-color: rgba(82, 196, 26, 0.1) !important;
  662. opacity: 0.95;
  663. border-radius: 16upx;
  664. transform: scale(1.01);
  665. transition: all 0.2s ease !important; /* 目标位置保持平滑过渡 */
  666. /* 添加一个内部指示器 */
  667. &::before {
  668. content: '移动到这儿';
  669. position: absolute;
  670. top: 50%;
  671. left: 50%;
  672. transform: translate(-50%, -50%);
  673. padding: 8upx 22upx;
  674. background: linear-gradient(90deg, #52c41a, #73d13d);
  675. border-radius: 8upx;
  676. color: #fff;
  677. font-size: 26upx;
  678. font-weight: bold;
  679. opacity: 0.9;
  680. z-index: 99990;
  681. }
  682. }
  683. .drag-handle {
  684. position: absolute;
  685. top: 75upx;
  686. right: 12upx;
  687. display: flex;
  688. flex-direction: column;
  689. align-items: center;
  690. justify-content: center;
  691. width: 76upx;
  692. height: 76upx;
  693. border-radius: 50%;
  694. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  695. box-shadow: 0 4upx 16upx rgba(102, 126, 234, 0.4);
  696. -webkit-touch-callout: none;
  697. -webkit-user-select: none;
  698. user-select: none;
  699. touch-action: none;
  700. border: 2px solid rgba(255, 255, 255, 0.3);
  701. transition: all 0.2s ease;
  702. cursor: grab;
  703. &:active {
  704. transform: scale(0.92);
  705. cursor: grabbing;
  706. box-shadow: 0 2upx 10upx rgba(102, 126, 234, 0.5);
  707. }
  708. &:hover {
  709. transform: translateY(-3upx);
  710. box-shadow: 0 6upx 20upx rgba(102, 126, 234, 0.45);
  711. }
  712. .drag-tip {
  713. font-size: 19upx;
  714. color: rgba(255, 255, 255, 0.95);
  715. font-weight: 700;
  716. letter-spacing: 0.5upx;
  717. line-height: 1.4;
  718. text-align: center;
  719. word-break: break-word;
  720. display: block;
  721. }
  722. }
  723. /* 防止拖拽时页面滚动 */
  724. .dragging-mode {
  725. overflow: hidden !important;
  726. touch-action: none !important;
  727. -webkit-overflow-scrolling: auto !important;
  728. }
  729. </style>