wujingjing
2025-03-03 254816a712847b099184d84ca8631a50fb32f39e
初步对接
已修改12个文件
318 ■■■■ 文件已修改
customer_list/common/static/fonts/ywiconfont/iconfont.css 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
customer_list/common/static/fonts/ywiconfont/iconfont.ttf 补丁 | 查看 | 原始文档 | blame | 历史
customer_list/common/static/fonts/ywiconfont/iconfont.woff 补丁 | 查看 | 原始文档 | blame | 历史
customer_list/common/static/fonts/ywiconfont/iconfont.woff2 补丁 | 查看 | 原始文档 | blame | 历史
src/api/ai/chat.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/attach/index.ts 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/Chat.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/components/playBar/PlayBar.vue 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/components/playBar/businessTable/index.vue 204 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/components/playBar/businessTable/search/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/components/playBar/hook/useUploadFile.ts 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/layout/AHMContainer.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
customer_list/common/static/fonts/ywiconfont/iconfont.css
@@ -1,8 +1,8 @@
@font-face {
  font-family: "ywifont"; /* Project id 4655417 */
  src: url('iconfont.woff2?t=1740909922081') format('woff2'),
       url('iconfont.woff?t=1740909922081') format('woff'),
       url('iconfont.ttf?t=1740909922081') format('truetype');
  src: url('iconfont.woff2?t=1740972827582') format('woff2'),
       url('iconfont.woff?t=1740972827582') format('woff'),
       url('iconfont.ttf?t=1740972827582') format('truetype');
}
.ywifont {
@@ -13,6 +13,14 @@
  -moz-osx-font-smoothing: grayscale;
}
.ywicon-TXTtubiao:before {
  content: "\e64d";
}
.ywicon-csv:before {
  content: "\e64b";
}
.ywicon-fujian:before {
  content: "\e88a";
}
customer_list/common/static/fonts/ywiconfont/iconfont.ttf
Binary files differ
customer_list/common/static/fonts/ywiconfont/iconfont.woff
Binary files differ
customer_list/common/static/fonts/ywiconfont/iconfont.woff2
Binary files differ
src/api/ai/chat.ts
@@ -267,7 +267,7 @@
            params: {},
            data: params,
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Content-Type': 'multipart/form-data',
            },
            ...extraData,
        },
src/api/attach/index.ts
@@ -3,14 +3,14 @@
export const getAttachTableList = () => {
    return request({
        url: '/attach/get_attach_table_list',
        method: 'get',
        method: 'POST',
    });
};
export const queryAttachTableRecords = (params: any) => {
    return request({
        url: '/attach/query_attach_table_records',
        method: 'get',
        params,
        method: 'POST',
        data: params,
    });
};
src/components/chat/Chat.vue
@@ -142,10 +142,10 @@
        raw_mode: roomConfig.value?.[currentRouteId]?.isAnswerByLLM ?? false,
        ...judgeParams,
    } as any;
    const formDataParams = toFormData(params);
    for (const item of attachFileList.value) {
        formDataParams.append('files', item.file);
    if(businessTableData.value?.length > 0) {
        params.tables = JSON.stringify(businessTableData.value);
    }
    // if (!position) {
    //     const loadingInstance = ElLoadingService({
    //         text: '获取位置中...',
@@ -170,7 +170,10 @@
        params.sample_id = currentSampleId;
        currentSampleId = '';
    }
    const formDataParams = toFormData(params);
    for (const item of attachFileList.value) {
        formDataParams.append('files', item.file);
    }
    let lastTimestamp = new Date().getTime();
    questionRes = {};
    let lastIsResult = false;
