wujingjing
2025-03-27 7f97bb126bfb6450ba0f8d2b5d4a632db74c3c58
维度管理
已修改3个文件
已添加4个文件
582 ■■■■■ 文件已修改
customer_list/yw/static/config/globalConfig.spump.js 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
customer_list/yw/static/config/globalConfig.test.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/dimension/index.ts 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/yw/systemManage/dimensionMgr/DimensionMgr.vue 93 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/yw/systemManage/dimensionMgr/components/OptDlg.vue 341 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/yw/systemManage/dimensionMgr/components/TagInput.vue 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/yw/systemManage/dimensionMgr/components/constants.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
customer_list/yw/static/config/globalConfig.spump.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
window.globalConfig = {
    // é¡¹ç›®åç§°
    Name: 'WI水务智能',
    // é¡¹ç›®ç‰ˆæœ¬å·
    Version: '1.0.0242',
    // ç½‘站备案号
    ICPLicense: '沪ICP备14049296号-2',
    WebApiUrl: {
        MainUrl: 'http://47.100.245.85:8802/',
        AuthUrl: 'http://47.100.245.85:8190/',
    },
    SoftWareInfo: {
        // ç½‘站主标题(菜单导航、浏览器当前网页标题)
        globalTitle: 'WI水务智能',
        // ç½‘站副标题(登录页顶部文字)
        globalViceTitle: 'WI水务智能',
        // ç½‘站副标题(登录页顶部文字)
        globalViceTitleMsg: '拉瓦锡卡文智能科技有限公司',
        // tab é¡µ icon
        favicon: './favicon.ico',
        // ç™»å½•左侧 logo
        logoMini: './static/images/logo/logoWithNoName.png',
        // ç™»å½•左侧图片
        loginMain: './static/images/login/login-main.svg',
        // ç™»å½•背景
        loginBg: './static/images/login/login-bg.svg',
        // ä¸»é¡µé¢å·¦ä¸Š logo
        logoTopMenu: './static/images/logo/logo-mini-white.png',
    },
    // å¯¹æŽ¥æƒé™ç³»ç»Ÿç›¸å…³
    Auth: {
        // ç™»å½•软件编码
        SoftWareCode: 'Istation_web_demo',
        // ç™»å½•信息
        Message: '',
    },
};
customer_list/yw/static/config/globalConfig.test.js
@@ -7,9 +7,9 @@
    ICPLicense: '沪ICP备14049296号-2',
    WebApiUrl: {
        MainUrl: 'https://widev.cpolar.top/ai_dev/',
        // MainUrl: 'https://widev.cpolar.top/ai_dev/',
        // MainUrl: 'http://192.168.1.58:8080/',
        // MainUrl: 'http://192.168.1.41:8080/',
        MainUrl: 'http://192.168.1.41:8080/',
        AuthUrl: 'http://47.100.245.85:8190/',
src/api/dimension/index.ts
@@ -1,4 +1,5 @@
import request, { ExtraConfig } from '/@/utils/request';
import type { ExtraConfig } from '/@/utils/request';
import request from '/@/utils/request';
/**
 * @description èŽ·å–ç»´åº¦åç§°åˆ—è¡¨
@@ -11,3 +12,27 @@
        data: {},
        ...extraData,
    });
/**
 * @description æ–°å¢žç»´åº¦
 * @param {FormData} params
 **/
export const addDimensionByPost = (params) =>
    request({
        url: `/admin/dimension/add_dimension`,
        method: 'post',
        params: {},
        data: params,
    });
/**
 * @description ä¿®æ”¹ç»´åº¦
 * @param {FormData} params
 **/
export const updateDimensionByPost = (params) =>
    request({
        url: `/admin/dimension/update_dimension`,
        method: 'post',
        params: {},
        data: params,
    });
src/views/project/yw/systemManage/dimensionMgr/DimensionMgr.vue
@@ -29,7 +29,7 @@
                <el-form-item>
                    <!-- <el-button type="primary" icon="ele-Search" @click="handleQueryTable"> æŸ¥è¯¢ </el-button> -->
                    <el-button icon="ele-Refresh" @click="resetQuery">重置 </el-button>
                    <!-- <el-button icon="ele-Plus" @click="openOptDlg()"> æ·»åŠ  </el-button> -->
                    <el-button type="primary" icon="ele-Plus" @click="openOptDlg()"> æ·»åŠ  </el-button>
                </el-form-item>
            </el-form>
        </template>
@@ -71,7 +71,11 @@
                    </el-table-column>
                    <el-table-column label="单位" prop="unit" width="100" show-overflow-tooltip> </el-table-column>
                    <el-table-column label="类型" prop="type" width="100" show-overflow-tooltip> </el-table-column>
                    <el-table-column label="类型" prop="type" width="100" show-overflow-tooltip>
                        <template #default="scope">
                            {{ DimensionTypeMap[scope.row.type] }}
                        </template>
                    </el-table-column>
                    <el-table-column label="操作" width="150" fixed="right" show-overflow-tooltip>
                        <template #default="scope">
@@ -82,6 +86,9 @@
                                        @click="openAliasDisplayDlg(scope.row)"
                                    ></i>
                                </el-tooltip>
                                <el-tooltip effect="dark" content="编辑" placement="top">
                                    <i class="ywifont ywicon-bianji !text-[15px] text-blue-400 cursor-pointer" @click="openOptDlg(scope.row)"></i>
                                </el-tooltip>
                            </div>
                        </template>
                    </el-table-column>
@@ -89,28 +96,43 @@
            </div>
        </template>
        <AliasDisplayDlg v-model="aliasDisplayDlgIsShow" :item="aliasDisplayDlgMapRow"></AliasDisplayDlg>
        <!-- <OptDlg v-model="optDlgIsShow" :item="optDlgMapRow" @insert="insertOpt" @update="updateOpt" :groupId="currentListID"></OptDlg> -->
        <OptDlg
            :groupId="queryParams.group"
            v-model="optDlgIsShow"
            :listTreeData="listTreeData"
            :item="optDlgMapRow"
            @insert="insertOpt"
            @update="updateOpt"
            :tableData="tableData"
            :dataSources="dataSources"
        ></OptDlg>
        <!-- <MetricPrompt v-model="infoDlgIsShow" :metricItem="infoDlgMapRow"></MetricPrompt>
        <MetricName v-model="metricNameIsShow" :metricItem="metricNameMapRow"></MetricName> -->
    </HMContainer>
</template>
<script setup lang="ts">
import { convertListToTree, debounce, getItemMap } from '/@/utils/util';
import { convertListToTree, debounce, getItemMap, travelTree } from '/@/utils/util';
import { computed, onMounted, ref } from 'vue';
import { usePageDisplay } from '/@/hooks/usePageDisplay';
import { useQueryTable } from '/@/hooks/useQueryTable';
// import { useTableSort } from '/@/hooks/useTableSort';
// import { useValidateUniqueness } from '/@/hooks/useValidateUniqueness';
import { SupervisorPublished } from '../../lowCode/sqlAmis/types';
import type { SupervisorPublished } from '../../lowCode/sqlAmis/types';
import AliasDisplayDlg from './components/AliasDisplayDlg.vue';
import { DimensionTypeMap } from './components/constants';
import OptDlg from './components/OptDlg.vue';
import { getDataSourceList } from '/@/api/dataSource';
import * as dimensionApi from '/@/api/dimension';
import { getSceneGroupTreeByPost } from '/@/api/scene';
import HMContainer from '/@/components/layout/HMContainer.vue';
import { OptClassificationMap, classificationEnum } from '/@/views/types/metrics';
import { ElMessage } from 'element-plus';
const dataSources = ref([]);
const initDataSources = async () => {
    const res = await getDataSourceList();
    dataSources.value = res.values;
};
const themeDomainData = ref([]);
const initThemeDomainData = async () => {
    const res = await getSceneGroupTreeByPost();
@@ -188,18 +210,62 @@
    optDlgIsShow.value = true;
};
const updateOpt = (formValue) => {
    const foundIndex = tableData.value.findIndex((item) => item.id === formValue.id);
const updateOptData = (optData, formValue) => {
    const foundIndex = optData.findIndex((item) => item.id === formValue.id);
    if (foundIndex > -1) {
        tableData.value[foundIndex] = {
            ...tableData.value[foundIndex],
        optData[foundIndex] = {
            ...optData[foundIndex],
            ...formValue,
        };
    }
};
const updateOpt = (formValue) => {
    if (formValue.group === queryParams.value.group || queryParams.value.group === '') {
        updateOptData(displayTableData.value, formValue);
        updateOptData(tableData.value, formValue);
    } else {
        displayTableData.value = displayTableData.value.filter((item) => item.group !== formValue.group);
        updateOptData(tableData.value, formValue);
    }
};
const insertOptData = (optData, newData) => {
    optData.unshift({ ...newData });
};
// åˆ¤æ–­æ˜¯å¦å±žäºŽå½“前主题域,或当期主题域下属主题域
const isIncludeGroup = (group, currentGroup) => {
    if (!currentGroup) return true;
    if (group === currentGroup) return true;
    let curGroupItem;
    travelTree(listTreeData.value, (item) => {
        if (item.group_id === currentGroup) {
            curGroupItem = item;
            return true;
        }
    });
    if (!curGroupItem) return false;
    let isFind = false;
    travelTree(curGroupItem.Children as any[], (item) => {
        if (item.group_id === group) {
            isFind = true;
            return true;
        }
    });
    return isFind;
};
const insertOpt = (newData) => {
    tableData.value.unshift({ ...newData, published: SupervisorPublished.N });
    if (newData.group === queryParams.value.group || queryParams.value.group === '') {
        insertOptData(displayTableData.value, newData);
        insertOptData(tableData.value, newData);
    } else {
        insertOptData(tableData.value, newData);
    }
};
//#endregion
@@ -250,8 +316,6 @@
    return isExist;
};
const openAliasDisplayDlg = (row) => {
    aliasDisplayDlgMapRow.value = row;
    aliasDisplayDlgIsShow.value = true;
};
@@ -260,6 +324,7 @@
onMounted(() => {
    getTableData();
    initThemeDomainData();
    initDataSources();
});
</script>
<style scoped lang="scss"></style>
src/views/project/yw/systemManage/dimensionMgr/components/OptDlg.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,341 @@
<template>
    <ywDialog
        v-model="dialogIsShow"
        :headerIcon="dialogHeaderIcon"
        :title="dialogTitle"
        width="470"
        @dlgClosed="closeDialog"
        @submit="submitFormValue"
    >
        <el-form :model="dialogFormValue" ref="dialogFormRef" :rules="dialogFormRules" label-width="96">
            <el-form-item label="维度ID" prop="dim_id" v-if="!isEditDialog">
                <el-input v-model="dialogFormValue.dim_id"></el-input>
            </el-form-item>
            <el-form-item label="定义" prop="dim_title">
                <el-input type="textarea" :rows="3" v-model="dialogFormValue.dim_title"></el-input>
            </el-form-item>
            <el-form-item label="类型" prop="dim_type">
                <el-select v-model="dialogFormValue.dim_type">
                    <el-option
                        v-for="item in Object.keys(DimensionTypeMap)"
                        :key="item"
                        :value="item"
                        :label="DimensionTypeMap[item]"
                    ></el-option>
                </el-select>
            </el-form-item>
            <el-form-item label="别名" prop="dim_alias">
                <div class="flex flex-col gap-2">
                    <div class="flex flex-col gap-3">
                        <div class="flex items-center gap-2" v-for="(item, index) in editAlias" :key="index">
                            <el-input v-model="item.key" class="w-[60px] flex-0 mb-auto font-bold"></el-input>
                            <TagInput class="flex-auto mt-1" v-model="item.value" />
                            <el-button size="small" type="danger" circle @click="deleteAlias(index)">
                                <el-icon><Delete /></el-icon>
                            </el-button>
                        </div>
                    </div>
                    <el-button size="small" type="primary" @click="addAlias">添加别名</el-button>
                </div>
            </el-form-item>
            <el-form-item label="名称" prop="dim_name">
                <el-input v-model="dialogFormValue.dim_name"></el-input>
            </el-form-item>
            <el-form-item label="主题域" prop="dim_group">
                <el-tree-select
                    filterable
                    :props="{
                        id: 'group_id',
                        label: 'group_name',
                        children: 'Children',
                    }"
                    v-model="dialogFormValue.dim_group"
                    node-key="group_id"
                    clearable
                    defaultExpandAll
                    :data="listTreeData"
                    placeholder="请选择主题域"
                    check-strictly
                >
                </el-tree-select>
            </el-form-item>
            <el-form-item label="单位" prop="dim_unit">
                <el-input v-model="dialogFormValue.dim_unit"></el-input>
            </el-form-item>
            <el-form-item label="值列表" prop="dim_value_list">
                <div class="flex flex-col gap-2 w-full">
                    <el-select v-model="activeValueListType" @change="changeValueListType">
                        <el-option
                            v-for="item in Object.keys(ValueListTypeMap)"
                            :key="item"
                            :value="item"
                            :label="ValueListTypeMap[item]"
                        ></el-option>
                    </el-select>
                    <!-- <el-input v-model="dialogFormValue.dim_value_list" type="textarea" :rows="3"></el-input> -->
                    <TagInput
                        v-if="activeValueListType === ValueListType.Value"
                        class="flex-auto mt-1"
                        v-model="dialogFormValue.dim_value_list.values"
                    />
                    <el-input
                        placeholder="请输入SQL语句"
                        v-if="activeValueListType === ValueListType.Sql"
                        v-model="dialogFormValue.dim_value_list.sql.sql"
                        type="textarea"
                        :rows="3"
                    ></el-input>
                    <el-select placeholder="请选择数据库" v-if="activeValueListType === ValueListType.Sql" v-model="dialogFormValue.dim_value_list.sql.ds_id">
                        <el-option v-for="item in dataSources" :key="item.id" :label="item.title" :value="item.id"></el-option>
                    </el-select>
                </div>
            </el-form-item>
        </el-form>
    </ywDialog>
</template>
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus';
import { ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia';
import { computed, ref, watch } from 'vue';
import * as dimensionApi from '/@/api/dimension';
import ywDialog from '/@/components/dialog/yw-dialog.vue';
import { useUserInfo } from '/@/stores/userInfo';
import { formatDate } from '/@/utils/formatTime';
import { deepClone } from '/@/utils/other';
import TagInput from './TagInput.vue';
import { DimensionTypeMap } from './constants';
import { getDataSourceList } from '/@/api/dataSource';
const enum ValueListType {
    Sql = 'sql',
    Value = 'value',
}
const activeValueListType = ref(ValueListType.Value);
const ValueListTypeMap = {
    [ValueListType.Sql]: 'SQL查询',
    [ValueListType.Value]: '值输入',
};
const getEmptyValueList = (type = activeValueListType.value) => {
    return type === ValueListType.Sql
        ? {
                sql: {
                    ds_id: '',
                    sql: '',
                },
          }
        : {
                values: [],
          };
};
const changeValueListType = (val) => {
    dialogFormValue.value.dim_value_list = getEmptyValueList(val);
};
const checkIsSql = (valueList) => {
    return valueList && valueList.hasOwnProperty('sql');
};
const checkIsEmptyValueList = (valueList) => {
    if (!checkIsSql(valueList)) {
        return valueList.values.length === 0;
    }
    return valueList.sql.ds_id === '' && valueList.sql.sql === '';
};
const props = defineProps(['item', 'groupId', 'listTreeData', 'tableData', 'dataSources']);
const emit = defineEmits(['update', 'insert']);
const addAlias = () => {
    editAlias.value.push({
        key: '',
        value: [],
    });
};
const deleteAlias = (index) => {
    editAlias.value.splice(index, 1);
};
//#region ====================== å¢žåŠ ã€ä¿®æ”¹è®°å½•æ“ä½œ, dialog init======================
const isEditDialog = ref(false);
const dialogTitle = computed(() => {
    return isEditDialog.value ? '修改维度' : '添加维度';
});
const dialogHeaderIcon = computed(() => {
    return isEditDialog.value ? 'ele-Edit' : 'ele-Plus';
});
const dialogFormValue = ref(null);
const dialogIsShow = defineModel({
    type: Boolean,
});
const dialogFormRef = ref<FormInstance>(null);
const dialogFormRules = ref<FormRules>({
    dim_id: [
        { required: true, message: '请输入维度ID', trigger: 'blur' },
        {
            validator: (rule, value, callback) => {
                if (!value) {
                    callback();
                    return;
                }
                const existItem = props.tableData.find((item) => item.id === value);
                if (existItem) {
                    callback(new Error('维度ID已存在'));
                    return;
                }
                callback();
            },
            trigger: 'blur',
        },
    ],
    dim_title: [{ required: true, message: '请输入维度定义', trigger: 'blur' }],
    dim_type: [{ required: true, message: '请选择维度类型', trigger: 'change' }],
    dim_name: [{ required: true, message: '请输入维度名称', trigger: 'blur' }],
});
const editAlias = ref([]);
const openOperateDialog = (row?) => {
    if (row) {
        isEditDialog.value = true;
        const {
            id: dim_id,
            prompt: dim_title,
            type: dim_type,
            alias: dim_alias,
            title: dim_name,
            group: dim_group,
            unit: dim_unit,
            dimValueList: dim_value_list,
        } = row;
        dialogFormValue.value = deepClone({
            dim_id,
            dim_title,
            dim_type,
            dim_alias,
            dim_name,
            dim_group,
            dim_unit,
            dim_value_list,
        });
    } else {
        isEditDialog.value = false;
        dialogFormValue.value = {
            dim_id: null,
            dim_title: null,
            dim_type: null,
            dim_alias: {},
            dim_name: null,
            dim_group: props.groupId,
            dim_unit: null,
            dim_value_list: null,
        };
    }
    editAlias.value = Object.keys(dialogFormValue.value.dim_alias ?? {}).map((item) => {
        return {
            key: item,
            value: dialogFormValue.value.dim_alias[item],
        };
    });
    dialogFormValue.value.dim_value_list = dialogFormValue.value.dim_value_list ?? getEmptyValueList();
    activeValueListType.value = checkIsSql(dialogFormValue.value.dim_value_list) ? ValueListType.Sql : ValueListType.Value;
    console.log("🚀 ~ dialogFormValue.value.dim_value_list:", dialogFormValue.value.dim_value_list)
};
const closeDialog = () => {
    dialogIsShow.value = false;
    dialogFormRef.value?.clearValidate();
};
const stores = useUserInfo();
const { userInfos } = storeToRefs(stores);
const checkAlias = () => {
    for (const item of editAlias.value) {
        if (!item.key) {
            ElMessage.error('别名的键不能为空');
            return false;
        }
    }
    const keyCount = editAlias.value.reduce((acc, item) => {
        acc[item.key] = (acc[item.key] || 0) + 1;
        return acc;
    }, {});
    for (const key in keyCount) {
        if (keyCount[key] > 1) {
            ElMessage.error(`别名的键 "${key}" é‡å¤å‡ºçް`);
            return false;
        }
    }
    return true;
};
const submitFormValue = async () => {
    const valid = await dialogFormRef.value?.validate().catch(() => {});
    if (!valid) return;
    if (!checkAlias()) return;
    dialogFormValue.value.dim_alias = editAlias.value.reduce((acc, item) => {
        acc[item.key] = item.value;
        return acc;
    }, {});
    const sendForm = {
        ...dialogFormValue.value,
        dim_alias: JSON.stringify(dialogFormValue.value.dim_alias),
        dim_value_list: checkIsEmptyValueList(dialogFormValue.value.dim_value_list)
            ? null
            : JSON.stringify(dialogFormValue.value.dim_value_list),
    };
    if (isEditDialog.value) {
        const res = await dimensionApi.updateDimensionByPost(sendForm);
        emit('update', {
            id: dialogFormValue.value.dim_id,
            prompt: dialogFormValue.value.dim_title,
            type: dialogFormValue.value.dim_type,
            alias: dialogFormValue.value.dim_alias,
            title: dialogFormValue.value.dim_name,
            group: dialogFormValue.value.dim_group,
            unit: dialogFormValue.value.dim_unit,
            dimValueList: dialogFormValue.value.dim_value_list,
        });
        closeDialog();
        ElMessage.success('修改维度成功');
    } else {
        const res = await dimensionApi.addDimensionByPost(sendForm);
        const newData = {
            id: dialogFormValue.value.dim_id,
            prompt: dialogFormValue.value.dim_title,
            type: dialogFormValue.value.dim_type,
            alias: dialogFormValue.value.dim_alias,
            title: dialogFormValue.value.dim_name,
            group: dialogFormValue.value.dim_group,
            unit: dialogFormValue.value.dim_unit,
            dimValueList: dialogFormValue.value.dim_value_list,
        };
        emit('insert', newData);
        closeDialog();
        ElMessage.success('添加维度成功');
    }
};
//#endregion
watch(
    () => dialogIsShow.value,
    (val) => {
        if (!val) return;
        openOperateDialog(props.item);
    }
);
</script>
<style scoped lang="scss"></style>
src/views/project/yw/systemManage/dimensionMgr/components/TagInput.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
<template>
    <div class="flex gap-2 flex-wrap">
        <el-tag v-for="tag in dynamicTags" :key="tag" closable :disable-transitions="false" @close="handleClose(tag)">
            {{ tag }}
        </el-tag>
        <el-input
            v-if="inputVisible"
            ref="InputRef"
            v-model="inputValue"
            class="w-32"
            size="small"
            @keyup.enter="handleInputConfirm"
            @blur="handleInputConfirm"
        />
        <el-button v-else class="button-new-tag" size="small" @click="showInput"> + {{ newLabel }} </el-button>
    </div>
</template>
<script lang="ts" setup>
import { nextTick, ref } from 'vue';
import { ElInput } from 'element-plus';
import type { InputInstance } from 'element-plus';
const props = defineProps({
    newLabel: {
        type: String,
        default: '添加',
    },
});
const inputValue = ref('');
const dynamicTags = defineModel({
    type: Array<string>,
});
const inputVisible = ref(false);
const InputRef = ref<InputInstance>();
const handleClose = (tag: string) => {
    dynamicTags.value.splice(dynamicTags.value.indexOf(tag), 1);
};
const showInput = () => {
    inputVisible.value = true;
    nextTick(() => {
        InputRef.value!.input!.focus();
    });
};
const handleInputConfirm = () => {
    if (inputValue.value) {
        if (!dynamicTags.value) {
            dynamicTags.value = [inputValue.value];
        } else {
            dynamicTags.value.push(inputValue.value);
        }
    }
    inputVisible.value = false;
    inputValue.value = '';
};
</script>
src/views/project/yw/systemManage/dimensionMgr/components/constants.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
export const enum DimensionType {
    String = 'S',
    Double = 'D',
    Integer = 'I',
    Long = 'L',
    Time = 'T',
}
export const DimensionTypeMap = {
    [DimensionType.String]: '字符串',
    [DimensionType.Double]: '浮点数',
    [DimensionType.Integer]: '整数',
    [DimensionType.Long]: '长整数',
    [DimensionType.Time]: '时间',
};