Parcourir la source

feat: 新增数据校验,数据导入导出

mtruning il y a 3 ans
Parent
commit
285fff6add

+ 12 - 4
src/components/GoSkeleton/index.vue

@@ -1,16 +1,16 @@
 <template>
-  <div v-show="load">
+  <div v-show="load" class="go-skeleton">
     <div v-show="repeat == 1">
       <n-skeleton v-bind="$attrs"></n-skeleton>
     </div>
     <div v-show="repeat == 2">
       <n-skeleton v-bind="$attrs"></n-skeleton>
-      <n-skeleton v-bind="$attrs" style="width: 60%;"></n-skeleton>
+      <n-skeleton class="item" v-bind="$attrs" style="width: 60%;"></n-skeleton>
     </div>
     <div v-show="repeat > 2">
       <n-skeleton v-bind="$attrs" :repeat="repeat - 2"></n-skeleton>
-      <n-skeleton v-bind="$attrs" style="width: 60%;"></n-skeleton>
-      <n-skeleton v-bind="$attrs" style="width: 50%;"></n-skeleton>
+      <n-skeleton class="item" v-bind="$attrs" style="width: 60%;"></n-skeleton>
+      <n-skeleton class="item" v-bind="$attrs" style="width: 50%;"></n-skeleton>
     </div>
   </div>
 </template>
@@ -27,3 +27,11 @@ defineProps({
   }
 })
 </script>
+
+<style lang="scss" scoped>
+@include go('skeleton') {
+  .item {
+    margin-top: 5px;
+  }
+}
+</style>

+ 10 - 0
src/enums/fileTypeEnum.ts

@@ -0,0 +1,10 @@
+// 文件上传时的格式映射
+export enum FileTypeEnum {
+  // 文档
+  TXT = 'text/plain',
+  JSON = 'application/json',
+  // 图片
+  PNG = 'image/png',
+  JPEG = 'image/jpeg',
+  GIF = 'image/gif',
+}

+ 3 - 1
src/packages/components/Charts/Bars/BarCommon/config.ts

@@ -2,8 +2,9 @@ import { echartOptionProfixHandle, publicConfig } from '@/packages/public'
 import { BarCommonConfig } from './index'
 import { CreateComponentType } from '@/packages/index.d'
 import cloneDeep from 'lodash/cloneDeep'
+import dataJson from './data.json'
 