@@ -190,7 +193,7 @@
            return isEmpty;
        };
        questionStreamByPost(
            params,
            formDataParams,
            (chunkRes) => {
                Logger.info('chunk response:\n\n' + JSON.stringify(chunkRes));
                if (chunkRes.mode === 'result') {
@@ -380,12 +383,15 @@
    const content = parseContent(questionRes, true);
    return content;
};
const playBarRef = useCompRef(PlayBar);
const businessTableData = computed(() => playBarRef.value?.businessTableData ?? []);
const clearMessageContent = () =>
    (messageContent.value = {
        type: AnswerType.Text,
        values: '',
    });
    playBarRef.value?.clearFileList();
    playBarRef.value?.clearBusinessTable();
let currentSampleId = '';
@@ -595,7 +601,6 @@
    messageContent.value.values = content;
};
//#endregion
const playBarRef = useCompRef(PlayBar);
//用户问题设置为常用语
const setCommonPhraseClick = (item) => {
    playBarRef.value.addPhrase(item);
src/components/chat/components/playBar/PlayBar.vue
@@ -86,7 +86,10 @@
                        </el-button>
                        <div class="flex-items-center gap-2">
                            <el-tooltip placement="top" content="关联业务表格">
                                <div class="cursor-pointer size-[38px] relative !z-10 rounded flex-center hover:bg-[#f2f2f2]" @click="openBusinessTable">
                                <div
                                    class="cursor-pointer size-[38px] relative !z-10 rounded flex-center hover:bg-[#f2f2f2]"
                                    @click="openBusinessTable"
                                >
                                    <span class="ywifont ywicon-biaoge !text-[24px]"></span>
                                </div>
                            </el-tooltip>
@@ -129,9 +132,7 @@
                ref="commonPhraseRef"
                @updateInput="updateInputValue"
            />
            <BusinessTable
                v-model="businessTableIsShow"
            />
            <BusinessTable v-model="businessTableIsShow" @submit="submitBusinessTable" />
        </div>
    </div>
</template>
@@ -229,13 +230,21 @@
    pastTarget: inputRef as any,
});
const businessTableData = ref([]);
const submitBusinessTable = (data) => {
    businessTableData.value = data;
};
const clearBusinessTable = () => {
    businessTableData.value = [];
};
//#region ====================== 业务表格 ======================
const businessTableIsShow = ref(false);
const openBusinessTable = () => {
    businessTableIsShow.value = true;
};
//#endregion
defineExpose({ addPhrase, showSyncTip, attachFileList });
defineExpose({ addPhrase, showSyncTip, attachFileList, clearFileList, businessTableData, clearBusinessTable });
</script>
<style scoped lang="scss">
@use './index.scss';
src/components/chat/components/playBar/businessTable/index.vue
@@ -1,12 +1,19 @@
<template>
    <el-dialog :destroy-on-close="true" v-model="dialogIsShow" width="70%" :close-on-click-modal="false" @closed="closeDialog">
    <el-dialog
        class="limit-height"
        :destroy-on-close="true"
        v-model="dialogIsShow"
        width="70%"
        :close-on-click-modal="false"
        @closed="closeDialog"
    >
        <template #header>
            <div style="color: #fff">
                <SvgIcon name="ele-Document" :size="16" style="margin-right: 3px; display: inline; vertical-align: middle" />
                <span>业务表关联</span>
            </div>
        </template>
        <AHMContainer type="card">
        <AHMContainer type="card" class="h100" v-loading="submitLoading">
            <template #aside>
                <!-- 目录树 -->
                <LeftTreeByMgr
@@ -20,7 +27,7 @@
                    }"
                    defaultExpandAll
                    :treedata="listTreeData"
                    title-name="场景列表"
                    title-name="附件表"
                    :show-more-operate="false"
                    :show-add="false"
                    :current-node-key="currentListID"
@@ -32,7 +39,7 @@
            <template #header>
                <el-form ref="queryFormRef" :inline="true" class="relative">
                    <el-form-item :label="item.title" prop="title" v-for="item in filterColumns as any" :key="item.name">
                        <TableSearch v-model="item.value" :filter="item.filter" @input="debounceSearch(item)" />
                        <TableSearch v-model="item.values" :filter="item.filter" @input="debounceSearch()" />
                    </el-form-item>
                </el-form>
            </template>
