Merge branch 'feature/new-sys' into 'release'

feat: 新增图表渲染

See merge request workbench/y-code!3
This commit is contained in:
吴骆婷
2024-07-17 11:54:41 +08:00
21 changed files with 3706 additions and 155 deletions

View File

@@ -12,7 +12,7 @@ module.exports = {
ecmaVersion: 'latest', ecmaVersion: 'latest',
}, },
rules: { rules: {
semi: 2, semi: 0,
'vue/multi-word-component-names': 0, 'vue/multi-word-component-names': 0,
indent: [ indent: [
2, 2, { 2, 2, {

4
components.d.ts vendored
View File

@@ -25,6 +25,9 @@ declare module 'vue' {
AModal: typeof import('ant-design-vue/es')['Modal'] AModal: typeof import('ant-design-vue/es')['Modal']
APagination: typeof import('ant-design-vue/es')['Pagination'] APagination: typeof import('ant-design-vue/es')['Pagination']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm'] 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'] ASelect: typeof import('ant-design-vue/es')['Select']
ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
ASpace: typeof import('ant-design-vue/es')['Space'] ASpace: typeof import('ant-design-vue/es')['Space']
@@ -33,6 +36,7 @@ declare module 'vue' {
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
Table: typeof import('./src/components/common/table.vue')['default'] 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'] YTable: typeof import('./src/components/common/y-table.vue')['default']
} }
} }

3212
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,14 +6,16 @@
"scripts": { "scripts": {
"dev": "vite --mode staging", "dev": "vite --mode staging",
"build:pre": "vite build --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", "type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
}, },
"dependencies": { "dependencies": {
"@antv/g2plot": "^2.4.31",
"@vueuse/core": "^10.11.0", "@vueuse/core": "^10.11.0",
"ant-design-vue": "^4.1.2", "ant-design-vue": "^4.1.2",
"axios": "^1.6.7", "axios": "^1.6.7",
"lodash": "^4.17.21",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"vue": "^3.4.15", "vue": "^3.4.15",
"vue-router": "^4.2.5" "vue-router": "^4.2.5"
@@ -35,4 +37,4 @@
"vite": "^5.0.11", "vite": "^5.0.11",
"vue-tsc": "^1.8.27" "vue-tsc": "^1.8.27"
} }
} }

View 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>

View File

@@ -11,6 +11,7 @@
v-if="item.type === 'select'" v-if="item.type === 'select'"
class="input-item" class="input-item"
:options="item.options" :options="item.options"
allow-clear
v-model:value="filterData[item.name]" v-model:value="filterData[item.name]"
@change="toFilt" @change="toFilt"
></a-select> ></a-select>
@@ -18,6 +19,7 @@
v-if="item.type === 'text'" v-if="item.type === 'text'"
class="input-item" class="input-item"
placeholder="请输入" placeholder="请输入"
allow-clear
v-model:value="filterData[item.name]" v-model:value="filterData[item.name]"
@change="toFilt" @change="toFilt"
/> />
@@ -28,6 +30,7 @@
:columns="columnConfig" :columns="columnConfig"
:data-source="dataList" :data-source="dataList"
:pagination="false" :pagination="false"
:scroll="{ x: 1000, y: `calc(100vh - 260px)` }"
size="small" size="small"
bordered bordered
></a-table> ></a-table>
@@ -47,6 +50,7 @@
<script setup> <script setup>
import { reactive, ref, watch } from "vue"; import { reactive, ref, watch } from "vue";
import { useDebounceFn } from "@vueuse/core"; import { useDebounceFn } from "@vueuse/core";
import _ from "lodash";
const props = defineProps({ const props = defineProps({
filterConfig: { filterConfig: {
@@ -69,9 +73,6 @@ const props = defineProps({
const emit = defineEmits(["handleFilt"]); const emit = defineEmits(["handleFilt"]);
const filterData = ref({}); const filterData = ref({});
// const filterConfig = ref([]);
// const columnConfig = ref([]);
// const dataList = ref([]);
const pageState = reactive({ const pageState = reactive({
page: 1, page: 1,
@@ -81,26 +82,28 @@ const pageState = reactive({
watch( watch(
() => props.filterConfig, () => props.filterConfig,
(newVal) => { (newVal) => {
console.log("newVal", newVal);
newVal.forEach((item) => { newVal.forEach((item) => {
filterData.value[item.name] = undefined; 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 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", { emit("toFilt", {
filter: filterData.value, filter,
page: pageState.page, page: pageState.page,
perPage: pageState.perPage, perPage: pageState.perPage,
}); });
@@ -123,6 +126,7 @@ const pageChange = () => {
.filter-item { .filter-item {
margin-right: 10px; margin-right: 10px;
margin-bottom: 6px; margin-bottom: 6px;
font-size: 14px;
} }
.input-item { .input-item {
width: 180px; width: 180px;

View 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>

View 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>

View 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>

View 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,
};
}

View File

@@ -74,7 +74,7 @@ const routeList: RouteType[] = [
path: 'create-view', path: 'create-view',
name: 'create-view', name: 'create-view',
component: () => import('@/views/view-all-manage/create-view/index.vue'), component: () => import('@/views/view-all-manage/create-view/index.vue'),
meta: { title: '创建' }, meta: { title: '创建视图' },
isMenu: true, isMenu: true,
children: [], children: [],
}, },

View File

@@ -14,9 +14,9 @@
:options="projectSelect" :options="projectSelect"
/> />
</a-form-item> </a-form-item>
<a-form-item label="数据表名称" name="modular_name"> <a-form-item label="数据来源" name="modular_name">
<a-input <a-input
placeholder="请输入数据表名称" placeholder="请输入数据来源"
v-model:value="formData.modular_name" v-model:value="formData.modular_name"
/> />
</a-form-item> </a-form-item>
@@ -27,26 +27,48 @@
:unCheckedValue="0" :unCheckedValue="0"
/> />
</a-form-item> </a-form-item>
<a-form-item label="展示类型" name="show_type_id"> <a-form-item label="数据源类型" name="original_type">
<a-select <a-radio-group v-model:value="formData.original_type">
placeholder="请选择展示类型" <a-radio :value="1">自定义</a-radio>
v-model:value="formData.show_type_id" <a-radio :value="2">指定表</a-radio>
:options="showTypes" </a-radio-group>
/>
</a-form-item> </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 <a-input
placeholder="请输入sql数据源" placeholder="请输入sql数据源"
v-model:value="formData.original_sql" v-model:value="formData.original_sql"
/> />
</a-form-item> </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-form>
</a-modal> </a-modal>
</template> </template>
<script setup> <script setup>
import { onMounted, ref, watch } from "vue"; 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({ const props = defineProps({
open: { open: {
@@ -70,20 +92,24 @@ const props = defineProps({
const emit = defineEmits(["ok"]); const emit = defineEmits(["ok"]);
const formRules = ref({ const formRules = ref({
modular_name: [ 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" }], original_sql: [{ required: true, message: "请输入", trigger: "submit" }],
table: [{ required: true, message: "请选择", trigger: "submit" }],
}); });
const showTypes = ref([]); const tableTypes = ref([]);
const formRef = ref(); const formRef = ref();
const formData = ref({ const formData = ref({
project_id: undefined, project_id: undefined,
modular_name: undefined, modular_name: undefined,
is_show: 0, is_show: 0,
show_type_id: undefined, original_type: 1, // 1 - 自定义2 - 指定表
// show_type_id: undefined,
original_sql: undefined, original_sql: undefined,
table: undefined,
}); });
watch( watch(
@@ -97,20 +123,21 @@ watch(
project_id: newVal.project_id, project_id: newVal.project_id,
modular_name: newVal.modular_name, modular_name: newVal.modular_name,
is_show: newVal.is_show, is_show: newVal.is_show,
show_type_id: newVal.show_type_id, // show_type_id: newVal.show_type_id,
original_sql: newVal.original_sql, original_sql: newVal.original_sql,
table: newVal.table,
}; };
} }
} }
); );
onMounted(() => { onMounted(() => {
toGetShowType(); toGetDbTable();
}); });
const toGetShowType = () => { const toGetDbTable = () => {
getShowTypeSelect().then((res) => { getDbTableSelect().then((res) => {
showTypes.value = res.data; tableTypes.value = res.data;
}); });
}; };
@@ -119,8 +146,10 @@ const resetFormData = () => {
project_id: undefined, project_id: undefined,
modular_name: undefined, modular_name: undefined,
is_show: 0, is_show: 0,
show_type_id: undefined, original_type: 1, // 1 - 自定义2 - 指定表
// show_type_id: undefined,
original_sql: undefined, original_sql: undefined,
table: undefined,
}; };
}; };

View File

@@ -200,6 +200,7 @@ const handleCancel = (record) => {
const handleSave = (record) => { const handleSave = (record) => {
const params = { const params = {
field_id: record.field_id,
field_title: record.field_title, field_title: record.field_title,
field_name: record.field_name, field_name: record.field_name,
is_search: record.is_search, is_search: record.is_search,

View File

@@ -3,7 +3,7 @@ export const moduleCfgCols = [
{ dataIndex: 'modular_name', title: '数据表名称', align: 'center'}, { dataIndex: 'modular_name', title: '数据表名称', align: 'center'},
{ dataIndex: 'project_name', title: '项目名称', align: 'center'}, { dataIndex: 'project_name', title: '项目名称', align: 'center'},
{ dataIndex: 'is_show', 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: 'sort', title: '排序', align: 'center'},
{ dataIndex: 'action', title: '操作', align: 'center'}, { dataIndex: 'action', title: '操作', align: 'center'},
]; ];

View File

@@ -54,10 +54,10 @@ export function getProjectSelect() {
}); });
} }
// 展示类型下拉 // 数据源表下拉
export function getShowTypeSelect() { export function getDbTableSelect() {
return get({ return get({
url: `/api/v1/modular/get-show-type-drop`, url: `/api/v1/modular/get-database-table-drop`,
}); });
} }

View File

@@ -13,6 +13,9 @@
v-model:value="formData.project_name" v-model:value="formData.project_name"
/> />
</a-form-item> </a-form-item>
<a-form-item label="项目标识" name="mark">
<a-input placeholder="请输入项目标识例如oa" />
</a-form-item>
<a-form-item label="展示状态" name="is_show"> <a-form-item label="展示状态" name="is_show">
<a-switch <a-switch
v-model:checked="formData.is_show" v-model:checked="formData.is_show"
@@ -85,6 +88,7 @@ const formRules = {
project_name: [ project_name: [
{ required: true, message: "请输入项目名称", trigger: "submit" }, { required: true, message: "请输入项目名称", trigger: "submit" },
], ],
mark: [{ required: true, message: "请输入项目标识", trigger: "submit" }],
database_address: [ database_address: [
{ required: true, message: "请输入数据库地址", trigger: "submit" }, { required: true, message: "请输入数据库地址", trigger: "submit" },
], ],
@@ -105,6 +109,7 @@ const formRules = {
const formRef = ref(); const formRef = ref();
const formData = ref({ const formData = ref({
project_name: undefined, project_name: undefined,
mark: undefined,
is_show: 0, is_show: 0,
database_address: undefined, database_address: undefined,
database_port: undefined, database_port: undefined,
@@ -159,6 +164,7 @@ const validateConnect = () => {
const resetFormData = () => { const resetFormData = () => {
formData.value = { formData.value = {
project_name: undefined, project_name: undefined,
mark: undefined,
is_show: 0, is_show: 0,
database_address: undefined, database_address: undefined,
database_port: undefined, database_port: undefined,

View File

@@ -1,6 +1,7 @@
export const projectCfgCols = [ export const projectCfgCols = [
{ dataIndex: 'project_id', title: '编号', align: 'center'}, { dataIndex: 'project_id', title: '编号', align: 'center'},
{ dataIndex: 'project_name', title: '项目名称', align: 'center'}, { dataIndex: 'project_name', title: '项目名称', align: 'center'},
{ dataIndex: 'mark', title: '项目标识', align: 'center'},
{ dataIndex: 'database_name', title: '数据库名', align: 'center'}, { dataIndex: 'database_name', title: '数据库名', align: 'center'},
{ dataIndex: 'is_show', title: '展示状态', align: 'center'}, { dataIndex: 'is_show', title: '展示状态', align: 'center'},
// { dataIndex: 'sort', title: '排序', align: 'center'}, // { dataIndex: 'sort', title: '排序', align: 'center'},

View File

@@ -2,16 +2,16 @@
<div class="normal-container"> <div class="normal-container">
<div class="view-create-box"> <div class="view-create-box">
<div class="left-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-form-item label="项目"
><a-select ><a-select
placeholder="请选择项目" placeholder="请选择项目"
:options="projectSel" :options="projectSel"
v-model:value="projectId" v-model:value="projectId"
@change="onProjectChange" @change="onProjectChange"
></a-select ></a-select
></a-form-item> ></a-form-item>
<a-form-item label="数据"> <a-form-item label="数据来源">
<a-select <a-select
placeholder="请选择" placeholder="请选择"
:options="modularSel" :options="modularSel"
@@ -19,16 +19,29 @@
@change="onModularChange" @change="onModularChange"
></a-select> ></a-select>
</a-form-item> </a-form-item>
<a-form-item label="字段"> <a-form-item label="展示类型">
<a-checkbox-group v-if="fieldList.length" v-model:value="fieldIds"> <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 <a-checkbox
v-for="(item, index) in fieldList" v-for="(item, index) in fieldList"
:key="index" :key="index"
:value="item.value" :value="item.value"
>{{ item.label }}</a-checkbox >{{ item.label }}</a-checkbox
> >
</a-checkbox-group> </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-item>
</a-form> </a-form>
<div class="footer"> <div class="footer">
@@ -36,7 +49,7 @@
class="preview-btn" class="preview-btn"
:loading="previewLoading" :loading="previewLoading"
@click="toPreview" @click="toPreview"
>预览</a-button >预览</a-button
> >
<a-button type="primary" @click="addViewName">点击保存</a-button> <a-button type="primary" @click="addViewName">点击保存</a-button>
</div> </div>
@@ -56,18 +69,16 @@
:options="item.options" :options="item.options"
v-model:value="previewData.filterData[item.name]" v-model:value="previewData.filterData[item.name]"
placeholder="请选择" placeholder="请选择"
allow-clear
@change="toFilt" @change="toFilt"
></a-select> ></a-select>
<a-input <a-input
v-else v-if="item.type === 'text'"
class="input-item" class="input-item"
placeholder="请输入" placeholder="请输入"
allow-clear
v-model:value="previewData.filterData[item.name]" v-model:value="previewData.filterData[item.name]"
@change=" @change="toFilt"
(e) => {
toFilt(item.name, e.target.value);
}
"
/> />
</div> </div>
</div> </div>
@@ -76,6 +87,7 @@
:columns="previewData.columnConfig" :columns="previewData.columnConfig"
:data-source="previewData.dataList" :data-source="previewData.dataList"
:pagination="false" :pagination="false"
:scroll="{ x: 1000, y: `calc(100vh - 260px)` }"
size="small" size="small"
bordered bordered
></a-table> ></a-table>
@@ -90,6 +102,11 @@
/> />
</div> </div>
</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> </div>
<a-modal <a-modal
:open="nameVisible" :open="nameVisible"
@@ -110,15 +127,29 @@
<script setup> <script setup>
import { onMounted, reactive, ref } from "vue"; 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 { message } from "ant-design-vue";
import { BarChartOutlined } from "@ant-design/icons-vue";
import yChart from "@/components/common/y-chart.vue";
const projectSel = ref([]); const projectSel = ref([]); // 项目下拉
const modularSel = ref([]); const modularSel = ref([]) // 数据来源下拉
const fieldList = ref([]); const showTypeSel = ref([]); // 展示类型下拉
const fieldList = ref([]); // 字段列表
const xDataList = ref([]); // x轴数据列表
const yDataList = ref([]); // y轴数据列表
const projectId = ref(); const projectId = ref();
const modularId = ref(); const modularId = ref();
const showTypeId = ref();
const fieldIds = ref([]); const fieldIds = ref([]);
const xDataId = ref();
const yDataId = ref();
const previewLoading = ref(false); const previewLoading = ref(false);
const nameVisible = ref(false); const nameVisible = ref(false);
@@ -130,6 +161,7 @@ const previewData = reactive({
columnConfig: [], // 表格表头 columnConfig: [], // 表格表头
dataList: [], // 表格数据 dataList: [], // 表格数据
filterData: {}, filterData: {},
chartCfg: {},
page: 1, page: 1,
perPage: 20, perPage: 20,
total: 0, total: 0,
@@ -137,6 +169,7 @@ const previewData = reactive({
onMounted(() => { onMounted(() => {
toGetProModularField(); toGetProModularField();
toGetShowTypes();
}); });
const toGetProModularField = () => { 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 onProjectChange = (val) => {
const target = projectSel.value.find((item) => item.value === val); const target = projectSel.value.find((item) => item.value === val);
modularSel.value = target.child; modularSel.value = target.child;
modularId.value = undefined; modularId.value = undefined;
fieldList.value = []; fieldList.value = [];
fieldIds.value = []; fieldIds.value = [];
resetPreviewData();
}; };
const onModularChange = (val) => { const onModularChange = (val) => {
const target = modularSel.value.find((item) => item.value === val); // const target = modularSel.value.find((item) => item.value === val);
fieldList.value = target.child; // 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 = () => { const toPreview = () => {
previewLoading.value = true; previewLoading.value = true;
const filter = previewData.filterConfig.map((item) => { const filter = previewData.filterConfig
return { .filter((item) => {
name: item.name, return previewData.filterData[item.name] !== undefined;
type: item.type, })
value: previewData.filterData[item.name], .map((item) => {
}; return {
}); name: item.name,
type: item.type,
value: previewData.filterData[item.name],
};
});
preview({ preview({
modularId: modularId.value, modularId: modularId.value,
fieldIds: fieldIds.value.toString(), fieldIds: fieldIds.value.toString(),
page: previewData.page, page: previewData.page,
perPage: previewData.perPage, perPage: previewData.perPage,
filter, filter,
showTypeId: showTypeId.value,
xDataId: xDataId.value.toString(),
yDataId: yDataId.value.toString(),
}) })
.then((res) => { .then((res) => {
previewData.type = res.data.type; previewData.type = res.data.type;
@@ -183,10 +274,11 @@ const toPreview = () => {
? previewData.filterData[item.name] ? previewData.filterData[item.name]
: undefined; : undefined;
}); });
console.log("filterData", previewData.filterData);
previewData.columnConfig = res.data.header; previewData.columnConfig = res.data.header;
previewData.dataList = res.data.data; previewData.dataList = res.data.data;
previewData.total = res.data.count; previewData.total = res.data.count;
} else {
previewData.chartCfg = res.data.config;
} }
}) })
.finally(() => { .finally(() => {
@@ -208,14 +300,16 @@ const toSaveView = () => {
modularId: modularId.value, modularId: modularId.value,
fieldIds: fieldIds.value.toString(), fieldIds: fieldIds.value.toString(),
previewName: previewName.value, previewName: previewName.value,
showTypeId: showTypeId.value,
xDataId: xDataId.value.toString(),
yDataId: yDataId.value.toString(),
}).then(() => { }).then(() => {
message.success("保存成功"); message.success("保存成功,可前往视图列表查看");
nameVisible.value = false; nameVisible.value = false;
}); });
}; };
const toFilt = (key, value) => { const toFilt = () => {
previewData.filterData[key] = value;
previewData.page = 1; previewData.page = 1;
toPreview(); toPreview();
}; };
@@ -224,13 +318,15 @@ const toFilt = (key, value) => {
<style lang="less" scoped> <style lang="less" scoped>
.normal-container { .normal-container {
height: calc(100vh - 120px); height: calc(100vh - 120px);
padding: 16px;
} }
.view-create-box { .view-create-box {
display: flex; display: flex;
height: 100%; height: 100%;
} }
.left-box { .left-box {
width: 400px; width: 320px;
flex-shrink: 0;
height: calc(100% - 20px); height: calc(100% - 20px);
padding: 10px; padding: 10px;
border-right: 1px solid #ddd; border-right: 1px solid #ddd;
@@ -243,11 +339,24 @@ const toFilt = (key, value) => {
} }
} }
.right-box { .right-box {
padding: 10px; padding: 0 0 0 10px;
flex-grow: 1; flex-grow: 1;
overflow: auto; 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 { .y-table-filter {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -255,6 +364,7 @@ const toFilt = (key, value) => {
.filter-item { .filter-item {
margin-right: 10px; margin-right: 10px;
margin-bottom: 6px; margin-bottom: 6px;
font-size: 14px;
} }
.input-item { .input-item {
width: 180px; width: 180px;
@@ -264,5 +374,6 @@ const toFilt = (key, value) => {
} }
.pagination-box { .pagination-box {
text-align: center; text-align: center;
margin-top: 10px;
} }
</style> </style>

View File

@@ -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({ return post({
url: "api/v1/preview/view", url: "api/v1/preview/view",
data: { data: {
@@ -17,18 +35,24 @@ export function preview({ modularId, fieldIds, page, perPage, filter }) {
page, page,
per_page: perPage, per_page: perPage,
filter, 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({ return post({
url: "api/v1/preview/save", url: "api/v1/preview/save",
data: { data: {
modular_id: modularId, modular_id: modularId,
field_ids: fieldIds, field_ids: fieldIds,
preview_name: previewName, preview_name: previewName,
show_type_id: showTypeId,
x_data_id: xDataId,
y_data_id: yDataId,
}, },
}); });
} }

View File

@@ -2,7 +2,7 @@
<div class="normal-container"> <div class="normal-container">
<div class="view-list-box"> <div class="view-list-box">
<div class="left-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-form-item label="项目">
<a-select <a-select
:options="projectSel" :options="projectSel"
@@ -11,11 +11,11 @@
@change="onProjectChange" @change="onProjectChange"
></a-select> ></a-select>
</a-form-item> </a-form-item>
<a-form-item label="数据"> <a-form-item label="数据来源">
<a-select <a-select
:options="modularSel" :options="modularSel"
v-model:value="modularId" v-model:value="modularId"
placeholder="请选择数据表" placeholder="请先选好项目再选择"
@change="onModularChange" @change="onModularChange"
></a-select> ></a-select>
</a-form-item> </a-form-item>
@@ -40,7 +40,26 @@
" "
size="small" size="small"
bordered 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 <a-pagination
v-model:current="pageState.page" v-model:current="pageState.page"
:total="pageState.total" :total="pageState.total"
@@ -64,6 +83,10 @@
} }
" "
/> />
<div class="preview-area" v-else>
<div><BarChartOutlined /></div>
<div>展示区</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -71,9 +94,11 @@
<script setup> <script setup>
import { onMounted, ref, reactive } from "vue"; import { onMounted, ref, reactive } from "vue";
import { getProModular, getViewList, getViewInfo } from "./service"; import { getProModular, getViewList, getViewInfo, deleteView } from "./service";
import { viewListCols } from "./config"; import { viewListCols } from "./config";
import yTable from "@/components/common/y-table.vue"; 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 projectSel = ref([]);
const modularSel = ref([]); const modularSel = ref([]);
@@ -83,6 +108,7 @@ const dataList = ref([]);
const selectedRowId = ref(); const selectedRowId = ref();
const selectViewInfo = ref({ const selectViewInfo = ref({
type: "", type: "",
filter: [],
}); });
const pageState = reactive({ const pageState = reactive({
@@ -130,11 +156,19 @@ const onModularChange = () => {
pageState.page = 1; pageState.page = 1;
toGetList(); toGetList();
}; };
const toDelete = (previewId) => {
deleteView({ previewId }).then(() => {
message.success("删除成功");
toGetList();
});
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.normal-container { .normal-container {
height: calc(100vh - 120px); height: calc(100vh - 120px);
padding: 16px;
} }
.view-list-box { .view-list-box {
@@ -143,14 +177,20 @@ const onModularChange = () => {
} }
.left-box { .left-box {
width: 400px; width: 320px;
height: calc(100% - 20px); height: calc(100% - 20px);
padding: 10px; padding: 10px;
flex-shrink: 0;
border-right: 1px solid #ddd; border-right: 1px solid #ddd;
} }
.selected-row { :deep(.ant-table-row:hover) {
background-color: beige; cursor: pointer;
background-color: #e6f4ff;
}
:deep(.selected-row) {
background-color: #e6f4ff;
} }
.pagination-box { .pagination-box {
@@ -158,8 +198,23 @@ const onModularChange = () => {
margin-top: 10px; margin-top: 10px;
} }
.right-box { .right-box {
padding: 10px; padding: 0 0 0 10px;
flex-grow: 1; flex-grow: 1;
overflow: auto; 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> </style>

View File

@@ -1,4 +1,4 @@
import { get } from "@/utils/request"; import { get, post } from "@/utils/request";
// 联动下拉 // 联动下拉
export function getProModular() { export function getProModular() {
@@ -24,15 +24,25 @@ export function getViewInfo({
previewId, previewId,
page = 1, page = 1,
perPage = 20, perPage = 20,
filter = {}, filter = [],
}) { }) {
return get({ return post({
url: "/api/v1/preview/info", url: "/api/v1/preview/info",
params: { data: {
preview_id: previewId, preview_id: previewId,
page, page,
perPage: perPage, per_page: perPage,
filter, filter,
}, },
}); });
} }
// 删除视图
export function deleteView({ previewId }) {
return post({
url: "/api/v1/preview/del",
data: {
preview_id: previewId,
},
});
}