25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 

372 satır
11 KiB

  1. <template>
  2. <up-popup :show="popupShow" mode="bottom" :popup="false"
  3. :mask="true" :closeable="closeable" :safe-area-inset-bottom="true"
  4. :close-icon-color="closeIconColor" :z-index="uZIndex"
  5. :maskCloseAble="maskCloseAble" @close="close">
  6. <view class="up-p-t-30 up-p-l-20 up-m-b-10" v-if="headerDirection ==='column'">
  7. <up-steps v-if="popupShow" dot direction="column" v-model:current="tabsIndex">
  8. <view v-for="(item, index) in genTabsList" @click="toFatherIndex(index)">
  9. <up-steps-item
  10. :title="item.name"/>
  11. </view>
  12. </up-steps>
  13. </view>
  14. <view class="up-p-t-20 up-m-b-10" v-else>
  15. <up-tabs v-if="popupShow" :list="genTabsList"
  16. :scrollable="true" v-model:current="tabsIndex" @change="tabsChange" ref="tabs"></up-tabs>
  17. </view>
  18. <view class="area-box">
  19. <view class="u-flex" :class="{ 'change':isChange }"
  20. :style="{transform: optionsCols == 2 && isChange ? 'translateX(-33.3333333%)' : ''}">
  21. <template v-for="(levelData, levelIndex) in levelList" :key="levelIndex">
  22. <view v-if="optionsCols == 2 || levelIndex == tabsIndex" class="area-item"
  23. :style="{ width: optionsCols == 2 ? '33.33333%' : '750rpx'}">
  24. <view class="u-padding-10" :style="[levelPaneStyle, { height: '100%' }]">
  25. <scroll-view :scroll-y="true" style="height: 100%">
  26. <up-cell-group v-if="levelIndex === 0 || selectedValueIndexs[levelIndex - 1] !== undefined">
  27. <up-cell v-for="(item,index) in levelData"
  28. :title="item[labelKey]" :arrow="false"
  29. :index="index" :key="index"
  30. @click="levelChange(levelIndex, index)">
  31. <template v-slot:right-icon>
  32. <up-icon v-if="selectedValueIndexs[levelIndex] === index"
  33. size="17" name="checkbox-mark"></up-icon>
  34. </template>
  35. </up-cell>
  36. </up-cell-group>
  37. </scroll-view>
  38. </view>
  39. </view>
  40. </template>
  41. </view>
  42. </view>
  43. <!-- 添加按钮区域 -->
  44. <view class="u-cascader-action up-flex up-flex-between">
  45. <view class="u-padding-20 up-flex-fill">
  46. <up-button @click="handleCancel" type="default">{{ t("up.common.cancel") }}</up-button>
  47. </view>
  48. <view class="u-padding-20 up-flex-fill">
  49. <up-button @click="handleConfirm" type="primary">{{ t("up.common.confirm") }}</up-button>
  50. </view>
  51. </view>
  52. </up-popup>
  53. </template>
  54. <script>
  55. /**
  56. * u-cascader 通用无限级联选择器
  57. * @property {String Number} z-index 弹出时的z-index值(默认1075)
  58. * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
  59. * @property {Array} data 级联数据
  60. * @property {Array} default-value 默认选中的值
  61. * @property {String} valueKey 指定选项的值为选项对象中的哪个属性值
  62. * @property {String} labelKey 指定选项标签为选项对象中的哪个属性值
  63. * @property {String} childrenKey 指定选项的子选项为选项对象中的哪个属性值
  64. * @property {Boolean} autoClose 是否在选择最后一级时自动关闭并触发confirm(默认false)
  65. * @property {Boolean} closeable 是否显示关闭图标(默认true)
  66. */
  67. import { t } from '../../libs/i18n'
  68. export default {
  69. name: 'up-cascader',
  70. props: {
  71. // 通过双向绑定控制组件的弹出与收起
  72. show: {
  73. type: Boolean,
  74. default: false
  75. },
  76. // 级联数据
  77. data: {
  78. type: Array,
  79. default() {
  80. return [];
  81. }
  82. },
  83. // 默认选中的值
  84. modelValue: {
  85. type: Array,
  86. default() {
  87. return [];
  88. }
  89. },
  90. // 指定选项的值为选项对象中的哪个属性值
  91. valueKey: {
  92. type: String,
  93. default: 'value'
  94. },
  95. // 指定选项标签为选项对象中的哪个属性值
  96. labelKey: {
  97. type: String,
  98. default: 'label'
  99. },
  100. // 指定选项的子选项为选项对象中的哪个属性值
  101. childrenKey: {
  102. type: String,
  103. default: 'children'
  104. },
  105. // 是否允许通过点击遮罩关闭Picker
  106. maskCloseAble: {
  107. type: Boolean,
  108. default: true
  109. },
  110. // 弹出的z-index值
  111. zIndex: {
  112. type: [String, Number],
  113. default: 0
  114. },
  115. // 是否在选择最后一级时自动关闭并触发confirm
  116. autoClose: {
  117. type: Boolean,
  118. default: false
  119. },
  120. // 选中项目的展示方向direction垂直方向适合文字长度过长
  121. headerDirection: {
  122. type: String,
  123. default: 'row'
  124. },
  125. // 选项区域列数,支持1列和2列,默认为2列
  126. optionsCols: {
  127. type: [Number],
  128. default: 2
  129. },
  130. // 是否显示关闭图标
  131. closeable: {
  132. type: Boolean,
  133. default: true
  134. }
  135. },
  136. data() {
  137. return {
  138. // 存储每一级的数据
  139. levelList: [],
  140. // 存储每一级选中的索引
  141. selectedValueIndexs: [],
  142. tabsIndex: 0,
  143. popupShow: false,
  144. // 新增confirmValues用于存储确认的值
  145. confirmValues: []
  146. }
  147. },
  148. watch: {
  149. data: {
  150. handler() {
  151. this.initLevelList();
  152. this.setDefaultValue();
  153. },
  154. immediate: true
  155. },
  156. show() {
  157. this.popupShow = this.show;
  158. },
  159. modelValue: {
  160. handler() {
  161. // 初始化选中值
  162. this.setDefaultValue();
  163. },
  164. immediate: true
  165. }
  166. },
  167. computed: {
  168. isChange() {
  169. return this.tabsIndex > 1;
  170. },
  171. genTabsList() {
  172. let tabsList = [{
  173. name: "请选择"
  174. }];
  175. // 根据选中的值动态生成tabs
  176. for (let i = 0; i < this.selectedValueIndexs.length; i++) {
  177. if (this.selectedValueIndexs[i] !== undefined && this.levelList[i]) {
  178. const selectedItem = this.levelList[i][this.selectedValueIndexs[i]];
  179. if (selectedItem) {
  180. tabsList[i] = {
  181. name: selectedItem[this.labelKey]
  182. };
  183. // 如果还有下一级,则添加"请选择"
  184. if (i === this.selectedValueIndexs.length - 1 &&
  185. selectedItem[this.childrenKey] &&
  186. selectedItem[this.childrenKey].length > 0) {
  187. tabsList.push({
  188. name: "请选择"
  189. });
  190. }
  191. }
  192. }
  193. }
  194. return tabsList;
  195. },
  196. uZIndex() {
  197. // 如果用户有传递z-index值,优先使用
  198. return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
  199. },
  200. closeIconColor() {
  201. return 'var(--up-main-color, #303133)';
  202. },
  203. levelPaneStyle() {
  204. return {
  205. backgroundColor: 'var(--up-bg-color, #f7f7f7)'
  206. };
  207. }
  208. },
  209. // 新增confirm事件
  210. emits: ['update:modelValue', 'update:show', 'change', 'confirm', 'cancel'],
  211. methods: {
  212. t,
  213. getSelectedValues() {
  214. const result = [];
  215. for (let i = 0; i < this.selectedValueIndexs.length; i++) {
  216. const selectedIndex = this.selectedValueIndexs[i];
  217. if (selectedIndex === undefined) continue;
  218. if (!this.levelList[i] || !this.levelList[i][selectedIndex]) continue;
  219. result.push(this.levelList[i][selectedIndex][this.valueKey]);
  220. }
  221. return result;
  222. },
  223. initLevelList() {
  224. // 初始化第一级数据
  225. if (this.data && this.data.length > 0) {
  226. this.levelList = [this.data];
  227. this.selectedValueIndexs = [];
  228. }
  229. },
  230. setDefaultValue() {
  231. // 检查data是否为空
  232. if (!this.data || this.data.length == 0) return;
  233. // 检查modelValue是否为空
  234. if (!this.modelValue || this.modelValue.length == 0) {
  235. this.confirmValues = [];
  236. return;
  237. }
  238. // 根据默认值设置选中项
  239. // 根据modelValue获取indexs给selectedValueIndexs
  240. this.selectedValueIndexs = [];
  241. this.levelList = []; // 设置层级数据为空
  242. let currentLevelData = this.data;
  243. for (let i = 0; i < this.modelValue.length; i++) {
  244. const value = this.modelValue[i];
  245. const index = currentLevelData.findIndex(item => item[this.valueKey] === value);
  246. this.levelList[i] = currentLevelData; // 设置每一层级的数据
  247. if (index !== -1) {
  248. this.selectedValueIndexs.push(index);
  249. // 更新下一级的数据
  250. if (currentLevelData[index][this.childrenKey]) {
  251. currentLevelData = currentLevelData[index][this.childrenKey];
  252. } else {
  253. // 如果没有子级数据,则停止处理
  254. break;
  255. }
  256. } else {
  257. // 如果找不到匹配项,则停止处理
  258. break;
  259. }
  260. }
  261. // 同步确认值,避免“仅回显未改动时确认返回空数组”
  262. this.confirmValues = this.getSelectedValues();
  263. },
  264. close() {
  265. this.$emit('cancel');
  266. this.$emit('update:show', false);
  267. },
  268. tabsChange(item) {
  269. },
  270. levelChange(levelIndex, index) {
  271. // 设置当前级的选中值
  272. this.$set(this.selectedValueIndexs, levelIndex, index);
  273. // 清除后续级别的选中值
  274. this.selectedValueIndexs.splice(levelIndex + 1);
  275. this.tabsIndex = Math.min(this.tabsIndex, levelIndex);
  276. // 清除后续级别的列表
  277. this.levelList.splice(levelIndex + 1);
  278. // 获取当前选中项
  279. const currentItem = this.levelList[levelIndex][index];
  280. // 如果有子级数据,则初始化下一级
  281. if (currentItem && currentItem[this.childrenKey] && currentItem[this.childrenKey].length > 0) {
  282. // 确保levelList数组足够长
  283. if (this.levelList.length <= levelIndex + 1) {
  284. this.levelList.push(currentItem[this.childrenKey]);
  285. } else {
  286. this.$set(this.levelList, levelIndex + 1, currentItem[this.childrenKey]);
  287. }
  288. // 切换到下一级tab
  289. this.tabsIndex = levelIndex + 1;
  290. } else {
  291. // 没有子级数据,说明是最后一级
  292. if (this.autoClose) {
  293. // 如果启用自动关闭,则触发change事件并关闭
  294. this.emitChange();
  295. this.handleConfirm();
  296. } else {
  297. // 否则只触发change事件,不关闭
  298. this.emitChange(false);
  299. }
  300. }
  301. },
  302. // 修改emitChange方法,增加closePopup参数
  303. emitChange(closePopup = true) {
  304. // 构造选中结果
  305. const result = this.getSelectedValues();
  306. // 更新confirmValues
  307. this.confirmValues = [...result];
  308. // 触发change事件,返回value数组
  309. this.$emit('change', this.confirmValues);
  310. // 根据参数决定是否关闭弹窗
  311. if (closePopup) {
  312. this.close();
  313. }
  314. },
  315. handleCancel() {
  316. this.close();
  317. },
  318. handleConfirm() {
  319. const values = this.confirmValues.length ? this.confirmValues : this.getSelectedValues();
  320. this.confirmValues = [...values];
  321. // 确认时触发confirm事件
  322. this.$emit('update:modelValue', values);
  323. this.$emit('confirm', values);
  324. this.close();
  325. },
  326. // 跳转父节点
  327. toFatherIndex(index){
  328. this.tabsIndex = index;
  329. },
  330. }
  331. }
  332. </script>
  333. <style lang="scss">
  334. .area-box {
  335. width: 100%;
  336. overflow: hidden;
  337. height: 800rpx;
  338. >view {
  339. width: 150%;
  340. transition: transform 0.3s ease-in-out 0s;
  341. transform: translateX(0);
  342. &.change {
  343. // transform: translateX(-33.3333333%);
  344. }
  345. }
  346. .area-item {
  347. // width: 750rpx;
  348. height: 800rpx;
  349. }
  350. }
  351. // 添加按钮区域样式
  352. .u-cascader-action {
  353. border-top: 1px solid var(--up-border-color, #eee);
  354. }
  355. </style>