refactor: 悦码项目重构

This commit is contained in:
wangxuefeng
2025-02-19 13:42:56 +08:00
parent c8c9406fd5
commit eab709f94f
494 changed files with 50986 additions and 27639 deletions

View File

@@ -0,0 +1,538 @@
<template>
<div class="normal-container">
<div class="view-create-box">
<div class="left-box">
<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-form-item>
<a-form-item label="数据来源">
<a-select
placeholder="请选择"
:options="modularSel"
v-model:value="modularId"
@change="onModularChange"
></a-select>
</a-form-item>
<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="showTypeId === 1">
<a-switch
v-model:checked="isExport"
:checkedValue="1"
:unCheckedValue="0"
>
</a-switch>
</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
>
</a-checkbox-group>
</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">
<a-button
class="preview-btn"
:loading="previewLoading"
@click="() => {toPreview({})}"
>预览</a-button
>
<a-button type="primary" @click="addViewName">点击保存</a-button>
</div>
</div>
<div class="right-box">
<div class="y-table-container" v-if="previewData.type === 'table'">
<div class="y-table-name">
<div class="title">{{ previewData.preview_name }}</div>
</div>
<div class="y-table-filter">
<div
v-for="item in previewData.filterConfig"
:key="item.name"
class="filter-item"
>
<span>{{ item.label }}</span>
<a-select
v-if="item.type === 'select'"
class="input-item"
:options="item.options"
v-model:value="previewData.filterData[item.name]"
placeholder="请选择"
allow-clear
@change="toFilt"
></a-select>
<a-input
v-if="item.type === 'text'"
class="input-item"
placeholder="请输入"
allow-clear
v-model:value="previewData.filterData[item.name]"
@change="toFilt"
/>
<a-range-picker
v-if="item.type === 'time'"
class="date-item"
v-model:value="previewData.filterData[item.name]"
@change="toFilt"
/>
<a-range-picker
v-else-if="item.type === 'date_time'"
class="date-time-item"
v-model:value="previewData.filterData[item.name]"
show-time
format="YYYY-MM-DD HH:mm"
@change="toFilt"
/>
<!-- 数值区间 -->
<div
v-else-if="item.type === 'number_range' && previewData.filterData[item.name]"
class="number_range_box"
>
<a-input-number
placeholder="最小值"
style="width: 140px"
v-model:value="previewData.filterData[item.name].min"
@change="toFilt"
/>
<span class="divider"> - </span>
<a-input-number
placeholder="最大值"
style="width: 140px"
v-model:value="previewData.filterData[item.name].max"
@change="toFilt"
/>
</div>
</div>
<div v-if="previewData.isExport" class="filter-item">
<a-button type="primary">导出</a-button>
</div>
</div>
<div class="y-table-content">
<a-table
:columns="previewData.columnConfig"
:data-source="previewData.dataList"
:pagination="false"
:scroll="{ x: 1000, y: `calc(100vh - 260px)` }"
size="small"
bordered
>
<template #bodyCell="{ column, record }">
<a-image
v-if="column.show_type === 'img'"
:src="record[column.dataIndex]"
:width="160"
/>
<a
v-else-if="column.show_type === 'link'"
target="_blank"
:href="record[column.dataIndex]"
>{{ record[column.dataIndex] }}</a
>
<div
v-else-if="column.show_type === 'richText'"
v-html="record[column.dataIndex]"
></div>
<template v-else>{{ record[column.dataIndex] }}</template>
</template>
</a-table>
<a-pagination
v-model:current="previewData.page"
:total="previewData.total"
:page-size="previewData.perPage"
:hide-on-single-page="false"
size="small"
class="pagination-box"
@change="() => { toPreview({}) }"
/>
</div>
</div>
<y-chart
v-else-if="previewData.type === 'chart'"
:chart-cfg="previewData.chartCfg"
:filter-config="previewData.filter"
@toFilt="() => {toPreview({})}"
></y-chart>
<div class="preview-area" v-else>
<div><BarChartOutlined /></div>
<div>预览区</div>
</div>
</div>
<a-modal
:open="nameVisible"
title="保存视图"
:bodyStyle="{ paddingTop: '20px' }"
@cancel="nameVisible = false"
@ok="toSaveView"
>
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-form-item label="视图名称">
<a-input placeholder="请输入视图名称" v-model:value="previewName" />
</a-form-item>
</a-form>
</a-modal>
</div>
</div>
</template>
<script setup>
import { onMounted, reactive, ref } from "vue";
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";
import { cloneDeep } from "lodash-es";
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 isExport = ref(0);
const previewLoading = ref(false);
const nameVisible = ref(false);
const previewName = ref();
const previewData = reactive({
type: "",
filterConfig: [], // 筛选条件
columnConfig: [], // 表格表头
dataList: [], // 表格数据
filterData: {},
config: {},
filter: [],
page: 1,
perPage: 20,
total: 0,
});
onMounted(() => {
toGetProModularField();
toGetShowTypes();
});
const toGetProModularField = () => {
getProModularField().then((res) => {
projectSel.value = res.data;
});
};
// 获取展示类型下拉
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;
resetSelectData();
resetPreviewData();
};
const onModularChange = () => {
resetSelectData();
resetPreviewData();
};
const onShowTypeChange = () => {
toGetFieldOpts();
};
const tranformList = (list) => {
return list.map((item) => {
return {
label: item.field_name,
value: item.field_id,
};
});
}
// 重置配置数据
const resetSelectData = () => {
showTypeId.value = undefined
isExport.value = 0
fieldList.value = [];
fieldIds.value = [];
xDataList.value = [];
yDataList.value = [];
xDataId.value = undefined;
yDataId.value = [];
}
// 重置预览数据
const resetPreviewData = () => {
previewData.type = "";
previewData.filterConfig = [];
previewData.columnConfig = [];
previewData.dataList = [];
previewData.filterData = {};
previewData.config = {};
previewData.page = 1;
previewData.perPage = 20;
previewData.total = 0;
};
// 请求预览数据
const toPreview = ({filter}) => {
previewLoading.value = true;
let filterData
if (!filter) {
const cloneFilter = cloneDeep(previewData.filterConfig)
filterData = cloneFilter
.filter((item) => {
return previewData.filterData[item.name] !== undefined && previewData.filterData[item.name] !== null;
})
.map((item) => {
if (item.type === 'time' && previewData.filterData[item.name]) {
// 日期类型的参数
return {
name: item.name,
type: item.type,
start_time: previewData.filterData[item.name][0].format('YYYY-MM-DD'),
end_time: previewData.filterData[item.name][1].format('YYYY-MM-DD'),
};
} else if (item.type === 'date_time' && previewData.filterData[item.name]) {
// 带时分的日期类型参数
return {
name: item.name,
type: item.type,
start_time: previewData.filterData[item.name][0].format('YYYY-MM-DD HH:mm') + ':00',
end_time: previewData.filterData[item.name][1].format('YYYY-MM-DD HH:mm') + ':59',
};
} else if (item.type === 'number_range') {
// 数值区间
return {
name: item.name,
type: item.type,
min: previewData.filterData[item.name].min ? String(previewData.filterData[item.name].min) : '',
max: previewData.filterData[item.name].max ? String(previewData.filterData[item.name].max) : '',
};
} else {
return {
name: item.name,
type: item.type,
value: previewData.filterData[item.name],
};
}
});
} else {
filterData = filter;
}
preview({
modularId: modularId.value,
fieldIds: fieldIds.value.toString(),
page: previewData.page,
perPage: previewData.perPage,
filter: filterData,
showTypeId: showTypeId.value,
xDataId: xDataId.value?.toString(),
yDataId: yDataId.value?.toString(),
isExport: isExport.value,
})
.then((res) => {
previewData.type = res.data.type;
if (res.data.type === "table") {
previewData.filterConfig = res.data.filter;
previewData.filterConfig.forEach((item) => {
if (item.type === 'number_range' && !previewData.filterData[item.name]) {
previewData.filterData[item.name] = {
...item,
min: undefined,
max: undefined,
};
} else {
previewData.filterData[item.name] = previewData.filterData[item.name]
? previewData.filterData[item.name]
: undefined;
}
});
previewData.columnConfig = res.data.header;
previewData.dataList = res.data.data;
previewData.total = res.data.count;
previewData.isExport = res.data.is_export
} else {
previewData.chartCfg = res.data.config;
previewData.filter = res.data.filter;
}
})
.finally(() => {
previewLoading.value = false;
});
};
const addViewName = () => {
nameVisible.value = true;
previewName.value = null;
};
const toSaveView = () => {
if (!previewName.value) {
message.error("请输入名称");
return;
}
saveView({
modularId: modularId.value,
fieldIds: fieldIds.value.toString(),
previewName: previewName.value,
showTypeId: showTypeId.value,
xDataId: xDataId.value?.toString(),
yDataId: yDataId.value?.toString(),
isExport: isExport.value,
}).then(() => {
message.success("保存成功,可前往视图列表查看");
nameVisible.value = false;
});
};
const toFilt = () => {
previewData.page = 1;
toPreview({});
};
</script>
<style lang="less" scoped>
.normal-container {
height: calc(100vh - 120px);
padding: 16px;
}
.view-create-box {
display: flex;
height: 100%;
}
.left-box {
width: 320px;
flex-shrink: 0;
height: calc(100% - 20px);
padding: 10px;
border-right: 1px solid #ddd;
.footer {
text-align: right;
.preview-btn {
margin-right: 10px;
}
}
}
.right-box {
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-name {
.title {
font-size: 18px;
font-weight: bold;
}
}
.y-table-filter {
display: flex;
flex-wrap: wrap;
}
.filter-item {
margin-right: 10px;
margin-bottom: 6px;
font-size: 14px;
}
.input-item {
width: 180px;
}
.date-item {
width: 240px;
}
.date-time-item {
width: 300px;
}
.number_range_box {
display: inline-flex;
align-items: center;
.divider {
margin: 0 4px;
}
}
.y-table-content {
margin-top: 10px;
}
.pagination-box {
text-align: center;
margin-top: 10px;
}
</style>

View File

@@ -0,0 +1,59 @@
import { get, post } from "@/utils/request";
// 项目-表-字段下拉
export function getProModularField() {
return get({
url: "/api/v1/field/get-project-modular-field-drop",
});
}
// 展示类型下拉
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, showTypeId, xDataId, yDataId, isExport }) {
return post({
url: "api/v1/preview/view",
data: {
modular_id: modularId,
field_ids: fieldIds,
page,
per_page: perPage,
filter,
show_type_id: showTypeId,
x_data_id: xDataId,
y_data_id: yDataId,
is_export: isExport,
},
});
}
// 点击保存
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,
},
});
}

