diff --git a/src/packages/components/Tables/Tables/TableScrollBoard/config.ts b/src/packages/components/Tables/Tables/TableScrollBoard/config.ts new file mode 100644 index 00000000..e39ddded --- /dev/null +++ b/src/packages/components/Tables/Tables/TableScrollBoard/config.ts @@ -0,0 +1,29 @@ +import { publicConfig } from '@/packages/public' +import { CreateComponentType } from '@/packages/index.d' +import { TableScrollBoardConfig } from './index' +import cloneDeep from 'lodash/cloneDeep' +import dataJson from './data.json' + +export const option = { + header: ['列1', '列2', '列3'], + dataset: dataJson, + index: true, + columnWidth: [30, 100, 100], + align: ['center','right','right','right'], + rowNum: 5, + waitTime: 2, + headerHeight: 35, + carousel: 'single', + headerBGC: '#00BAFF', + oddRowBGC: '#003B51', + evenRowBGC: '#0A2732', +} + +export default class Config + extends publicConfig + implements CreateComponentType +{ + public key = TableScrollBoardConfig.key + public chartConfig = cloneDeep(TableScrollBoardConfig) + public option = cloneDeep(option) +} diff --git a/src/packages/components/Tables/Tables/TableScrollBoard/config.vue b/src/packages/components/Tables/Tables/TableScrollBoard/config.vue new file mode 100644 index 00000000..9dd1e12d --- /dev/null +++ b/src/packages/components/Tables/Tables/TableScrollBoard/config.vue @@ -0,0 +1,121 @@ +<template> + <CollapseItem name="列表" :expanded="true"> + <SettingItemBox name="基础"> + <SettingItem name="表行数"> + <n-input-number + v-model:value="optionData.rowNum" + :min="1" + size="small" + placeholder="请输入自动计算" + ></n-input-number> + </SettingItem> + <SettingItem name="轮播时间(s)"> + <n-input-number + v-model:value="optionData.waitTime" + :min="1" + size="small" + placeholder="请输入轮播时间" + ></n-input-number> + </SettingItem> + <SettingItem name="表头高度"> + <n-input-number + v-model:value="optionData.headerHeight" + :min="1" + size="small" + placeholder="请输入表头高度" + ></n-input-number> + </SettingItem> + <SettingItem name="显示行号"> + <n-switch size="small" v-model:value="optionData.index" /> + </SettingItem> + + </SettingItemBox> + + <SettingItemBox name="配置" :alone="true"> + <SettingItem name="表头数据"> + <n-input + v-model:value="header" + :min="1" + size="small" + placeholder="表头数据(英文','分割)" + ></n-input> + </SettingItem> + <SettingItem name="列对齐方式"> + <n-input + v-model:value="align" + :min="1" + size="small" + placeholder="对齐方式(英文','分割)" + ></n-input> + </SettingItem> + <SettingItem name="列宽度"> + <n-input + v-model:value="columnWidth" + :min="1" + size="small" + placeholder="列宽度(英文','分割)" + ></n-input> + </SettingItem> + </SettingItemBox> + + <SettingItemBox name="样式"> + <SettingItem name="表头背景色"> + <n-color-picker + size="small" + :modes="['hex']" + v-model:value="optionData.headerBGC" + ></n-color-picker> + </SettingItem> + <SettingItem name="奇数行背景色"> + <n-color-picker + size="small" + :modes="['hex']" + v-model:value="optionData.oddRowBGC" + ></n-color-picker> + </SettingItem> + <SettingItem name="偶数行背景色"> + <n-color-picker + size="small" + :modes="['hex']" + v-model:value="optionData.evenRowBGC" + ></n-color-picker> + </SettingItem> + </SettingItemBox> + </CollapseItem> +</template> + +<script setup lang="ts"> +import { PropType, ref, watch } from 'vue' +import { + CollapseItem, + SettingItemBox, + SettingItem, +} from '@/components/Pages/ChartItemSetting' +import { option } from './config' + +const props = defineProps({ + optionData: { + type: Object as PropType<typeof option>, + required: true, + }, +}) + +const header = ref(props.optionData.header.toString()) +const align = ref(props.optionData.align.toString()) +const columnWidth = ref(props.optionData.columnWidth.toString()) + +watch([header, align, columnWidth],([headerNew,alignNew,columnWidthNew],[headerOld,alignOld,columnWidthOld])=>{ + if(headerNew !== headerOld){ + props.optionData.header = headerNew.split(',') + } + if(alignNew !== alignOld){ + props.optionData.align = alignNew.split(',') + } + if(columnWidthNew !== columnWidthOld){ + // @ts-ignore + props.optionData.columnWidth = columnWidthNew.split(',') + } +}) + + +</script> diff --git a/src/packages/components/Tables/Tables/TableScrollBoard/data.json b/src/packages/components/Tables/Tables/TableScrollBoard/data.json new file mode 100644 index 00000000..2508e6ff --- /dev/null +++ b/src/packages/components/Tables/Tables/TableScrollBoard/data.json @@ -0,0 +1,12 @@ +[ + ["行1列1", "行1列2", "行1列3"], + ["行2列1", "行2列2", "行2列3"], + ["行3列1", "行3列2", "行3列3"], + ["行4列1", "行4列2", "行4列3"], + ["行5列1", "行5列2", "行5列3"], + ["行6列1", "行6列2", "行6列3"], + ["行7列1", "行7列2", "行7列3"], + ["行8列1", "行8列2", "行8列3"], + ["行9列1", "行9列2", "行9列3"], + ["行10列1", "行10列2", "行10列3"] +] diff --git a/src/packages/components/Tables/Tables/TableScrollBoard/index.ts b/src/packages/components/Tables/Tables/TableScrollBoard/index.ts new file mode 100644 index 00000000..0ceb94bb --- /dev/null +++ b/src/packages/components/Tables/Tables/TableScrollBoard/index.ts @@ -0,0 +1,14 @@ +import image from '@/assets/images/chart/tables/table_scrollboard.png' +import { ConfigType, PackagesCategoryEnum } from '@/packages/index.d' +import { ChatCategoryEnum, ChatCategoryEnumName } from '../../index.d' + +export const TableScrollBoardConfig: ConfigType = { + key: 'TableScrollBoard', + chartKey: 'VTableScrollBoard', + conKey: 'VCTableScrollBoard', + title: '轮播列表', + category: ChatCategoryEnum.TABLE, + categoryName: ChatCategoryEnumName.TABLE, + package: PackagesCategoryEnum.TABLES, + image +} diff --git a/src/packages/components/Tables/Tables/TableScrollBoard/index.vue b/src/packages/components/Tables/Tables/TableScrollBoard/index.vue new file mode 100644 index 00000000..9792e796 --- /dev/null +++ b/src/packages/components/Tables/Tables/TableScrollBoard/index.vue @@ -0,0 +1,355 @@ +<template> + <div class="dv-scroll-board"> + <div class="header" v-if="status.header.length && status.mergedConfig" + :style="`background-color: ${status.mergedConfig.headerBGC};`"> + <div class="header-item" v-for="(headerItem, i) in status.header" :key="`${headerItem}${i}`" :style="` + height: ${status.mergedConfig.headerHeight}px; + line-height: ${status.mergedConfig.headerHeight}px; + width: ${status.widths[i]}px; + `" :align="status.aligns[i]" v-html="headerItem" /> + </div> + + <div v-if="status.mergedConfig" class="rows" + :style="`height: ${h - (status.header.length ? status.mergedConfig.headerHeight : 0)}px;`"> + <div class="row-item" v-for="(row, ri) in status.rows" :key="`${row.toString()}${row.scroll}`" :style="` + height: ${status.heights[ri]}px; + line-height: ${status.heights[ri]}px; + background-color: ${status.mergedConfig[row.rowIndex % 2 === 0 ? 'evenRowBGC' : 'oddRowBGC']}; + `"> + <div class="ceil" v-for="(ceil, ci) in row.ceils" :key="`${ceil}${ri}${ci}`" + :style="`width: ${status.widths[ci]}px;`" :align="status.aligns[ci]" v-html="ceil" /> + + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { PropType, onUnmounted, reactive, toRefs, watch, onMounted } from 'vue' +import { CreateComponentType } from '@/packages/index.d' +import { useChartDataFetch } from '@/hooks' +import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore' +import merge from 'lodash/merge' +import cloneDeep from 'lodash/cloneDeep' + +const props = defineProps({ + chartConfig: { + type: Object as PropType<CreateComponentType>, + required: true, + }, +}) + +// 这里能拿到图表宽高等 +const { w, h } = toRefs(props.chartConfig.attr) +// 这里能拿到上面 config.ts 里的 option 数据 +// const { rowNum, headerHeight, index, backgroundColor } = toRefs(props.chartConfig.option) + +const status = reactive({ + defaultConfig: { + /** + * @description Board header + * @type {Array<String>} + * @default header = [] + * @example header = ['column1', 'column2', 'column3'] + */ + header: [], + /** + * @description Board dataset + * @type {Array<Array>} + * @default dataset = [] + */ + dataset: [], + /** + * @description Row num + * @type {Number} + * @default rowNum = 5 + */ + rowNum: 5, + /** + * @description Header background color + * @type {String} + * @default headerBGC = '#00BAFF' + */ + headerBGC: '#00BAFF', + /** + * @description Odd row background color + * @type {String} + * @default oddRowBGC = '#003B51' + */ + oddRowBGC: '#003B51', + /** + * @description Even row background color + * @type {String} + * @default evenRowBGC = '#003B51' + */ + evenRowBGC: '#0A2732', + /** + * @description Scroll wait time + * @type {Number} + * @default waitTime = 2 + */ + waitTime: 2, + /** + * @description Header height + * @type {Number} + * @default headerHeight = 35 + */ + headerHeight: 35, + /** + * @description Column width + * @type {Array<Number>} + * @default columnWidth = [] + */ + columnWidth: [], + /** + * @description Column align + * @type {Array<String>} + * @default align = [] + * @example align = ['left', 'center', 'right'] + */ + align: [], + /** + * @description Show index + * @type {Boolean} + * @default index = false + */ + index: false, + /** + * @description index Header + * @type {String} + * @default indexHeader = '#' + */ + indexHeader: '#', + /** + * @description Carousel type + * @type {String} + * @default carousel = 'single' + * @example carousel = 'single' | 'page' + */ + carousel: 'single', + /** + * @description Pause scroll when mouse hovered + * @type {Boolean} + * @default hoverPause = true + * @example hoverPause = true | false + */ + hoverPause: true + }, + mergedConfig: props.chartConfig.option, + header: [], + rowsData: [], + rows: [{ + ceils: [], + rowIndex: 0, + scroll: 0 + }], + widths: [], + heights: [0], + avgHeight: 0, + aligns: [], + animationIndex: 0, + animationHandler: 0, + updater: 0, + needCalc: false +}) + +const calcData = () => { + mergeConfig() + calcHeaderData() + calcRowsData() + calcWidths() + calcHeights() + calcAligns() + animation(true) +} + +onMounted(()=> { + calcData() +}) + +const mergeConfig = () => { + status.mergedConfig = merge(cloneDeep(status.defaultConfig), props.chartConfig.option) +} + +const calcHeaderData = () => { + let { header, index, indexHeader } = status.mergedConfig + if (!header.length) { + status.header = [] + return + } + header = [...header] + if (index) header.unshift(indexHeader) + status.header = header +} + +const calcRowsData = () => { + let { dataset, index, headerBGC, rowNum } = status.mergedConfig + if (index) { + dataset = dataset.map((row:any, i:number) => { + row = [...row] + const indexTag = `<span class="index" style="background-color: ${headerBGC};border-radius: 3px;padding: 0px 3px;">${i + 1}</span>` + row.unshift(indexTag) + return row + }) + } + dataset = dataset.map((ceils:any, i:number) => ({ ceils, rowIndex: i })) + const rowLength = dataset.length + if (rowLength > rowNum && rowLength < 2 * rowNum) { + dataset = [...dataset, ...dataset] + } + dataset = dataset.map((d:any, i:number) => ({ ...d, scroll: i })) + + status.rowsData = dataset + status.rows = dataset +} + +const calcWidths = () => { + const { mergedConfig, rowsData } = status + const { columnWidth, header } = mergedConfig + const usedWidth = columnWidth.reduce((all:any, ws:number) => all + ws, 0) + let columnNum = 0 + if (rowsData[0]) { + columnNum = (rowsData[0] as any).ceils.length + } else if (header.length) { + columnNum = header.length + } + const avgWidth = (w.value - usedWidth) / (columnNum - columnWidth.length) + const widths = new Array(columnNum).fill(avgWidth) + status.widths = merge(widths, columnWidth) +} + +const calcHeights = (onresize = false) => { + const { mergedConfig, header } = status + const { headerHeight, rowNum, dataset } = mergedConfig + let allHeight = h.value + if (header.length) allHeight -= headerHeight + const avgHeight = allHeight / rowNum + status.avgHeight = avgHeight + if (!onresize) status.heights = new Array(dataset.length).fill(avgHeight) +} + +const calcAligns = () => { + const { header, mergedConfig } = status + + const columnNum = header.length + + let aligns = new Array(columnNum).fill('left') + + const { align } = mergedConfig + + status.aligns = merge(aligns, align) +} + +const animation = async (start = false) => { + const { needCalc } = status + + if (needCalc) { + calcRowsData() + calcHeights() + status.needCalc = false + } + let { avgHeight, animationIndex, mergedConfig, rowsData, updater } = status + const { waitTime, carousel, rowNum } = mergedConfig + + const rowLength = rowsData.length + if (rowNum >= rowLength) return + if (start) { + await new Promise(resolve => setTimeout(resolve, waitTime*1000)) + if (updater !== status.updater) return + } + const animationNum = carousel === 'single' ? 1 : rowNum + let rows = rowsData.slice(animationIndex) + rows.push(...rowsData.slice(0, animationIndex)) + status.rows = rows.slice(0, carousel === 'page' ? rowNum * 2 : rowNum + 1) + status.heights = new Array(rowLength).fill(avgHeight) + await new Promise(resolve => setTimeout(resolve, 300)) + if (updater !== status.updater) return + status.heights.splice(0, animationNum, ...new Array(animationNum).fill(0)) + animationIndex += animationNum + const back = animationIndex - rowLength + if (back >= 0) animationIndex = back + status.animationIndex = animationIndex + status.animationHandler = setTimeout(animation, waitTime*1000 - 300) as any +} + +const stopAnimation = () => { + status.updater = (status.updater + 1) % 999999 + if (!status.animationHandler) return + clearTimeout(status.animationHandler) +} + +const onRestart = async () => { + if (!status.mergedConfig) return + stopAnimation() + calcData() +} + +watch( + () => w.value, + () => { + onRestart() + } +) + +watch( + () => h.value, + () => { + onRestart() + } +) + +// 数据更新 +watch( + () => props.chartConfig.option, + () => { + onRestart() + }, + {deep:true} +) + +// 数据更新 (默认更新 dataset,若更新之后有其它操作,可添加回调函数) +useChartDataFetch(props.chartConfig, useChartEditStore) + +onUnmounted(() => { + stopAnimation() +}) + +</script> + +<style lang="scss" scoped> +.dv-scroll-board { + position: relative; + width: 100%; + height: 100%; + color: #fff; + + .text { + padding: 0 10px; + box-sizing: border-box; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .header { + display: flex; + flex-direction: row; + font-size: 15px; + + .header-item { + transition: all 0.3s; + } + } + + .rows { + overflow: hidden; + + .row-item { + display: flex; + font-size: 14px; + transition: all 0.3s; + overflow: hidden; + } + } +} +</style> diff --git a/src/packages/components/Tables/Tables/index.ts b/src/packages/components/Tables/Tables/index.ts index a2443e35..388951a9 100644 --- a/src/packages/components/Tables/Tables/index.ts +++ b/src/packages/components/Tables/Tables/index.ts @@ -1,5 +1,6 @@ import { TableListConfig } from './TableList' import { TableCommonConfig } from './TableCommon' import { TableCategoryConfig } from './TableCategory' +import { TableScrollBoardConfig } from './TableScrollBoard' -export default [TableListConfig, TableCommonConfig, TableCategoryConfig] +export default [TableListConfig, TableScrollBoardConfig, TableCommonConfig, TableCategoryConfig]