Merge branch 'feature/new-sys' into 'release'
feat: 新增图表渲染 See merge request workbench/y-code!3
This commit is contained in:
		
						commit
						a7d333069c
					
				| @ -12,7 +12,7 @@ module.exports = { | ||||
|     ecmaVersion: 'latest', | ||||
|   }, | ||||
|   rules: { | ||||
|     semi: 2, | ||||
|     semi: 0, | ||||
|     'vue/multi-word-component-names': 0, | ||||
|     indent: [ | ||||
|       2, 2, { | ||||
|  | ||||
							
								
								
									
										4
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -25,6 +25,9 @@ declare module 'vue' { | ||||
|     AModal: typeof import('ant-design-vue/es')['Modal'] | ||||
|     APagination: typeof import('ant-design-vue/es')['Pagination'] | ||||
|     APopconfirm: typeof import('ant-design-vue/es')['Popconfirm'] | ||||
|     ARadio: typeof import('ant-design-vue/es')['Radio'] | ||||
|     ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] | ||||
|     ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] | ||||
|     ASelect: typeof import('ant-design-vue/es')['Select'] | ||||
|     ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] | ||||
|     ASpace: typeof import('ant-design-vue/es')['Space'] | ||||
| @ -33,6 +36,7 @@ declare module 'vue' { | ||||
|     RouterLink: typeof import('vue-router')['RouterLink'] | ||||
|     RouterView: typeof import('vue-router')['RouterView'] | ||||
|     Table: typeof import('./src/components/common/table.vue')['default'] | ||||
|     YChart: typeof import('./src/components/common/y-chart.vue')['default'] | ||||
|     YTable: typeof import('./src/components/common/y-table.vue')['default'] | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										3212
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3212
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -6,14 +6,16 @@ | ||||
|   "scripts": { | ||||
|     "dev": "vite --mode staging", | ||||
|     "build:pre": "vite build --mode staging", | ||||
|     "build": "vite build --mode production", | ||||
|     "build:pro": "vite build --mode production", | ||||
|     "type-check": "vue-tsc --build --force", | ||||
|     "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@antv/g2plot": "^2.4.31", | ||||
|     "@vueuse/core": "^10.11.0", | ||||
|     "ant-design-vue": "^4.1.2", | ||||
|     "axios": "^1.6.7", | ||||
|     "lodash": "^4.17.21", | ||||
|     "pinia": "^2.1.7", | ||||
|     "vue": "^3.4.15", | ||||
|     "vue-router": "^4.2.5" | ||||
| @ -35,4 +37,4 @@ | ||||
|     "vite": "^5.0.11", | ||||
|     "vue-tsc": "^1.8.27" | ||||
|   } | ||||
| } | ||||
| } | ||||
							
								
								
									
										39
									
								
								src/components/common/y-chart.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/components/common/y-chart.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | ||||