@@ -49,7 +56,14 @@
                            highlight-current-row
                            @sort-change="handleSortChange"
                        >
                            <el-table-column v-for="item in tableColumns" :prop="item.name" :label="item.title" show-overflow-tooltip>
                            <el-table-column
                                v-for="item in tableColumns"
                                :prop="item.name"
                                sortable="custom"
                                :key="item.name"
                                :label="item.title"
                                show-overflow-tooltip
                            >
                            </el-table-column>
                        </el-table>
                    </div>
@@ -81,8 +95,29 @@
const closeDialog = () => {
    dialogIsShow.value = false;
};
const submitFormValue = () => {
    emit('submit', tableData.value);
const submitLoading = ref(false);
const getSubmitData = async () => {
    submitLoading.value = true;
    await getUnGetData().finally(() => {
        submitLoading.value = false;
    });
    const tables =  listTreeData.value.map((item) => {
        return {
            title: item.title,
            columns: item.columns.map((item) => {
                return item.title;
            }),
            values: item.tableData.map((item) => {
                return Object.values(item);
            }),
        };
    });
    return tables;
};
const submitFormValue = async () => {
    const data = await getSubmitData();
    emit('submit', data);
    dialogIsShow.value = false;
};
//#region ====================== 左侧树数据,tree init ======================
@@ -104,7 +139,7 @@
                return {
                    ...item,
                    // 初始查询值
                    value: '',
                    values: [''],
                };
            });
        }
@@ -112,31 +147,47 @@
    return treeData;
});
/**
 * 记录 orderMap
 */
