collectionManage.vue 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <template>
  2. <view class="wrap">
  3. <!-- 可移动区域:高度随数据多少自动撑开 -->
  4. <mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :down="downOption"
  5. :up="upOption">
  6. <view v-if="list.length>0">
  7. <movable-area class="movable-area" :style="{ height: areaHeight + 'px' }">
  8. <movable-view v-for="(item, index) in list" :key="item.id" class="movable-row"
  9. :style="{ height: rowHeight + 'px' }" direction="vertical" :y="item.y" :disabled="!canMove"
  10. @longpress="handleLongPress(index)" @change="onChange" @touchend="handleTouchEnd">
  11. <!-- 左侧勾选(可选) -->
  12. <view class="choose" @tap.stop="toggleCheck(item)">
  13. <image v-if="item.checked" src="@/static/image/hall/choose_icon.png" />
  14. <image v-else src="@/static/image/hall/choose_moren_icon.png" />
  15. </view>
  16. <!-- 主内容 -->
  17. <image class="pic" :src="item.cover || item.thumbnail" mode="aspectFill" />
  18. <view class="title textTwo">{{ item.title }}</view>
  19. <!-- 右侧拖拽把手 -->
  20. <view v-if="type == 'edit'" class="handle" @longpress.stop="handleLongPress(index)">
  21. <u-icon name="list" color="#999" size="24" />
  22. </view>
  23. </movable-view>
  24. </movable-area>
  25. </view>
  26. </mescroll-body>
  27. <!-- 底部操作栏 -->
  28. <view class="footer">
  29. <view>已选择 {{ checkedCount }} 个视频</view>
  30. <view class="addbtn" @tap="handleSubmit">
  31. {{ type === "add" ? "加入合集" : "移除合集" }}
  32. </view>
  33. </view>
  34. </view>
  35. </template>
  36. <script>
  37. import {
  38. videoAdd,
  39. removeVideo,
  40. updateVideo,
  41. videoCollectionMyList,
  42. videoCollectionList,
  43. getVideoList,
  44. collectionList,
  45. updateSort,
  46. getMyVideoList
  47. } from "@/api/expert.js"
  48. const ROW = 120; // 每行高度 rpx → px 转换后
  49. export default {
  50. data() {
  51. return {
  52. type: "add",
  53. isOperate: true, // 是否进入编辑模式
  54. list: [], // 数据
  55. rowHeight: 0, // 每行 px 高度
  56. areaHeight: 0, // 区域 px 高度
  57. canMove: false, // 是否正在拖拽
  58. dragIndex: -1, // 当前拖拽项下标
  59. mescroll: null,
  60. downOption: { //下拉刷新
  61. use: true,
  62. auto: false // 不自动加载 (mixin已处理第一个tab触发downCallback)
  63. },
  64. upOption: {
  65. onScroll: false,
  66. use: true, // 是否启用上拉加载; 默认true
  67. page: {
  68. pae: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
  69. size: 10 // 每页数据的数量,默认10
  70. },
  71. noMoreSize: 5, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
  72. textNoMore: "已经到底了",
  73. empty: {
  74. icon: 'https://cos.his.cdwjyyh.com/fs/20240423/cf4a86b913a04341bb44e34bb4d37aa2.png',
  75. tip: '暂无数据'
  76. }
  77. },
  78. };
  79. },
  80. computed: {
  81. checkedCount() {
  82. return this.list.filter((i) => i.checked).length;
  83. },
  84. },
  85. onLoad(opt) {
  86. this.type = opt.type || "add";
  87. // this.getList()
  88. const sys = uni.getSystemInfoSync();
  89. this.rowHeight = (ROW * sys.windowWidth) / 750;
  90. // 模拟 8 条数据
  91. // const raw = Array.from({
  92. // length: 8
  93. // }, (_, i) => ({
  94. // id: i,
  95. // title: "西藏旅游" + i,
  96. // checked: false,
  97. // }));
  98. // this.initData(raw);
  99. },
  100. methods: {
  101. mescrollInit(mescroll) {
  102. this.mescroll = mescroll;
  103. },
  104. downCallback(mescroll) {
  105. mescroll.resetUpScroll()
  106. },
  107. async upCallback(page) {
  108. let that = this;
  109. let res = ''
  110. if (this.type == 'edit') {
  111. let params = {
  112. status: 1,
  113. collectionId: uni.getStorageSync('collectionData').collectionId || '',
  114. pageNum: page.num,
  115. pageSize: page.size
  116. }
  117. res = await collectionList(params)
  118. } else {
  119. let params = {
  120. status: 1,
  121. collectionId: uni.getStorageSync('collectionData').collectionId || '',
  122. talentId: uni.getStorageSync('expertInfo').talentId,
  123. pageNum: page.num,
  124. pageSize: page.size
  125. }
  126. res = await getMyVideoList(params)
  127. // res = await getVideoList(params)
  128. }
  129. if (res.code == 200) {
  130. let data = res.data.list.map((item, index) => ({
  131. ...item,
  132. checked: false,
  133. sort: index + 1,
  134. y: index * this.rowHeight,
  135. }));
  136. if (page.num == 1) that.list = []
  137. that.list = that.list.concat(data);
  138. that.mescroll.endBySize(data.length, res.data.total);
  139. this.areaHeight = data.length * this.rowHeight;
  140. }
  141. },
  142. async getList() {
  143. let res = ''
  144. if (this.type == 'edit') {
  145. let params = {
  146. status: 1,
  147. collectionId: uni.getStorageSync('collectionData').collectionId || ''
  148. }
  149. res = await collectionList(params)
  150. } else {
  151. let params = {
  152. status: 1,
  153. collectionId: uni.getStorageSync('collectionData').collectionId || '',
  154. talentId: uni.getStorageSync('expertInfo').talentId,
  155. }
  156. res = await getMyVideoList(params)
  157. // res = await getVideoList(params)
  158. }
  159. if (res.code == 200) {
  160. this.list = res.data.list.map((item, index) => ({
  161. ...item,
  162. checked: false,
  163. sort: index + 1,
  164. y: index * this.rowHeight,
  165. }));
  166. this.areaHeight = this.list.length * this.rowHeight;
  167. }
  168. },
  169. // 初始化坐标
  170. initData(src) {
  171. const sys = uni.getSystemInfoSync();
  172. // rpx → px
  173. this.rowHeight = (ROW * sys.windowWidth) / 750;
  174. },
  175. // 长按把手启动拖拽
  176. handleLongPress(index) {
  177. this.canMove = true;
  178. this.dragIndex = index;
  179. },
  180. // 拖拽过程中 movable-view 会不断触发 change
  181. onChange(e) {
  182. if (!this.canMove || e.detail.source !== "touch") return;
  183. const curY = e.detail.y;
  184. let targetIdx = Math.round(curY / this.rowHeight);
  185. targetIdx = Math.max(0, Math.min(targetIdx, this.list.length - 1));
  186. if (targetIdx !== this.dragIndex) {
  187. this.swapData(this.dragIndex, targetIdx);
  188. this.dragIndex = targetIdx;
  189. }
  190. },
  191. // 交换数据并重置 y
  192. async swapData(from, to) {
  193. const arr = [...this.list];
  194. const tmp = arr.splice(from, 1)[0];
  195. arr.splice(to, 0, tmp);
  196. let updateData = arr.map((item, idx) => ({
  197. ...item,
  198. sort: idx + 1,
  199. y: idx * this.rowHeight
  200. }));
  201. let params = updateData.map(({
  202. id,
  203. sort
  204. }) => ({
  205. id,
  206. sort
  207. }));
  208. const res = await updateSort(params)
  209. // this.list =
  210. if (res.code == 200) {
  211. this.getList()
  212. }
  213. },
  214. // 松手
  215. handleTouchEnd() {
  216. this.canMove = false;
  217. this.dragIndex = -1;
  218. },
  219. toggleCheck(item) {
  220. item.checked = !item.checked;
  221. },
  222. async handleSubmit() {
  223. if (this.type === "add") {
  224. let params = []
  225. this.list.forEach((item, idx) => {
  226. if (item.checked) {
  227. params.push({
  228. collectionId: 7,
  229. videoId: item.id * 1
  230. })
  231. }
  232. });
  233. const res = await videoAdd(params)
  234. // const res = await updateVideo(params)
  235. if (res.code == 200) {
  236. uni.showToast({
  237. title: '加入成功!',
  238. })
  239. setTimeout(() => {
  240. uni.navigateBack()
  241. }, 1000)
  242. }
  243. }
  244. if (this.type === "edit") {
  245. uni.showModal({
  246. title: "移除合集",
  247. content: "合集内的视频移除后,视频会继续保留在个人页",
  248. confirmText: "移除",
  249. confirmColor: "#ff5c03",
  250. success: async (result) => {
  251. let params = this.list.filter(item => item.checked).map(item => item.id);
  252. const res = await removeVideo(params)
  253. if (res.code == 200) {
  254. uni.showToast({
  255. title: '移除成功!',
  256. })
  257. setTimeout(() => {
  258. uni.navigateBack()
  259. }, 1000)
  260. }
  261. }
  262. });
  263. }
  264. },
  265. },
  266. };
  267. </script>
  268. <style lang="scss" scoped>
  269. .wrap {
  270. padding-bottom: 140rpx; // 给底部栏留空
  271. }
  272. .movable-area {
  273. width: 100%;
  274. }
  275. .movable-row {
  276. width: 100%;
  277. display: flex;
  278. align-items: center;
  279. padding: 28rpx;
  280. box-sizing: border-box;
  281. background: #fff;
  282. border-bottom: 1rpx solid #eee;
  283. }
  284. .choose {
  285. margin-right: 20rpx;
  286. image {
  287. width: 36rpx;
  288. height: 36rpx;
  289. }
  290. }
  291. .pic {
  292. width: 100rpx;
  293. height: 100rpx;
  294. border-radius: 10rpx;
  295. flex-shrink: 0;
  296. }
  297. .title {
  298. flex: 1;
  299. padding: 0 20rpx;
  300. font-size: 30rpx;
  301. color: #333;
  302. }
  303. .handle {
  304. padding: 20rpx;
  305. }
  306. .footer {
  307. position: fixed;
  308. left: 0;
  309. bottom: 0;
  310. width: 100%;
  311. display: flex;
  312. align-items: center;
  313. justify-content: space-between;
  314. padding: 24rpx 40rpx;
  315. box-sizing: border-box;
  316. background: #fff;
  317. border-top: 1rpx solid #eee;
  318. font-size: 28rpx;
  319. color: #999;
  320. .addbtn {
  321. padding: 20rpx 40rpx;
  322. background: #ff5c03;
  323. color: #fff;
  324. border-radius: 50rpx;
  325. }
  326. }
  327. </style>