| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799 |
- <script setup lang="ts">
- import { VueDraggable } from 'vue-draggable-plus'
- import { useRoute } from 'vue-router'
- const route = useRoute()
- const dialogVisible = ref(false)
- // search筛选的字段
- const searchColumn = ref('')
- // search筛选的options
- const searchOptions: any = ref()
- // 右侧箭头消失所需要translateX的值
- const rightArrowHideDistance = ref(0)
- // 控制tab栏的左右切换箭头
- const handleTabArrow = () => {
- const parentElement: HTMLElement | null = document.querySelector('.left-all-columns')
- if (!parentElement) return
- // 左侧切换箭头
- const leftArrow: HTMLElement | null = parentElement.querySelector('.el-tabs__nav-prev')
- // 右侧切换箭头
- const rightArrow: HTMLElement | null = parentElement.querySelector('.el-tabs__nav-next')
- const targetObserverElement = parentElement.querySelector('.el-tabs__nav')
- if (!targetObserverElement || !leftArrow || !rightArrow) return
- // 创建一个函数来获取 translateX
- const getTranslateX = () => {
- const style = window.getComputedStyle(targetObserverElement)
- const matrix = style.transform
- if (matrix !== 'none' && matrix) {
- // 提取 matrix 中的 translateX 值
- const values = matrix.match(/matrix\(([^)]+)\)/)?.[1].split(', ')
- if (!values) return 0
- const translateX = parseFloat(values[4])
- return translateX
- }
- return 0 // 如果没有 transform 或 translateX,默认返回 0
- }
- // 检查并更新箭头显示状态
- const updateArrowVisibility = () => {
- const translateX = getTranslateX()
- if (translateX === 0) {
- leftArrow.style.display = 'none'
- } else {
- leftArrow.style.display = 'inline-block'
- }
- if (translateX === rightArrowHideDistance.value) {
- rightArrow.style.display = 'none'
- } else {
- rightArrow.style.display = 'inline-block'
- }
- }
- // 监听 transitionend 事件,等待动画结束后再获取 translateX 值
- targetObserverElement.addEventListener('transitionend', (event: any) => {
- if (event.propertyName === 'transform') {
- // 只有 transform 动画结束时才触发
- updateArrowVisibility()
- }
- })
- // 初次运行时手动检查一次
- updateArrowVisibility()
- }
- // 筛选选中时滚动到对应的元素
- const handleDocumentClick = (event: any) => {
- if (!scrollTargetElement.value.contains(event.target)) {
- scrollTargetElement.value.className = scrollTargetElement.value.className.replace(
- 'search-select-item',
- ''
- )
- scrollTargetElement.value = null
- document.removeEventListener('click', handleDocumentClick)
- }
- }
- const scrollTargetElement = ref()
- const scrollToItem = (itemId: string) => {
- if (activeName.value !== 'All') {
- activeName.value = 'All'
- }
- setTimeout(() => {
- // 重置
- if (scrollTargetElement.value) {
- scrollTargetElement.value.className = scrollTargetElement.value.className.replace(
- 'search-select-item',
- ''
- )
- }
- // 获取目标元素
- scrollTargetElement.value = document.querySelector(`[data-field='${itemId}']`)
- if (scrollTargetElement.value) {
- // 使用 scrollIntoView 滚动到该元素
- scrollTargetElement.value.scrollIntoView({ behavior: 'smooth', block: 'center' })
- scrollTargetElement.value.className += ' search-select-item'
- // 或者使用自定义滚动
- // const container = this.$refs.dataContainer
- // container.scrollTop = targetElement.offsetTop - container.offsetTop
- document.addEventListener('click', handleDocumentClick)
- }
- }, 100)
- }
- // 系统首次加载时,会有引导操作
- let firstLoad = ref()
- const step1 = ref()
- const open1 = ref(false)
- const isShowStep1 = ref(false)
- const step2 = ref()
- const open2 = ref(false)
- const isShowStep2 = ref(false)
- const handleCloseTour = (stepStr: string) => {
- if (stepStr === 'step1') {
- isShowStep1.value = false
- open1.value = false
- } else {
- isShowStep2.value = false
- open2.value = false
- }
- localStorage.setItem('firstLoadCustomizeColumns', 'true')
- // firstLoad = 'true'
- }
- // 左侧选中的tab
- const activeName = ref()
- // 分组列
- const groupColumns: any = ref([])
- // 所有数据
- const allDataCopy: any = ref()
- const loading = ref(false)
- // 获取数据
- const getData = async (reset?: string) => {
- loading.value = true
- let paramsData: any = { ...params.value.getData }
- if (reset === 'yes') {
- paramsData.reset = 'yes'
- }
- await $api.getTableSettingColumns(paramsData).then((res: any) => {
- if (res.code === 200) {
- // allDataCopy就是所有的数据
- allDataCopy.value = res.data.GroupColumnsAll
- groupColumns.value = res.data.GroupColumnsLeft
- activeName.value = allDataCopy.value?.[0]?.name
- searchOptions.value = res.data.GroupColumnsLeft?.[0]?.children
- // 右侧选中的数据
- selectColumns.value = res.data.GroupColumnsRight
- nextTick(() => {
- handleTabArrow()
- // 八秒后关闭引导
- if (!firstLoad.value) {
- setTimeout(() => {
- handleCloseTour('step1')
- handleCloseTour('step2')
- }, 8000)
- }
- })
- }
- })
- loading.value = false
- }
- const params = ref()
- // rightDistance是右侧箭头消失所需要translateX的值
- const openDialog = async (paramsData: Object, rightDistance: number) => {
- firstLoad.value = localStorage.getItem('firstLoadCustomizeColumns')
- params.value = paramsData
- dialogVisible.value = true
- await getData()
- rightArrowHideDistance.value = rightDistance
- nextTick(() => {
- if (!firstLoad.value) {
- open1.value = true
- isShowStep1.value = true
- open2.value = true
- isShowStep2.value = true
- }
- })
- }
- const selectColumns: any = ref([])
- // 左侧Icon的显隐
- const hoverAllIcon = ref('')
- // 右侧Icon的显隐
- const hoverSelectIcon = ref('')
- const handleAddSelect = (item: any) => {
- groupColumns.value.forEach((groupItem: any) => {
- groupItem.children.forEach((child: any, index: number) => {
- if (child.field === item.field) {
- groupItem.children.splice(index, 1)
- }
- })
- })
- selectColumns.value.push(item)
- }
- // 从左侧拖拽到右侧时,删除其他分组中相同的数据
- const handleLeftRemove = (e: any) => {
- if (e.to === e.from) return
- const curItem = e.data
- groupColumns.value.forEach((groupItem: any) => {
- groupItem.children.forEach((child: any, index: number) => {
- if (child.field === curItem.field) {
- groupItem.children.splice(index, 1)
- }
- })
- })
- }
- // 从右侧拖拽到左侧时,左侧根据分组添加数据
- const handleRightRemove = (e: any) => {
- if (e.to === e.from) return
- const curItem = e.data
- // 获取当前移动项移入到了那一组
- const curGroup = groupColumns.value.find((item: any) => {
- return item.name == activeName.value
- })
- // 获取当前项应该对应哪一组
- const originalGroup = allDataCopy.value.find((item: any) => {
- if (item.name === 'All') {
- return false
- }
- const index = item.children.findIndex((child: any) => {
- return child.field === curItem.field
- })
- return index !== -1
- })
- if (curGroup.name !== originalGroup.name && curGroup.name !== 'All') {
- // 从当前分组中删除移入的数据
- curGroup.children.forEach((item: any, index: number) => {
- item.field === curItem.field && curGroup.children.splice(index, 1)
- })
- // 在对应分组中添加移入的数据
- groupColumns.value.forEach((item: any) => {
- item.name === originalGroup.name && item.children.push(curItem)
- })
- // 添加到All分组里
- groupColumns.value[0].children.push(curItem)
- } else if (curGroup.name === 'All') {
- // 在对应分组中添加移入的数据
- groupColumns.value.forEach((item: any) => {
- item.name === originalGroup.name && item.children.push(curItem)
- })
- } else if (curGroup.name === originalGroup.name) {
- groupColumns.value[0].children.push(curItem)
- }
- }
- // 点击右侧的减号删除选中的列,并添加到左侧
- const handleDeleteSelect = (curItem: any) => {
- selectColumns.value.forEach((item: any, index: number) => {
- if (item.field === curItem.field) {
- selectColumns.value.splice(index, 1)
- }
- })
- // 获取当前项应该对应哪一组
- const originalGroup = allDataCopy.value.find((item: any) => {
- if (item.name === 'All') {
- return false
- }
- const index = item.children.findIndex((child: any) => {
- return child.field === curItem.field
- })
- return index !== -1
- })
- // 在对应分组中添加移入的数据
- groupColumns.value.forEach((item: any) => {
- item.name === originalGroup.name && item.children.push(curItem)
- })
- // 添加到All分组里
- groupColumns.value[0].children.push(curItem)
- }
- const handleMoveUpSelect = (item: any) => {
- const index = selectColumns.value.findIndex((i: any) => i.field === item.field)
- if (index === 0) return
- const temp = selectColumns.value[index]
- selectColumns.value[index] = selectColumns.value[index - 1]
- selectColumns.value[index - 1] = temp
- }
- const handleMoveDownSelect = (item: any) => {
- const index = selectColumns.value.findIndex((i: any) => i.field === item.field)
- if (index === selectColumns.value.length - 1) return
- const temp = selectColumns.value[index]
- selectColumns.value[index] = selectColumns.value[index + 1]
- selectColumns.value[index + 1] = temp
- }
- const emits = defineEmits<{
- customize: []
- reset: []
- }>()
- const handleReset = () => {
- getData('yes')
- }
- const handleApply = () => {
- const columnsList = selectColumns.value.map((item: any) => {
- return item.ids
- })
- $api
- .saveTableSettingColumns({
- ...params.value.saveData,
- ids: columnsList
- })
- .then((res: any) => {
- if (res.code === 200) {
- // ElMessage.success('Save successfully')
- emits('customize')
- dialogVisible.value = false
- }
- })
- }
- const clearData = () => {
- open1.value = false
- open2.value = false
- activeName.value = ''
- groupColumns.value = []
- selectColumns.value = []
- searchColumn.value = ''
- }
- defineExpose({
- openDialog
- })
- </script>
- <template>
- <el-dialog
- class="customize-columns"
- v-model="dialogVisible"
- :width="1000"
- title="Customize Columns"
- @close="clearData"
- >
- <div class="search-header">
- <div class="search-input" ref="searchRef">
- <el-select
- v-model="searchColumn"
- @change="scrollToItem"
- filterable
- placeholder="Search columns you preffered"
- >
- <template #prefix>
- <span class="iconfont_icon">
- <svg class="iconfont" aria-hidden="true">
- <use xlink:href="#icon-icon_search_b"></use>
- </svg>
- </span>
- </template>
- <el-option
- v-for="item in searchOptions"
- :key="item.field"
- :label="item.label"
- :value="item.field"
- />
- </el-select>
- </div>
- <div class="tips">
- <span style="font-size: 16px">* </span>
- <span
- >Drag item over to this selection or click "add" icon to show the column on your
- {{ route.path.includes('booking') ? 'booking' : 'shipment' }} list</span
- >
- </div>
- </div>
- <div class="draggable-list">
- <div class="left-all-columns" v-vloading="loading">
- <div class="tabs">
- <el-tabs v-model="activeName">
- <el-tab-pane
- v-for="groupItem in groupColumns"
- :key="groupItem.name"
- :label="groupItem.name"
- :name="groupItem.name"
- >
- <VueDraggable
- v-model="groupItem.children"
- class="column-list"
- ghost-class="ghost-column"
- :forceFallback="true"
- fallbackClass="fallback-class"
- group="customizeColumns"
- item-key="field"
- @end="handleLeftRemove"
- >
- <template v-for="(item, index) in groupItem.children" :key="item.field">
- <div
- :data-field="item.field"
- class="column-item"
- @mouseenter="hoverAllIcon = item.field"
- @mouseleave="hoverAllIcon = ''"
- >
- <span class="font_family icon-icon_dragsort__b draggable-icon"></span>
- <span class="title">{{ item.label }}</span>
- <span
- ref="step1"
- v-if="hoverAllIcon === item.field || (index === 0 && isShowStep1)"
- class="font_family icon-icon_add_b move-icon"
- @click="handleAddSelect(item)"
- ></span>
- </div>
- </template>
- </VueDraggable>
- </el-tab-pane>
- </el-tabs>
- </div>
- </div>
- <div class="right-select-columns">
- <div class="title">
- Selected columns on your
- {{ route.path.includes('booking') ? 'booking' : 'shipment' }} list
- </div>
- <VueDraggable
- v-vloading="loading"
- v-model="selectColumns"
- class="column-list"
- ghost-class="ghost-column"
- :forceFallback="true"
- fallback-class="fallback-class"
- group="customizeColumns"
- item-key="field"
- @end="handleRightRemove"
- >
- <template v-for="(item, index) in selectColumns" :key="item.field">
- <div
- class="column-item"
- @mouseenter="hoverSelectIcon = item.field"
- @mouseleave="hoverSelectIcon = ''"
- >
- <span
- class="font_family icon-icon_dragsort__b draggable-icon"
- style="font-size: 16px"
- ></span>
- <span class="title">{{ item.label }}</span>
- <span
- v-if="hoverSelectIcon === item.field || (index === 0 && isShowStep2)"
- class="font_family icon-icon_moveup_b move-icon"
- @click="handleMoveUpSelect(item)"
- ></span>
- <span
- v-if="hoverSelectIcon === item.field || (index === 0 && isShowStep2)"
- class="font_family icon-icon_movedown_b move-icon"
- @click="handleMoveDownSelect(item)"
- ></span>
- <span
- ref="step2"
- v-if="hoverSelectIcon === item.field || (index === 0 && isShowStep2)"
- class="font_family icon-icon_reduce_b move-icon"
- @click="handleDeleteSelect(item)"
- ></span>
- </div>
- </template>
- </VueDraggable>
- </div>
- </div>
- <template #footer>
- <el-button
- type="default"
- style="height: 40px; padding: 8px 40px"
- @click="dialogVisible = false"
- >Cancel</el-button
- >
- <el-button type="default" style="height: 40px; padding: 8px 20px" @click="handleReset"
- >Reset to default</el-button
- >
- <el-button
- class="el-button--dark"
- style="height: 40px; padding: 8px 40px"
- @click="handleApply"
- >
- Apply
- </el-button>
- </template>
- <el-tour
- :target-area-clickable="false"
- class="step1-tour"
- v-model="open1"
- :mask="false"
- type="primary"
- v-if="step1?.[0]"
- >
- <el-tour-step :show-close="false" :target="step1?.[0]">
- <template #default>
- <div class="description">
- <span>Drag</span> items to the right group or click the "<span>Add</span>" icon to add
- columns to the {{ route.path.includes('booking') ? 'booking' : 'shipment' }} list.
- </div>
- <div class="got-it-text" @click="handleCloseTour('step1')">Got it</div>
- </template>
- </el-tour-step>
- </el-tour>
- <el-tour
- :target-area-clickable="false"
- class="step2-tour"
- v-model="open2"
- type="primary"
- :mask="false"
- v-if="step2?.[0]"
- >
- <el-tour-step :show-close="false" :target="step2?.[0]">
- <template #default>
- <div class="description">
- <span>Drag</span> items to the left group or click the "<span>Remove</span>" icon to
- delete columns from the
- {{ route.path.includes('booking') ? 'booking' : 'shipment' }} list.
- </div>
- <div class="description">
- <span>Drag</span> items up or down to reorder the
- {{ route.path.includes('booking') ? 'booking' : 'shipment' }} list, or use the "<span
- >Move up</span
- >" and "<span>Move down</span>" icons.
- </div>
- <div class="got-it-text" @click="handleCloseTour('step2')">Got it</div>
- </template>
- </el-tour-step>
- </el-tour>
- </el-dialog>
- </template>
- <style lang="scss">
- .customize-columns {
- .search-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 8px;
- padding: 10px 0;
- .search-input {
- width: 50%;
- padding-right: 16px;
- }
- .tips {
- display: flex;
- align-items: flex-start;
- gap: 3px;
- width: 50%;
- padding-left: 5px;
- vertical-align: middle;
- span {
- font-size: 12px;
- color: var(--color-neutral-2);
- }
- }
- }
- .draggable-list {
- display: flex;
- user-select: none;
- gap: 8px;
- }
- }
- .right-select-columns,
- .left-all-columns {
- width: 50%;
- .column-list {
- height: 400px;
- overflow: auto;
- .column-item {
- display: flex;
- align-items: center;
- height: 40px;
- margin-bottom: 5px;
- padding-left: 12px;
- border: 1px solid var(--color-border);
- border-radius: 6px;
- &:hover {
- background-color: #fff1e5;
- box-shadow: 4px 4px 32px 0px rgba(0, 0, 0, 0.1);
- }
- & > .title {
- flex: 1;
- }
- span.draggable-icon {
- margin-right: 12px;
- color: var(--color-neutral-3);
- }
- .font_family {
- font-size: 16px;
- cursor: pointer;
- margin-right: 16px;
- }
- .move-icon {
- &:hover {
- color: var(--color-theme);
- }
- }
- }
- }
- .ghost-column {
- opacity: 0;
- cursor: move !important;
- }
- .fallback-class {
- opacity: 1 !important;
- background-color: #fff1e5 !important;
- cursor: move !important;
- }
- }
- .left-all-columns {
- border: 1px solid var(--color-border);
- border-radius: 12px;
- .tabs {
- position: relative;
- height: 100%;
- .el-tabs {
- .el-tabs__header {
- margin-bottom: 0px;
- border-bottom: 1px solid #ebeef5;
- }
- .el-tabs__item {
- padding: 10px;
- }
- }
- }
- .column-list {
- padding: 8px;
- padding-bottom: 0px;
- }
- .search-select-item {
- border: 1px solid var(--color-theme) !important;
- box-shadow: 2px 2px 12px 0px rgba(237, 109, 0, 0.2);
- .title {
- color: var(--color-theme) !important;
- }
- }
- }
- .right-select-columns {
- background-color: #fffbf7;
- padding-top: 0;
- border: 1px dashed var(--color-border);
- border-radius: 12px;
- & > .title {
- height: 40px;
- padding: 8px;
- line-height: 24px;
- font-size: 16px;
- font-weight: 700;
- }
- .column-list {
- padding: 8px;
- padding-bottom: 0px;
- }
- .column-item {
- background-color: #fff;
- }
- }
- </style>
- <style lang="scss">
- .left-all-columns {
- .el-tabs__nav-prev,
- .el-tabs__nav-next {
- height: 40px;
- width: 40px;
- }
- .el-tabs__item {
- color: var(--color-neutral-1);
- font-weight: 400;
- font-size: 14px;
- }
- .el-tabs__item.is-active,
- .el-tabs__item:hover {
- font-weight: 700;
- font-size: 14px;
- color: var(--color-neutral-1);
- }
- .el-tabs__nav-prev {
- border-right: 1px solid var(--color-border);
- box-shadow: 1px 0px 10px rgba(0, 0, 0, 0.2);
- /* 左侧阴影 */
- }
- .el-tabs__nav-next {
- border-left: 1px solid var(--color-border);
- box-shadow: -1px 0px 10px rgba(0, 0, 0, 0.2);
- /* 左侧阴影 */
- }
- .el-tabs__nav-wrap {
- padding: 0 40px;
- }
- .el-tabs__item.is-active,
- .el-tabs__item:hover {
- color: var(--color-theme);
- }
- .el-tabs__active-bar {
- background-color: var(--color-theme);
- }
- }
- .search-header {
- & > .search-input {
- .el-select {
- width: 100%;
- .el-select__wrapper {
- border-radius: 20px;
- }
- }
- }
- }
- </style>
- <style lang="scss">
- .step1-tour {
- .el-tour__content {
- width: 240px;
- height: 124px;
- background-color: var(--color-theme);
- z-index: 9999 !important;
- }
- .el-tour__arrow {
- background-color: var(--color-theme);
- }
- .el-tour__footer {
- display: none;
- }
- }
- .step2-tour {
- .el-tour__content {
- width: 240px;
- height: 200px;
- background-color: var(--color-theme);
- z-index: 9999 !important;
- }
- .el-tour__arrow {
- background-color: var(--color-theme);
- }
- .el-tour__footer {
- display: none;
- }
- }
- .step1-tour,
- .step2-tour {
- .el-tour__header {
- display: none;
- }
- .description {
- margin-bottom: 16px;
- color: white;
- line-height: 22px;
- span {
- color: white;
- font-weight: 600;
- }
- }
- .got-it-text {
- float: right;
- color: white;
- font-weight: 700;
- cursor: pointer;
- }
- }
- </style>
|