-export const includes = ['legend', 'xAxis', 'yAxis', 'dataset']
+export const includes = ['legend', 'xAxis', 'yAxis']
 
 export const option = {
   tooltip: {
@@ -25,6 +26,7 @@ export const option = {
     show: true,
     type: 'value'
   },
+  dataset: { ...dataJson },
   series: [
     {
       type: 'bar',

+ 2 - 1
src/packages/components/Charts/Bars/BarCommon/index.vue

@@ -43,6 +43,7 @@ use([
 ])
 
 const option = computed(() => {
-  return setData(mergeTheme(props.chartConfig.option, props.themeSetting, includes), dataJson)
+  // TODO dataset的数据要设计一下,不能这样把数据进行监听,太耗性能
+  return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
 })
 </script>

+ 8 - 2
src/plugins/icon.ts

@@ -69,7 +69,9 @@ import {
   AlignVerticalTop as AlignVerticalTopIcon,
   AlignHorizontalCenter as AlignHorizontalCenterIcon,
   AlignHorizontalRight as AlignHorizontalRightIcon,
-  AlignVerticalBottom as AlignVerticalBottomIcon
+  AlignVerticalBottom as AlignVerticalBottomIcon,
+  DocumentAdd as DocumentAddIcon,
+  DocumentDownload as DocumentDownloadIcon,
 } from '@vicons/carbon'
 
 const ionicons5 = {
@@ -205,7 +207,11 @@ const carbon = {
   AlignVerticalTopIcon,
   AlignHorizontalCenterIcon,
   AlignHorizontalRightIcon,
-  AlignVerticalBottomIcon
+  AlignVerticalBottomIcon,
+  // 添加文件
+  DocumentAddIcon,
+  // 下载文件
+  DocumentDownloadIcon
 }
 
 // https://www.xicons.org/#/ 还有很多

+ 2 - 0
src/plugins/naive.ts

@@ -7,6 +7,7 @@ import {
   NH2,
   NH3,
   NH4,
+  NCode,
   NText,
   NTime,
   NEllipsis,
@@ -101,6 +102,7 @@ const naive = create({
     NH2,
     NH3,
     NH4,
+    NCode,
     NText,
     NTime,
     NEllipsis,

+ 7 - 7
src/store/modules/chartEditStore/chartEditStore.d.ts

@@ -2,7 +2,7 @@ import { CreateComponentType } from '@/packages/index.d'
 import { HistoryActionTypeEnum } from '@/store/modules/chartHistoryStore/chartHistoryStore.d'
 import type {
   ChartColorsNameType,
-  GlobalThemeJsonType
+  GlobalThemeJsonType,
 } from '@/settings/chartThemes/index'
 
 // 编辑画布属性
@@ -13,7 +13,7 @@ export enum EditCanvasTypeEnum {
   SCALE = 'scale',
   USER_SCALE = 'userScale',
   LOCK_SCALE = 'lockScale',
-  IS_DRAG = 'isDrag'
+  IS_DRAG = 'isDrag',
 }
 
 // 编辑区域
@@ -46,7 +46,7 @@ export enum EditCanvasConfigEnum {
   CHART_THEME_SETTING = 'chartThemeSetting',
   BACKGROUND = 'background',
   BACKGROUND_IAMGE = 'backgroundImage',
-  SELECT_COLOR = 'selectColor'
+  SELECT_COLOR = 'selectColor',
 }
 
 export interface EditCanvasConfigType {
@@ -80,7 +80,7 @@ export enum EditCanvasTypeEnum {
   START_X = 'startX',
   START_Y = 'startY',
   X = 'x',
-  Y = 'y'
+  Y = 'y',
 }
 
 // 鼠标位置
@@ -118,15 +118,15 @@ export enum ChartEditStoreEnum {
   // 以下需要存储
   EDIT_CANVAS_CONFIG = 'editCanvasConfig',
   REQUEST_CONFIG = 'requestConfig',
-  COMPONENT_LIST = 'componentList'
+  COMPONENT_LIST = 'componentList',
 }
 
 // 数据相关
 export enum RequestDataTypeEnum {
   // 静态数据
-  STATIC,
+  STATIC = 0,
   // 请求数据
-  AJAX
+  AJAX = 1,
 }
 
 // 数据配置

+ 43 - 0
src/utils/file.ts

@@ -0,0 +1,43 @@
+/**
+ * *获取上传的文件数据
+ * @param { File } file 文件对象
+ */
+export const readFile = (file: File) => {
+  return new Promise((resolve: Function) => {
+    try {
+      const reader = new FileReader()
+      reader.onload = (evt: ProgressEvent<FileReader>) => {
+        if (evt.target) {
+          resolve(evt.target.result)
+        }
+      }
+      reader.readAsText(file)
+    } catch (error) {
+      window['$message'].error('文件读取失败!')
+    }
+  })
+}
+
+/**
+ * 下载数据
+ * @param { string } content 数据内容
+ * @param { ?string } filename 文件名称(默认随机字符)
+ * @param { ?string } fileSuffix 文件名称(默认随机字符)
+ */
+export const downloadFile = (
+  content: string,
+  filename = new Date().getDate().toString(),
+  fileSuffix?: string
+) => {
+  const ele = document.createElement('a') // 创建下载链接
+  ele.download = `${filename}.${fileSuffix}` //设置下载的名称
+  ele.style.display = 'none' // 隐藏的可下载链接
+  // 字符内容转变成blob地址
+  const blob = new Blob([content])
+  ele.href = URL.createObjectURL(blob)
+  // 绑定点击时间
+  document.body.appendChild(ele)
+  ele.click()
+  // 然后移除
+  document.body.removeChild(ele)
+}

+ 2 - 1
src/utils/index.ts

@@ -5,4 +5,5 @@ export * from '@/utils/storage'
 export * from '@/utils/style'
 export * from '@/utils/plugin'
 export * from '@/utils/componets'
-export * from '@/utils/type'
+export * from '@/utils/type'
+export * from '@/utils/file'

+ 19 - 0
src/views/chart/ContentConfigurations/components/ChartData/index.d.ts

@@ -0,0 +1,19 @@
+import { RequestDataTypeEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
+
+// 匹配结果
+export enum DataResultEnum {
+  NULL = 0,
+  SUCCESS = 1,
+  FAILURE = 2,
+}
+
+export enum SelcetOptionsLableEnum {
+  STATIC = '静态数据',
+  AJAX = '动态请求',
+}
+
+export interface SelectOptionsType {
+  label: SelcetOptionsLableEnum
+  value: RequestDataTypeEnum
+  disabled?: boolean
+}

+ 161 - 49
src/views/chart/ContentConfigurations/components/ChartData/index.vue

@@ -1,33 +1,33 @@
 <template>
-  <div class="go-chart-configurations-data">
-    <setting-item-box v-if="targetData" name="请求方式" :alone="true">
+  <div class="go-chart-configurations-data" v-if="targetData">
+    <setting-item-box name="请求方式" :alone="true">
       <n-select
         v-model:value="targetData.data.requestDataType"
-        :options="selcetOpeions"
-        @on-update="updateHandle"
+        :options="selectOptions"
+        @on-update="selectHandle"
       />
     </setting-item-box>
     <n-timeline>
-      <n-timeline-item type="info" title="数据结构">
+      <n-timeline-item type="info" title="数据映射">
         <n-table striped>
           <thead>
             <tr>
-              <th>字段</th>
-              <th>映射</th>
-              <th>状态</th>
+              <th v-for="item in tableTitle" :key="item">{{ item }}</th>
             </tr>
           </thead>
-          <tbody>
-            <tr v-for="(item, index) in dataStructure" :key="index">
+          <go-skeleton :repeat="3" :load="tableLoad" style="width: 300px;"></go-skeleton>
+          <tbody v-show="!tableLoad">
+            <tr v-for="(item, index) in getDimensionsAndSource" :key="index">
               <td>{{ item.field }}</td>
               <td>{{ item.mapping }}</td>
               <td>
-                <n-space>
-                  <n-badge
-                    dot
-                    :type="item.result ? 'success' : 'error'"
-                  ></n-badge>
-                  <n-text>匹配{{ item.result ? '成功' : '失败' }}</n-text>
+                <n-space v-if="item.result === 0">
+                  <n-badge dot type="success"></n-badge>
+                  <n-text>无</n-text>
+                </n-space>
+                <n-space v-else>
+                  <n-badge dot :type="item.result === 1 ? 'success' : 'error'"></n-badge>
+                  <n-text>匹配{{ item.result === 1 ? '成功' : '失败' }}</n-text>
                 </n-space>
               </td>
             </tr>
@@ -35,60 +35,172 @@
         </n-table>
       </n-timeline-item>
       <n-timeline-item type="success" title="静态数据">
-        <n-code
-          v-for="(item, index) in code"
-          :key="index"
-          :code="item.data"
-          language="json"
-        ></n-code>
+        <n-space vertical>
+          <n-space class="source-btn-box">
+            <n-upload
+              v-model:file-list="uploadFileListRef"
+              :show-file-list="false"
+              :customRequest="customRequest"
+              @before-upload="beforeUpload"
+            >
+              <n-button class="sourceBtn-item">
+                <template #icon>
+                  <n-icon>
+                    <document-add-icon />
+                  </n-icon>
+                </template>
+                导入(json / txt)
+              </n-button>
+            </n-upload>
+            <n-button class="sourceBtn-item" @click="download">
+              <template #icon>
+                <n-icon>
+                  <document-download-icon />
+                </n-icon>
+              </template>
+              下载
+            </n-button>
+          </n-space>
+          <n-card>
+            <n-code :code="getSource" language="json"></n-code>
+          </n-card>
+        </n-space>
       </n-timeline-item>
     </n-timeline>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, toRaw } from 'vue'
+import { ref, toRefs, computed, watch, nextTick } from 'vue'
 import { SettingItemBox } from '@/components/ChartItemSetting/index'
 import { RequestDataTypeEnum } from '@/store/modules/chartEditStore/chartEditStore.d'
 import { useTargetData } from '../hooks/useTargetData.hook'
+import { UploadCustomRequestOptions } from 'naive-ui'
+import { FileTypeEnum } from '@/enums/fileTypeEnum'
+import { readFile, downloadFile } from '@/utils'
+import { DataResultEnum, SelcetOptionsLableEnum, SelectOptionsType } from './index.d'
+import { icon } from '@/plugins'
 
-const { targetData } = useTargetData()
+const { DocumentAddIcon, DocumentDownloadIcon } = icon.carbon
 
-const code = toRaw((targetData.value.option as any).series)
+const uploadFileListRef = ref()
+const { targetData } = useTargetData()
+const source = ref()
+const dimensions = ref()
 
-const selcetOpeions = [
+// 表格标题
+const tableTitle = ['字段', '映射', '状态']
+// 选项
+const selectOptions: SelectOptionsType[] = [
   {
-    label: '静态数据',
+    label: SelcetOptionsLableEnum.STATIC,
     value: RequestDataTypeEnum.STATIC
   },
   {
-    label: '动态请求',
-    value: RequestDataTypeEnum.AJAX
+    label: SelcetOptionsLableEnum.AJAX,
+    value: RequestDataTypeEnum.AJAX,
+    disabled: true,
   }
 ]
 
-const dataStructure = ref([
-  {
-    // 字段
-    field: 'x',
-    // 映射
-    mapping: 'xData',
-    // 结果
-    result: true
-  },
-  {
-    // 字段
-    field: 'y',
-    // 映射
-    mapping: 'yData',
-    // 结果
-    result: true
+
+// 获取数据
+const getSource = computed(() => {
+  return JSON.stringify(source.value)
+})
+
+watch(() => targetData.value?.option?.dataset, (newData) => {
+  if (newData) {
+    source.value = newData.source
+    dimensions.value = newData.dimensions
   }
-])
+}, {
+  immediate: true
+})
 
-const updateHandle = (value: any) => {
-  console.log(value)
+// 处理映射列表状态结果
+const matchingHandle = (mapping: string) => {
+  for (let i = 0; i < source.value.length; i++) {
+    let res = DataResultEnum.FAILURE
+    if (source.value[i][mapping] !== undefined) {
+      return DataResultEnum.SUCCESS
+    }
+    return res
+  }
+  return DataResultEnum.SUCCESS
+}
+// 处理映射列表
+const getDimensionsAndSource = computed(() => {
+  // 去除首项数据轴标识
+  return dimensions.value.map((item: string, index: number) => {
+    return index === 0 ?
+      {
+        // 字段
+        field: '通用标识',
+        // 映射
+        mapping: item,
+        // 结果
+        result: DataResultEnum.NULL
+      } : {
+        field: `数据项-${index}`,
+        mapping: item,
+        result: matchingHandle(item)
+      }
+  })
+})
+
+// 表格数据加载
+const tableLoad = computed(() => {
+  return !getDimensionsAndSource.value || getDimensionsAndSource.value.length === 0
+})
+
+// 选择方式
+const selectHandle = () => { }
+
+//@ts-ignore
+const beforeUpload = ({ file }) => {
+  uploadFileListRef.value = []
+  const type = file.file.type
+  if (type !== FileTypeEnum.JSON && type !== FileTypeEnum.TXT) {
+    window['$message'].warning('仅支持上传 【JSON】 格式文件,请重新上传!')
+    return false
+  }
+  return true
+}
+
+// 自定义上传操作
+const customRequest = (options: UploadCustomRequestOptions) => {
+  const { file } = options
+  nextTick(() => {
+    if (file.file) {
+      readFile(file.file).then((fileData: any) => {
+        targetData.value.option.dataset = JSON.parse(fileData)
+      });
+    } else {
+      window['$message'].error('导入数据失败,请稍后重试或联系管理员!')
+    }
+  })
+}
+
+// 下载文件
+const download = () => {
+  window['$message'].success('正在下载文件...')
+  downloadFile(getSource.value, undefined, 'json')
 }
 </script>
 
-<style lang="scss" scoped></style>
+<style>
+</style>
+<style lang="scss" scoped>
+@include go("chart-configurations-data") {
+  @include deep() {
+    pre {
+      white-space: pre-wrap;
+      word-wrap: break-word;
+    }
+  }
+  .source-btn-box {
+    margin-top: 10px !important;
+  }
+}
+</style>