View File

@@ -0,0 +1,6 @@
export const viewListCols = [
{ dataIndex: 'preview_id', title: 'id', align: 'center' },
{ dataIndex: 'preview_name', title: '视图名称', align: 'center' },
{ dataIndex: 'created_at', title: '创建时间', align: 'center' },
{ dataIndex: 'action', title: '操作', align: 'center' },
];

View File

@@ -0,0 +1,239 @@
<template>
<div class="normal-container">
<div class="view-list-box">
<div class="left-box">
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-form-item label="项目">
<a-select
:options="projectSel"
v-model:value="projectId"
placeholder="请选择项目"
@change="onProjectChange"
></a-select>
</a-form-item>
<a-form-item label="数据来源">
<a-select
:options="modularSel"
v-model:value="modularId"
placeholder="请先选好项目再选择"
@change="onModularChange"
></a-select>
</a-form-item>
</a-form>
<a-table
:data-source="dataList"
:columns="viewListCols"
:pagination="false"
:row-class-name="
(record) =>
record.preview_id === selectedRowId ? 'selected-row' : ''
"
:custom-row="
(record, index) => {
return {
onClick: () => {
selectedRowId = record.preview_id;
toGetViewInfo();
},
};
}
"
size="small"
bordered
>
<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"
:page-size="pageState.perPage"
:hide-on-single-page="false"
size="small"
class="pagination-box"
@change="toGetList"
/>
</div>
<div class="right-box">
<y-table
v-if="selectViewInfo.type === 'table'"
:previewId="selectedRowId"
:filter-config="selectViewInfo.filter"
:data-list="selectViewInfo.data"
:column-config="selectViewInfo.header"
:total="selectViewInfo.count"
:title="selectViewInfo.preview_name"
:is-export="selectViewInfo.is_export"
@toFilt="
(params) => {
toGetViewInfo(params);
}"
/>
<y-chart
v-else-if="selectViewInfo.type === 'chart'"
:chartCfg="selectViewInfo.config"
:title="selectViewInfo.preview_name"
:filter-config="selectViewInfo.filter"
@toFilt="toGetViewInfo"
/>
<div class="preview-area" v-else>
<div><BarChartOutlined /></div>
<div>展示区</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref, reactive } from "vue";
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([]);
const projectId = ref();
const modularId = ref();
const dataList = ref([]);
const selectedRowId = ref();
const selectViewInfo = ref({
type: "",
filter: [],
config: {
line: {
data: []
}
}
});
const pageState = reactive({
page: 1,
perPage: 20,
total: 0,
});
onMounted(() => {
toGetProModular();
});
const toGetProModular = () => {
getProModular().then((res) => {
projectSel.value = res.data;
if (res.data.length) {
projectId.value = res.data[0].value;
onProjectChange(projectId.value)
}
});
};
const toGetList = () => {
getViewList({
modularId: modularId.value,
page: pageState.page,
perPage: pageState.perPage,
}).then((res) => {
dataList.value = res.data.list;
pageState.total = res.data.total;
});
};
const toGetViewInfo = (params = {}) => {
getViewInfo({
previewId: selectedRowId.value,
...params,
}).then((res) => {
selectViewInfo.value = res.data;
});
};
const onProjectChange = (val) => {
modularSel.value = projectSel.value.find((item) => item.value === val).child;
modularId.value = null;
};
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 {
display: flex;
height: 100%;
}
.left-box {
width: 320px;
height: calc(100% - 20px);
padding: 10px;
flex-shrink: 0;
border-right: 1px solid #ddd;
overflow: auto;
}
:deep(.ycode-ant-table-row:hover) {
cursor: pointer;
background-color: #e6f4ff;
}
:deep(.selected-row) {
background-color: #e6f4ff;
}
.pagination-box {
text-align: center;
margin-top: 10px;
}
.right-box {
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>

View File

@@ -0,0 +1,48 @@
import { get, post } from "@/utils/request";
// 联动下拉
export function getProModular() {
return get({
url: "/api/v1/field/get-project-modular-drop",
});
}
// 视图列表
export function getViewList({ modularId, page = 1, perPage = 20 }) {
return get({
url: "/api/v1/preview/list",
params: {
modular_id: modularId,
page,
perPage: perPage,
},
});
}
// 查看视图
export function getViewInfo({
previewId,
page = 1,
perPage = 20,
filter = [],
}) {
return post({
url: "/api/v1/preview/info",
data: {
preview_id: previewId,
page,
per_page: perPage,
filter,
},
});
}
// 删除视图
export function deleteView({ previewId }) {
return post({
url: "/api/v1/preview/del",
data: {
preview_id: previewId,
},
});
}