AppLinkSelectDialog.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <template>
  2. <Dialog v-model="dialogVisible" title="选择链接" width="65%">
  3. <div class="h-500px flex gap-8px">
  4. <!-- 左侧分组列表 -->
  5. <el-scrollbar wrap-class="h-full" ref="groupScrollbar" view-class="flex flex-col">
  6. <el-button
  7. v-for="(group, groupIndex) in APP_LINK_GROUP_LIST"
  8. :key="groupIndex"
  9. :class="[
  10. 'm-r-16px m-l-0px! justify-start! w-90px',
  11. { active: activeGroup === group.name }
  12. ]"
  13. ref="groupBtnRefs"
  14. :text="activeGroup !== group.name"
  15. :type="activeGroup === group.name ? 'primary' : 'default'"
  16. @click="handleGroupSelected(group.name)"
  17. >
  18. {{ group.name }}
  19. </el-button>
  20. </el-scrollbar>
  21. <!-- 右侧链接列表 -->
  22. <el-scrollbar class="h-full flex-1" @scroll="handleScroll" ref="linkScrollbar">
  23. <div v-for="(group, groupIndex) in APP_LINK_GROUP_LIST" :key="groupIndex">
  24. <!-- 分组标题 -->
  25. <div class="font-bold" ref="groupTitleRefs">{{ group.name }}</div>
  26. <!-- 链接列表 -->
  27. <el-tooltip
  28. v-for="(appLink, appLinkIndex) in group.links"
  29. :key="appLinkIndex"
  30. :content="appLink.path"
  31. placement="bottom"
  32. :show-after="300"
  33. >
  34. <el-button
  35. class="m-b-8px m-r-8px m-l-0px!"
  36. :type="isSameLink(appLink.path, activeAppLink.path) ? 'primary' : 'default'"
  37. @click="handleAppLinkSelected(appLink)"
  38. >
  39. {{ appLink.name }}
  40. </el-button>
  41. </el-tooltip>
  42. </div>
  43. </el-scrollbar>
  44. </div>
  45. <!-- 底部对话框操作按钮 -->
  46. <template #footer>
  47. <el-button type="primary" @click="handleSubmit">确 定</el-button>
  48. <el-button @click="dialogVisible = false">取 消</el-button>
  49. </template>
  50. </Dialog>
  51. <Dialog v-model="detailSelectDialog.visible" title="" width="50%">
  52. <el-form class="min-h-200px">
  53. <el-form-item
  54. label="选择分类"
  55. v-if="detailSelectDialog.type === APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST"
  56. >
  57. <ProductCategorySelect
  58. v-model="detailSelectDialog.id"
  59. :parent-id="0"
  60. @update:model-value="handleProductCategorySelected"
  61. />
  62. </el-form-item>
  63. </el-form>
  64. </Dialog>
  65. </template>
  66. <script lang="ts" setup>
  67. import { APP_LINK_GROUP_LIST, APP_LINK_TYPE_ENUM, AppLink } from './data'
  68. import { ButtonInstance, ScrollbarInstance } from 'element-plus'
  69. import { split } from 'lodash-es'
  70. import ProductCategorySelect from '@/views/mall/product/category/components/ProductCategorySelect.vue'
  71. import { getUrlNumberValue } from '@/utils'
  72. // APP 链接选择弹框
  73. defineOptions({ name: 'AppLinkSelectDialog' })
  74. // 选中的分组,默认选中第一个
  75. const activeGroup = ref(APP_LINK_GROUP_LIST[0].name)
  76. // 选中的 APP 链接
  77. const activeAppLink = ref({} as AppLink)
  78. /** 打开弹窗 */
  79. const dialogVisible = ref(false)
  80. const open = (link: string) => {
  81. // 进入页面时先重置 activeAppLink
  82. activeAppLink.value = { name: '', path: '' }
  83. dialogVisible.value = true
  84. // 滚动到当前的链接
  85. const group = APP_LINK_GROUP_LIST.find((group) =>
  86. group.links.some((linkItem) => {
  87. const sameLink = isSameLink(linkItem.path, link)
  88. if (sameLink) {
  89. activeAppLink.value = { ...linkItem, path: link }
  90. }
  91. return sameLink
  92. })
  93. )
  94. if (group) {
  95. // 使用 nextTick 的原因:可能 Dom 还没生成,导致滚动失败
  96. nextTick(() => handleGroupSelected(group.name))
  97. }
  98. }
  99. defineExpose({ open })
  100. // 处理 APP 链接选中
  101. const handleAppLinkSelected = (appLink: AppLink) => {
  102. // 只有不同链接时才更新(避免重复触发)
  103. if (!isSameLink(appLink.path, activeAppLink.value.path)) {
  104. // 如果新链接的 path 为空,则沿用当前 activeAppLink 的 path
  105. const path = appLink.path || activeAppLink.value.path
  106. activeAppLink.value = { ...appLink, path: path }
  107. }
  108. switch (appLink.type) {
  109. case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST:
  110. detailSelectDialog.value.visible = true
  111. detailSelectDialog.value.type = appLink.type
  112. // 返显
  113. detailSelectDialog.value.id =
  114. getUrlNumberValue('id', 'http://127.0.0.1' + activeAppLink.value.path) || undefined
  115. break
  116. default:
  117. break
  118. }
  119. }
  120. // 处理绑定值更新
  121. const emit = defineEmits<{
  122. change: [link: string]
  123. appLinkChange: [appLink: AppLink]
  124. }>()
  125. const handleSubmit = () => {
  126. dialogVisible.value = false
  127. emit('change', activeAppLink.value.path)
  128. emit('appLinkChange', activeAppLink.value)
  129. }
  130. // 分组标题引用列表
  131. const groupTitleRefs = ref<HTMLInputElement[]>([])
  132. /**
  133. * 处理右侧链接列表滚动
  134. * @param scrollTop 滚动条的位置
  135. */
  136. const handleScroll = ({ scrollTop }: { scrollTop: number }) => {
  137. const titleEl = groupTitleRefs.value.find((titleEl: HTMLInputElement) => {
  138. // 获取标题的位置信息
  139. const { offsetHeight, offsetTop } = titleEl
  140. // 判断标题是否在可视范围内
  141. return scrollTop >= offsetTop && scrollTop < offsetTop + offsetHeight
  142. })
  143. // 只需处理一次
  144. if (titleEl && activeGroup.value !== titleEl.textContent) {
  145. activeGroup.value = titleEl.textContent || ''
  146. // 同步左侧的滚动条位置
  147. scrollToGroupBtn(activeGroup.value)
  148. }
  149. }
  150. // 右侧滚动条
  151. const linkScrollbar = ref<ScrollbarInstance>()
  152. // 处理分组选中
  153. const handleGroupSelected = (group: string) => {
  154. activeGroup.value = group
  155. const titleRef = groupTitleRefs.value.find((item: HTMLInputElement) => item.textContent === group)
  156. if (titleRef) {
  157. // 滚动分组标题
  158. linkScrollbar.value?.setScrollTop(titleRef.offsetTop)
  159. }
  160. }
  161. // 分组滚动条
  162. const groupScrollbar = ref<ScrollbarInstance>()
  163. // 分组引用列表
  164. const groupBtnRefs = ref<ButtonInstance[]>([])
  165. // 自动滚动分组按钮,确保分组按钮保持在可视区域内
  166. const scrollToGroupBtn = (group: string) => {
  167. const groupBtn = groupBtnRefs.value
  168. .map((btn: ButtonInstance) => btn['ref'])
  169. .find((ref: HTMLButtonElement) => ref.textContent === group)
  170. if (groupBtn) {
  171. groupScrollbar.value?.setScrollTop(groupBtn.offsetTop)
  172. }
  173. }
  174. // 是否为相同的链接(不比较参数,只比较链接)
  175. const isSameLink = (link1: string, link2: string) => {
  176. return split(link1, '?', 1)[0] === split(link2, '?', 1)[0]
  177. }
  178. // 详情选择对话框
  179. const detailSelectDialog = ref<{
  180. visible: boolean
  181. id?: number
  182. type?: APP_LINK_TYPE_ENUM
  183. }>({
  184. visible: false,
  185. id: undefined,
  186. type: undefined
  187. })
  188. // 处理详情选择
  189. const handleProductCategorySelected = (id: number) => {
  190. const url = new URL(activeAppLink.value.path, 'http://127.0.0.1')
  191. // 修改 id 参数
  192. url.searchParams.set('id', `${id}`)
  193. // 排除域名
  194. activeAppLink.value.path = `${url.pathname}${url.search}`
  195. // 关闭对话框
  196. detailSelectDialog.value.visible = false
  197. // 重置 id
  198. detailSelectDialog.value.id = undefined
  199. }
  200. </script>
  201. <style lang="scss" scoped></style>