const setOrderMap = (node) => {
    if (!node.orderMap) {
        node.orderMap = new Map();
    }
};
const handleClickNode = (data) => {
    nextTick(() => {
        leftTreeRef.value?.treeRef.setCurrentKey(data.id);
    });
    currentNode.value = data;
    setOrderMap(data);
    getTableData();
};
const getListTreeData = async () => {
    const res = await attachApi.getAttachTableList();
    treeLoading.value = true;
    const res = await attachApi.getAttachTableList().finally(() => {
        treeLoading.value = false;
    });
    listData.value = res.tables || [];
    const firstListTreeNode = listTreeData.value[0];
    if (firstListTreeNode) {
        handleClickNode(firstListTreeNode);
    } else {
        tableData.value = [];
        currentNode.value = null;
    }
};
//#endregion
//#region ====================== 指标管理表格数据,table init ======================
const tableLoading = ref(false);
const tableData = ref([]);
const tableData = computed(() => {
    return currentNode.value?.tableData || [];
});
const isDragStatus = ref(false);
const tableColumns = ref([]);
const tableColumns = computed(() => {
    return currentNode.value?.columns || [];
});
const getTableData = async () => {
    // allTableData.value = (res.values || []).map((item) => {
    //     item.create_time = item.create_time?.slice(0, 10);
@@ -146,10 +197,21 @@
    //     const len = getLenById(allTableData.value, value.id, value);
    //     value.title = `${value.title} (${len})`;
    // });
    tableColumns.value = currentNode.value.columns;
    if (!currentNode.value.tableData) {
        handleSearchInput();
    }
};
//#endregion
const indexMapItem = computed(() => {
const getIndexMapItem = (columns) => {
    return new Map<number, any>(
        columns.map((item, index) => {
            return [index, item];
        })
    );
};
const indexMapItem = computed<Map<number, any>>(() => {
    return new Map(
        tableColumns.value.map((item, index) => {
            return [index, item];
@@ -157,41 +219,111 @@
    );
});
//#region ====================== 查询 ======================
const handleSearchInput = async (item) => {
    const params = {
        id: currentNode.value.id,
    } as any;
const getFilterColumns = (node) => {
    return node?.columns?.filter((item) => item.filter) as any[];
};
const filterColumns = computed(() => {
    return getFilterColumns(currentNode.value);
});
    if (filterColumns.value && filterColumns.value.length) {
        params.filter = filterColumns.value.map((item) => {
const getSearchParams = (node) => {
    const params = {
        id: node.id,
    } as any;
    const filterColumns = getFilterColumns(node);
    if (filterColumns && filterColumns.length) {
        params.filter = filterColumns.map((item) => {
            return {
                col: item.name,
                filter: item.filter,
                value: item.value,
                values: item.values,
            };
        });
    }
    setOrderMap(node);
    const orderMap = node.orderMap;
    const orderList = Array.from(orderMap.entries()).map(([key, value]) => {
        return {
            col: key,
            order: value,
        };
    });
    if (orderList?.length > 0) {
        params.order = orderList;
    }
    return params;
};
    const res = await attachApi.queryAttachTableRecords(params);
    tableData.value = (res.values || []).map((item, index) => {
const parseRecordData = (res, columns) => {
    const indexMapItem = getIndexMapItem(columns);
    return (res.values || []).map((item, index) => {
        const row = {} as any;
        item?.forEach((item, index) => {
            row[indexMapItem.value.get(index).name] = item;
            row[indexMapItem.get(index).name] = item;
        });
        return row;
    });
};
const handleSearchItem = async (node, prop?, order?, column?) => {
    const params = getSearchParams(node);
    // 修正为当前要修改的 order
    if (prop) {
        // const foundItem = params.order.find((item) => item.col === prop);
        // if (foundItem) {
        //     foundItem.order = order;
        // }
        // 排序只会有一个字段
        params.order = [
            {
                col: prop,
                order: order,
            },
        ];
    }
    tableLoading.value = true;
    const res = await attachApi.queryAttachTableRecords(params).finally(() => {
        tableLoading.value = false;
    });
    if (prop) {
        const orderMap = node.orderMap;
        orderMap.clear();
        orderMap.set(prop, order);
        column.order = getEleOrder(order);
    }
    node.tableData = parseRecordData(res, tableColumns.value);
};
const handleSearchInput = async (prop?, order?, column?) => {
    handleSearchItem(currentNode.value, prop, order, column);
};
const getUnGetData = async () => {
    const getDataPromiseList = [];
    for (const item of listTreeData.value) {
        if (!item.tableData) {
            getDataPromiseList.push(handleSearchItem(item));
        }
    }
    // 等待所有数据获取完成
    await Promise.all(getDataPromiseList);
};
const debounceSearch = debounce(handleSearchInput, 400);
//#endregion
const filterColumns = computed(() => {
    return tableColumns.value.filter((item) => item.filter) as any[];
});
const orderMap = new Map();
const getEleOrder = (order) => {
    if (order === 'DESC') {
        return 'descending';
    } else if (order === 'ASC') {
        return 'ascending';
    } else {
        return '';
    }
};
const handleSortChange = ({ column, prop, order }) => {
    setOrderMap(currentNode.value);
    const orderMap = currentNode.value.orderMap;
    // 恢复原状,更新后再显示排序状态
    const curOrder = orderMap.get(prop) ?? null;
    column.order = curOrder;
@@ -204,6 +336,7 @@
    } else {
        sendOrder = '';
    }
    handleSearchInput(prop, sendOrder, column);
};
watch(
@@ -230,4 +363,15 @@
        font-size: 14px;
    }
}
:deep(.el-dialog__body) {
    height: calc(90vh - 111px) !important;
}
</style>
<style lang="scss">
.limit-height {
    .el-dialog__body {
        height: calc(90vh - 111px) !important;
    }
}
</style>
src/components/chat/components/playBar/businessTable/search/index.vue
@@ -3,7 +3,7 @@
        <el-input
            v-if="filter === 'like'"
            @input="(value) => input(value)"
            v-model="modelValue"
            v-model="modelValue[0]"
            style="width: 226.4px"
            clearable
        />
@@ -14,8 +14,8 @@
const props = defineProps(['filter']);
const emit = defineEmits(['input']);
const modelValue = defineModel({
    type: String,
    default: '',
    type: Array<string>,
    default: [''],
});
const input = (value) => {
src/components/chat/components/playBar/hook/useUploadFile.ts
@@ -6,8 +6,8 @@
    pastTarget: Ref<HTMLElement | null>;
};
export type FileType = 'doc' | 'docx' | 'pdf' | 'md' | 'xls' | 'xlsx' | 'png' | 'jpg' | 'jpeg' | 'gif' | 'json';
export type FileGroupType = 'word' | 'pdf' | 'excel' | 'image' | 'json' | 'md';
export type FileType = 'doc' | 'docx' | 'pdf' | 'md' | 'xls' | 'xlsx' | 'png' | 'jpg' | 'jpeg' | 'gif' | 'json' | 'txt' | 'csv';
export type FileGroupType = 'word' | 'pdf' | 'excel' | 'image' | 'json' | 'md' | 'csv' | 'txt';
export type UploadFile = {
    name: string;
    type: FileType;
@@ -24,6 +24,10 @@
        case 'doc':
        case 'docx':
            return 'word';
        case 'csv':
            return 'csv';
        case 'txt':
            return 'txt';
        case 'md':
            return 'md';
@@ -52,6 +56,10 @@
            return 'pdf';
        case 'excel':
            return 'excel';
        case 'csv':
            return 'csv';
        case 'txt':
            return 'txt';
        case 'json':
            return 'json';
@@ -70,6 +78,10 @@
            return 'text-red-400';
        case 'excel':
            return 'text-green-400';
        case 'csv':
            return 'text-green-500';
        case 'txt':
            return 'text-cyan-400';
        case 'json':
            return 'text-yellow-400';
@@ -79,12 +91,13 @@
            return '';
    }
};
// const supportFileType = ['doc', 'docx', 'md', 'xls', 'xlsx', 'png', 'jpg', 'jpeg', 'gif', 'json', 'pdf'];
const supportFileType = ['csv', 'txt'];
const acceptFiles = [].join(',')
export const useUploadFile = (options: UseUploadFileOptions) => {
    const supportFileType = ['doc', 'docx', 'md', 'xls', 'xlsx', 'png', 'jpg', 'jpeg', 'gif', 'json', 'pdf'];
    const attachFileList = ref<UploadFile[]>([]);
    const parseFiles = (files:FileList) => {
    const parseFiles = (files: FileList) => {
        const filterFiles: UploadFile[] = [];
        for (const file of files) {
            const suffix = file.name.split('.').pop() as FileType;
@@ -114,7 +127,7 @@
            }
        }
        attachFileList.value.push(...filterFiles);
    }
    };
    /**
     * 解析粘贴板文件
@@ -124,7 +137,7 @@
        event.stopPropagation();
        const data = event.clipboardData || window.clipboardData;
        const files = data.files as FileList;
        parseFiles(files)
        parseFiles(files);
    };
    const clearFileList = () => {
@@ -132,22 +145,21 @@
    };
    const {
        files,
        open:openFileDialog,
        reset:resetOpenFileDialog,
        open: openFileDialog,
        reset: resetOpenFileDialog,
        onChange: onPickFileChange,
    } = useFileDialog({
        accept:
            'application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,text/markdown,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,image/png,image/jpeg,image/gif,application/json,application/pdf', // Set to accept only image files
            reset:true
            'text/csv,text/plain', // Only accept csv and txt files
        reset: true,
    });
    const openFileClick = () =>{
    const openFileClick = () => {
        openFileDialog();
    }
    };
    onPickFileChange((files) => {
        if(!files) return;
        parseFiles(files)
        if (!files) return;
        parseFiles(files);
    });
    const deleteIndexFile = (index) => {
        const file = attachFileList.value[index];
@@ -164,6 +176,6 @@
        attachFileList,
        clearFileList,
        deleteIndexFile,
        openFileClick
        openFileClick,
    };
};
src/components/layout/AHMContainer.vue
@@ -8,7 +8,7 @@
            <slot v-else name="aside"></slot>
        </el-col>
        <el-col :span="20" :xs="24" class="flex-column h100">
            <el-card class="yw-layout-card yw-layout-header" v-if="type === 'card'" shadow="hover" :body-style="{ paddingBottom: '0' }">
            <el-card class="yw-layout-card yw-layout-header flex-0" v-if="type === 'card'" shadow="hover" :body-style="{ paddingBottom: '0' }">
                <slot name="header"> </slot>
            </el-card>
            <slot v-else name="header"> </slot>