feat: knowledge

This commit is contained in:
wangdan-fit2cloud 2025-06-05 20:15:55 +08:00
parent 0cb33dad5a
commit d33bc56db1
20 changed files with 1210 additions and 260 deletions

View File

@ -75,7 +75,7 @@ const putSyncWebKnowledge: (
*
* @param knowledge_id
*/
const putReEmbeddingDataset: (
const putReEmbeddingKnowledge: (
wordspace_id: string,
knowledge_id: string,
loading?: Ref<boolean>,
@ -110,7 +110,7 @@ const getKnowledgeDetail: (
"embedding": "string"
}
*/
const postDataset: (
const postKnowledge: (
wordspace_id: string,
data: knowledgeData,
loading?: Ref<boolean>,
@ -130,7 +130,7 @@ const postDataset: (
"selector": "string"
}
*/
const postWebDataset: (
const postWebKnowledge: (
wordspace_id: string,
data: any,
loading?: Ref<boolean>,
@ -138,29 +138,30 @@ const postWebDataset: (
return post(`${prefix}/${wordspace_id}/knowledge/web`, data, undefined, loading)
}
/**
* Lark知识库
*
* @param
* knowledge_id
* {
"name": "string",
"desc": "string",
"app_id": "string",
"app_secret": "string",
"folder_token": "string",
"desc": true
}
*/
const postLarkDataset: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
data,
loading,
) => {
return post(`${prefix}/lark/save`, data, undefined, loading)
const putKnowledge: (
wordspace_id: string,
knowledge_id: string,
data: any,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (wordspace_id, knowledge_id, data, loading) => {
return put(`${prefix}/${wordspace_id}/knowledge/${knowledge_id}`, data, undefined, loading)
}
export default {
getKnowledgeByFolder,
getKnowledgeList,
putReEmbeddingDataset,
putReEmbeddingKnowledge,
putSyncWebKnowledge,
getKnowledgeDetail,
postDataset,
postWebDataset
postKnowledge,
postWebKnowledge,
putKnowledge,
}

View File

@ -3,11 +3,11 @@ import { get, post, del, put } from '@/request/index'
import type { Ref } from 'vue'
import type { KeyValue } from '@/api/type/common'
import type { pageRequest } from '@/api/type/common'
const prefix = '/dataset'
const prefix = '/workspace'
/**
*
* @param dataset_id,
*
* @param dataset_id,
* page {
"current_page": "string",
"page_size": "string",
@ -18,15 +18,16 @@ const prefix = '/dataset'
*/
const getProblems: (
workspace_id: string,
dataset_id: string,
page: pageRequest,
param: any,
loading?: Ref<boolean>
) => Promise<Result<any>> = (dataset_id, page, param, loading) => {
loading?: Ref<boolean>,
) => Promise<Result<any>> = (wordspace_id, dataset_id, page, param, loading) => {
return get(
`${prefix}/${dataset_id}/problem/${page.current_page}/${page.page_size}`,
`${prefix}/${wordspace_id}/knowledge/${dataset_id}/problem/${page.current_page}/${page.page_size}`,
param,
loading
loading,
)
}
@ -38,7 +39,7 @@ const getProblems: (
const postProblems: (
dataset_id: string,
data: any,
loading?: Ref<boolean>
loading?: Ref<boolean>,
) => Promise<Result<any>> = (dataset_id, data, loading) => {
return post(`${prefix}/${dataset_id}/problem`, data, undefined, loading)
}
@ -50,7 +51,7 @@ const postProblems: (
const delProblems: (
dataset_id: string,
problem_id: string,
loading?: Ref<boolean>
loading?: Ref<boolean>,
) => Promise<Result<boolean>> = (dataset_id, problem_id, loading) => {
return del(`${prefix}/${dataset_id}/problem/${problem_id}`, loading)
}
@ -62,15 +63,15 @@ const delProblems: (
const delMulProblem: (
dataset_id: string,
data: any,
loading?: Ref<boolean>
loading?: Ref<boolean>,
) => Promise<Result<boolean>> = (dataset_id, data, loading) => {
return del(`${prefix}/${dataset_id}/problem/_batch`, undefined, data, loading)
}
/**
*
* @param
* dataset_id, problem_id,
* @param
* dataset_id, problem_id,
* {
"content": "string",
}
@ -79,7 +80,7 @@ const putProblems: (
dataset_id: string,
problem_id: string,
data: any,
loading?: Ref<boolean>
loading?: Ref<boolean>,
) => Promise<Result<any>> = (dataset_id, problem_id, data: any, loading) => {
return put(`${prefix}/${dataset_id}/problem/${problem_id}`, data, undefined, loading)
}
@ -92,7 +93,7 @@ const putProblems: (
const getDetailProblems: (
dataset_id: string,
problem_id: string,
loading?: Ref<boolean>
loading?: Ref<boolean>,
) => Promise<Result<any>> = (dataset_id, problem_id, loading) => {
return get(`${prefix}/${dataset_id}/problem/${problem_id}/paragraph`, undefined, loading)
}
@ -108,7 +109,7 @@ const getDetailProblems: (
const postMulAssociationProblem: (
dataset_id: string,
data: any,
loading?: Ref<boolean>
loading?: Ref<boolean>,
) => Promise<Result<boolean>> = (dataset_id, data, loading) => {
return post(`${prefix}/${dataset_id}/problem/_batch`, data, undefined, loading)
}
@ -120,5 +121,5 @@ export default {
putProblems,
getDetailProblems,
delMulProblem,
postMulAssociationProblem
postMulAssociationProblem,
}

View File

@ -25,4 +25,44 @@ export default {
])
},
},
'app-setting': {
iconReader: () => {
return h('i', [
h(
'svg',
{
viewBox: '0 0 20 20',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
},
[
h('path', {
d: 'M3.60734 16.4448L3.34807 16.1624C2.44036 15.1737 1.75935 13.9944 1.36011 12.7066L1.24756 12.3435L2.95427 10.0001L1.24756 7.65668L1.36011 7.29361C1.75935 6.00574 2.44036 4.82649 3.34807 3.83779L3.60734 3.55539L6.47552 3.86889L7.64049 1.21319L8.01405 1.12909C8.66134 0.983366 9.32633 0.90918 10.0004 0.90918C10.6744 0.90918 11.3394 0.983366 11.9867 1.12909L12.3603 1.21319L13.5252 3.86889L16.3934 3.55539L16.6527 3.83779C17.5604 4.82649 18.2414 6.00574 18.6406 7.29361L18.7532 7.65668L17.0465 10.0001L18.7532 12.3435L18.6406 12.7066C18.2414 13.9944 17.5604 15.1737 16.6527 16.1624L16.3934 16.4448L13.5252 16.1313L12.3603 18.787L11.9867 18.8711C11.3394 19.0168 10.6744 19.091 10.0004 19.091C9.32633 19.091 8.66134 19.0168 8.01405 18.8711L7.64049 18.787L6.47552 16.1313L3.60734 16.4448ZM6.51159 14.6031C7.05002 14.5443 7.56436 14.8417 7.78194 15.3377L8.71565 17.4662C9.13677 17.5389 9.56603 17.5758 10.0004 17.5758C10.4347 17.5758 10.864 17.5389 11.2851 17.4662L12.2188 15.3377C12.4364 14.8417 12.9507 14.5443 13.4892 14.6031L15.7844 14.854C16.3387 14.1868 16.7757 13.4286 17.0741 12.6116L15.7038 10.7301C15.3869 10.295 15.3869 9.70511 15.7038 9.26999L17.0741 7.38847C16.7757 6.57146 16.3387 5.81331 15.7844 5.14609L13.4892 5.39696C12.9507 5.45581 12.4364 5.1584 12.2188 4.66238L11.2851 2.53389C10.864 2.46117 10.4347 2.42429 10.0004 2.42429C9.56603 2.42429 9.13677 2.46117 8.71565 2.53389L7.78194 4.66238C7.56436 5.1584 7.05002 5.45581 6.51159 5.39696L4.21641 5.14609C3.66208 5.81331 3.22502 6.57146 2.92666 7.38847L4.29697 9.26999C4.61387 9.70511 4.61387 10.295 4.29697 10.7301L2.92666 12.6116C3.22502 13.4286 3.66208 14.1868 4.21641 14.854L6.51159 14.6031ZM10.0004 13.788C7.91555 13.788 6.22693 12.0913 6.22693 10.0001C6.22693 7.9089 7.91555 6.2122 10.0004 6.2122C12.0852 6.2122 13.7738 7.9089 13.7738 10.0001C13.7738 12.0913 12.0852 13.788 10.0004 13.788ZM10.0004 12.2729C11.2468 12.2729 12.2587 11.2561 12.2587 10.0001C12.2587 8.74413 11.2468 7.72741 10.0004 7.72741C8.75397 7.72741 7.74208 8.74413 7.74208 10.0001C7.74208 11.2561 8.75397 12.2729 10.0004 12.2729Z',
fill: 'currentColor',
}),
],
),
])
},
},
'app-setting-active': {
iconReader: () => {
return h('i', [
h(
'svg',
{
viewBox: '0 0 20 20',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
},
[
h('path', {
d: 'M3.26425 16.2151C2.35478 15.2292 1.65887 14.0432 1.25 12.7305L2.70785 10.7384C3.02952 10.2988 3.02952 9.70154 2.70785 9.26197L1.25 7.26979C1.65887 5.95714 2.35478 4.77112 3.26425 3.78522L5.71416 4.05172C6.25589 4.11065 6.77338 3.81185 6.99316 3.31321L7.98848 1.05505C8.63579 0.910018 9.30896 0.833496 10 0.833496C10.691 0.833496 11.3642 0.910018 12.0115 1.05505L13.0068 3.31321C13.2266 3.81185 13.7441 4.11065 14.2858 4.05172L16.7357 3.78522C17.6452 4.77112 18.3411 5.95714 18.75 7.26979L17.2921 9.26197C16.9705 9.70154 16.9705 10.2988 17.2921 10.7384L18.75 12.7305C18.3411 14.0432 17.6452 15.2292 16.7357 16.2151L14.2858 15.9486C13.7441 15.8897 13.2266 16.1885 13.0068 16.6871L12.0115 18.9453C11.3642 19.0903 10.691 19.1668 10 19.1668C9.30896 19.1668 8.63579 19.0903 7.98848 18.9453L6.99316 16.6871C6.77338 16.1885 6.25589 15.8897 5.71416 15.9486L3.26425 16.2151ZM10 13.3335C11.8409 13.3335 13.3333 11.8411 13.3333 10.0002C13.3333 8.15921 11.8409 6.66683 10 6.66683C8.15905 6.66683 6.66667 8.15921 6.66667 10.0002C6.66667 11.8411 8.15905 13.3335 10 13.3335Z',
fill: 'currentColor',
}),
],
),
])
},
},
}

View File

@ -79,4 +79,5 @@ export default {
fileSizeExceeded: 'File size exceeds 10 MB',
uploadImagePrompt: 'Please upload an image',
},
info: 'Base Information',
}

View File

@ -2,7 +2,6 @@ export default {
title: 'User',
createUser: 'Create User',
editUser: 'Edit User',
info: 'Base Information',
roleSetting: 'Role Setting',
setting: {
updatePwd: 'Change Password',

View File

@ -83,4 +83,5 @@ export default {
fileSizeExceeded: '文件大小超过 10 MB',
uploadImagePrompt: '请上传一张图片',
},
info: '基本信息',
}

View File

@ -2,7 +2,6 @@ export default {
title: '用户管理',
createUser: '创建用户',
editUser: '编辑用户',
info: '基本信息',
roleSetting: '角色设置',
setting: {
updatePwd: '修改用户密码',

View File

@ -79,4 +79,5 @@ export default {
fileSizeExceeded: '檔案大小超過 10 MB',
uploadImagePrompt: '請上傳一張圖片',
},
info: '使用者資訊',
}

View File

@ -2,23 +2,22 @@ export default {
title: '使用者管理',
createUser: '建立使用者',
editUser: '編輯使用者',
info: '使用者資訊',
roleSetting: '角色設定',
setting: {
updatePwd: '修改使用者密碼'
updatePwd: '修改使用者密碼',
},
tip: {
professionalMessage: '社群版最多支援 2 個使用者,如需擁有更多使用者,請升級為專業版。',
updatePwdSuccess: '使用者密碼修改成功'
updatePwdSuccess: '使用者密碼修改成功',
},
delete: {
confirmTitle: '是否刪除該使用者?',
confirmMessage:
'刪除該使用者後,該使用者建立的所有資源(應用、知識庫、模型)都會被刪除,請謹慎操作。'
'刪除該使用者後,該使用者建立的所有資源(應用、知識庫、模型)都會被刪除,請謹慎操作。',
},
disabled: {
confirmTitle: '是否停用函數?',
confirmMessage: '停用後,引用該函數的應用在查詢時會報錯,請謹慎操作。'
confirmMessage: '停用後,引用該函數的應用在查詢時會報錯,請謹慎操作。',
},
userForm: {
form: {
@ -26,45 +25,45 @@ export default {
label: '使用者名稱',
placeholder: '請輸入使用者名稱',
requiredMessage: '請輸入使用者名稱',
lengthMessage: '長度須介於 6 到 20 個字元之間'
lengthMessage: '長度須介於 6 到 20 個字元之間',
},
nick_name: {
label: '姓名',
placeholder: '請輸入姓名'
placeholder: '請輸入姓名',
},
email: {
label: '電子信箱',
placeholder: '請輸入電子信箱',
requiredMessage: '請輸入電子信箱'
requiredMessage: '請輸入電子信箱',
},
phone: {
label: '手機號碼',
placeholder: '請輸入手機號碼'
placeholder: '請輸入手機號碼',
},
password: {
label: '登入密碼',
placeholder: '請輸入密碼',
requiredMessage: '請輸入密碼',
lengthMessage: '長度須介於 6 到 20 個字元之間'
lengthMessage: '長度須介於 6 到 20 個字元之間',
},
new_password: {
label: '新密碼',
placeholder: '請輸入新密碼',
requiredMessage: '請輸入新密碼'
requiredMessage: '請輸入新密碼',
},
re_password: {
label: '確認密碼',
placeholder: '請輸入確認密碼',
requiredMessage: '請輸入確認密碼',
validatorMessage: '密碼不一致'
}
}
validatorMessage: '密碼不一致',
},
},
},
source: {
label: '使用者來源',
local: '系統使用者',
wecom: '企業微信',
lark: '飛書',
dingtalk: '釘釘'
}
dingtalk: '釘釘',
},
}

View File

@ -43,19 +43,19 @@ const DocumentRouter = {
},
component: () => import('@/views/hit-test/index.vue')
},
// {
// path: 'setting',
// name: 'DatasetSetting',
// meta: {
// icon: 'app-setting',
// iconActive: 'app-setting-active',
// title: 'common.setting',
// active: 'setting',
// parentPath: '/knowledge/:id/:folderId',
// parentName: 'KnowledgeDetail'
// },
// component: () => import('@/views/dataset/DatasetSetting.vue')
// }
{
path: 'setting',
name: 'KnowledgeSetting',
meta: {
icon: 'app-setting',
iconActive: 'app-setting-active',
title: 'common.setting',
active: 'setting',
parentPath: '/knowledge/:id/:folderId',
parentName: 'KnowledgeDetail'
},
component: () => import('@/views/knowledge/KnowledgeSetting.vue')
}
],
}

View File

@ -6,6 +6,7 @@ import useThemeStore from './modules/theme'
import useKnowledgeStore from './modules/knowledge'
import useModelStore from './modules/model'
import usePromptStore from './modules/prompt'
import useProblemStore from './modules/problem'
const useStore = () => ({
common: useCommonStore(),
@ -16,6 +17,7 @@ const useStore = () => ({
knowledge: useKnowledgeStore(),
model: useModelStore(),
prompt: usePromptStore(),
problem: useProblemStore(),
})
export default useStore

View File

@ -59,10 +59,15 @@ const useKnowledgeStore = defineStore('knowledge', {
})
})
},
async asyncSyncDataset(id: string, sync_type: string, loading?: Ref<boolean>) {
async asyncSyncDataset(
workspace_id: string,
id: string,
sync_type: string,
loading?: Ref<boolean>,
) {
return new Promise((resolve, reject) => {
knowledgeApi
.putSyncWebKnowledge(id, sync_type, loading)
.putSyncWebKnowledge(workspace_id, id, sync_type, loading)
.then((data) => {
resolve(data)
})

View File

@ -0,0 +1,79 @@
import { defineStore } from 'pinia'
import { type Ref } from 'vue'
import problemApi from '@/api/knowledge/problem'
import paragraphApi from '@/api/knowledge/paragraph'
import type { pageRequest } from '@/api/type/common'
const useProblemStore = defineStore('problem', {
state: () => ({}),
actions: {
async asyncPostProblem(datasetId: string, data: any, loading?: Ref<boolean>) {
return new Promise((resolve, reject) => {
problemApi
.postProblems(datasetId, data, loading)
.then((data) => {
resolve(data)
})
.catch((error) => {
reject(error)
})
})
},
async asyncGetProblem(
workspace_id: string,
datasetId: string,
page: pageRequest,
param: any,
loading?: Ref<boolean>,
) {
return new Promise((resolve, reject) => {
problemApi
.getProblems(workspace_id, datasetId, page, param, loading)
.then((data) => {
resolve(data)
})
.catch((error) => {
reject(error)
})
})
},
async asyncDisassociationProblem(
datasetId: string,
documentId: string,
paragraphId: string,
problemId: string,
loading?: Ref<boolean>,
) {
return new Promise((resolve, reject) => {
paragraphApi
.disassociationProblem(datasetId, documentId, paragraphId, problemId, loading)
.then((data) => {
resolve(data)
})
.catch((error) => {
reject(error)
})
})
},
async asyncAssociationProblem(
datasetId: string,
documentId: string,
paragraphId: string,
problemId: string,
loading?: Ref<boolean>,
) {
return new Promise((resolve, reject) => {
paragraphApi
.associationProblem(datasetId, documentId, paragraphId, problemId, loading)
.then((data) => {
resolve(data)
})
.catch((error) => {
reject(error)
})
})
},
},
})
export default useProblemStore

View File

@ -1,94 +1,88 @@
<template>
<div class="hit-test">
<LayoutContainer>
<template #header>
<h4>
{{ $t('views.application.hitTest.title') }}
<el-text type="info" class="ml-4"> {{ $t('views.application.hitTest.text') }}</el-text>
</h4>
</template>
<div class="hit-test__main p-16" v-loading="loading">
<div class="question-title" :style="{ visibility: questionTitle ? 'visible' : 'hidden' }">
<div class="avatar">
<el-avatar>
<img src="@/assets/user-icon.svg" style="width: 54%" alt="" />
</el-avatar>
</div>
<div class="content">
<h4 class="text break-all">{{ questionTitle }}</h4>
</div>
<div class="hit-test p-16-24">
<h4>
{{ $t('views.application.hitTest.title') }}
<el-text type="info" class="ml-4"> {{ $t('views.application.hitTest.text') }}</el-text>
</h4>
<el-card style="--el-card-padding: 0" class="hit-test__main p-16 mt-16 mb-16" v-loading="loading">
<div class="question-title" :style="{ visibility: questionTitle ? 'visible' : 'hidden' }">
<div class="avatar">
<el-avatar>
<img src="@/assets/user-icon.svg" style="width: 54%" alt="" />
</el-avatar>
</div>
<div class="content">
<h4 class="text break-all">{{ questionTitle }}</h4>
</div>
<el-scrollbar>
<div class="hit-test-height">
<el-empty
v-if="first"
:image="emptyImg"
:description="$t('views.application.hitTest.emptyMessage1')"
style="padding-top: 160px"
:image-size="125"
/>
<el-empty
v-else-if="paragraphDetail.length == 0"
:description="$t('views.application.hitTest.emptyMessage2')"
style="padding-top: 160px"
:image-size="125"
/>
<el-row v-else>
<el-col
:xs="24"
:sm="12"
:md="12"
:lg="8"
:xl="6"
v-for="(item, index) in paragraphDetail"
:key="index"
class="p-8"
>
<CardBox
shadow="hover"
:title="item.title || '-'"
:description="item.content"
class="document-card layout-bg layout-bg cursor"
:class="item.is_active ? '' : 'disabled'"
:showIcon="false"
@click="editParagraph(item)"
>
<template #icon>
<el-avatar class="mr-12 avatar-light" :size="22">
{{ index + 1 + '' }}</el-avatar
>
</template>
<div class="active-button primary">{{ item.similarity?.toFixed(3) }}</div>
<template #footer>
<div class="footer-content flex-between">
<el-text>
<el-icon>
<Document />
</el-icon>
{{ item?.document_name }}
</el-text>
<div v-if="item.trample_num || item.star_num">
<span v-if="item.star_num">
<AppIcon iconName="app-like-color"></AppIcon>
{{ item.star_num }}
</span>
<span v-if="item.trample_num" class="ml-4">
<AppIcon iconName="app-oppose-color"></AppIcon>
{{ item.trample_num }}
</span>
</div>
</div>
</template>
</CardBox>
</el-col>
</el-row>
</div>
</el-scrollbar>
</div>
<el-scrollbar>
<div class="hit-test-height">
<el-empty
v-if="first"
:image="emptyImg"
:description="$t('views.application.hitTest.emptyMessage1')"
style="padding-top: 160px"
:image-size="125"
/>
<el-empty
v-else-if="paragraphDetail.length == 0"
:description="$t('views.application.hitTest.emptyMessage2')"
style="padding-top: 160px"
:image-size="125"
/>
<el-row v-else>
<el-col
:xs="24"
:sm="12"
:md="12"
:lg="8"
:xl="6"
v-for="(item, index) in paragraphDetail"
:key="index"
class="p-8"
>
<CardBox
shadow="hover"
:title="item.title || '-'"
:description="item.content"
class="document-card layout-bg layout-bg cursor"
:class="item.is_active ? '' : 'disabled'"
:showIcon="false"
@click="editParagraph(item)"
>
<template #icon>
<el-avatar class="mr-12 avatar-light" :size="22"> {{ index + 1 + '' }}</el-avatar>
</template>
<div class="active-button primary">{{ item.similarity?.toFixed(3) }}</div>
<template #footer>
<div class="footer-content flex-between">
<el-text>
<el-icon>
<Document />
</el-icon>
{{ item?.document_name }}
</el-text>
<div v-if="item.trample_num || item.star_num">
<span v-if="item.star_num">
<AppIcon iconName="app-like-color"></AppIcon>
{{ item.star_num }}
</span>
<span v-if="item.trample_num" class="ml-4">
<AppIcon iconName="app-oppose-color"></AppIcon>
{{ item.trample_num }}
</span>
</div>
</div>
</template>
</CardBox>
</el-col>
</el-row>
</div>
</el-scrollbar>
</el-card>
<ParagraphDialog ref="ParagraphDialogRef" :title="title" @refresh="refresh" />
<ParagraphDialog ref="ParagraphDialogRef" :title="title" @refresh="refresh" />
</LayoutContainer>
<div class="hit-test__operate p-24 pt-0">
<div class="hit-test__operate">
<el-popover :visible="popoverVisible" placement="right-end" :width="500" trigger="click">
<template #reference>
<el-button icon="Setting" class="mb-8" @click="settingChange('open')">{{
@ -230,7 +224,7 @@ import { t } from '@/locales'
const route = useRoute()
const {
meta: { activeMenu },
params: { id }
params: { id },
} = route as any
const quickInputRef = ref()
@ -242,7 +236,7 @@ const inputValue = ref('')
const formInline = ref({
similarity: 0.6,
top_number: 5,
search_mode: 'embedding'
search_mode: 'embedding',
})
//
@ -299,7 +293,7 @@ function sendChatHandle(event: any) {
}
const insertNewlineAtCursor = (event?: any) => {
const textarea = quickInputRef.value.$el.querySelector(
'.el-textarea__inner'
'.el-textarea__inner',
) as HTMLTextAreaElement
const startPos = textarea.selectionStart
const endPos = textarea.selectionEnd
@ -315,7 +309,7 @@ const insertNewlineAtCursor = (event?: any) => {
function getHitTestList() {
const obj = {
query_text: inputValue.value,
...formInline.value
...formInline.value,
}
if (isDataset.value) {
datasetApi.getDatasetHitTest(id, obj, loading).then((res) => {
@ -407,7 +401,7 @@ onMounted(() => {})
}
.hit-test-height {
height: calc(var(--app-main-height) - 170px);
height: calc(100vh - 300px);
}
.document-card {
height: 210px;

View File

@ -0,0 +1,290 @@
<template>
<LayoutContainer :header="$t('views.document.importDocument')" class="create-dataset">
<template #backButton>
<back-button @click="back"></back-button>
</template>
<div class="create-dataset__main flex" v-loading="loading">
<div class="create-dataset__component main-calc-height">
<div class="upload-document p-24" style="min-width: 850px">
<h4 class="title-decoration-1 mb-8">
{{ $t('views.document.feishu.selectDocument') }}
</h4>
<el-form
ref="FormRef"
:model="form"
:rules="rules"
label-position="top"
require-asterisk-position="right"
>
<div class="mt-16 mb-16">
<el-radio-group v-model="form.fileType" class="app-radio-button-group">
<el-radio-button value="txt"
>{{ $t('views.document.fileType.txt.label') }}
</el-radio-button>
</el-radio-group>
</div>
<div class="update-info flex p-8-12 border-r-4 mb-16">
<div class="mt-4">
<AppIcon iconName="app-warning-colorful" style="font-size: 16px"></AppIcon>
</div>
<div class="ml-16 lighter">
<p>{{ $t('views.document.feishu.tip1') }}</p>
<p>{{ $t('views.document.feishu.tip2') }}</p>
</div>
</div>
<div class="card-never border-r-4 mb-16">
<el-checkbox
v-model="allCheck"
:label="$t('views.document.feishu.allCheck')"
size="large"
class="ml-24"
@change="handleAllCheckChange"
/>
</div>
<div style="height: calc(100vh - 450px)">
<el-scrollbar>
<el-tree
:props="props"
:load="loadNode"
lazy
show-checkbox
node-key="token"
ref="treeRef"
>
<template #default="{ node, data }">
<div class="custom-tree-node flex align-center lighter">
<img
src="@/assets/fileType/file-icon.svg"
alt=""
height="20"
v-if="data.type === 'folder'"
/>
<img
src="@/assets/fileType/docx-icon.svg"
alt=""
height="22"
v-else-if="data.type === 'docx' || data.name.endsWith('.docx')"
/>
<img
src="@/assets/fileType/xlsx-icon.svg"
alt=""
height="22"
v-else-if="data.type === 'sheet' || data.name.endsWith('.xlsx')"
/>
<img
src="@/assets/fileType/xls-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('xls')"
/>
<img
src="@/assets/fileType/csv-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('csv')"
/>
<img
src="@/assets/fileType/pdf-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.pdf')"
/>
<img
src="@/assets/fileType/html-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.html')"
/>
<img
src="@/assets/fileType/txt-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.txt')"
/>
<img
src="@/assets/fileType/zip-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.zip')"
/>
<img
src="@/assets/fileType/md-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.md')"
/>
<span class="ml-4">{{ node.label }}</span>
</div>
</template>
</el-tree>
</el-scrollbar>
</div>
</el-form>
</div>
</div>
</div>
<div class="create-dataset__footer text-right border-t">
<el-button @click="router.go(-1)">{{ $t('common.cancel') }}</el-button>
<el-button @click="submit" type="primary" :disabled="disabled">
{{ $t('views.document.buttons.import') }}
</el-button>
</div>
</LayoutContainer>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onUnmounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { MsgConfirm, MsgSuccess, MsgWarning } from '@/utils/message'
import { getImgUrl } from '@/utils/utils'
import { t } from '@/locales'
import type Node from 'element-plus/es/components/tree/src/model/node'
import dataset from '@/api/dataset'
const router = useRouter()
const route = useRoute()
const {
query: { id, folder_token } // iddatasetIDid folder_tokentoken
} = route
const datasetId = id as string
const folderToken = folder_token as string
const loading = ref(false)
const disabled = ref(false)
const allCheck = ref(false)
const treeRef = ref<any>(null)
interface Tree {
name: string
leaf?: boolean
type: string
token: string
is_exist: boolean
}
const form = ref({
fileType: 'txt',
fileList: [] as any
})
const rules = reactive({
fileList: [
{ required: true, message: t('views.document.upload.requiredMessage'), trigger: 'change' }
]
})
const props = {
label: 'name',
children: 'zones',
isLeaf: (data: any) => data.type !== 'folder',
disabled: (data: any) => data.is_exist
}
const loadNode = (node: Node, resolve: (nodeData: Tree[]) => void) => {
const token = node.level === 0 ? folderToken : node.data.token // 使 folder_token使 node.data.token
dataset
.getLarkDocumentList(datasetId, token, {}, loading)
.then((res: any) => {
const nodes = res.data.files as Tree[]
resolve(nodes)
nodes.forEach((childNode) => {
if (childNode.is_exist) {
treeRef.value?.setChecked(childNode.token, true, false)
}
})
})
.catch((err) => {
console.error('Failed to load tree nodes:', err)
})
}
const handleAllCheckChange = (checked: boolean) => {
if (checked) {
//
const nodes = Object.values(treeRef.value?.store.nodesMap || {}) as any[]
nodes.forEach((node) => {
//
if (!node.disabled) {
treeRef.value?.setChecked(node.data, true, false)
}
})
} else {
treeRef.value?.setCheckedKeys([])
}
}
function submit() {
loading.value = true
disabled.value = true
// token
const checkedNodes = treeRef.value?.getCheckedNodes() || []
const filteredNodes = checkedNodes.filter((node: any) => !node.is_exist)
const newList = filteredNodes.map((node: any) => {
return {
name: node.name,
token: node.token,
type: node.type
}
})
if (newList.length === 0) {
disabled.value = false
MsgWarning(t('views.document.feishu.errorMessage1'))
loading.value = false
return
}
dataset
.importLarkDocument(datasetId, newList, loading)
.then((res) => {
MsgSuccess(t('views.document.tip.importMessage'))
disabled.value = false
router.go(-1)
})
.catch((err) => {
console.error('Failed to load tree nodes:', err)
})
.finally(() => {
disabled.value = false
})
loading.value = false
}
function back() {
router.go(-1)
}
</script>
<style lang="scss" scoped>
.create-dataset {
&__component {
width: 100%;
margin: 0 auto;
overflow: hidden;
}
&__footer {
padding: 16px 24px;
position: fixed;
bottom: 0;
left: 0;
background: #ffffff;
width: 100%;
box-sizing: border-box;
}
.upload-document {
width: 70%;
margin: 0 auto;
margin-bottom: 20px;
}
}
.xlsx-icon {
svg {
width: 24px;
height: 24px;
stroke: #000000 !important;
fill: #ffffff !important;
}
}
</style>

View File

@ -0,0 +1,302 @@
<template>
<div class="p-16-24">
<h2 class="mb-16">{{ $t('common.setting') }}</h2>
<el-card style="--el-card-padding: 0">
<div class="knowledge-setting main-calc-height">
<el-scrollbar>
<div class="p-24" v-loading="loading">
<h4 class="title-decoration-1 mb-16">
{{ $t('common.info') }}
</h4>
<BaseForm ref="BaseFormRef" :data="detail" />
<el-form
ref="webFormRef"
:rules="rules"
:model="form"
label-position="top"
require-asterisk-position="right"
>
<el-form-item :label="$t('views.knowledge.knowledgeType.label')" required>
<el-card shadow="never" class="mb-8" style="width: 50%" v-if="detail.type === 0">
<div class="flex align-center">
<el-avatar class="mr-8 avatar-blue" shape="square" :size="32">
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
</el-avatar>
<div>
<div>{{ $t('views.knowledge.knowledgeType.generalKnowledge') }}</div>
<el-text type="info"
>{{ $t('views.knowledge.knowledgeType.generalInfo') }}
</el-text>
</div>
</div>
</el-card>
<el-card shadow="never" class="mb-8" style="width: 50%" v-if="detail?.type === 1">
<div class="flex align-center">
<el-avatar class="mr-8 avatar-purple" shape="square" :size="32">
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
</el-avatar>
<div>
<div>{{ $t('views.knowledge.knowledgeType.webKnowledge') }}</div>
<el-text type="info">
{{ $t('views.knowledge.knowledgeType.webInfo') }}
</el-text>
</div>
</div>
</el-card>
<el-card shadow="never" class="mb-8" style="width: 50%" v-if="detail?.type === 2">
<div class="flex align-center">
<el-avatar shape="square" :size="32" style="background: none">
<img src="@/assets/knowledge/logo_lark.svg" style="width: 100%" alt="" />
</el-avatar>
<div>
<p>
<el-text>{{ $t('views.knowledge.knowledgeType.larkKnowledge') }}</el-text>
</p>
<el-text type="info"
>{{ $t('views.knowledge.knowledgeType.larkInfo') }}
</el-text>
</div>
</div>
</el-card>
</el-form-item>
<el-form-item
:label="$t('views.knowledge.form.source_url.label')"
prop="source_url"
v-if="detail.type === 1"
>
<el-input
v-model="form.source_url"
:placeholder="$t('views.knowledge.form.source_url.placeholder')"
@blur="form.source_url = form.source_url.trim()"
/>
</el-form-item>
<el-form-item
:label="$t('views.knowledge.form.selector.label')"
v-if="detail.type === 1"
>
<el-input
v-model="form.selector"
:placeholder="$t('views.knowledge.form.selector.placeholder')"
@blur="form.selector = form.selector.trim()"
/>
</el-form-item>
<el-form-item label="App ID" prop="app_id" v-if="detail.type === 2">
<el-input
v-model="form.app_id"
:placeholder="
$t('views.application.applicationAccess.larkSetting.appIdPlaceholder')
"
/>
</el-form-item>
<el-form-item label="App Secret" prop="app_id" v-if="detail.type === 2">
<el-input
v-model="form.app_secret"
type="password"
show-password
:placeholder="
$t('views.application.applicationAccess.larkSetting.appSecretPlaceholder')
"
/>
</el-form-item>
<el-form-item label="Folder Token" prop="folder_token" v-if="detail.type === 2">
<el-input
v-model="form.folder_token"
:placeholder="
$t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder')
"
/>
</el-form-item>
</el-form>
<div v-if="application_id_list.length > 0">
<h4 class="title-decoration-1 mb-16">
{{ $t('views.dataset.relatedApplications') }}
</h4>
<el-row :gutter="12">
<el-col
:span="12"
v-for="(item, index) in application_list.filter((obj: any) =>
application_id_list.some((v: any) => v === obj?.id),
)"
:key="index"
class="mb-16"
>
<el-card shadow="never">
<div class="flex-between">
<div class="flex align-center">
<el-avatar
v-if="isAppIcon(item?.icon)"
shape="square"
:size="32"
style="background: none"
class="mr-12"
>
<img :src="item?.icon" alt="" />
</el-avatar>
<el-avatar
v-else-if="item?.name"
:name="item?.name"
pinyinColor
shape="square"
:size="32"
class="mr-12"
/>
{{ item.name }}
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<div class="text-right">
<el-button @click="submit" type="primary"> {{ $t('common.save') }}</el-button>
</div>
</div>
</el-scrollbar>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue'
import { useRoute } from 'vue-router'
import BaseForm from '@/views/knowledge/component/BaseForm.vue'
import KnowledgeApi from '@/api/knowledge/knowledge'
import type { ApplicationFormType } from '@/api/type/application'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { isAppIcon } from '@/utils/common'
import useStore from '@/stores'
import { t } from '@/locales'
const route = useRoute()
const {
params: { id },
} = route as any
const { knowledge } = useStore()
const webFormRef = ref()
const BaseFormRef = ref()
const loading = ref(false)
const detail = ref<any>({})
const application_list = ref<Array<ApplicationFormType>>([])
const application_id_list = ref([])
const cloneModelId = ref('')
const form = ref<any>({
source_url: '',
selector: '',
app_id: '',
app_secret: '',
folder_token: '',
})
const rules = reactive({
source_url: [
{
required: true,
message: t('views.knowledge.form.source_url.requiredMessage'),
trigger: 'blur',
},
],
app_id: [
{
required: true,
message: t('views.application.applicationAccess.larkSetting.appIdPlaceholder'),
trigger: 'blur',
},
],
app_secret: [
{
required: true,
message: t('views.application.applicationAccess.larkSetting.appSecretPlaceholder'),
trigger: 'blur',
},
],
folder_token: [
{
required: true,
message: t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder'),
trigger: 'blur',
},
],
})
async function submit() {
if (await BaseFormRef.value?.validate()) {
await webFormRef.value.validate((valid: any) => {
if (valid) {
const obj =
detail.value.type === '1' || detail.value.type === '2'
? {
application_id_list: application_id_list.value,
meta: form.value,
...BaseFormRef.value.form,
}
: {
application_id_list: application_id_list.value,
...BaseFormRef.value.form,
}
if (cloneModelId.value !== BaseFormRef.value.form.embedding_mode_id) {
MsgConfirm(t('common.tip'), t('views.knowledge.tip.updateModeMessage'), {
confirmButtonText: t('views.knowledge.setting.vectorization'),
})
.then(() => {
if (detail.value.type === 2) {
KnowledgeApi.putLarkDataset(id, obj, loading).then((res) => {
KnowledgeApi.putReEmbeddingDataset(id).then(() => {
MsgSuccess(t('common.saveSuccess'))
})
})
} else {
KnowledgeApi.putKnowledge('default', id, obj, loading).then((res) => {
KnowledgeApi.putReEmbeddingDataset(id).then(() => {
MsgSuccess(t('common.saveSuccess'))
})
})
}
})
.catch(() => {})
} else {
if (detail.value.type === 2) {
KnowledgeApi.putLarkDataset(id, obj, loading).then((res) => {
KnowledgeApi.putReEmbeddingDataset(id).then(() => {
MsgSuccess(t('common.saveSuccess'))
})
})
} else {
KnowledgeApi.putKnowledge('default', id, obj, loading).then((res) => {
MsgSuccess(t('common.saveSuccess'))
})
}
}
}
})
}
}
function getDetail() {
knowledge.asyncGetDatasetDetail(id, loading).then((res: any) => {
detail.value = res.data
cloneModelId.value = res.data?.embedding_mode_id
if (detail.value.type === '1' || detail.value.type === '2') {
form.value = res.data.meta
}
application_id_list.value = res.data?.application_id_list
KnowledgeApi.listUsableApplication(id, loading).then((ok) => {
application_list.value = ok.data
})
})
}
onMounted(() => {
getDetail()
})
</script>
<style lang="scss" scoped>
.knowledge-setting {
width: 70%;
margin: 0 auto;
}
</style>

View File

@ -0,0 +1,212 @@
<template>
<LayoutContainer :header="$t('views.document.uploadDocument')" class="create-dataset">
<template #backButton>
<back-button @click="back"></back-button>
</template>
<div class="create-dataset__main flex" v-loading="loading">
<div class="create-dataset__component main-calc-height">
<el-scrollbar>
<template v-if="active === 0">
<div class="upload-document p-24">
<!-- 上传文档 -->
<UploadComponent ref="UploadComponentRef" />
</div>
</template>
<template v-else-if="active === 1">
<SetRules ref="SetRulesRef" />
</template>
<template v-else-if="active === 2">
<ResultSuccess :data="successInfo" />
</template>
</el-scrollbar>
</div>
</div>
<div class="create-dataset__footer text-right border-t" v-if="active !== 2">
<el-button @click="router.go(-1)" :disabled="SetRulesRef?.loading || loading">{{
$t('common.cancel')
}}</el-button>
<el-button @click="prev" v-if="active === 1" :disabled="SetRulesRef?.loading || loading">{{
$t('views.document.buttons.prev')
}}</el-button>
<el-button
@click="next"
type="primary"
v-if="active === 0"
:disabled="SetRulesRef?.loading || loading"
>
{{
documentsType === 'txt'
? $t('views.document.buttons.next')
: $t('views.document.buttons.import')
}}
</el-button>
<el-button
@click="submit"
type="primary"
v-if="active === 1"
:disabled="SetRulesRef?.loading || loading"
>
{{ $t('views.document.buttons.import') }}
</el-button>
</div>
</LayoutContainer>
</template>
<script setup lang="ts">
import { ref, computed, onUnmounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import SetRules from './component/SetRules.vue'
import ResultSuccess from './component/ResultSuccess.vue'
import UploadComponent from './component/UploadComponent.vue'
import documentApi from '@/api/document'
import { MsgConfirm, MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
import useStore from '@/stores'
const { dataset, document } = useStore()
const documentsFiles = computed(() => dataset.documentsFiles)
const documentsType = computed(() => dataset.documentsType)
const router = useRouter()
const route = useRoute()
const {
query: { id } // iddatasetIDid
} = route
const SetRulesRef = ref()
const UploadComponentRef = ref()
const loading = ref(false)
const disabled = ref(false)
const active = ref(0)
const successInfo = ref<any>(null)
async function next() {
disabled.value = true
if (await UploadComponentRef.value.validate()) {
if (documentsType.value === 'QA') {
let fd = new FormData()
documentsFiles.value.forEach((item: any) => {
if (item?.raw) {
fd.append('file', item?.raw)
}
})
if (id) {
// QA
documentApi.postQADocument(id as string, fd, loading).then((res) => {
MsgSuccess(t('common.submitSuccess'))
clearStore()
router.push({ path: `/dataset/${id}/document` })
})
}
} else if (documentsType.value === 'table') {
let fd = new FormData()
documentsFiles.value.forEach((item: any) => {
if (item?.raw) {
fd.append('file', item?.raw)
}
})
if (id) {
// table
documentApi.postTableDocument(id as string, fd, loading).then((res) => {
MsgSuccess(t('common.submitSuccess'))
clearStore()
router.push({ path: `/dataset/${id}/document` })
})
}
} else {
if (active.value++ > 2) active.value = 0
}
} else {
disabled.value = false
}
}
const prev = () => {
active.value = 0
}
function clearStore() {
dataset.saveDocumentsFile([])
dataset.saveDocumentsType('')
}
function submit() {
loading.value = true
const documents = [] as any
SetRulesRef.value?.paragraphList.map((item: any) => {
if (!SetRulesRef.value?.checkedConnect) {
item.content.map((v: any) => {
delete v['problem_list']
})
}
documents.push({
name: item.name,
paragraphs: item.content
})
})
if (id) {
//
document
.asyncPostDocument(id as string, documents)
.then(() => {
MsgSuccess(t('common.submitSuccess'))
clearStore()
router.push({ path: `/dataset/${id}/document` })
})
.catch(() => {
loading.value = false
})
}
}
function back() {
if (documentsFiles.value?.length > 0) {
MsgConfirm(t('common.tip'), t('views.document.tip.saveMessage'), {
confirmButtonText: t('common.confirm'),
type: 'warning'
})
.then(() => {
router.go(-1)
clearStore()
})
.catch(() => {})
} else {
router.go(-1)
}
}
onUnmounted(() => {
clearStore()
})
</script>
<style lang="scss" scoped>
.create-dataset {
&__steps {
min-width: 450px;
max-width: 800px;
width: 80%;
margin: 0 auto;
padding-right: 60px;
:deep(.el-step__line) {
left: 64% !important;
right: -33% !important;
}
}
&__component {
width: 100%;
margin: 0 auto;
overflow: hidden;
}
&__footer {
padding: 16px 24px;
position: fixed;
bottom: 0;
left: 0;
background: #ffffff;
width: 100%;
box-sizing: border-box;
}
.upload-document {
width: 70%;
margin: 0 auto;
margin-bottom: 20px;
}
}
</style>

View File

@ -174,6 +174,7 @@ const remoteMethod = (query: string) => {
function getProblemOption(filterText?: string) {
return problem
.asyncGetProblem(
'default',
props.datasetId || (id as string),
{ current_page: 1, page_size: 100 },
filterText && { content: filterText },

View File

@ -1,101 +1,123 @@
<template>
<LayoutContainer :header="$t('views.problem.title')">
<div class="main-calc-height">
<div class="p-24">
<div class="flex-between">
<div>
<el-button type="primary" @click="createProblem">{{$t('views.problem.createProblem')}}</el-button>
<el-button @click="relateProblem()" :disabled="multipleSelection.length === 0"
>{{$t('views.problem.relateParagraph.title')}}</el-button
>
<el-button @click="deleteMulDocument" :disabled="multipleSelection.length === 0"
>{{$t('views.problem.setting.batchDelete')}}</el-button
>
</div>
<div class="document p-16-24">
<h2 class="mb-16">{{ $t('views.problem.title') }}</h2>
<el-card style="--el-card-padding: 0">
<div class="main-calc-height">
<div class="p-24">
<div class="flex-between">
<div>
<el-button type="primary" @click="createProblem">{{
$t('views.problem.createProblem')
}}</el-button>
<el-button @click="relateProblem()" :disabled="multipleSelection.length === 0">{{
$t('views.problem.relateParagraph.title')
}}</el-button>
<el-button @click="deleteMulDocument" :disabled="multipleSelection.length === 0">{{
$t('views.problem.setting.batchDelete')
}}</el-button>
</div>
<el-input
v-model="filterText"
:placeholder="$t('views.problem.searchBar.placeholder')"
prefix-icon="Search"
class="w-240"
@change="getList"
clearable
/>
<el-input
v-model="filterText"
:placeholder="$t('views.problem.searchBar.placeholder')"
prefix-icon="Search"
class="w-240"
@change="getList"
clearable
/>
</div>
<app-table
ref="multipleTableRef"
class="mt-16"
:data="problemData"
:pagination-config="paginationConfig"
quick-create
:quickCreateName="$t('views.problem.quickCreateName')"
:quickCreatePlaceholder="$t('views.problem.quickCreateProblem')"
:quickCreateMaxlength="256"
@sizeChange="handleSizeChange"
@changePage="getList"
@cell-mouse-enter="cellMouseEnter"
@cell-mouse-leave="cellMouseLeave"
@creatQuick="creatQuickHandle"
@row-click="rowClickHandle"
@selection-change="handleSelectionChange"
:row-class-name="setRowClass"
v-loading="loading"
:row-key="(row: any) => row.id"
>
<el-table-column type="selection" width="55" :reserve-selection="true" />
<el-table-column prop="content" :label="$t('views.problem.title')" min-width="280">
<template #default="{ row }">
<ReadWrite
@change="editName($event, row.id)"
:data="row.content"
:showEditIcon="row.id === currentMouseId"
:maxlength="256"
/>
</template>
</el-table-column>
<el-table-column
prop="paragraph_count"
:label="$t('views.problem.table.paragraph_count')"
align="right"
min-width="100"
>
<template #default="{ row }">
<el-link
type="primary"
@click.stop="rowClickHandle(row)"
v-if="row.paragraph_count"
>
{{ row.paragraph_count }}
</el-link>
<span v-else>
{{ row.paragraph_count }}
</span>
</template>
</el-table-column>
<el-table-column prop="create_time" :label="$t('common.createTime')" width="170">
<template #default="{ row }">
{{ datetimeFormat(row.create_time) }}
</template>
</el-table-column>
<el-table-column
prop="update_time"
:label="$t('views.problem.table.updateTime')"
width="170"
>
<template #default="{ row }">
{{ datetimeFormat(row.update_time) }}
</template>
</el-table-column>
<el-table-column :label="$t('common.operation')" align="left" fixed="right">
<template #default="{ row }">
<div>
<span class="mr-4">
<el-tooltip
effect="dark"
:content="$t('views.problem.relateParagraph.title')"
placement="top"
>
<el-button type="primary" text @click.stop="relateProblem(row)">
<el-icon><Connection /></el-icon>
</el-button>
</el-tooltip>
</span>
<span>
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
<el-button type="primary" text @click.stop="deleteProblem(row)">
<el-icon><Delete /></el-icon>
</el-button>
</el-tooltip>
</span>
</div>
</template>
</el-table-column>
</app-table>
</div>
<app-table
ref="multipleTableRef"
class="mt-16"
:data="problemData"
:pagination-config="paginationConfig"
quick-create
:quickCreateName="$t('views.problem.quickCreateName')"
:quickCreatePlaceholder="$t('views.problem.quickCreateProblem')"
:quickCreateMaxlength="256"
@sizeChange="handleSizeChange"
@changePage="getList"
@cell-mouse-enter="cellMouseEnter"
@cell-mouse-leave="cellMouseLeave"
@creatQuick="creatQuickHandle"
@row-click="rowClickHandle"
@selection-change="handleSelectionChange"
:row-class-name="setRowClass"
v-loading="loading"
:row-key="(row: any) => row.id"
>
<el-table-column type="selection" width="55" :reserve-selection="true" />
<el-table-column prop="content" :label="$t('views.problem.title')" min-width="280">
<template #default="{ row }">
<ReadWrite
@change="editName($event, row.id)"
:data="row.content"
:showEditIcon="row.id === currentMouseId"
:maxlength="256"
/>
</template>
</el-table-column>
<el-table-column prop="paragraph_count" :label="$t('views.problem.table.paragraph_count')" align="right" min-width="100">
<template #default="{ row }">
<el-link type="primary" @click.stop="rowClickHandle(row)" v-if="row.paragraph_count">
{{ row.paragraph_count }}
</el-link>
<span v-else>
{{ row.paragraph_count }}
</span>
</template>
</el-table-column>
<el-table-column prop="create_time" :label="$t('common.createTime')" width="170">
<template #default="{ row }">
{{ datetimeFormat(row.create_time) }}
</template>
</el-table-column>
<el-table-column prop="update_time" :label="$t('views.problem.table.updateTime')" width="170">
<template #default="{ row }">
{{ datetimeFormat(row.update_time) }}
</template>
</el-table-column>
<el-table-column :label="$t('common.operation')" align="left" fixed="right">
<template #default="{ row }">
<div>
<span class="mr-4">
<el-tooltip effect="dark" :content="$t('views.problem.relateParagraph.title')" placement="top">
<el-button type="primary" text @click.stop="relateProblem(row)">
<el-icon><Connection /></el-icon>
</el-button>
</el-tooltip>
</span>
<span>
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
<el-button type="primary" text @click.stop="deleteProblem(row)">
<el-icon><Delete /></el-icon>
</el-button>
</el-tooltip>
</span>
</div>
</template>
</el-table-column>
</app-table>
</div>
</div>
</el-card>
<CreateProblemDialog ref="CreateProblemDialogRef" @refresh="refresh" />
<DetailProblemDrawer
:next="nextChatRecord"
@ -108,7 +130,7 @@
@refresh="refreshRelate"
/>
<RelateProblemDialog ref="RelateProblemDialogRef" @refresh="refreshRelate" />
</LayoutContainer>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, reactive, onBeforeUnmount, computed } from 'vue'
@ -125,7 +147,7 @@ import useStore from '@/stores'
import { t } from '@/locales'
const route = useRoute()
const {
params: { id } // id
params: { id }, // id
} = route as any
const { problem } = useStore()
@ -144,7 +166,7 @@ const currentContent = ref('')
const paginationConfig = reactive({
current_page: 1,
page_size: 10,
total: 0
total: 0,
})
const filterText = ref('')
@ -152,7 +174,7 @@ const problemData = ref<any[]>([])
const problemIndexMap = computed<Dict<number>>(() => {
return problemData.value
.map((row, index) => ({
[row.id]: index
[row.id]: index,
}))
.reduce((pre, next) => ({ ...pre, ...next }), {})
})
@ -220,8 +242,8 @@ function deleteProblem(row: any) {
`${t('views.problem.delete.confirmMessage1')} ${row.paragraph_count} ${t('views.problem.delete.confirmMessage2')}`,
{
confirmButtonText: t('common.confirm'),
confirmButtonClass: 'danger'
}
confirmButtonClass: 'danger',
},
)
.then(() => {
problemApi.delProblems(id, row.id, loading).then(() => {
@ -235,7 +257,7 @@ function deleteProblem(row: any) {
function editName(val: string, problemId: string) {
if (val) {
const obj = {
content: val
content: val,
}
problemApi.putProblems(id, problemId, obj, loading).then(() => {
getList()
@ -334,10 +356,11 @@ function handleSizeChange() {
function getList() {
return problem
.asyncGetProblem(
'default',
id as string,
paginationConfig,
filterText.value && { content: filterText.value },
loading
loading,
)
.then((res: any) => {
problemData.value = res.data.records

View File

@ -3,7 +3,7 @@
<template #header>
<h4>{{ title }}</h4>
</template>
<h4 class="title-decoration-1 mb-16 mt-8">{{ $t('views.userManage.info') }}</h4>
<h4 class="title-decoration-1 mb-16 mt-8">{{ $t('common.info') }}</h4>
<el-form
ref="userFormRef"
:model="userForm"