| <template> | ||||
|   <div class="chart-show-box"> | ||||
|     <div class="switch-type"> | ||||
|       <a-radio-group v-model:value="chartType"> | ||||
|         <a-radio-button value="line">折线图</a-radio-button> | ||||
|         <a-radio-button value="bar">柱状图</a-radio-button> | ||||
|       </a-radio-group> | ||||
|     </div> | ||||
|     <div class="chart-wrap"> | ||||
|       <Column v-if="chartType === 'bar'" :config="currentChart" /> | ||||
|       <Line v-if="chartType === 'line'" :config="currentChart" /> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import { computed, ref } from "vue"; | ||||
| import Line from "@/plugins/antv-g2plot/line.vue"; | ||||
| import Column from "@/plugins/antv-g2plot/column.vue"; | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   chartCfg: { | ||||
|     type: Array, | ||||
|     default: () => [], | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| const chartType = ref("line"); | ||||
| 
 | ||||
| const currentChart = computed(() => { | ||||
|   return props.chartCfg[chartType.value]; | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <style lang="less" scoped> | ||||
| .chart-wrap { | ||||
|   padding: 20px; | ||||
| } | ||||
| </style> | ||||
| @ -11,6 +11,7 @@ | ||||
|           v-if="item.type === 'select'" | ||||
|           class="input-item" | ||||
|           :options="item.options" | ||||
|           allow-clear | ||||
|           v-model:value="filterData[item.name]" | ||||
|           @change="toFilt" | ||||
|         ></a-select> | ||||
| @ -18,6 +19,7 @@ | ||||
|           v-if="item.type === 'text'" | ||||
|           class="input-item" | ||||
|           placeholder="请输入" | ||||
|           allow-clear | ||||
|           v-model:value="filterData[item.name]" | ||||
|           @change="toFilt" | ||||
|         /> | ||||
| @ -28,6 +30,7 @@ | ||||
|         :columns="columnConfig" | ||||
|         :data-source="dataList" | ||||
|         :pagination="false" | ||||
|         :scroll="{ x: 1000, y: `calc(100vh - 260px)` }" | ||||
|         size="small" | ||||
|         bordered | ||||
|       ></a-table> | ||||
| @ -47,6 +50,7 @@ | ||||
| <script setup> | ||||
| import { reactive, ref, watch } from "vue"; | ||||
| import { useDebounceFn } from "@vueuse/core"; | ||||
| import _ from "lodash"; | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   filterConfig: { | ||||
| @ -69,9 +73,6 @@ const props = defineProps({ | ||||
| const emit = defineEmits(["handleFilt"]); | ||||
| 
 | ||||
| const filterData = ref({}); | ||||
| // const filterConfig = ref([]); | ||||
| // const columnConfig = ref([]); | ||||
| // const dataList = ref([]); | ||||
| 
 | ||||
| const pageState = reactive({ | ||||
|   page: 1, | ||||
| @ -81,26 +82,28 @@ const pageState = reactive({ | ||||
| watch( | ||||
|   () => props.filterConfig, | ||||
|   (newVal) => { | ||||
|     console.log("newVal", newVal); | ||||
| 
 | ||||
|     newVal.forEach((item) => { | ||||
|       filterData.value[item.name] = undefined; | ||||
|     }); | ||||
| 
 | ||||
|     for (let i = 0; i < newVal.length; i++) { | ||||
|       console.log( | ||||
|         "props.filterConfig", | ||||
|         newVal[i].label, | ||||
|         newVal[i].name, | ||||
|         newVal[i].type | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| ); | ||||
| 
 | ||||
| const getData = () => { | ||||
|   const cloneFilter = _.cloneDeep(props.filterConfig); | ||||
|   const filter = cloneFilter | ||||
|     .filter((item) => { | ||||
|       return filterData.value[item.name] !== undefined; | ||||
|     }) | ||||
|     .map((item) => { | ||||
|       return { | ||||
|         name: item.name, | ||||
|         type: item.type, | ||||
|         value: filterData.value[item.name], | ||||
|       }; | ||||
|     }); | ||||
|   emit("toFilt", { | ||||
|     filter: filterData.value, | ||||
|     filter, | ||||
|     page: pageState.page, | ||||
|     perPage: pageState.perPage, | ||||
|   }); | ||||
| @ -123,6 +126,7 @@ const pageChange = () => { | ||||
| .filter-item { | ||||
|   margin-right: 10px; | ||||
|   margin-bottom: 6px; | ||||
|   font-size: 14px; | ||||
| } | ||||
| .input-item { | ||||
|   width: 180px; | ||||
|  | ||||
							
								
								
									
										26
									
								
								src/plugins/antv-g2plot/column.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/plugins/antv-g2plot/column.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| <template> | ||||
|   <div :class="className" :style="style" ref="container"></div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import { Column } from "@antv/g2plot"; | ||||
| // hooks | ||||
| import useChart from "./useChart"; | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   className: { | ||||
|     type: String, | ||||
|     default: "", | ||||
|   }, | ||||
|   style: { | ||||
|     type: Object, | ||||
|     default: () => ({}), | ||||
|   }, | ||||
|   config: { | ||||
|     type: Object, | ||||
|     default: () => ({}), | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| const { container } = useChart(Column, props.config); | ||||
| </script> | ||||
							
								
								
									
										26
									
								
								src/plugins/antv-g2plot/line.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/plugins/antv-g2plot/line.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| <template> | ||||
|   <div :class="className" :style="style" ref="container"></div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import { Line } from "@antv/g2plot"; | ||||
| // hooks | ||||
| import useChart from "./useChart"; | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   className: { | ||||
|     type: String, | ||||
|     default: "", | ||||
|   }, | ||||
|   style: { | ||||
|     type: Object, | ||||
|     default: () => ({}), | ||||
|   }, | ||||
|   config: { | ||||
|     type: Object, | ||||
|     default: () => ({}), | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| const { container } = useChart(Line, props.config); | ||||
| </script> | ||||
							
								
								
									
										26
									
								
								src/plugins/antv-g2plot/pie.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/plugins/antv-g2plot/pie.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| <template> | ||||
|   <div :class="className" :style="style" ref="container"></div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import { Pie } from "@antv/g2plot"; | ||||
| // hooks | ||||
| import useChart from "./useChart"; | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   className: { | ||||
|     type: String, | ||||
|     default: "", | ||||
|   }, | ||||
|   style: { | ||||
|     type: Object, | ||||
|     default: () => ({}), | ||||
|   }, | ||||
|   config: { | ||||
|     type: Object, | ||||
|     default: () => ({}), | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| const { container } = useChart(Pie, props.config); | ||||
| </script> | ||||
							
								
								
									
										91
									
								
								src/plugins/antv-g2plot/useChart.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/plugins/antv-g2plot/useChart.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | ||||
| import { onBeforeUnmount, onMounted, ref, watch } from "vue"; | ||||
| import _ from "lodash"; | ||||
| 
 | ||||
| export default function useChart(ChartClass, config) { | ||||
|   const chart = ref(null); // 表格实例
 | ||||
|   const chartOptions = ref(null); // 图表配置
 | ||||
|   const container = ref(null); // 渲染图表元素
 | ||||
|   const { onReady, onEvent } = config; | ||||
| 
 | ||||
|   // 全局事件侦听器
 | ||||
|   let handler; | ||||
| 
 | ||||
|   onMounted(() => { | ||||
|     chartOptions.value = _.cloneDeep(config); | ||||
| 
 | ||||
|     // 实例化图表
 | ||||
|     const chartInstance = new ChartClass(container.value, { ...config }); | ||||
|     chartInstance.toDataURL = (type, encoderOptions) => { | ||||
|       return toDataURL(type, encoderOptions); | ||||
|     }; | ||||
|     chartInstance.downloadImage = (name, type, encoderOptions) => { | ||||
|       return downloadImage(name, type, encoderOptions); | ||||
|     }; | ||||
|     chartInstance.render(); // 渲染图表
 | ||||
| 
 | ||||
|     chart.value = chartInstance; | ||||
| 
 | ||||
|     // 图表渲染完成回调
 | ||||
|     if (onReady) { | ||||
|       onReady(chartInstance); | ||||
|     } | ||||
|     // 侦听全局事件
 | ||||
|     handler = (event) => { | ||||
|       if (onEvent) { | ||||
|         onEvent(chartInstance, event); | ||||
|       } | ||||
|     }; | ||||
|     chartInstance.on("*", handler); | ||||
|   }); | ||||
| 
 | ||||
|   onBeforeUnmount(() => { | ||||
|     chart.value.destroy(); | ||||
|     chart.value.off("*", handler); | ||||
|     chart.value = undefined; | ||||
|   }); | ||||
| 
 | ||||
|   // 配置更改时更新图表
 | ||||
|   watch( | ||||
|     () => config, | ||||
|     (config) => { | ||||
|       const newConfig = _.cloneDeep(config); | ||||
|       chartOptions.value = newConfig; | ||||
|       chart.value.update(newConfig); | ||||
|     }, | ||||
|     { | ||||
|       deep: true, | ||||
|     } | ||||
|   ); | ||||
| 
 | ||||
|   const toDataURL = (type = "image/png", encoderOptions) => { | ||||
|     return chart.value?.chart.canvas.cfg.el.toDataURL(type, encoderOptions); | ||||
|   }; | ||||
| 
 | ||||
|   const downloadImage = ( | ||||
|     name = "download", | ||||
|     type = "image/png", | ||||
|     encoderOptions | ||||
|   ) => { | ||||
|     let imageName = name; | ||||
|     if (name.indexOf(".") === -1) { | ||||
|       imageName = `${name}.${type.split("/")[1]}`; | ||||
|     } | ||||
|     const base64 = chart.value?.chart.canvas.cfg.el.toDataURL( | ||||
|       type, | ||||
|       encoderOptions | ||||
|     ); | ||||
|     let a = document.createElement("a"); | ||||
|     a.href = base64; | ||||
|     a.download = imageName; | ||||
|     document.body.appendChild(a); | ||||
|     a.click(); | ||||
|     document.body.removeChild(a); | ||||
|     a = null; | ||||
|     return imageName; | ||||
|   }; | ||||
| 
 | ||||
|   return { | ||||
|     chart, | ||||
|     container, | ||||
|   }; | ||||
| } | ||||
| @ -74,7 +74,7 @@ const routeList: RouteType[] = [ | ||||
|             path: 'create-view', | ||||
|             name: 'create-view', | ||||
|             component: () => import('@/views/view-all-manage/create-view/index.vue'), | ||||
|             meta: { title: '创建' }, | ||||
|             meta: { title: '创建视图' }, | ||||
|             isMenu: true, | ||||
|             children: [], | ||||
|           }, | ||||
|  | ||||
| @ -14,9 +14,9 @@ | ||||
|           :options="projectSelect" | ||||
|         /> | ||||
|       </a-form-item> | ||||
|       <a-form-item label="数据表名称" name="modular_name"> | ||||
|       <a-form-item label="数据来源" name="modular_name"> | ||||
|         <a-input | ||||
|           placeholder="请输入数据表名称" | ||||
|           placeholder="请输入数据来源" | ||||
|           v-model:value="formData.modular_name" | ||||
|         /> | ||||
|       </a-form-item> | ||||
| @ -27,26 +27,48 @@ | ||||
|           :unCheckedValue="0" | ||||
|         /> | ||||
|       </a-form-item> | ||||
|       <a-form-item label="展示类型" name="show_type_id"> | ||||
|         <a-select | ||||
|           placeholder="请选择展示类型" | ||||
|           v-model:value="formData.show_type_id" | ||||
|           :options="showTypes" | ||||
|         /> | ||||
|       <a-form-item label="数据源类型" name="original_type"> | ||||
|         <a-radio-group v-model:value="formData.original_type"> | ||||
|           <a-radio :value="1">自定义</a-radio> | ||||
|           <a-radio :value="2">指定表</a-radio> | ||||
|         </a-radio-group> | ||||
|       </a-form-item> | ||||
|       <a-form-item label="sql数据源" name="original_sql"> | ||||
|       <a-form-item | ||||
|         v-if="formData.original_type === 1" | ||||
|         label="sql数据源" | ||||
|         name="original_sql" | ||||
|       > | ||||
|         <a-input | ||||
|           placeholder="请输入sql数据源" | ||||
|           v-model:value="formData.original_sql" | ||||
|         /> | ||||
|       </a-form-item> | ||||
|       <a-form-item | ||||
|         v-if="formData.original_type === 2" | ||||
|         label="数据表" | ||||
|         name="table" | ||||
|       > | ||||
|         <a-select | ||||
|           placeholder="请选择数据表" | ||||
|           v-model:value="formData.table" | ||||
|           :options="tableTypes" | ||||
|         /> | ||||
|       </a-form-item> | ||||
| 
 | ||||
|       <!-- <a-form-item label="展示类型" name="show_type_id"> | ||||
|         <a-select | ||||
|           placeholder="请选择展示类型" | ||||
|           v-model:value="formData.show_type_id" | ||||
|           :options="showTypes" | ||||
|         /> | ||||
|       </a-form-item> --> | ||||
|     </a-form> | ||||
|   </a-modal> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import { onMounted, ref, watch } from "vue"; | ||||
| import { getShowTypeSelect } from "@/views/config-manage/module-cfg/service"; | ||||
| import { getDbTableSelect } from "@/views/config-manage/module-cfg/service"; | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   open: { | ||||
| @ -70,20 +92,24 @@ const props = defineProps({ | ||||
| const emit = defineEmits(["ok"]); | ||||
| const formRules = ref({ | ||||
|   modular_name: [ | ||||
|     { required: true, message: "请输入数据表名称", trigger: "submit" }, | ||||
|     { required: true, message: "请输入数据来源", trigger: "submit" }, | ||||
|   ], | ||||
|   show_type_id: [{ required: true, message: "请选择", trigger: "submit" }], | ||||
|   // show_type_id: [{ required: true, message: "请选择", trigger: "submit" }], | ||||
|   original_type: [{ required: true, message: "请选择", trigger: "submit" }], | ||||
|   original_sql: [{ required: true, message: "请输入", trigger: "submit" }], | ||||
|   table: [{ required: true, message: "请选择", trigger: "submit" }], | ||||
| }); | ||||
| 
 | ||||
| const showTypes = ref([]); | ||||
| const tableTypes = ref([]); | ||||
| const formRef = ref(); | ||||
| const formData = ref({ | ||||
|   project_id: undefined, | ||||
|   modular_name: undefined, | ||||
|   is_show: 0, | ||||
|   show_type_id: undefined, | ||||
|   original_type: 1, // 1 - 自定义,2 - 指定表 | ||||
|   // show_type_id: undefined, | ||||
|   original_sql: undefined, | ||||
|   table: undefined, | ||||
| }); | ||||
| 
 | ||||
| watch( | ||||
| @ -97,20 +123,21 @@ watch( | ||||
|         project_id: newVal.project_id, | ||||
|         modular_name: newVal.modular_name, | ||||
|         is_show: newVal.is_show, | ||||
|         show_type_id: newVal.show_type_id, | ||||
|         // show_type_id: newVal.show_type_id, | ||||
|         original_sql: newVal.original_sql, | ||||
|         table: newVal.table, | ||||
|       }; | ||||
|     } | ||||
|   } | ||||
| ); | ||||
| 
 | ||||
| onMounted(() => { | ||||
|   toGetShowType(); | ||||
|   toGetDbTable(); | ||||
| }); | ||||
| 
 | ||||
| const toGetShowType = () => { | ||||
|   getShowTypeSelect().then((res) => { | ||||
|     showTypes.value = res.data; | ||||
| const toGetDbTable = () => { | ||||
|   getDbTableSelect().then((res) => { | ||||
|     tableTypes.value = res.data; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| @ -119,8 +146,10 @@ const resetFormData = () => { | ||||
|     project_id: undefined, | ||||
|     modular_name: undefined, | ||||
|     is_show: 0, | ||||
|     show_type_id: undefined, | ||||
|     original_type: 1, // 1 - 自定义,2 - 指定表 | ||||
|     // show_type_id: undefined, | ||||
|     original_sql: undefined, | ||||
|     table: undefined, | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -200,6 +200,7 @@ const handleCancel = (record) => { | ||||
| 
 | ||||
| const handleSave = (record) => { | ||||
|   const params = { | ||||
|     field_id: record.field_id, | ||||
|     field_title: record.field_title, | ||||
|     field_name: record.field_name, | ||||
|     is_search: record.is_search, | ||||
|  | ||||
| @ -3,7 +3,7 @@ export const moduleCfgCols = [ | ||||
|   { dataIndex: 'modular_name', title: '数据表名称', align: 'center'}, | ||||
|   { dataIndex: 'project_name', title: '项目名称', align: 'center'}, | ||||
|   { dataIndex: 'is_show', title: '展示状态', align: 'center'}, | ||||
|   { dataIndex: 'show_type_handle', title: '展示类型', align: 'center'}, | ||||
|   { dataIndex: 'original_type_handle', title: '数据源类型', align: 'center'}, | ||||
|   // { dataIndex: 'sort', title: '排序', align: 'center'},
 | ||||
|   { dataIndex: 'action', title: '操作', align: 'center'}, | ||||
| ]; | ||||
|  | ||||
| @ -54,10 +54,10 @@ export function getProjectSelect() { | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| // 展示类型下拉
 | ||||
| export function getShowTypeSelect() { | ||||
| // 数据源表下拉
 | ||||
| export function getDbTableSelect() { | ||||
|   return get({ | ||||
|     url: `/api/v1/modular/get-show-type-drop`, | ||||
|     url: `/api/v1/modular/get-database-table-drop`, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -13,6 +13,9 @@ | ||||
|           v-model:value="formData.project_name" | ||||
|         /> | ||||
|       </a-form-item> | ||||
|       <a-form-item label="项目标识" name="mark"> | ||||
|         <a-input placeholder="请输入项目标识,例如:oa" /> | ||||
|       </a-form-item> | ||||
|       <a-form-item label="展示状态" name="is_show"> | ||||
|         <a-switch | ||||
|           v-model:checked="formData.is_show" | ||||
| @ -85,6 +88,7 @@ const formRules = { | ||||
|   project_name: [ | ||||
|     { required: true, message: "请输入项目名称", trigger: "submit" }, | ||||
|   ], | ||||
|   mark: [{ required: true, message: "请输入项目标识", trigger: "submit" }], | ||||
|   database_address: [ | ||||
|     { required: true, message: "请输入数据库地址", trigger: "submit" }, | ||||
|   ], | ||||
| @ -105,6 +109,7 @@ const formRules = { | ||||
| const formRef = ref(); | ||||
| const formData = ref({ | ||||
|   project_name: undefined, | ||||
|   mark: undefined, | ||||
|   is_show: 0, | ||||
|   database_address: undefined, | ||||
|   database_port: undefined, | ||||
| @ -159,6 +164,7 @@ const validateConnect = () => { | ||||
| const resetFormData = () => { | ||||
|   formData.value = { | ||||
|     project_name: undefined, | ||||
|     mark: undefined, | ||||
|     is_show: 0, | ||||
|     database_address: undefined, | ||||
|     database_port: undefined, | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| export const projectCfgCols = [ | ||||
|   { dataIndex: 'project_id', title: '编号', align: 'center'}, | ||||
|   { dataIndex: 'project_name', title: '项目名称', align: 'center'}, | ||||
|   { dataIndex: 'mark', title: '项目标识', align: 'center'}, | ||||
|   { dataIndex: 'database_name', title: '数据库名', align: 'center'}, | ||||
|   { dataIndex: 'is_show', title: '展示状态', align: 'center'}, | ||||
|   // { dataIndex: 'sort', title: '排序', align: 'center'},
 | ||||
|  | ||||
| @ -2,16 +2,16 @@ | ||||
|   <div class="normal-container"> | ||||
|     <div class="view-create-box"> | ||||
|       <div class="left-box"> | ||||
|         <a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }"> | ||||
|         <a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }"> | ||||
|           <a-form-item label="项目" | ||||
|             ><a-select | ||||
|               placeholder="请选择项目" | ||||
|               :options="projectSel" | ||||
|               v-model:value="projectId" | ||||
|               @change="onProjectChange" | ||||
|             ></a-select | ||||
|           ><a-select | ||||
|             placeholder="请选择项目" | ||||
|             :options="projectSel" | ||||
|             v-model:value="projectId" | ||||
|             @change="onProjectChange" | ||||
|           ></a-select | ||||
|           ></a-form-item> | ||||
|           <a-form-item label="数据表"> | ||||
|           <a-form-item label="数据来源"> | ||||
|             <a-select | ||||
|               placeholder="请选择" | ||||
|               :options="modularSel" | ||||
| @ -19,16 +19,29 @@ | ||||
|               @change="onModularChange" | ||||
|             ></a-select> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="字段"> | ||||
|             <a-checkbox-group v-if="fieldList.length" v-model:value="fieldIds"> | ||||
|           <a-form-item label="展示类型"> | ||||
|             <a-select | ||||
|               placeholder="请选择展示类型" | ||||
|               :options="showTypeSel" | ||||
|               v-model:value="showTypeId" | ||||
|               @change="onShowTypeChange" | ||||
|             ></a-select> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="字段" v-if="fieldList.length"> | ||||
|             <a-checkbox-group v-model:value="fieldIds"> | ||||
|               <a-checkbox | ||||
|                 v-for="(item, index) in fieldList" | ||||
|                 :key="index" | ||||
|                 :value="item.value" | ||||
|                 >{{ item.label }}</a-checkbox | ||||
|               >{{ item.label }}</a-checkbox | ||||
|               > | ||||
|             </a-checkbox-group> | ||||
|             <a-empty v-else description="暂无字段数据" /> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="x轴" v-if="xDataList.length"> | ||||
|             <a-radio-group :options="xDataList" v-model:value="xDataId"></a-radio-group> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="y轴" v-if="yDataList.length"> | ||||
|             <a-checkbox-group :options="yDataList" v-model:value="yDataId"></a-checkbox-group> | ||||
|           </a-form-item> | ||||
|         </a-form> | ||||
|         <div class="footer"> | ||||
| @ -36,7 +49,7 @@ | ||||
|             class="preview-btn" | ||||
|             :loading="previewLoading" | ||||
|             @click="toPreview" | ||||
|             >预览</a-button | ||||
|           >预览</a-button | ||||
|           > | ||||
|           <a-button type="primary" @click="addViewName">点击保存</a-button> | ||||
|         </div> | ||||
| @ -56,18 +69,16 @@ | ||||
|                 :options="item.options" | ||||
|                 v-model:value="previewData.filterData[item.name]" | ||||
|                 placeholder="请选择" | ||||
|                 allow-clear | ||||
|                 @change="toFilt" | ||||
|               ></a-select> | ||||
|               <a-input | ||||
|                 v-else | ||||
|                 v-if="item.type === 'text'" | ||||
|                 class="input-item" | ||||
|                 placeholder="请输入" | ||||
|                 allow-clear | ||||
|                 v-model:value="previewData.filterData[item.name]" | ||||
|                 @change=" | ||||
|                   (e) => { | ||||
|                     toFilt(item.name, e.target.value); | ||||
|                   } | ||||
|                 " | ||||
|                 @change="toFilt" | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
| @ -76,6 +87,7 @@ | ||||
|               :columns="previewData.columnConfig" | ||||
|               :data-source="previewData.dataList" | ||||
|               :pagination="false" | ||||
|               :scroll="{ x: 1000, y: `calc(100vh - 260px)` }" | ||||
|               size="small" | ||||
|               bordered | ||||
|             ></a-table> | ||||
| @ -90,6 +102,11 @@ | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
|         <y-chart v-if="previewData.type === 'chart'" :chartCfg="previewData.chartCfg"></y-chart> | ||||
|         <div class="preview-area" v-else> | ||||
|           <div><BarChartOutlined /></div> | ||||
|           <div>预览区</div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <a-modal | ||||
|         :open="nameVisible" | ||||
| @ -110,15 +127,29 @@ | ||||
| 
 | ||||
| <script setup> | ||||
| import { onMounted, reactive, ref } from "vue"; | ||||
| import { getProModularField, preview, saveView } from "./service"; | ||||
| import { | ||||
|   getProModularField, | ||||
|   preview, | ||||
|   saveView, | ||||
|   getShowTypeSelect, | ||||
|   getFieldOpts, | ||||
| } from "./service"; | ||||
| import { message } from "ant-design-vue"; | ||||
| import { BarChartOutlined } from "@ant-design/icons-vue"; | ||||
| import yChart from "@/components/common/y-chart.vue"; | ||||
| 
 | ||||
| const projectSel = ref([]); | ||||
| const modularSel = ref([]); | ||||
| const fieldList = ref([]); | ||||
| const projectSel = ref([]); // 项目下拉 | ||||
| const modularSel = ref([])  // 数据来源下拉 | ||||
| const showTypeSel = ref([]); // 展示类型下拉 | ||||
| const fieldList = ref([]); // 字段列表 | ||||
| const xDataList = ref([]); // x轴数据列表 | ||||
| const yDataList = ref([]); // y轴数据列表 | ||||
| const projectId = ref(); | ||||
| const modularId = ref(); | ||||
| const showTypeId = ref(); | ||||
| const fieldIds = ref([]); | ||||
| const xDataId = ref(); | ||||
| const yDataId = ref(); | ||||
| 
 | ||||
| const previewLoading = ref(false); | ||||
| const nameVisible = ref(false); | ||||
| @ -130,6 +161,7 @@ const previewData = reactive({ | ||||
|   columnConfig: [], // 表格表头 | ||||
|   dataList: [], // 表格数据 | ||||
|   filterData: {}, | ||||
|   chartCfg: {}, | ||||
|   page: 1, | ||||
|   perPage: 20, | ||||
|   total: 0, | ||||
| @ -137,6 +169,7 @@ const previewData = reactive({ | ||||
| 
 | ||||
| onMounted(() => { | ||||
|   toGetProModularField(); | ||||
|   toGetShowTypes(); | ||||
| }); | ||||
| 
 | ||||
| const toGetProModularField = () => { | ||||
| @ -145,34 +178,92 @@ const toGetProModularField = () => { | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 获取展示类型下拉 | ||||
| const toGetShowTypes = () => { | ||||
|   getShowTypeSelect().then((res) => { | ||||
|     showTypeSel.value = res.data; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 获取字段列表 | ||||
| const toGetFieldOpts = () => { | ||||
|   getFieldOpts({ | ||||
|     modularId: modularId.value, | ||||
|     showTypeId: showTypeId.value, | ||||
|   }).then((res) => { | ||||
|     fieldList.value = res.data.list ? tranformList(res.data.list) : []; | ||||
|     xDataList.value = res.data.x_data ? tranformList(res.data.x_data) : []; | ||||
|     yDataList.value = res.data.y_data ? tranformList(res.data.y_data) : []; | ||||
|     if (!fieldList.value.length) { | ||||
|       fieldIds.value = []; | ||||
|     } else { | ||||
|       xDataId.value = undefined; | ||||
|       yDataId.value = []; | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| const onProjectChange = (val) => { | ||||
|   const target = projectSel.value.find((item) => item.value === val); | ||||
|   modularSel.value = target.child; | ||||
|   modularId.value = undefined; | ||||
|   fieldList.value = []; | ||||
|   fieldIds.value = []; | ||||
|   resetPreviewData(); | ||||
| }; | ||||
| 
 | ||||
| const onModularChange = (val) => { | ||||
|   const target = modularSel.value.find((item) => item.value === val); | ||||
|   fieldList.value = target.child; | ||||
|   // const target = modularSel.value.find((item) => item.value === val); | ||||
|   // fieldList.value = target.child; | ||||
|   resetPreviewData(); | ||||
| }; | ||||
| 
 | ||||
| const onShowTypeChange = () => { | ||||
|   toGetFieldOpts(); | ||||
| }; | ||||
| 
 | ||||
| const tranformList = (list) => { | ||||
|   return list.map((item) => { | ||||
|     return { | ||||
|       label: item.field_name, | ||||
|       value: item.field_id, | ||||
|     }; | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| const resetPreviewData = () => { | ||||
|   previewData.type = ""; | ||||
|   previewData.filterConfig = []; | ||||
|   previewData.columnConfig = []; | ||||
|   previewData.dataList = []; | ||||
|   previewData.filterData = {}; | ||||
|   previewData.page = 1; | ||||
|   previewData.perPage = 20; | ||||
|   previewData.total = 0; | ||||
| }; | ||||
| 
 | ||||
| const toPreview = () => { | ||||
|   previewLoading.value = true; | ||||
|   const filter = previewData.filterConfig.map((item) => { | ||||
|     return { | ||||
|       name: item.name, | ||||
|       type: item.type, | ||||
|       value: previewData.filterData[item.name], | ||||
|     }; | ||||
|   }); | ||||
|   const filter = previewData.filterConfig | ||||
|     .filter((item) => { | ||||
|       return previewData.filterData[item.name] !== undefined; | ||||
|     }) | ||||
|     .map((item) => { | ||||
|       return { | ||||
|         name: item.name, | ||||
|         type: item.type, | ||||
|         value: previewData.filterData[item.name], | ||||
|       }; | ||||
|     }); | ||||
|   preview({ | ||||
|     modularId: modularId.value, | ||||
|     fieldIds: fieldIds.value.toString(), | ||||
|     page: previewData.page, | ||||
|     perPage: previewData.perPage, | ||||
|     filter, | ||||
|     showTypeId: showTypeId.value, | ||||
|     xDataId: xDataId.value.toString(), | ||||
|     yDataId: yDataId.value.toString(), | ||||
|   }) | ||||
|     .then((res) => { | ||||
|       previewData.type = res.data.type; | ||||
| @ -183,10 +274,11 @@ const toPreview = () => { | ||||
|             ? previewData.filterData[item.name] | ||||
|             : undefined; | ||||
|         }); | ||||
|         console.log("filterData", previewData.filterData); | ||||
|         previewData.columnConfig = res.data.header; | ||||
|         previewData.dataList = res.data.data; | ||||
|         previewData.total = res.data.count; | ||||
|       } else { | ||||
|         previewData.chartCfg = res.data.config; | ||||
|       } | ||||
|     }) | ||||
|     .finally(() => { | ||||
| @ -208,14 +300,16 @@ const toSaveView = () => { | ||||
|     modularId: modularId.value, | ||||
|     fieldIds: fieldIds.value.toString(), | ||||
|     previewName: previewName.value, | ||||
|     showTypeId: showTypeId.value, | ||||
|     xDataId: xDataId.value.toString(), | ||||
|     yDataId: yDataId.value.toString(), | ||||
|   }).then(() => { | ||||
|     message.success("保存成功"); | ||||
|     message.success("保存成功,可前往视图列表查看"); | ||||
|     nameVisible.value = false; | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| const toFilt = (key, value) => { | ||||
|   previewData.filterData[key] = value; | ||||
| const toFilt = () => { | ||||
|   previewData.page = 1; | ||||
|   toPreview(); | ||||
| }; | ||||
| @ -224,13 +318,15 @@ const toFilt = (key, value) => { | ||||
| <style lang="less" scoped> | ||||
| .normal-container { | ||||
|   height: calc(100vh - 120px); | ||||
|   padding: 16px; | ||||
| } | ||||
| .view-create-box { | ||||
|   display: flex; | ||||
|   height: 100%; | ||||
| } | ||||
| .left-box { | ||||
|   width: 400px; | ||||
|   width: 320px; | ||||
|   flex-shrink: 0; | ||||
|   height: calc(100% - 20px); | ||||
|   padding: 10px; | ||||
|   border-right: 1px solid #ddd; | ||||
| @ -243,11 +339,24 @@ const toFilt = (key, value) => { | ||||
|   } | ||||
| } | ||||
| .right-box { | ||||
|   padding: 10px; | ||||
|   padding: 0 0 0 10px; | ||||
|   flex-grow: 1; | ||||
|   overflow: auto; | ||||
| } | ||||
| 
 | ||||
| .preview-area { | ||||
|   background-color: #f6f6f6; | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   font-size: 20px; | ||||
|   color: #999; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   .anticon-bar-chart { | ||||
|     font-size: 100px; | ||||
|   } | ||||
| } | ||||
| .y-table-filter { | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
| @ -255,6 +364,7 @@ const toFilt = (key, value) => { | ||||
| .filter-item { | ||||
|   margin-right: 10px; | ||||
|   margin-bottom: 6px; | ||||
|   font-size: 14px; | ||||
| } | ||||
| .input-item { | ||||
|   width: 180px; | ||||
| @ -264,5 +374,6 @@ const toFilt = (key, value) => { | ||||
| } | ||||
| .pagination-box { | ||||
|   text-align: center; | ||||
|   margin-top: 10px; | ||||
| } | ||||
| </style> | ||||
|  | ||||
| @ -7,8 +7,26 @@ export function getProModularField() { | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| // 展示类型下拉
 | ||||
| export function getShowTypeSelect() { | ||||
|   return get({ | ||||
|     url: `/api/v1/modular/get-show-type-drop`, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| // 字段列表
 | ||||
| export function getFieldOpts({ modularId, showTypeId }) { | ||||
|   return get({ | ||||
|     url: "/api/v1/preview/get-preview-field", | ||||
|     params: { | ||||
|       modular_id: modularId, | ||||
|       show_type_id: showTypeId, | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| // 预览
 | ||||
| export function preview({ modularId, fieldIds, page, perPage, filter }) { | ||||
| export function preview({ modularId, fieldIds, page, perPage, filter, showTypeId, xDataId, yDataId }) { | ||||
|   return post({ | ||||
|     url: "api/v1/preview/view", | ||||
|     data: { | ||||
| @ -17,18 +35,24 @@ export function preview({ modularId, fieldIds, page, perPage, filter }) { | ||||
|       page, | ||||
|       per_page: perPage, | ||||
|       filter, | ||||
|       show_type_id: showTypeId, | ||||
|       x_data_id: xDataId, | ||||
|       y_data_id: yDataId, | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| // 点击保存
 | ||||
| export function saveView({ modularId, fieldIds, previewName }) { | ||||
| export function saveView({ modularId, fieldIds, previewName, showTypeId, xDataId, yDataId }) { | ||||
|   return post({ | ||||
|     url: "api/v1/preview/save", | ||||
|     data: { | ||||
|       modular_id: modularId, | ||||
|       field_ids: fieldIds, | ||||
|       preview_name: previewName, | ||||
|       show_type_id: showTypeId, | ||||
|       x_data_id: xDataId, | ||||
|       y_data_id: yDataId, | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|   <div class="normal-container"> | ||||
|     <div class="view-list-box"> | ||||
|       <div class="left-box"> | ||||
|         <a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }"> | ||||
|         <a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }"> | ||||
|           <a-form-item label="项目"> | ||||
|             <a-select | ||||
|               :options="projectSel" | ||||
| @ -11,11 +11,11 @@ | ||||
|               @change="onProjectChange" | ||||
|             ></a-select> | ||||
|           </a-form-item> | ||||
|           <a-form-item label="数据表"> | ||||
|           <a-form-item label="数据来源"> | ||||
|             <a-select | ||||
|               :options="modularSel" | ||||
|               v-model:value="modularId" | ||||
|               placeholder="请选择数据表" | ||||
|               placeholder="请先选好项目再选择" | ||||
|               @change="onModularChange" | ||||
|             ></a-select> | ||||
|           </a-form-item> | ||||
| @ -40,7 +40,26 @@ | ||||
|           " | ||||
|           size="small" | ||||
|           bordered | ||||
|         ></a-table> | ||||
|         > | ||||
|           <template #bodyCell="{ column, record }"> | ||||
|             <template v-if="column.dataIndex === 'action'"> | ||||
|               <a-popconfirm | ||||
|                 title="确定删除?" | ||||
|                 @confirm="toDelete(record.preview_id)" | ||||
|               > | ||||
|                 <a-button | ||||
|                   type="link" | ||||
|                   @click=" | ||||
|                     (e) => { | ||||
|                       e.stopPropagation(); | ||||
|                     } | ||||
|                   " | ||||
|                   >删除</a-button | ||||
|                 > | ||||
|               </a-popconfirm> | ||||
|             </template> | ||||
|           </template> | ||||
|         </a-table> | ||||
|         <a-pagination | ||||
|           v-model:current="pageState.page" | ||||
|           :total="pageState.total" | ||||
| @ -64,6 +83,10 @@ | ||||
|             } | ||||
|           " | ||||
|         /> | ||||
|         <div class="preview-area" v-else> | ||||
|           <div><BarChartOutlined /></div> | ||||
|           <div>展示区</div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| @ -71,9 +94,11 @@ | ||||
| 
 | ||||
| <script setup> | ||||
| import { onMounted, ref, reactive } from "vue"; | ||||
| import { getProModular, getViewList, getViewInfo } from "./service"; | ||||
| import { getProModular, getViewList, getViewInfo, deleteView } from "./service"; | ||||
| import { viewListCols } from "./config"; | ||||
| import yTable from "@/components/common/y-table.vue"; | ||||
| import { message } from "ant-design-vue"; | ||||
| import { BarChartOutlined } from "@ant-design/icons-vue"; | ||||
| 
 | ||||
| const projectSel = ref([]); | ||||
| const modularSel = ref([]); | ||||
| @ -83,6 +108,7 @@ const dataList = ref([]); | ||||
| const selectedRowId = ref(); | ||||
| const selectViewInfo = ref({ | ||||
|   type: "", | ||||
|   filter: [], | ||||
| }); | ||||
| 
 | ||||
| const pageState = reactive({ | ||||
| @ -130,11 +156,19 @@ const onModularChange = () => { | ||||
|   pageState.page = 1; | ||||
|   toGetList(); | ||||
| }; | ||||
| 
 | ||||
| const toDelete = (previewId) => { | ||||
|   deleteView({ previewId }).then(() => { | ||||
|     message.success("删除成功"); | ||||
|     toGetList(); | ||||
|   }); | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <style lang="less" scoped> | ||||
| .normal-container { | ||||
|   height: calc(100vh - 120px); | ||||
|   padding: 16px; | ||||
| } | ||||
| 
 | ||||
| .view-list-box { | ||||
| @ -143,14 +177,20 @@ const onModularChange = () => { | ||||
| } | ||||
| 
 | ||||
| .left-box { | ||||
|   width: 400px; | ||||
|   width: 320px; | ||||
|   height: calc(100% - 20px); | ||||
|   padding: 10px; | ||||
|   flex-shrink: 0; | ||||
|   border-right: 1px solid #ddd; | ||||
| } | ||||
| 
 | ||||
| .selected-row { | ||||
|   background-color: beige; | ||||
| :deep(.ant-table-row:hover) { | ||||
|   cursor: pointer; | ||||
|   background-color: #e6f4ff; | ||||
| } | ||||
| 
 | ||||
| :deep(.selected-row) { | ||||
|   background-color: #e6f4ff; | ||||
| } | ||||
| 
 | ||||
| .pagination-box { | ||||
| @ -158,8 +198,23 @@ const onModularChange = () => { | ||||
|   margin-top: 10px; | ||||
| } | ||||
| .right-box { | ||||
|   padding: 10px; | ||||
|   padding: 0 0 0 10px; | ||||
|   flex-grow: 1; | ||||
|   overflow: auto; | ||||
| } | ||||
| 
 | ||||
| .preview-area { | ||||
|   background-color: #f6f6f6; | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   font-size: 20px; | ||||
|   color: #999; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   .anticon-bar-chart { | ||||
|     font-size: 100px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { get } from "@/utils/request"; | ||||
| import { get, post } from "@/utils/request"; | ||||
| 
 | ||||
| // 联动下拉
 | ||||
| export function getProModular() { | ||||
| @ -24,15 +24,25 @@ export function getViewInfo({ | ||||
|   previewId, | ||||
|   page = 1, | ||||
|   perPage = 20, | ||||
|   filter = {}, | ||||
|   filter = [], | ||||
| }) { | ||||
|   return get({ | ||||
|   return post({ | ||||
|     url: "/api/v1/preview/info", | ||||
|     params: { | ||||
|     data: { | ||||
|       preview_id: previewId, | ||||
|       page, | ||||
|       perPage: perPage, | ||||
|       per_page: perPage, | ||||
|       filter, | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| // 删除视图
 | ||||
| export function deleteView({ previewId }) { | ||||
|   return post({ | ||||
|     url: "/api/v1/preview/del", | ||||
|     data: { | ||||
|       preview_id: previewId, | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 吴骆婷
						吴骆婷