Merge branch 'main' of github.com:maxkb-dev/maxkb
commit
24eba13fa7
|
|
@ -1,4 +1,4 @@
|
|||
/// <reference types="vite/client" />
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
VITE_APP_NAME=ui
|
||||
VITE_BASE_PATH=/ui/
|
||||
VITE_APP_PORT=3000
|
||||
VITE_APP_TITLE = '智能知识库'
|
||||
VITE_APP_TITLE = 'MaxKB'
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Result } from '@/request/Result'
|
||||
import { get, post, del, put } from '@/request/index'
|
||||
import type { datasetListRequest } from '@/api/type/dataset'
|
||||
import type { datasetListRequest, datasetData } from '@/api/type/dataset'
|
||||
|
||||
const prefix = '/dataset'
|
||||
|
||||
|
|
@ -9,19 +9,22 @@ const prefix = '/dataset'
|
|||
* @param 参数 {
|
||||
"current_page": "string",
|
||||
"page_size": "string",
|
||||
"search_text": "string",
|
||||
"name": "string",
|
||||
}
|
||||
*/
|
||||
const getDateset: (param: datasetListRequest) => Promise<Result<any[]>> = (param) => {
|
||||
return get(`${prefix}`, param)
|
||||
const getDateset: (param: datasetListRequest) => Promise<Result<any>> = (param) => {
|
||||
return get(
|
||||
`${prefix}/${param.current_page}/${param.page_size}`,
|
||||
param.name && { name: param.name }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全部数据集
|
||||
* @param 参数 search_text
|
||||
* @param 参数 name
|
||||
*/
|
||||
const getAllDateset: (param?: String) => Promise<Result<any[]>> = (param) => {
|
||||
return get(`${prefix}`, param && { search_text: param })
|
||||
const getAllDateset: (param?: string) => Promise<Result<any[]>> = (param) => {
|
||||
return get(`${prefix}`, param && { name: param })
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -32,9 +35,279 @@ const delDateset: (dataset_id: String) => Promise<Result<boolean>> = (dataset_id
|
|||
return del(`${prefix}/${dataset_id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建数据集
|
||||
* @param 参数
|
||||
* {
|
||||
"name": "string",
|
||||
"desc": "string",
|
||||
"documents": [
|
||||
{
|
||||
"name": "string",
|
||||
"paragraphs": [
|
||||
{
|
||||
"content": "string",
|
||||
"title": "string",
|
||||
"problem_list": [
|
||||
{
|
||||
"id": "string",
|
||||
"content": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
const postDateset: (data: datasetData) => Promise<Result<any>> = (data) => {
|
||||
return post(`${prefix}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据集详情
|
||||
* @param 参数 dataset_id
|
||||
*/
|
||||
const getDatesetDetail: (dataset_id: string) => Promise<Result<any>> = (dataset_id) => {
|
||||
return get(`${prefix}/${dataset_id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改数据集信息
|
||||
* @param 参数
|
||||
* dataset_id, document_id,
|
||||
* {
|
||||
"name": "string",
|
||||
"desc": true
|
||||
}
|
||||
*/
|
||||
const putDateset: (dataset_id: string, data: any) => Promise<Result<any>> = (
|
||||
dataset_id,
|
||||
data: any
|
||||
) => {
|
||||
return put(`${prefix}/${dataset_id}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 分段预览(上传文档)
|
||||
* @param 参数 file:file,limit:number,patterns:array,with_filter:boolean
|
||||
*/
|
||||
const postSplitDocument: (data: any) => Promise<Result<any>> = (data) => {
|
||||
return post(`${prefix}/document/split`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文档列表
|
||||
* @param 参数 dataset_id, name
|
||||
*/
|
||||
|
||||
const getDocument: (dataset_id: string, name?: string) => Promise<Result<any>> = (
|
||||
dataset_id,
|
||||
name
|
||||
) => {
|
||||
return get(`${prefix}/${dataset_id}/document`, name && { name })
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文档
|
||||
* @param 参数
|
||||
* {
|
||||
"name": "string",
|
||||
"paragraphs": [
|
||||
{
|
||||
"content": "string",
|
||||
"title": "string",
|
||||
"problem_list": [
|
||||
{
|
||||
"id": "string",
|
||||
"content": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
const postDocument: (dataset_id: string, data: any) => Promise<Result<any>> = (
|
||||
dataset_id,
|
||||
data
|
||||
) => {
|
||||
return post(`${prefix}/${dataset_id}/document`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改文档
|
||||
* @param 参数
|
||||
* dataset_id, document_id,
|
||||
* {
|
||||
"name": "string",
|
||||
"is_active": true
|
||||
}
|
||||
*/
|
||||
const putDocument: (dataset_id: string, document_id: string, data: any) => Promise<Result<any>> = (
|
||||
dataset_id,
|
||||
document_id,
|
||||
data: any
|
||||
) => {
|
||||
return put(`${prefix}/${dataset_id}/document/${document_id}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文档
|
||||
* @param 参数 dataset_id, document_id,
|
||||
*/
|
||||
const delDocument: (dataset_id: string, document_id: string) => Promise<Result<boolean>> = (
|
||||
dataset_id,
|
||||
document_id
|
||||
) => {
|
||||
return del(`${prefix}/${dataset_id}/document/${document_id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文档详情
|
||||
* @param 参数 dataset_id
|
||||
*/
|
||||
const getDocumentDetail: (dataset_id: string, document_id: string) => Promise<Result<any>> = (
|
||||
dataset_id,
|
||||
document_id
|
||||
) => {
|
||||
return get(`${prefix}/${dataset_id}/document/${document_id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 段落列表
|
||||
* @param 参数 dataset_id
|
||||
*/
|
||||
const getParagraph: (dataset_id: string, document_id: string) => Promise<Result<any>> = (
|
||||
dataset_id,
|
||||
document_id
|
||||
) => {
|
||||
return get(`${prefix}/${dataset_id}/document/${document_id}/paragraph`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除段落
|
||||
* @param 参数 dataset_id, document_id, paragraph_id
|
||||
*/
|
||||
const delParagraph: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
paragraph_id: string
|
||||
) => Promise<Result<boolean>> = (dataset_id, document_id, paragraph_id) => {
|
||||
return del(`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建段落
|
||||
* @param 参数
|
||||
* dataset_id, document_id
|
||||
* {
|
||||
"content": "string",
|
||||
"title": "string",
|
||||
"is_active": true,
|
||||
"problem_list": [
|
||||
{
|
||||
"id": "string",
|
||||
"content": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
const postParagraph: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
data: any
|
||||
) => Promise<Result<any>> = (dataset_id, document_id, data: any) => {
|
||||
return post(`${prefix}/${dataset_id}/document/${document_id}/paragraph`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改段落
|
||||
* @param 参数
|
||||
* dataset_id, document_id, paragraph_id
|
||||
* {
|
||||
"content": "string",
|
||||
"title": "string",
|
||||
"is_active": true,
|
||||
"problem_list": [
|
||||
{
|
||||
"id": "string",
|
||||
"content": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
const putParagraph: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
paragraph_id: string,
|
||||
data: any
|
||||
) => Promise<Result<any>> = (dataset_id, document_id, paragraph_id, data: any) => {
|
||||
return put(`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 问题列表
|
||||
* @param 参数 dataset_id,document_id,paragraph_id
|
||||
*/
|
||||
const getProblem: (dataset_id: string, document_id: string, paragraph_id: string) => Promise<Result<any>> = (
|
||||
dataset_id,
|
||||
document_id,
|
||||
paragraph_id: string,
|
||||
) => {
|
||||
return get(`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}/problem`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建问题
|
||||
* @param 参数
|
||||
* dataset_id, document_id, paragraph_id
|
||||
* {
|
||||
"id": "string",
|
||||
content": "string"
|
||||
}
|
||||
*/
|
||||
const postProblem: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
paragraph_id: string,
|
||||
data: any
|
||||
) => Promise<Result<any>> = (dataset_id, document_id, paragraph_id, data: any) => {
|
||||
return post(
|
||||
`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}/problem`,
|
||||
data
|
||||
)
|
||||
}
|
||||
/**
|
||||
* 删除问题
|
||||
* @param 参数 dataset_id, document_id, paragraph_id,problem_id
|
||||
*/
|
||||
const delProblem: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
paragraph_id: string,
|
||||
problem_id: string,
|
||||
) => Promise<Result<boolean>> = (dataset_id, document_id, paragraph_id,problem_id) => {
|
||||
return del(`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}/problem/${problem_id}`)
|
||||
}
|
||||
|
||||
export default {
|
||||
getDateset,
|
||||
getAllDateset,
|
||||
delDateset
|
||||
delDateset,
|
||||
postDateset,
|
||||
getDatesetDetail,
|
||||
putDateset,
|
||||
postSplitDocument,
|
||||
getDocument,
|
||||
postDocument,
|
||||
putDocument,
|
||||
delDocument,
|
||||
getDocumentDetail,
|
||||
getParagraph,
|
||||
delParagraph,
|
||||
putParagraph,
|
||||
postParagraph,
|
||||
getProblem,
|
||||
postProblem,
|
||||
delProblem
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,9 +56,10 @@ const putMemberPermissions: (member_id: String, body: any) => Promise<Result<any
|
|||
member_id,
|
||||
body
|
||||
) => {
|
||||
return put(`${prefix}/${member_id}`, undefined, body)
|
||||
return put(`${prefix}/${member_id}`, body)
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
getTeamMember,
|
||||
postCreatTeamMember,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
interface datasetListRequest {
|
||||
current_page: number
|
||||
page_size: number
|
||||
search_text: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type { datasetListRequest }
|
||||
interface datasetData {
|
||||
name: String
|
||||
desc: String
|
||||
documents?: Array<any>
|
||||
}
|
||||
|
||||
export type { datasetListRequest, datasetData }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_332_3845)">
|
||||
<path opacity="0.5" d="M11.5903 10.7326C11.5903 8.64041 13.2864 6.9444 15.3785 6.9444C17.4706 6.9444 19.1667 8.64042 19.1667 10.7326V15.3784C19.1667 17.4705 17.4706 19.1666 15.3785 19.1666C13.2864 19.1666 11.5903 17.4705 11.5903 15.3784V10.7326Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.6212 0.833374H14.6211V2.36113H13.1059V0.833374H2.83347C1.72892 0.833374 0.833495 1.72878 0.833474 2.83333L0.833313 11.1665C0.833228 15.5848 4.41497 19.1666 8.83331 19.1666H15.1666C15.5076 19.1666 15.8387 19.1239 16.1547 19.0436C15.2624 18.7368 14.6212 17.8902 14.6212 16.8937V0.833374ZM4.51881 6.35876C4.51881 5.89468 4.89501 5.51848 5.35909 5.51848H10.3457C10.8098 5.51848 11.186 5.89468 11.186 6.35876C11.186 6.82283 10.8098 7.19904 10.3457 7.19904H5.35909C4.89501 7.19904 4.51881 6.82283 4.51881 6.35876ZM5.35909 9.4398C4.89501 9.4398 4.51881 9.816 4.51881 10.2801C4.51881 10.7441 4.89501 11.1204 5.35909 11.1204H10.3457C10.8098 11.1204 11.186 10.7441 11.186 10.2801C11.186 9.816 10.8098 9.4398 10.3457 9.4398H5.35909Z" fill="white"/>
|
||||
<ellipse cx="13.1058" cy="2.36108" rx="1.51527" ry="1.52776" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_332_3845">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="40" height="42" viewBox="0 0 40 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.66663 5.16667C6.66663 4.24619 7.41282 3.5 8.33329 3.5H24.6548C24.8758 3.5 25.0878 3.5878 25.244 3.74408L33.0892 11.5893C33.2455 11.7455 33.3333 11.9575 33.3333 12.1785V36.8333C33.3333 37.7538 32.5871 38.5 31.6666 38.5H8.33329C7.41282 38.5 6.66663 37.7538 6.66663 36.8333V5.16667Z" fill="#14C0FF"/>
|
||||
<path d="M10 29.2051V21H12.0513L15.1282 24.0769L18.2051 21H20.2564V29.2051H18.2051V23.9026L15.1282 26.9795L12.0513 23.9026V29.2051H10ZM24.359 21H27.4359V25.1026H30L25.8974 29.7179L21.7949 25.1026H24.359V21Z" fill="white"/>
|
||||
<path d="M25 3.57495C25.09 3.6159 25.1728 3.67292 25.2441 3.74418L33.0893 11.5894C33.1605 11.6606 33.2175 11.7434 33.2585 11.8334H26.6667C25.7462 11.8334 25 11.0872 25 10.1668V3.57495Z" fill="#11A3D9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 839 B |
|
|
@ -0,0 +1,7 @@
|
|||
<svg width="40" height="42" viewBox="0 0 40 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.66699 5.16667C6.66699 4.24619 7.41318 3.5 8.33366 3.5H24.6551C24.8762 3.5 25.0881 3.5878 25.2444 3.74408L33.0896 11.5893C33.2459 11.7455 33.3337 11.9575 33.3337 12.1785V36.8333C33.3337 37.7538 32.5875 38.5 31.667 38.5H8.33366C7.41318 38.5 6.66699 37.7538 6.66699 36.8333V5.16667Z" fill="#3370FF"/>
|
||||
<path d="M25.0532 21.8466H22.9132V20.5266H28.7632V21.8466H26.6232V27.6666H25.0532V21.8466Z" fill="white"/>
|
||||
<path d="M18.5533 23.9266L16.2533 20.5266H18.0733L19.4733 22.8066L20.9233 20.5266H22.6433L20.3533 23.9366L22.8433 27.6666H20.9733L19.4133 25.1966L17.8233 27.6666H16.0633L18.5533 23.9266Z" fill="white"/>
|
||||
<path d="M12.27 21.8466H10.13V20.5266H15.98V21.8466H13.84V27.6666H12.27V21.8466Z" fill="white"/>
|
||||
<path d="M25 3.57495C25.09 3.6159 25.1728 3.67292 25.2441 3.74418L33.0893 11.5894C33.1605 11.6606 33.2175 11.7434 33.2585 11.8334H26.6667C25.7462 11.8334 25 11.0872 25 10.1668V3.57495Z" fill="#2B5FD9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
|
@ -0,0 +1,6 @@
|
|||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M33.0404 11.3738C33.2279 11.5614 33.3333 11.8157 33.3333 12.0809V34.8149C33.3333 35.8376 32.5374 36.6667 31.5555 36.6667H8.4444C7.46256 36.6667 6.66663 35.8376 6.66663 34.8149V5.18523C6.66663 4.16248 7.46256 3.33337 8.4444 3.33337H24.5857C24.851 3.33337 25.1053 3.43873 25.2929 3.62627L33.0404 11.3738Z" fill="#3370FF"/>
|
||||
<path d="M20.6509 15.8135C20.3173 15.3965 19.683 15.3965 19.3494 15.8135L14.4166 21.9795C13.9801 22.5251 14.3686 23.3334 15.0673 23.3334H18.3335V26.6667H21.6668V23.3334H24.9329C25.6317 23.3334 26.0202 22.5251 25.5837 21.9795L20.6509 15.8135Z" fill="white"/>
|
||||
<path d="M18.3335 30.0001V28.3334H21.6668V30.0001H18.3335Z" fill="white"/>
|
||||
<path d="M25 3.33337L33.3333 11.6667H26.6667C25.7462 11.6667 25 10.9205 25 10V3.33337Z" fill="#2B5FD9"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 871 B |
|
|
@ -0,0 +1,14 @@
|
|||
<svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 7.33333H4C2.15905 7.33333 0 8.62098 0 10.9333V12.7333C0 13.0647 0.298477 13.3333 0.666667 13.3333H11.3333C11.7015 13.3333 12 13.0647 12 12.7333V10.9333C12 8.61904 9.84095 7.33333 8 7.33333Z" fill="url(#paint0_linear_264_32130)"/>
|
||||
<path d="M2.66667 3.33333C2.66667 5.17428 4.15905 6.66667 6 6.66667C7.84095 6.66667 9.33333 5.17428 9.33333 3.33333C9.33333 1.49238 7.84095 0 6 0C4.15905 0 2.66667 1.49238 2.66667 3.33333Z" fill="url(#paint1_linear_264_32130)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_264_32130" x1="6" y1="-1.34111e-08" x2="6" y2="13.6667" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_264_32130" x1="6" y1="-1.34111e-08" x2="6" y2="13.6667" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<div class="app-table" :class="quickCreate ? 'table-quick-append' : ''">
|
||||
<el-table v-bind="$attrs">
|
||||
<template #append v-if="quickCreate">
|
||||
<div v-if="showInput">
|
||||
<el-input
|
||||
ref="quickInputRef"
|
||||
v-model="inputValue"
|
||||
placeholder="请输入文档名称"
|
||||
class="w-240 mr-12"
|
||||
autofocus
|
||||
/>
|
||||
|
||||
<el-button type="primary" @click="submitHandle" :disabled="loading">创建</el-button>
|
||||
<el-button @click="showInput = false" :disabled="loading">取消</el-button>
|
||||
</div>
|
||||
<div v-else @click="quickCreateHandel" class="w-full">
|
||||
<el-button type="primary" link>
|
||||
<el-icon><Plus /></el-icon>
|
||||
<span class="ml-4">快速创建空白文档</span>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<slot></slot>
|
||||
</el-table>
|
||||
<div class="app-table__pagination mt-16" v-if="$slots.pagination || paginationConfig">
|
||||
<slot name="pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="paginationConfig.currentPage"
|
||||
v-model:page-size="paginationConfig.pageSize"
|
||||
:page-sizes="pageSizes"
|
||||
:total="paginationConfig.total"
|
||||
layout="total, prev, pager, next, sizes"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, watch, computed } from 'vue'
|
||||
defineOptions({ name: 'AppTable' })
|
||||
|
||||
const props = defineProps({
|
||||
paginationConfig: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
quickCreate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['changePage', 'sizeChange', 'creatQuick'])
|
||||
|
||||
const paginationConfig = computed(() => props.paginationConfig)
|
||||
|
||||
const pageSizes = [10, 20, 50, 100]
|
||||
|
||||
const quickInputRef = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
const showInput = ref(false)
|
||||
const inputValue = ref('')
|
||||
|
||||
watch(showInput, (bool) => {
|
||||
if (!bool) {
|
||||
inputValue.value = ''
|
||||
}
|
||||
})
|
||||
|
||||
function submitHandle() {
|
||||
loading.value = true
|
||||
emit('creatQuick', inputValue.value)
|
||||
setTimeout(() => {
|
||||
showInput.value = false
|
||||
loading.value = false
|
||||
}, 200)
|
||||
}
|
||||
|
||||
function quickCreateHandel() {
|
||||
showInput.value = true
|
||||
nextTick(() => {
|
||||
quickInputRef.value?.focus()
|
||||
})
|
||||
}
|
||||
|
||||
function handleSizeChange() {
|
||||
emit('sizeChange')
|
||||
}
|
||||
function handleCurrentChange() {
|
||||
emit('changePage')
|
||||
}
|
||||
defineExpose({})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-table {
|
||||
&__pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<el-icon class="back-button cursor mr-8" @click="jump">
|
||||
<Back />
|
||||
</el-icon>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter, type RouteLocationRaw } from 'vue-router'
|
||||
defineOptions({ name: 'BackButton' })
|
||||
const router = useRouter()
|
||||
const props = defineProps({
|
||||
to: String
|
||||
})
|
||||
|
||||
const back: any = router.options.history.state.back // 上一层路由
|
||||
function jump() {
|
||||
if (props.to === '-1') {
|
||||
back ? router.push(back) : router.go(-1)
|
||||
} else {
|
||||
router.push(props.to as RouteLocationRaw)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.back-button {
|
||||
font-size:20px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<el-card shadow="hover">
|
||||
<div class="card-add">
|
||||
<AppIcon :iconName="icon" class="add-icon" />
|
||||
<span class="ml-10">{{ title }}</span>
|
||||
<el-card shadow="never" class="card-add">
|
||||
<div class="flex-center">
|
||||
<AppIcon iconName="Plus" class="add-icon p-8" />
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
|
@ -12,27 +12,35 @@ defineProps({
|
|||
title: {
|
||||
type: String,
|
||||
default: '标题'
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'CirclePlusFilled'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.card-add {
|
||||
width: 100%;
|
||||
min-height: 110px;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 15px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
min-height: var(--card-min-height);
|
||||
border: 1px dashed var(--el-color-primary);
|
||||
background: #eff0f1;
|
||||
|
||||
.add-icon {
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--app-border-color-dark);
|
||||
background: var(--app-layout-bg-color);
|
||||
margin-right: 12px;
|
||||
}
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.add-icon {
|
||||
font-size: 16px;
|
||||
background: #ffffff;
|
||||
.add-icon {
|
||||
background: #ffffff;
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
<template>
|
||||
<el-card shadow="hover" class="card-box" @mouseenter="cardEnter()" @mouseleave="cardLeave()">
|
||||
<el-card shadow="always" class="card-box" @mouseenter="cardEnter()" @mouseleave="cardLeave()">
|
||||
<div class="card-header">
|
||||
<slot name="header">
|
||||
<div class="title flex align-center">
|
||||
<AppAvatar class="mr-10">
|
||||
<el-icon><Document /></el-icon>
|
||||
<AppAvatar class="mr-12" shape="square" :size="32" v-if="showIcon">
|
||||
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
|
||||
</AppAvatar>
|
||||
<h4>{{ title }}</h4>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="description mt-10">
|
||||
<div class="description mt-12">
|
||||
<slot name="description">
|
||||
{{ description }}
|
||||
</slot>
|
||||
|
|
@ -33,6 +33,10 @@ const props = defineProps({
|
|||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -48,7 +52,7 @@ function cardLeave() {
|
|||
.card-box {
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
min-height: 150px;
|
||||
min-height: var(--card-min-height);
|
||||
|
||||
.description {
|
||||
display: -webkit-box;
|
||||
|
|
@ -56,11 +60,20 @@ function cardLeave() {
|
|||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
height: 40px;
|
||||
color: var(--app-text-color-secondary);
|
||||
line-height: 22px;
|
||||
font-weight: 400;
|
||||
}
|
||||
.card-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
bottom: 8px;
|
||||
left: 0;
|
||||
min-height: 30px;
|
||||
color: var(--app-text-color-secondary);
|
||||
font-weight: 400;
|
||||
padding: 0 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<div class="common-list">
|
||||
<el-scrollbar>
|
||||
<ul v-if="data.length > 0">
|
||||
<template v-for="(item, index) in data" :key="index">
|
||||
<li
|
||||
@click.prevent="clickHandle(item, index)"
|
||||
:class="current === index ? 'active' : ''"
|
||||
class="cursor"
|
||||
>
|
||||
<slot :row="item" :index="index"> </slot>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<el-empty description="暂无数据" v-else />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
defineOptions({ name: 'CommonList' })
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array<any>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
const current = ref(0)
|
||||
|
||||
function clickHandle(row: any, index: number) {
|
||||
current.value = index
|
||||
emit('click', row)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
// 通用 ui li样式
|
||||
.common-list {
|
||||
li {
|
||||
padding: 11px 16px;
|
||||
&.active {
|
||||
background: var(--el-color-primary-light-9);
|
||||
border-radius: 4px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
<template>
|
||||
<div class="content-container">
|
||||
<div class="content-container__header mb-10" v-if="slots.header || header">
|
||||
<slot name="header">
|
||||
<span>{{ header }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
<el-scrollbar>
|
||||
<div class="content-container__main main-calc-height">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSlots } from 'vue'
|
||||
defineOptions({ name: 'LayoutContent' })
|
||||
const slots = useSlots()
|
||||
defineProps({
|
||||
header: String
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scope>
|
||||
.content-container {
|
||||
transition: 0.3s;
|
||||
padding: var(--app-view-padding);
|
||||
.content-container__header {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.content-container__main {
|
||||
background-color: var(--app-view-bg-color);
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
// overflow: auto;
|
||||
// height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -34,6 +34,52 @@ export const iconMap: any = {
|
|||
}
|
||||
},
|
||||
|
||||
'app-user': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 24 24',
|
||||
version: '1.1',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M14 12.3333H10C8.15905 12.3333 6 13.621 6 15.9333V17.7333C6 18.0647 6.29848 18.3333 6.66667 18.3333H17.3333C17.7015 18.3333 18 18.0647 18 17.7333V15.9333C18 13.619 15.841 12.3333 14 12.3333Z',
|
||||
fill: 'currentColor'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M8.66667 8.33333C8.66667 10.1743 10.1591 11.6667 12 11.6667C13.8409 11.6667 15.3333 10.1743 15.3333 8.33333C15.3333 6.49238 13.8409 5 12 5C10.1591 5 8.66667 6.49238 8.66667 8.33333Z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
|
||||
'app-add-users': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 20 20',
|
||||
version: '1.1',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M6.24984 5.41667C6.24984 6.7975 7.37067 7.91667 8.74984 7.91667C10.129 7.91667 11.2498 6.7975 11.2498 5.41667C11.2498 4.03583 10.129 2.91667 8.74984 2.91667C7.37067 2.91667 6.24984 4.03583 6.24984 5.41667ZM8.74984 1.25C11.0498 1.25 12.9165 3.11542 12.9165 5.41667C12.9165 7.71792 11.0498 9.58333 8.74984 9.58333C6.44984 9.58333 4.58317 7.71792 4.58317 5.41667C4.58317 3.11542 6.44984 1.25 8.74984 1.25ZM3.43734 15C3.37067 15.2663 3.33317 15.5454 3.33317 15.8333V16.6667H10.854C11.0841 16.6667 11.2706 16.8532 11.2706 17.0833V17.9167C11.2706 18.1468 11.0841 18.3333 10.854 18.3333H2.49984C2.0415 18.3333 1.6665 17.9604 1.6665 17.5V15.8333C1.6665 13.0721 3.904 10.8333 6.6665 10.8333H10.854C11.0841 10.8333 11.2706 11.0199 11.2706 11.25V12.0833C11.2706 12.3135 11.0841 12.5 10.854 12.5H6.6665C5.11234 12.5 3.80817 13.5625 3.43734 15ZM15.4165 11.6667C15.6466 11.6667 15.8332 11.8532 15.8332 12.0833V14.1667H17.9165C18.1466 14.1667 18.3332 14.3532 18.3332 14.5833V15.4167C18.3332 15.6468 18.1466 15.8333 17.9165 15.8333H15.8332V17.9167C15.8332 18.1468 15.6466 18.3333 15.4165 18.3333H14.5832C14.3531 18.3333 14.1665 18.1468 14.1665 17.9167V15.8333H12.0832C11.8531 15.8333 11.6665 15.6468 11.6665 15.4167V14.5833C11.6665 14.3532 11.8531 14.1667 12.0832 14.1667H14.1665V12.0833C14.1665 11.8532 14.3531 11.6667 14.5832 11.6667H15.4165Z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
|
||||
'app-dataset': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
|
|
@ -95,39 +141,32 @@ export const iconMap: any = {
|
|||
])
|
||||
}
|
||||
},
|
||||
|
||||
'app-team': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 1024 1024',
|
||||
viewBox: '0 0 20 20',
|
||||
version: '1.1',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M 824.2 699.9 c -25.4 -25.4 -54.7 -45.7 -86.4 -60.4 C 783.1 602.8 812 546.8 812 484 c 0 -110.8 -92.4 -201.7 -203.2 -200 c -109.1 1.7 -197 90.6 -197 200 c 0 62.8 29 118.8 74.2 155.5 c -31.7 14.7 -60.9 34.9 -86.4 60.4 C 345 754.6 314 826.8 312 903.8 c -0.1 4.5 3.5 8.2 8 8.2 h 56 c 4.3 0 7.9 -3.4 8 -7.7 c 1.9 -58 25.4 -112.3 66.7 -153.5 C 493.8 707.7 551.1 684 612 684 c 60.9 0 118.2 23.7 161.3 66.8 C 814.5 792 838 846.3 840 904.3 c 0.1 4.3 3.7 7.7 8 7.7 h 56 c 4.5 0 8.1 -3.7 8 -8.2 c -2 -77 -33 -149.2 -87.8 -203.9 Z M 612 612 c -34.2 0 -66.4 -13.3 -90.5 -37.5 c -24.5 -24.5 -37.9 -57.1 -37.5 -91.8 c 0.3 -32.8 13.4 -64.5 36.3 -88 c 24 -24.6 56.1 -38.3 90.4 -38.7 c 33.9 -0.3 66.8 12.9 91 36.6 c 24.8 24.3 38.4 56.8 38.4 91.4 c 0 34.2 -13.3 66.3 -37.5 90.5 c -24.2 24.2 -56.4 37.5 -90.6 37.5 Z M 361.5 510.4 c -0.9 -8.7 -1.4 -17.5 -1.4 -26.4 c 0 -15.9 1.5 -31.4 4.3 -46.5 c 0.7 -3.6 -1.2 -7.3 -4.5 -8.8 c -13.6 -6.1 -26.1 -14.5 -36.9 -25.1 c -25.8 -25.2 -39.7 -59.3 -38.7 -95.4 c 0.9 -32.1 13.8 -62.6 36.3 -85.6 c 24.7 -25.3 57.9 -39.1 93.2 -38.7 c 31.9 0.3 62.7 12.6 86 34.4 c 7.9 7.4 14.7 15.6 20.4 24.4 c 2 3.1 5.9 4.4 9.3 3.2 c 17.6 -6.1 36.2 -10.4 55.3 -12.4 c 5.6 -0.6 8.8 -6.6 6.3 -11.6 c -32.5 -64.3 -98.9 -108.7 -175.7 -109.9 c -110.9 -1.7 -203.3 89.2 -203.3 199.9 c 0 62.8 28.9 118.8 74.2 155.5 c -31.8 14.7 -61.1 35 -86.5 60.4 c -54.8 54.7 -85.8 126.9 -87.8 204 c -0.1 4.5 3.5 8.2 8 8.2 h 56.1 c 4.3 0 7.9 -3.4 8 -7.7 c 1.9 -58 25.4 -112.3 66.7 -153.5 c 29.4 -29.4 65.4 -49.8 104.7 -59.7 c 3.9 -1 6.5 -4.7 6 -8.7 Z',
|
||||
d: 'M7.08317 10C9.15424 10 10.8332 8.32107 10.8332 6.25C10.8332 4.17893 9.15424 2.5 7.08317 2.5C5.0121 2.5 3.33317 4.17893 3.33317 6.25C3.33317 8.32107 5.0121 10 7.08317 10Z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
'app-add-users': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 1024 1024',
|
||||
version: '1.1',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M 892 772 h -80 v -80 c 0 -4.4 -3.6 -8 -8 -8 h -48 c -4.4 0 -8 3.6 -8 8 v 80 h -80 c -4.4 0 -8 3.6 -8 8 v 48 c 0 4.4 3.6 8 8 8 h 80 v 80 c 0 4.4 3.6 8 8 8 h 48 c 4.4 0 8 -3.6 8 -8 v -80 h 80 c 4.4 0 8 -3.6 8 -8 v -48 c 0 -4.4 -3.6 -8 -8 -8 Z M 373.5 498.4 c -0.9 -8.7 -1.4 -17.5 -1.4 -26.4 c 0 -15.9 1.5 -31.4 4.3 -46.5 c 0.7 -3.6 -1.2 -7.3 -4.5 -8.8 c -13.6 -6.1 -26.1 -14.5 -36.9 -25.1 c -25.8 -25.2 -39.7 -59.3 -38.7 -95.4 c 0.9 -32.1 13.8 -62.6 36.3 -85.6 c 24.7 -25.3 57.9 -39.1 93.2 -38.7 c 31.9 0.3 62.7 12.6 86 34.4 c 7.9 7.4 14.7 15.6 20.4 24.4 c 2 3.1 5.9 4.4 9.3 3.2 c 17.6 -6.1 36.2 -10.4 55.3 -12.4 c 5.6 -0.6 8.8 -6.6 6.3 -11.6 c -32.5 -64.3 -98.9 -108.7 -175.7 -109.9 c -110.8 -1.7 -203.2 89.2 -203.2 200 c 0 62.8 28.9 118.8 74.2 155.5 c -31.8 14.7 -61.1 35 -86.5 60.4 c -54.8 54.7 -85.8 126.9 -87.8 204 c -0.1 4.5 3.5 8.2 8 8.2 h 56.1 c 4.3 0 7.9 -3.4 8 -7.7 c 1.9 -58 25.4 -112.3 66.7 -153.5 c 29.4 -29.4 65.4 -49.8 104.7 -59.7 c 3.8 -1.1 6.4 -4.8 5.9 -8.8 Z M 824 472 c 0 -109.4 -87.9 -198.3 -196.9 -200 C 516.3 270.3 424 361.2 424 472 c 0 62.8 29 118.8 74.2 155.5 c -31.7 14.7 -60.9 34.9 -86.4 60.4 C 357 742.6 326 814.8 324 891.8 c -0.1 4.5 3.5 8.2 8 8.2 h 56 c 4.3 0 7.9 -3.4 8 -7.7 c 1.9 -58 25.4 -112.3 66.7 -153.5 C 505.8 695.7 563 672 624 672 c 110.4 0 200 -89.5 200 -200 Z m -109.5 90.5 C 690.3 586.7 658.2 600 624 600 s -66.3 -13.3 -90.5 -37.5 C 509 538 495.7 505.4 496 470.7 c 0.3 -32.8 13.4 -64.5 36.3 -88 c 24 -24.6 56.1 -38.3 90.4 -38.7 c 33.9 -0.3 66.8 12.9 91 36.6 c 24.8 24.3 38.4 56.8 38.4 91.4 c -0.1 34.2 -13.4 66.3 -37.6 90.5 Z',
|
||||
d: 'M1.24984 18.3333C0.7896 18.3333 0.416504 17.9602 0.416504 17.5V15.8889C0.416504 13.0968 2.76035 10.8333 5.47333 10.8333H8.70065C11.4136 10.8333 13.7498 13.0968 13.7498 15.8889V17.5C13.7498 17.9602 13.3767 18.3333 12.9165 18.3333H1.24984Z',
|
||||
fill: 'currentColor'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M15.4165 17.5V17.2535C15.4165 15.3267 15.4165 13.3333 13.7498 12.0833C13.8196 12.0773 13.9366 12.0794 14.0491 12.0814C14.1036 12.0824 14.157 12.0833 14.2034 12.0833H15.8332C17.8679 12.0833 19.5832 13.3643 19.5832 15.4583V16.875C19.5832 17.2202 19.3033 17.5 18.9582 17.5H15.4165Z',
|
||||
fill: 'currentColor'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M14.5832 10.8333C15.9639 10.8333 17.0832 9.71405 17.0832 8.33333C17.0832 6.95262 15.9639 5.83333 14.5832 5.83333C13.2025 5.83333 12.0832 6.95262 12.0832 8.33333C12.0832 9.71405 13.2025 10.8333 14.5832 10.8333Z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@ import AppIcon from './icons/AppIcon.vue'
|
|||
import AppAvatar from './app-avatar/index.vue'
|
||||
import LoginLayout from './login-layout/index.vue'
|
||||
import LoginContainer from './login-container/index.vue'
|
||||
import LayoutContent from './content-container/LayoutContent.vue'
|
||||
import LayoutContainer from './layout-container/index.vue'
|
||||
import TagsInput from './tags-input/index.vue'
|
||||
import CardBox from './card-box/index.vue'
|
||||
import CardAdd from './card-add/index.vue'
|
||||
|
||||
|
||||
import BackButton from './back-button/index.vue'
|
||||
import AppTable from './app-table/index.vue'
|
||||
import ReadWrite from './read-write/index.vue'
|
||||
import TagEllipsis from './tag-ellipsis/index.vue'
|
||||
import CommonList from './common-list/index.vue'
|
||||
|
||||
export default {
|
||||
install(app: App) {
|
||||
|
|
@ -16,9 +19,14 @@ export default {
|
|||
app.component(AppAvatar.name, AppAvatar)
|
||||
app.component(LoginLayout.name, LoginLayout)
|
||||
app.component(LoginContainer.name, LoginContainer)
|
||||
app.component(LayoutContent.name, LayoutContent)
|
||||
app.component(LayoutContainer.name, LayoutContainer)
|
||||
app.component(TagsInput.name, TagsInput)
|
||||
app.component(CardBox.name, CardBox)
|
||||
app.component(CardAdd.name, CardAdd)
|
||||
app.component(BackButton.name, BackButton)
|
||||
app.component(AppTable.name, AppTable)
|
||||
app.component(ReadWrite.name, ReadWrite)
|
||||
app.component(TagEllipsis.name, TagEllipsis)
|
||||
app.component(CommonList.name, CommonList)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<div class="content-container">
|
||||
<div class="content-container__header flex align-center" v-if="slots.header || header">
|
||||
<back-button :to="backTo" v-if="showBack"></back-button>
|
||||
<h3>{{ header }}</h3>
|
||||
<slot name="header"> </slot>
|
||||
</div>
|
||||
|
||||
<div class="content-container__main">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, useSlots } from 'vue'
|
||||
defineOptions({ name: 'LayoutContainer' })
|
||||
const slots = useSlots()
|
||||
const props = defineProps({
|
||||
header: String || null,
|
||||
backTo: String
|
||||
})
|
||||
const showBack = computed(() => {
|
||||
const { backTo } = props
|
||||
return backTo
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scope>
|
||||
.content-container {
|
||||
transition: 0.3s;
|
||||
padding: 0 var(--app-view-padding) var(--app-view-padding);
|
||||
.content-container__header {
|
||||
box-sizing: border-box;
|
||||
padding: 16px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.content-container__main {
|
||||
background-color: var(--app-view-bg-color);
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
<div class="login-title">
|
||||
<div class="title flex-center">
|
||||
<div class="logo"></div>
|
||||
<div>{{ title || defaultTitle }}</div>
|
||||
<div class="app-logo-font">{{ title || defaultTitle }}</div>
|
||||
</div>
|
||||
<div class="sub-title" v-if="subTitle">{{ subTitle }}</div>
|
||||
</div>
|
||||
|
|
@ -27,8 +27,6 @@ defineProps({
|
|||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
color: #101010;
|
||||
height: 60px;
|
||||
.logo {
|
||||
background-image: url('@/assets/logo.png');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<div class="cursor">
|
||||
<slot name="read">
|
||||
<div class="flex align-center" v-if="!isEdit">
|
||||
<span>{{ data }}</span>
|
||||
<el-button @click.stop="editNameHandle" text v-if="showEditIcon">
|
||||
<el-icon><Edit /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</slot>
|
||||
<slot>
|
||||
<div class="flex align-center" v-if="isEdit">
|
||||
<div @click.stop>
|
||||
<el-input ref="inputRef" v-model="writeValue" placeholder="请输入" autofocus></el-input>
|
||||
</div>
|
||||
|
||||
<span class="ml-4">
|
||||
<el-button type="primary" text @click.stop="submit" :disabled="loading">
|
||||
<el-icon><Select /></el-icon>
|
||||
</el-button>
|
||||
</span>
|
||||
<span>
|
||||
<el-button text @click.stop="isEdit = false" :disabled="loading">
|
||||
<el-icon><CloseBold /></el-icon>
|
||||
</el-button>
|
||||
</span>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, nextTick } from 'vue'
|
||||
defineOptions({ name: 'ReadWrite' })
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showEditIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['change'])
|
||||
const inputRef = ref()
|
||||
const isEdit = ref(false)
|
||||
const writeValue = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
watch(isEdit, (bool) => {
|
||||
if (!bool) {
|
||||
writeValue.value = ''
|
||||
}
|
||||
})
|
||||
|
||||
function submit() {
|
||||
loading.value = true
|
||||
emit('change', writeValue.value)
|
||||
setTimeout(() => {
|
||||
isEdit.value = false
|
||||
loading.value = false
|
||||
}, 200)
|
||||
}
|
||||
function editNameHandle(row: any) {
|
||||
writeValue.value = props.data
|
||||
isEdit.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<el-tag class="tag-ellipsis flex-between mb-8" effect="plain" v-bind="$attrs">
|
||||
<el-tooltip
|
||||
:disabled="!isShowTooltip"
|
||||
effect="dark"
|
||||
:content="tooltipContent"
|
||||
placement="bottom"
|
||||
>
|
||||
<div ref="tagLabel">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</el-tag>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, useSlots } from 'vue'
|
||||
defineOptions({ name: 'TagEllipsis' })
|
||||
const slots = useSlots()
|
||||
const tooltipContent = slots.default()?.[0].children || ''
|
||||
const tagLabel = ref()
|
||||
const isShowTooltip = computed(() => {
|
||||
const containerWeight = tagLabel.value?.scrollWidth
|
||||
const contentWeight = tagLabel.value?.clientWidth
|
||||
if (containerWeight > contentWeight) {
|
||||
// 实际宽度 > 可视宽度
|
||||
return true
|
||||
} else {
|
||||
// 否则为不溢出
|
||||
return false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
// tag超出省略号
|
||||
.tag-ellipsis {
|
||||
border: 1px solid var(--el-border-color);
|
||||
color: var(--app-text-color);
|
||||
border-radius: 4px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0 9px;
|
||||
box-sizing: border-box;
|
||||
|
||||
:deep(.el-tag__content) {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
:deep(.el-tooltip__trigger) {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
:key="index"
|
||||
@close="removeTag(item)"
|
||||
closable
|
||||
class="mr-10"
|
||||
class="mr-8"
|
||||
>{{ item }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ import { TopBar, AppMain } from '../components'
|
|||
.app-main {
|
||||
height: calc(100vh - var(--app-header-height));
|
||||
padding: 0 !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.app-header {
|
||||
background-color: var(--app-header-bg-color);
|
||||
background: var(--app-header-bg-color);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ import { ref, onBeforeUpdate } from 'vue'
|
|||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const cachedViews: any = ref([])
|
||||
|
||||
const cachedViews: any = ref([])
|
||||
onBeforeUpdate(() => {
|
||||
let isCached = route.meta?.cache
|
||||
let name = route.name
|
||||
const { name, meta } = route
|
||||
let isCached = meta?.cache
|
||||
if (isCached && name && !cachedViews.value.includes(name)) {
|
||||
cachedViews.value.push(name)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@
|
|||
<div v-if="!menu.meta || !menu.meta.hidden" class="sidebar-item">
|
||||
<el-menu-item ref="subMenu" :index="menu.path" popper-class="sidebar-popper">
|
||||
<template #title>
|
||||
<AppIcon v-if="menu.meta && menu.meta.icon" :iconName="menu.meta.icon" />
|
||||
<AppIcon
|
||||
v-if="menu.meta && menu.meta.icon"
|
||||
:iconName="menu.meta.icon"
|
||||
class="sidebar-icon"
|
||||
/>
|
||||
<span v-if="menu.meta && menu.meta.title">{{ menu.meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
|
|
@ -19,9 +23,18 @@ defineProps<{
|
|||
|
||||
<style scoped lang="scss">
|
||||
.sidebar-item {
|
||||
.sidebar-icon {
|
||||
font-size: 20px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.el-menu-item {
|
||||
padding-left: 30px !important;
|
||||
padding: 13px 12px 13px 16px !important;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
background: none;
|
||||
color: var(--el-menu-active-color);
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item.is-active {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar p-8">
|
||||
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||
<el-menu :default-active="activeMenu">
|
||||
<el-menu :default-active="activeMenu" router>
|
||||
<sidebar-item
|
||||
:menu="menu"
|
||||
v-hasPermission="menu.meta?.permission"
|
||||
v-for="(menu, index) in subMenuList"
|
||||
:key="index"
|
||||
:menu="menu"
|
||||
>
|
||||
</sidebar-item>
|
||||
</el-menu>
|
||||
|
|
@ -21,16 +21,15 @@ import { getChildRouteListByPathAndName } from '@/router/index'
|
|||
import SidebarItem from './SidebarItem.vue'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const subMenuList = computed(() => {
|
||||
return getChildRouteListByPathAndName(route.path, route.name)
|
||||
const { meta } = route
|
||||
return getChildRouteListByPathAndName(meta.parentPath, meta.parentName)
|
||||
})
|
||||
|
||||
const activeMenu = computed(() => {
|
||||
const { meta, path } = route
|
||||
if (meta.activeMenu) {
|
||||
return meta.activeMenu
|
||||
}
|
||||
return path
|
||||
const { path, meta } = route
|
||||
return meta.active || path
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@
|
|||
class="input-item"
|
||||
:disabled="true"
|
||||
v-bind:modelValue="user.userInfo?.email"
|
||||
@change="() => {}"
|
||||
placeholder="请输入邮箱"
|
||||
>
|
||||
<template #prepend>
|
||||
|
|
@ -60,7 +59,7 @@
|
|||
</el-input>
|
||||
<el-button
|
||||
size="large"
|
||||
class="send-email-button ml-10"
|
||||
class="send-email-button ml-16"
|
||||
@click="sendEmail"
|
||||
:loading="loading"
|
||||
>获取验证码</el-button
|
||||
|
|
|
|||
|
|
@ -1,6 +1,15 @@
|
|||
<template>
|
||||
<el-dropdown trigger="click" type="primary">
|
||||
<AppAvatar :name="user.userInfo?.username" />
|
||||
<div class="flex-center cursor">
|
||||
<AppAvatar>
|
||||
<img src="@/assets/user-icon.svg" style="width: 54%" alt="" />
|
||||
</AppAvatar>
|
||||
<span class="ml-8">{{ user.userInfo?.username }}</span>
|
||||
<el-icon class="el-icon--right">
|
||||
<CaretBottom />
|
||||
</el-icon>
|
||||
</div>
|
||||
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="openResetPassword">
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
<template>
|
||||
<div class="top-bar-container flex-between border-b">
|
||||
<div class="top-bar-container border-b flex-between">
|
||||
<div class="flex-center h-full">
|
||||
<div class="app-title-container flex-center">
|
||||
<div class="app-title-icon"></div>
|
||||
<div class="app-title-text ml-10">{{ defaultTitle }}</div>
|
||||
<div class="app-title-text app-logo-font">{{ defaultTitle }}</div>
|
||||
</div>
|
||||
<el-divider direction="vertical" class="line" />
|
||||
<TopMenu></TopMenu>
|
||||
</div>
|
||||
<div class="avatar">
|
||||
|
|
@ -25,18 +24,16 @@ const defaultTitle = import.meta.env.VITE_APP_TITLE
|
|||
padding: var(--app-header-padding);
|
||||
|
||||
.app-title-container {
|
||||
margin-right: 20px;
|
||||
margin-right: 45px;
|
||||
.app-title-icon {
|
||||
background-image: url('@/assets/logo.png');
|
||||
background-size: 100% 100%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.app-title-text {
|
||||
color: var(--el-color-primary);
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
.line {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
:class="isActive ? 'active' : ''"
|
||||
@click="router.push({ name: menu.name })"
|
||||
>
|
||||
<div class="icon">
|
||||
<!-- <div class="icon">
|
||||
<AppIcon :iconName="menu.meta ? (menu.meta.icon as string) : '404'" />
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="title">{{ menu.meta?.title }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -15,21 +15,22 @@ import { useRouter, useRoute, type RouteRecordRaw } from 'vue-router'
|
|||
import { computed } from 'vue'
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const props = defineProps<{
|
||||
menu: RouteRecordRaw
|
||||
}>()
|
||||
|
||||
const isActive = computed(() => {
|
||||
return (
|
||||
(route.name == props.menu.name && route.path == props.menu.path) ||
|
||||
route?.meta?.activeMenu == props.menu.path
|
||||
)
|
||||
const { name, path, meta } = route
|
||||
return (name == props.menu.name && path == props.menu.path) || meta?.activeMenu == props.menu.path
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.menu-item-container {
|
||||
padding: 0 20px;
|
||||
margin-right: 28px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
.icon {
|
||||
font-size: 15px;
|
||||
margin-right: 5px;
|
||||
|
|
@ -41,9 +42,15 @@ const isActive = computed(() => {
|
|||
}
|
||||
|
||||
.active {
|
||||
font-weight: 600;
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border-bottom: 3px solid var(--el-color-primary);
|
||||
&::after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
content: '';
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border-bottom: 3px solid var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,5 @@ const topMenuList = computed(() => {
|
|||
})
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.top-menu-container {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<template>
|
||||
<div class="main-layout h-full flex">
|
||||
<div class="sidebar-container border-r"><Sidebar /></div>
|
||||
<div class="sidebar-container">
|
||||
<Sidebar />
|
||||
</div>
|
||||
<div class="view-container">
|
||||
<AppMain />
|
||||
</div>
|
||||
|
|
@ -19,6 +21,6 @@ import { Sidebar, AppMain } from '../components'
|
|||
background-color: var(--sidebar-bg-color);
|
||||
}
|
||||
.view-container {
|
||||
width: 100%;
|
||||
width: calc(100% - var(--sidebar-width));
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -149,8 +149,8 @@ export const put: (
|
|||
params?: unknown,
|
||||
data?: unknown,
|
||||
loading?: NProgress | Ref<boolean>
|
||||
) => Promise<Result<any>> = (url, params, data, loading) => {
|
||||
return promise(request({ url: url, method: 'put', params, data }), loading)
|
||||
) => Promise<Result<any>> = (url, data, params, loading) => {
|
||||
return promise(request({ url: url, method: 'put', data, params }), loading)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ router.beforeEach(
|
|||
}
|
||||
)
|
||||
|
||||
export const getChildRouteListByPathAndName = (path: string, name?: RouteRecordName | null | undefined) => {
|
||||
export const getChildRouteListByPathAndName = (path: any, name?: RouteRecordName | any) => {
|
||||
return getChildRouteList(routes, path, name)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,68 @@
|
|||
import Layout from '@/layout/main-layout/index.vue'
|
||||
const applicationRouter = {
|
||||
path: '/app',
|
||||
name: 'app',
|
||||
path: '/application',
|
||||
name: 'application',
|
||||
meta: { icon: 'app-applicaiton', title: '应用', permission: 'APPLICATION:READ' },
|
||||
component: () => import('@/views/app/index.vue')
|
||||
redirect: '/application',
|
||||
children: [
|
||||
{
|
||||
path: '/application',
|
||||
name: 'application',
|
||||
component: () => import('@/views/application/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/application/create', // create
|
||||
name: 'CreateApplication',
|
||||
meta: { activeMenu: '/application' },
|
||||
component: () => import('@/views/application/CreateApplication.vue'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/application/:appId',
|
||||
name: 'ApplicationDetail',
|
||||
meta: { title: '应用详情', activeMenu: '/application' },
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: 'overview',
|
||||
name: 'AppOverview',
|
||||
meta: {
|
||||
icon: 'Document',
|
||||
title: '概览',
|
||||
active: 'overview',
|
||||
parentPath: '/application/:appId',
|
||||
parentName: 'ApplicationDetail'
|
||||
},
|
||||
component: () => import('@/views/application/AppOverview.vue')
|
||||
},
|
||||
{
|
||||
path: 'setting',
|
||||
name: 'AppSetting',
|
||||
meta: {
|
||||
icon: 'Setting',
|
||||
title: '设置',
|
||||
active: 'setting',
|
||||
parentPath: '/application/:appId',
|
||||
parentName: 'ApplicationDetail'
|
||||
},
|
||||
component: () => import('@/views/application/AppSetting.vue')
|
||||
},
|
||||
{
|
||||
path: 'dialog',
|
||||
name: 'DialogLog',
|
||||
meta: {
|
||||
icon: 'Setting',
|
||||
title: '对话日志',
|
||||
active: 'dialog',
|
||||
parentPath: '/application/:appId',
|
||||
parentName: 'ApplicationDetail'
|
||||
},
|
||||
component: () => import('@/views/application/DialogLog.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default applicationRouter
|
||||
|
|
|
|||
|
|
@ -11,27 +11,51 @@ const datasetRouter = {
|
|||
component: () => import('@/views/dataset/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/dataset/create',
|
||||
path: '/dataset/:type', // create 或者 upload
|
||||
name: 'CreateDataset',
|
||||
meta: { activeMenu: '/dataset' },
|
||||
component: () => import('@/views/dataset/CreateDataset.vue'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/dataset/doc',
|
||||
name: 'DatasetDoc',
|
||||
meta: { icon: 'House', title: '文档', activeMenu: '/dataset' },
|
||||
path: '/dataset/:datasetId',
|
||||
name: 'DatasetDetail',
|
||||
meta: { title: '文档', activeMenu: '/dataset' },
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
redirect: '/dataset/doc',
|
||||
children: [
|
||||
{
|
||||
path: '/dataset/doc',
|
||||
name: 'DatasetDoc',
|
||||
meta: { icon: 'House', title: '文档' },
|
||||
component: () => import('@/views/dataset/DatasetDoc.vue')
|
||||
path: 'document',
|
||||
name: 'Document',
|
||||
meta: {
|
||||
icon: 'Document',
|
||||
title: '文档',
|
||||
active: 'document',
|
||||
parentPath: '/dataset/:datasetId',
|
||||
parentName: 'DatasetDetail'
|
||||
},
|
||||
component: () => import('@/views/document/index.vue')
|
||||
},
|
||||
{
|
||||
path: 'setting',
|
||||
name: 'DatasetSetting',
|
||||
meta: {
|
||||
icon: 'Setting',
|
||||
title: '设置',
|
||||
active: 'setting',
|
||||
parentPath: '/dataset/:datasetId',
|
||||
parentName: 'DatasetDetail'
|
||||
},
|
||||
component: () => import('@/views/document/DatasetSetting.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/dataset/:datasetId/:documentId', // 分段详情
|
||||
name: 'Paragraph',
|
||||
meta: { activeMenu: '/dataset' },
|
||||
component: () => import('@/views/paragraph/index.vue'),
|
||||
hidden: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,25 @@ const settingRouter = {
|
|||
{
|
||||
path: '/setting',
|
||||
name: 'setting',
|
||||
meta: { icon: 'app-team', title: '团队管理' },
|
||||
meta: {
|
||||
icon: 'app-team',
|
||||
title: '团队管理',
|
||||
parentPath: '/setting',
|
||||
parentName: 'setting'
|
||||
},
|
||||
component: () => import('@/views/setting/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/template',
|
||||
name: 'template',
|
||||
meta: {
|
||||
icon: 'app-team',
|
||||
title: '模版管理',
|
||||
activeMenu: '/setting',
|
||||
parentPath: '/setting',
|
||||
parentName: 'setting'
|
||||
},
|
||||
component: () => import('@/views/template/index.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export const routes: Array<RouteRecordRaw> = [
|
|||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('@/layout/app-layout/index.vue'),
|
||||
redirect: '/setting',
|
||||
redirect: '/dataset',
|
||||
children: [
|
||||
// {
|
||||
// path: '/first',
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@ import { createPinia } from 'pinia'
|
|||
const store = createPinia()
|
||||
export { store }
|
||||
import useUserStore from './modules/user'
|
||||
import useDatasetStore from './modules/dataset'
|
||||
import useParagraphStore from './modules/paragraph'
|
||||
|
||||
const useStore = () => ({
|
||||
user: useUserStore()
|
||||
user: useUserStore(),
|
||||
dataset: useDatasetStore(),
|
||||
paragraph: useParagraphStore(),
|
||||
})
|
||||
|
||||
export default useStore
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import type { datasetData } from '@/api/type/dataset'
|
||||
import type { UploadUserFile } from 'element-plus'
|
||||
|
||||
export interface datasetStateTypes {
|
||||
baseInfo: datasetData | null
|
||||
documentsFiles: UploadUserFile[]
|
||||
}
|
||||
|
||||
const useDatasetStore = defineStore({
|
||||
id: 'dataset',
|
||||
state: (): datasetStateTypes => ({
|
||||
baseInfo: null,
|
||||
documentsFiles: []
|
||||
}),
|
||||
actions: {
|
||||
saveBaseInfo(info: datasetData | null) {
|
||||
this.baseInfo = info
|
||||
},
|
||||
saveDocumentsFile(file: UploadUserFile[]) {
|
||||
this.documentsFiles = file
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default useDatasetStore
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import datasetApi from '@/api/dataset'
|
||||
|
||||
const useParagraphStore = defineStore({
|
||||
id: 'paragraph',
|
||||
state: () => ({}),
|
||||
actions: {
|
||||
async asyncPutParagraph(datasetId: string, documentId: string, paragraphId: string, data: any) {
|
||||
return new Promise((resolve, reject) => {
|
||||
datasetApi
|
||||
.putParagraph(datasetId, documentId, paragraphId, data)
|
||||
.then((data) => {
|
||||
resolve(data)
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default useParagraphStore
|
||||
|
|
@ -2,14 +2,14 @@ import { defineStore } from 'pinia'
|
|||
import type { User } from '@/api/type/user'
|
||||
import UserApi from '@/api/user'
|
||||
|
||||
export interface appStateTypes {
|
||||
export interface userStateTypes {
|
||||
userInfo: User | null
|
||||
token: any
|
||||
}
|
||||
|
||||
const useUserStore = defineStore({
|
||||
id: 'user',
|
||||
state: (): appStateTypes => ({
|
||||
state: (): userStateTypes => ({
|
||||
userInfo: null,
|
||||
token: ''
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -9,14 +9,17 @@ html {
|
|||
}
|
||||
|
||||
body {
|
||||
font-size: 14px;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-family: 'PingFang SC', 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei',
|
||||
'微软雅黑', Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--app-text-color);
|
||||
}
|
||||
|
||||
#app {
|
||||
|
|
@ -58,7 +61,6 @@ ul {
|
|||
// 滑块
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 5px;
|
||||
background-color: var(--ce-webkit-scrollbar-background-color, rgba(31, 35, 41, 0.3));
|
||||
}
|
||||
|
||||
// 轨道
|
||||
|
|
@ -73,6 +75,7 @@ h1 {
|
|||
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h3 {
|
||||
|
|
@ -83,38 +86,66 @@ h4 {
|
|||
font-size: 16px;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
.lighter {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
.w-240 {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.mt-8 {
|
||||
margin-top: 8px;
|
||||
margin-top: var(--app-base-px);
|
||||
}
|
||||
.mt-10 {
|
||||
margin-top: 10px;
|
||||
.mt-12 {
|
||||
margin-top: calc(var(--app-base-px) + 4px);
|
||||
}
|
||||
.mt-16 {
|
||||
margin-top: calc(var(--app-base-px) * 2);
|
||||
}
|
||||
.mb-8 {
|
||||
margin-bottom: var(--app-base-px);
|
||||
}
|
||||
.mb-16 {
|
||||
margin-bottom: calc(var(--app-base-px) * 2);
|
||||
}
|
||||
|
||||
.ml-10 {
|
||||
margin-left: 10px;
|
||||
.ml-4 {
|
||||
margin-left: calc(var(--app-base-px) - 4px);
|
||||
}
|
||||
.ml-8 {
|
||||
margin-left: var(--app-base-px);
|
||||
}
|
||||
.ml-16 {
|
||||
margin-left: 16px;
|
||||
margin-left: calc(var(--app-base-px) * 2);
|
||||
}
|
||||
.mr-10 {
|
||||
margin-right: 10px;
|
||||
.mr-8 {
|
||||
margin-right: var(--app-base-px);
|
||||
}
|
||||
.mb-10 {
|
||||
margin-bottom: 10px;
|
||||
.mr-12 {
|
||||
margin-right: calc(var(--app-base-px) + 4px);
|
||||
}
|
||||
.mb-20 {
|
||||
margin-bottom: 20px;
|
||||
.mr-16 {
|
||||
margin-right: calc(var(--app-base-px) * 2);
|
||||
}
|
||||
|
||||
.p-15 {
|
||||
padding: 15px;
|
||||
.p-8 {
|
||||
padding: var(--app-base-px);
|
||||
}
|
||||
.p-16 {
|
||||
padding: calc(var(--app-base-px) * 2);
|
||||
}
|
||||
.p-24 {
|
||||
padding: calc(var(--app-base-px) * 3);
|
||||
}
|
||||
|
||||
.flex {
|
||||
|
|
@ -144,6 +175,14 @@ h4 {
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
.vertical-middle {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.border-l {
|
||||
border-left: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
.border-b {
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
|
@ -172,5 +211,84 @@ h4 {
|
|||
|
||||
// 内容部分 自适应高度
|
||||
.main-calc-height {
|
||||
height: calc(100vh - 125px);
|
||||
height: var(--app-main-height);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 标题前带竖线样式
|
||||
.title-decoration-1 {
|
||||
position: relative;
|
||||
padding-left: 12px;
|
||||
&:before {
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 2px;
|
||||
height: 80%;
|
||||
content: '';
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.app-logo-font {
|
||||
background: var(--app-logo-color);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
font-family: Arial Black;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
// tag
|
||||
.default-tag {
|
||||
background: var(--tag-deflaut-bg);
|
||||
color: var(--tag-deflaut-color);
|
||||
}
|
||||
|
||||
// card 无边框无阴影
|
||||
.card-never {
|
||||
background: var(--app-layout-bg-color);
|
||||
border: none;
|
||||
}
|
||||
|
||||
// 图标旋转90度
|
||||
.rotate-90 {
|
||||
transform: rotateZ(90deg);
|
||||
}
|
||||
|
||||
// 表格第一行插入自定义行
|
||||
.table-quick-append {
|
||||
.el-table__append-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
border-bottom: var(--el-table-border);
|
||||
width: 100%;
|
||||
height: 49px;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
.el-table__body {
|
||||
margin-top: 49px;
|
||||
}
|
||||
}
|
||||
|
||||
.success {
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
.danger {
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
.warning {
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
.primary {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,28 @@
|
|||
:root {
|
||||
--el-color-primary: #3370ff;
|
||||
--el-color-primary-light-9: rgba(51, 112, 255, 0.1);
|
||||
--el-menu-item-height: 45px;
|
||||
--el-text-color-primary: '#1F2329';
|
||||
--el-box-shadow-light: 0px 2px 4px 0px rgba(31, 35, 41, 0.12);
|
||||
--el-border-color: #dee0e3;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
padding: 5px 12px;
|
||||
&.is-text {
|
||||
padding: 4px !important;
|
||||
font-size: 16px;
|
||||
max-height: 24px;
|
||||
&:not(.is-disabled):hover {
|
||||
background: var(--app-text-color-light-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-avatar {
|
||||
--el-avatar-bg-color: var(--el-color-primary);
|
||||
--el-avatar-size-small: 33px;
|
||||
--el-avatar-border-radius: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.el-popper {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.el-form {
|
||||
--el-form-inline-content-width: 100%;
|
||||
|
|
@ -26,28 +39,20 @@
|
|||
}
|
||||
|
||||
.el-message-box {
|
||||
padding-bottom: 24px;
|
||||
.app-confirm {
|
||||
.app-confirm-title {
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
.app-confirm-decription {
|
||||
margin-left: 40px;
|
||||
}
|
||||
--el-messagebox-font-size: 16px;
|
||||
padding: 24px;
|
||||
.el-message-box__header {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.el-message-box__content {
|
||||
padding: 24px;
|
||||
color: var(--el-text-color-primary);
|
||||
padding: 24px 0;
|
||||
color: var(--app-text-color);
|
||||
font-weight: 400;
|
||||
}
|
||||
.el-message-box__btns {
|
||||
padding: 5px 24px 0;
|
||||
padding: 0;
|
||||
button {
|
||||
min-width: 80px;
|
||||
&:nth-child(2) {
|
||||
|
|
@ -57,46 +62,122 @@
|
|||
button.danger {
|
||||
background: var(--el-color-danger);
|
||||
border: var(--el-color-danger);
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
.el-message-box__headerbtn {
|
||||
right: -5px;
|
||||
top: -5px;
|
||||
.el-message-box__close {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1200px) {
|
||||
.el-col-lg-5 {
|
||||
display: block;
|
||||
max-width: 25%;
|
||||
flex: 0 0 25%;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 1400px) {
|
||||
.el-col-lg-5 {
|
||||
display: block;
|
||||
max-width: 20.8333333333%;
|
||||
flex: 0 0 20.8333333333%;
|
||||
.app-list-row {
|
||||
.el-col-lg-6 {
|
||||
display: block;
|
||||
max-width: 20.8333333333%;
|
||||
flex: 0 0 20.8333333333%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 1920px) {
|
||||
.el-col-xl-4 {
|
||||
display: block;
|
||||
max-width: 16.6666666667%;
|
||||
flex: 0 0 16.6666666667%;
|
||||
.app-list-row {
|
||||
.el-col-xl-4 {
|
||||
display: block;
|
||||
max-width: 16.6666666667%;
|
||||
flex: 0 0 16.6666666667%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 抽屉样式整体修改
|
||||
.el-drawer {
|
||||
.el-drawer__header {
|
||||
padding: 0;
|
||||
margin: 0 24px;
|
||||
height: 56px;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
.el-drawer__title {
|
||||
color: #1f2329;
|
||||
.el-card {
|
||||
--el-card-border-radius: 8px;
|
||||
--el-card-padding: calc(var(--app-base-px) * 2);
|
||||
}
|
||||
.el-dropdown {
|
||||
color: var(--app-text-color);
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
--el-tag-border-radius: 2px;
|
||||
}
|
||||
.el-table {
|
||||
--el-table-header-bg-color: var(--app-layout-bg-color);
|
||||
--el-table-text-color: var(--app-text-color);
|
||||
font-weight: 400;
|
||||
thead {
|
||||
color: var(--app-text-color-secondary);
|
||||
th {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
.el-drawer__body {
|
||||
--el-drawer-padding-primary: 24px;
|
||||
|
||||
th.el-table__cell {
|
||||
border-top: var(--el-table-border);
|
||||
}
|
||||
.el-table__cell {
|
||||
padding: 12px 0;
|
||||
}
|
||||
.el-checkbox {
|
||||
height: 23px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-pagination .el-select .el-input {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
// el-steps
|
||||
.el-step__icon {
|
||||
background: none;
|
||||
}
|
||||
.el-step__head.is-process {
|
||||
.el-step__icon {
|
||||
&.is-text {
|
||||
color: #ffffff;
|
||||
border-color: var(--el-color-primary) !important;
|
||||
background: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-text {
|
||||
font-weight: 400;
|
||||
&.el-text--info {
|
||||
color: var(--app-text-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.el-switch {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.el-slider {
|
||||
--el-slider-button-size: 14px;
|
||||
--el-slider-height: 4px;
|
||||
}
|
||||
.el-slider__button {
|
||||
border: solid 1px var(--app-border-color-dark);
|
||||
&.hover {
|
||||
border: solid 2px var(--el-slider-main-bg-color);
|
||||
}
|
||||
}
|
||||
.el-slider__runway.show-input {
|
||||
margin-right: calc(var(--app-base-px) + 4px);
|
||||
}
|
||||
.el-slider__input {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.input-with-select {
|
||||
.el-input__wrapper {
|
||||
// border: 1px solid var(--el-border-color);
|
||||
// box-shadow: none!important;
|
||||
}
|
||||
|
||||
.el-input-group__prepend {
|
||||
background-color: var(--el-fill-color-blank);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,39 @@
|
|||
:root {
|
||||
--el-color-primary: rgba(51, 112, 255, 1);
|
||||
--app-layout-bg-color: #f3f5f6;
|
||||
--app-base-text-color: rgba(31, 35, 41, 1);
|
||||
--app-view-padding: 15px;
|
||||
--app-base-px: 8px;
|
||||
--app-layout-bg-color: #f5f6f7;
|
||||
--app-text-color: #1f2329;
|
||||
--app-text-color-light-1: rgba(31, 35, 41, 0.1);
|
||||
--app-text-color-secondary: #646a73;
|
||||
--app-text-color-disable: #bbbfc4;
|
||||
--app-view-padding: 24px;
|
||||
--app-view-bg-color: #ffffff;
|
||||
--hover-bg-color: #fafafa;
|
||||
--app-border-color-dark: #bbbfc4;
|
||||
|
||||
/** header 组件 */
|
||||
--app-header-height: 56px;
|
||||
--app-header-padding: 0 20px;
|
||||
--app-header-bg-color: #ffffff;
|
||||
--app-header-bg-color: linear-gradient(90deg, #ebf1ff 24.34%, #e5fbf8 56.18%, #f2ebfe 90.18%);
|
||||
--app-logo-color: linear-gradient(180deg, #3370ff 0%, #7f3bf5 100%);
|
||||
|
||||
// 计算高度
|
||||
--app-main-height: calc(100vh - var(--app-header-height) - var(--app-view-padding) * 2 - 40px);
|
||||
|
||||
/** sidebar 组件 */
|
||||
--sidebar-bg-color: #ffffff;
|
||||
--sidebar-width: 198px;
|
||||
--sidebar-width: 240px;
|
||||
/** tag */
|
||||
--tag-deflaut-bg: rgba(51, 112, 255, 0.2);
|
||||
--tag-deflaut-color: #2b5fd9;
|
||||
/** card */
|
||||
--card-width: 330px;
|
||||
--card-min-height: 160px;
|
||||
--card-min-width: 220px;
|
||||
|
||||
--team-manage-left-width : 280px;
|
||||
/** setting */
|
||||
--setting-left-width: 280px;
|
||||
|
||||
/** dataset */
|
||||
--create-dataset-height: calc(
|
||||
100vh - var(--app-header-height) - var(--app-view-padding) * 2 - 70px
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,20 +43,30 @@ export const MsgError = (message: string) => {
|
|||
* @param 参数 message: {title, decription,type}
|
||||
*/
|
||||
|
||||
export const MsgConfirm = ({ title, decription }: any, options?: any) => {
|
||||
const message: any = h('div', { class: 'app-confirm' }, [
|
||||
h('h4', { class: 'app-confirm-title flex align-center' }, [
|
||||
h(ElIcon, { class: 'icon' }, [h(WarningFilled)]),
|
||||
h('span', { class: 'ml-16' }, title)
|
||||
]),
|
||||
h('div', { class: 'app-confirm-decription mt-8' }, decription)
|
||||
])
|
||||
|
||||
export const MsgConfirm = (title: string, decription: string, options?: any) => {
|
||||
const defaultOptions: Object = {
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
...options
|
||||
}
|
||||
return ElMessageBox({ message, ...defaultOptions })
|
||||
return ElMessageBox.confirm(decription, title, defaultOptions)
|
||||
}
|
||||
|
||||
// export const MsgConfirm = ({ title, decription }: any, options?: any) => {
|
||||
// const message: any = h('div', { class: 'app-confirm' }, [
|
||||
// h('h4', { class: 'app-confirm-title flex align-center' }, [
|
||||
// h(ElIcon, { class: 'icon' }, [h(WarningFilled)]),
|
||||
// h('span', { class: 'ml-16' }, title)
|
||||
// ]),
|
||||
// h('div', { class: 'app-confirm-decription mt-8' }, decription)
|
||||
// ])
|
||||
|
||||
// const defaultOptions: Object = {
|
||||
// showCancelButton: true,
|
||||
// confirmButtonText: '确定',
|
||||
// cancelButtonText: '取消',
|
||||
// ...options
|
||||
// }
|
||||
// return ElMessageBox({ message, ...defaultOptions })
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
const getCheckDate = (timestamp: any) => {
|
||||
if (!timestamp) return false
|
||||
const dt = new Date(timestamp)
|
||||
if (isNaN(dt.getTime())) return false
|
||||
return dt
|
||||
}
|
||||
export const datetimeFormat = (timestamp: any) => {
|
||||
const dt = getCheckDate(timestamp)
|
||||
if (!dt) return timestamp
|
||||
|
||||
const y = dt.getFullYear()
|
||||
const m = (dt.getMonth() + 1 + '').padStart(2, '0')
|
||||
const d = (dt.getDate() + '').padStart(2, '0')
|
||||
const hh = (dt.getHours() + '').padStart(2, '0')
|
||||
const mm = (dt.getMinutes() + '').padStart(2, '0')
|
||||
const ss = (dt.getSeconds() + '').padStart(2, '0')
|
||||
|
||||
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
|
||||
}
|
||||
|
||||
export const dateFormat = (timestamp: any) => {
|
||||
const dt = getCheckDate(timestamp)
|
||||
if (!dt) return timestamp
|
||||
|
||||
const y = dt.getFullYear()
|
||||
const m = (dt.getMonth() + 1 + '').padStart(2, '0')
|
||||
const d = (dt.getDate() + '').padStart(2, '0')
|
||||
|
||||
return `${y}-${m}-${d}`
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
export function toThousands(num: any) {
|
||||
return num?.toString().replace(/\d+/, function (n: any) {
|
||||
return n.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
|
||||
})
|
||||
}
|
||||
export function numberFormat(num: number) {
|
||||
return num < 1000 ? toThousands(num) : toThousands((num / 1000).toFixed(1)) + 'k'
|
||||
}
|
||||
|
||||
export function filesize(size: number) {
|
||||
if (!size) return ''
|
||||
const num = 1024.0 //byte
|
||||
|
||||
if (size < num) return size + 'B'
|
||||
if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + 'K' //kb
|
||||
if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + 'M' //M
|
||||
if (size < Math.pow(num, 4)) return (size / Math.pow(num, 3)).toFixed(2) + 'G' //G
|
||||
return (size / Math.pow(num, 4)).toFixed(2) + 'T' //T
|
||||
}
|
||||
|
||||
// 获取文件后缀
|
||||
export function fileType(name: string) {
|
||||
const suffix = name.split('.')
|
||||
return suffix[suffix.length - 1]
|
||||
}
|
||||
|
||||
// 获得文件对应图片
|
||||
export function getImgUrl(name: string) {
|
||||
const type = fileType(name) || 'txt'
|
||||
return `/src/assets/${type}-icon.svg`
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ const router = useRouter()
|
|||
}
|
||||
|
||||
.message-container {
|
||||
color: var(--app-base-text-color);
|
||||
color: var(--app-text-color);
|
||||
|
||||
.title {
|
||||
font-size: 50px;
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
<template >
|
||||
<div>
|
||||
app
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<div>dataset 文档</div>
|
||||
<div>
|
||||
概览
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<div>
|
||||
设置
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<div>
|
||||
创建应用
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<div>
|
||||
对话日志
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
<template>
|
||||
<div class="application-list-container p-24">
|
||||
<div class="flex-between">
|
||||
<h3>应用</h3>
|
||||
<el-input
|
||||
v-model="pageConfig.name"
|
||||
@change="search"
|
||||
placeholder="按 名称 搜索"
|
||||
prefix-icon="Search"
|
||||
class="w-240"
|
||||
/>
|
||||
</div>
|
||||
<div v-loading.fullscreen.lock="loading">
|
||||
<el-row
|
||||
:gutter="15"
|
||||
v-infinite-scroll="loadDataset"
|
||||
:infinite-scroll-disabled="disabledScroll"
|
||||
class="app-list-row"
|
||||
>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mt-8">
|
||||
<CardAdd title="创建应用" @click="router.push({ path: '/application/create' })" />
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mt-8">
|
||||
<CardBox title="应用" @click="router.push({ path: '/application/1/overview' })" />
|
||||
</el-col>
|
||||
<!-- <el-col
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="8"
|
||||
:lg="6"
|
||||
:xl="4"
|
||||
v-for="(item, index) in applicationList"
|
||||
:key="index"
|
||||
class="mt-8"
|
||||
>
|
||||
<CardBox
|
||||
:title="item.name"
|
||||
:description="item.desc"
|
||||
class="cursor"
|
||||
@click="router.push({ path: `/dataset/${item.id}/document` })"
|
||||
>
|
||||
<template #mouseEnter>
|
||||
<el-tooltip effect="dark" content="删除" placement="top">
|
||||
<el-button text @click.stop="deleteDateset(item)" class="delete-button">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="footer-content">
|
||||
<span class="bold">{{ item?.document_count || 0 }}</span>
|
||||
文档<el-divider direction="vertical" />
|
||||
<span class="bold">{{ numberFormat(item?.char_length) || 0 }}</span>
|
||||
字符<el-divider direction="vertical" />
|
||||
<span class="bold">{{ item?.char_length || 0 }}</span>
|
||||
关联应用
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import datasetApi from '@/api/dataset'
|
||||
import type { datasetListRequest } from '@/api/type/dataset'
|
||||
// import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import { useRouter } from 'vue-router'
|
||||
// import { numberFormat } from '@/utils/utils'
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(false)
|
||||
const disabledScroll = ref(false)
|
||||
const pageConfig = reactive<datasetListRequest>({
|
||||
current_page: 1,
|
||||
page_size: 20,
|
||||
name: ''
|
||||
})
|
||||
|
||||
const applicationList = ref<any[]>([])
|
||||
|
||||
function loadDataset() {}
|
||||
|
||||
function search() {
|
||||
pageConfig.current_page = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// function deleteDateset(row: any) {
|
||||
// MsgConfirm(
|
||||
// `是否删除数据集:${row.name} ?`,
|
||||
// `此数据集关联 ${row.char_length} 个应用,删除后无法恢复,请谨慎操作。`,
|
||||
// {
|
||||
// confirmButtonText: '删除',
|
||||
// confirmButtonClass: 'danger'
|
||||
// }
|
||||
// )
|
||||
// .then(() => {
|
||||
// loading.value = true
|
||||
// datasetApi
|
||||
// .delDateset(row.id)
|
||||
// .then(() => {
|
||||
// MsgSuccess('删除成功')
|
||||
// getList()
|
||||
// })
|
||||
// .catch(() => {
|
||||
// loading.value = false
|
||||
// })
|
||||
// })
|
||||
// .catch(() => {})
|
||||
// }
|
||||
|
||||
function getList() {
|
||||
loading.value = true
|
||||
datasetApi
|
||||
.getDateset(pageConfig)
|
||||
.then((res) => {
|
||||
applicationList.value = res.data?.records
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// getList()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
// .dataset-list-container {
|
||||
// .delete-button {
|
||||
// position: absolute;
|
||||
// right: 12px;
|
||||
// top: 18px;
|
||||
// height: auto;
|
||||
// }
|
||||
// .footer-content {
|
||||
// .bold {
|
||||
// color: var(--app-text-color);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
|
||||
|
|
@ -1,21 +1,181 @@
|
|||
<template>
|
||||
<LayoutContent header="创建数据集">
|
||||
<el-steps :active="active" finish-status="success">
|
||||
<el-step title="步骤 1"></el-step>
|
||||
<el-step title="步骤 2"></el-step>
|
||||
<el-step title="步骤 3"></el-step>
|
||||
</el-steps>
|
||||
|
||||
<el-button style="margin-top: 12px" @click="next">下一步</el-button>
|
||||
</LayoutContent>
|
||||
<LayoutContainer header="创建数据集" back-to="-1" class="create-dataset">
|
||||
<template #header>
|
||||
<el-steps :active="active" finish-status="success" align-center class="create-dataset__steps">
|
||||
<el-step v-for="(item, index) in steps" :key="index">
|
||||
<template #icon>
|
||||
<div class="app-step flex align-center">
|
||||
<div class="el-step__icon is-text">
|
||||
<div class="el-step__icon-inner">
|
||||
<el-icon v-if="active == index + 1" style="margin-top: 1px"><Select /></el-icon>
|
||||
<span v-else> {{ index + 1 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="ml-4">{{ item.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-step>
|
||||
</el-steps>
|
||||
</template>
|
||||
<div class="create-dataset__main flex" v-loading="loading">
|
||||
<div class="create-dataset__component main-calc-height">
|
||||
<template v-if="steps[active]?.component">
|
||||
<component :is="steps[active].component" :ref="steps[active]?.ref" />
|
||||
</template>
|
||||
<template v-else-if="active === 2">
|
||||
<el-result icon="success" title="🎉 数据集创建成功 🎉">
|
||||
<template #sub-title>
|
||||
<div class="mt-8">
|
||||
<span class="bold">{{ successInfo?.document_count || 0 }}</span>
|
||||
<el-text type="info" class="ml-4">文档</el-text>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="bold">{{ successInfo?.document_list.length || 0 }}</span>
|
||||
<el-text type="info" class="ml-4">分段</el-text>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="bold">{{ toThousands(successInfo?.char_length) || 0 }}</span>
|
||||
<el-text type="info" class="ml-4">字符</el-text>
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-button @click="router.push({ path: `/dataset` })">返回数据集列表</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="router.push({ path: `/dataset/${successInfo?.id}/document` })"
|
||||
>前往文档</el-button
|
||||
>
|
||||
</template>
|
||||
</el-result>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="create-dataset__footer text-right border-t" v-if="active !== 2">
|
||||
<el-button @click="router.go(-1)" :disabled="loading">取 消</el-button>
|
||||
<el-button @click="prev" v-if="active === 1" :disabled="loading">上一步</el-button>
|
||||
<el-button @click="next" type="primary" v-if="active === 0" :disabled="loading"
|
||||
>下一步</el-button
|
||||
>
|
||||
<el-button @click="submit" type="primary" v-if="active === 1" :disabled="loading">
|
||||
开始导入
|
||||
</el-button>
|
||||
</div>
|
||||
</LayoutContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import StepFirst from './step/StepFirst.vue'
|
||||
import StepSecond from './step/StepSecond.vue'
|
||||
import datasetApi from '@/api/dataset'
|
||||
import type { datasetData } from '@/api/type/dataset'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import { toThousands } from '@/utils/utils'
|
||||
import useStore from '@/stores'
|
||||
const { dataset } = useStore()
|
||||
const baseInfo = computed(() => dataset.baseInfo)
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const {
|
||||
query: { id }
|
||||
} = route as any
|
||||
|
||||
const steps = [
|
||||
{
|
||||
ref: 'StepFirstRef',
|
||||
name: '上传文档',
|
||||
component: StepFirst
|
||||
},
|
||||
{
|
||||
ref: 'StepSecondRef',
|
||||
name: '设置分段规则',
|
||||
component: StepSecond
|
||||
}
|
||||
]
|
||||
|
||||
const StepFirstRef = ref()
|
||||
const StepSecondRef = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
const active = ref(0)
|
||||
const successInfo = ref<any>(null)
|
||||
|
||||
const next = () => {
|
||||
if (active.value++ > 2) active.value = 0
|
||||
async function next() {
|
||||
if (await StepFirstRef.value.onSubmit()) {
|
||||
if (active.value++ > 2) active.value = 0
|
||||
}
|
||||
}
|
||||
const prev = () => {
|
||||
active.value = 0
|
||||
}
|
||||
|
||||
function clearStore() {
|
||||
dataset.saveBaseInfo(null)
|
||||
dataset.saveDocumentsFile([])
|
||||
}
|
||||
function submit() {
|
||||
loading.value = true
|
||||
const documents = [] as any[]
|
||||
StepSecondRef.value.paragraphList.map((item: any) => {
|
||||
documents.push({
|
||||
name: item.name,
|
||||
paragraphs: item.content
|
||||
})
|
||||
})
|
||||
const obj = { ...baseInfo.value, documents } as datasetData
|
||||
if (id) {
|
||||
datasetApi
|
||||
.postDocument(id, documents)
|
||||
.then((res) => {
|
||||
MsgSuccess('提交成功')
|
||||
clearStore()
|
||||
router.push({ path: `/dataset/${id}/document` })
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
} else {
|
||||
datasetApi
|
||||
.postDateset(obj)
|
||||
.then((res) => {
|
||||
successInfo.value = res.data
|
||||
active.value = 2
|
||||
clearStore()
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
<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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<h4 class="title-decoration-1 mb-16">基本信息</h4>
|
||||
<el-form ref="FormRef" :model="form" :rules="rules" label-position="top">
|
||||
<el-form-item label="数据集名称" prop="name">
|
||||
<el-input
|
||||
v-model.trim="form.name"
|
||||
placeholder="请输入数据集名称"
|
||||
maxlength="64"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据集描述" prop="desc">
|
||||
<el-input
|
||||
v-model.trim="form.desc"
|
||||
type="textarea"
|
||||
placeholder="描述数据集的内容,详尽的描述将帮助AI能深入理解该数据集的内容,能更准确的检索到内容,提高该数据集的命中率。"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 3 }"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onUnmounted, computed, watch } from 'vue'
|
||||
import useStore from '@/stores'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
|
||||
const { dataset } = useStore()
|
||||
const baseInfo = computed(() => dataset.baseInfo)
|
||||
|
||||
const form = ref<any>({
|
||||
name: '',
|
||||
desc: ''
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入数据集名称', trigger: 'blur' }],
|
||||
desc: [{ required: true, message: '请输入数据集描述', trigger: 'blur' }]
|
||||
})
|
||||
const FormRef = ref()
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(value) => {
|
||||
if (value && JSON.stringify(value) !== '{}') {
|
||||
form.value.name = value.name
|
||||
form.value.desc = value.desc
|
||||
}
|
||||
},
|
||||
{
|
||||
// 初始化立即执行
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
// 表单校验
|
||||
function validate() {
|
||||
if (!FormRef.value) return
|
||||
return FormRef.value.validate((valid: any) => {
|
||||
return valid
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (baseInfo.value) {
|
||||
form.value = baseInfo.value
|
||||
}
|
||||
})
|
||||
onUnmounted(() => {
|
||||
form.value = {
|
||||
name: '',
|
||||
desc: ''
|
||||
}
|
||||
})
|
||||
defineExpose({
|
||||
validate,
|
||||
form
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<el-dialog title="编辑分段" v-model="dialogVisible" width="600">
|
||||
<ParagraphForm ref="paragraphFormRef" :data="detail" />
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> 取消 </el-button>
|
||||
<el-button type="primary" @click="submitHandle"> 保存 </el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import ParagraphForm from '@/views/paragraph/component/ParagraphForm.vue'
|
||||
|
||||
const emit = defineEmits(['updateContent'])
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
const detail = ref({})
|
||||
|
||||
const paragraphFormRef = ref()
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
detail.value = {}
|
||||
}
|
||||
})
|
||||
|
||||
const open = (data: any) => {
|
||||
detail.value = cloneDeep(data)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const submitHandle = async () => {
|
||||
if (await paragraphFormRef.value?.validate()) {
|
||||
emit('updateContent', paragraphFormRef.value?.form)
|
||||
dialogVisible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scope></style>
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<el-tabs v-model="activeName" class="paragraph-tabs" @tab-click="handleClick">
|
||||
<template v-for="(item, index) in newData" :key="index">
|
||||
<el-tab-pane :label="item.name" :name="index">
|
||||
<template #label>
|
||||
<div class="flex-center">
|
||||
<img :src="getImgUrl(item && item?.name)" alt="" height="16" />
|
||||
<span class="ml-4">{{ item?.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-scrollbar>
|
||||
<div class="paragraph-list">
|
||||
<el-card
|
||||
v-for="(child, cIndex) in item.content"
|
||||
:key="cIndex"
|
||||
shadow="never"
|
||||
class="card-never mb-16"
|
||||
>
|
||||
<div class="flex-between">
|
||||
<span>{{ child.title }}</span>
|
||||
<div>
|
||||
<!-- 编辑分段按钮 -->
|
||||
<el-button link @click="editHandle(child, index, cIndex)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
</el-button>
|
||||
<!-- 删除分段按钮 -->
|
||||
<el-button link @click="deleteHandle(child, index, cIndex)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lighter mt-12">
|
||||
{{ child.content }}
|
||||
</div>
|
||||
<div class="lighter mt-12">
|
||||
<el-text type="info"> {{ child.content.length }} 个字符 </el-text>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
<EditParagraphDialog ref="EditParagraphDialogRef" @updateContent="updateContent" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
import EditParagraphDialog from './EditParagraphDialog.vue'
|
||||
import { filesize, getImgUrl } from '@/utils/utils'
|
||||
import { MsgConfirm } from '@/utils/message'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array<any>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:data'])
|
||||
|
||||
const EditParagraphDialogRef = ref()
|
||||
|
||||
const activeName = ref(0)
|
||||
const currentPIndex = ref(null) as any
|
||||
const currentCIndex = ref(null) as any
|
||||
|
||||
const newData = ref<any[]>([])
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(value) => {
|
||||
newData.value = value
|
||||
},
|
||||
{
|
||||
// 初始化立即执行
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
function editHandle(item: any, index: number, cIndex: number) {
|
||||
currentPIndex.value = index
|
||||
currentCIndex.value = cIndex
|
||||
EditParagraphDialogRef.value.open(item)
|
||||
}
|
||||
|
||||
function deleteHandle(item: any, index: number, cIndex: number) {
|
||||
MsgConfirm(`是否删除分段:${item.title} ?`, `删除后将不会存入数据集,对本地文档无影响。`, {
|
||||
confirmButtonText: '删除',
|
||||
confirmButtonClass: 'danger'
|
||||
})
|
||||
.then(() => {
|
||||
newData.value[index].content.splice(cIndex, 1)
|
||||
emit('update:data', newData.value)
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function updateContent(data: any) {
|
||||
newData.value[currentPIndex.value].content[currentCIndex.value] = data
|
||||
emit('update:data', newData.value)
|
||||
}
|
||||
|
||||
const handleClick = (tab: TabsPaneContext, event: Event) => {
|
||||
// console.log(tab, event)
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.paragraph-tabs {
|
||||
:deep(.el-tabs__item) {
|
||||
background: var(--app-text-color-light-1);
|
||||
margin: 4px;
|
||||
border-radius: 4px;
|
||||
padding: 5px 10px 5px 8px !important;
|
||||
height: auto;
|
||||
&:nth-child(2) {
|
||||
margin-left: 0;
|
||||
}
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
&.is-active {
|
||||
border: 1px solid var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
:deep(.el-tabs__nav-wrap::after) {
|
||||
display: none;
|
||||
}
|
||||
:deep(.el-tabs__active-bar) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.paragraph-list {
|
||||
height: calc(var(--create-dataset-height) - 125px);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
<template>
|
||||
<h4 class="title-decoration-1 mb-8">上传文档</h4>
|
||||
<el-form ref="FormRef" :model="form" :rules="rules" label-position="top">
|
||||
<el-form-item prop="fileList">
|
||||
<el-upload
|
||||
class="w-full"
|
||||
drag
|
||||
multiple
|
||||
v-model:file-list="form.fileList"
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
accept=".txt, .md"
|
||||
>
|
||||
<img src="@/assets/upload-icon.svg" alt="" />
|
||||
<div class="el-upload__text">
|
||||
<p>
|
||||
将文件拖拽至此区域或
|
||||
<em> 选择文件上传 </em>
|
||||
</p>
|
||||
<div class="upload__decoration">
|
||||
<p>支持格式:TXT、Markdown,每次最多上传50个文件,每个文件不超过 10MB</p>
|
||||
<p>若使用【高级分段】建议上传前规范文件的分段标识</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row :gutter="8" v-if="form.fileList?.length">
|
||||
<template v-for="(item, index) in form.fileList" :key="index">
|
||||
<el-col :span="12" class="mb-8">
|
||||
<el-card shadow="never" class="file-List-card">
|
||||
<div class="flex-between">
|
||||
<div class="flex">
|
||||
<img :src="getImgUrl(item && item?.name)" alt="" />
|
||||
<div class="ml-8">
|
||||
<p>{{ item && item?.name }}</p>
|
||||
<el-text type="info">{{ filesize(item && item?.size) }}</el-text>
|
||||
</div>
|
||||
</div>
|
||||
<el-button text @click="deleteFlie(index)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onUnmounted, onMounted, computed } from 'vue'
|
||||
import type { UploadProps } from 'element-plus'
|
||||
import { filesize, getImgUrl } from '@/utils/utils'
|
||||
import { MsgError } from '@/utils/message'
|
||||
import useStore from '@/stores'
|
||||
const { dataset } = useStore()
|
||||
const documentsFiles = computed(() => dataset.documentsFiles)
|
||||
const form = ref({
|
||||
fileList: [] as any
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
fileList: [{ required: true, message: '请上传文件', trigger: 'change' }]
|
||||
})
|
||||
const FormRef = ref()
|
||||
|
||||
// const beforeUploadHandle: UploadProps['beforeUpload'] = (rawFile) => {
|
||||
// const type = fileType(rawFile?.name)
|
||||
// console.log(type)
|
||||
// if (type !== 'txt' || type !== 'md') {
|
||||
// MsgError('Avatar picture must be JPG format!')
|
||||
// return false
|
||||
// } else if (rawFile.size / 1024 / 1024 > 10) {
|
||||
// MsgError('文件不超过 10MB!')
|
||||
// return false
|
||||
// }
|
||||
// return true
|
||||
// }
|
||||
function deleteFlie(index: number) {
|
||||
form.value.fileList.splice(index, 1)
|
||||
}
|
||||
|
||||
// 表单校验
|
||||
function validate() {
|
||||
if (!FormRef.value) return
|
||||
return FormRef.value.validate((valid: any) => {
|
||||
return valid
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
if (documentsFiles.value) {
|
||||
form.value.fileList = documentsFiles.value
|
||||
}
|
||||
})
|
||||
onUnmounted(() => {
|
||||
form.value = {
|
||||
fileList: []
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
form
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.file-List-card {
|
||||
border-radius: 4px;
|
||||
:deep(.el-card__body) {
|
||||
padding: 8px 16px 8px 12px;
|
||||
}
|
||||
}
|
||||
.upload__decoration {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,87 +1,103 @@
|
|||
<template>
|
||||
<LayoutContent header="数据集">
|
||||
<div class="dataset-list-container p-15">
|
||||
<div class="text-right">
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
placeholder="搜索内容"
|
||||
suffix-icon="Search"
|
||||
style="width: 300px"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<el-row
|
||||
:gutter="15"
|
||||
v-infinite-scroll="loadDataset"
|
||||
:infinite-scroll-disabled="disabledScroll"
|
||||
>
|
||||
<el-col :xs="24" :sm="12" :md="6" :lg="5" :xl="4" class="mt-10">
|
||||
<CardAdd title="创建数据集" @click="router.push({ path: '/dataset/create' })" />
|
||||
</el-col>
|
||||
<el-col
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="6"
|
||||
:lg="5"
|
||||
:xl="4"
|
||||
v-for="(item, index) in datasetList"
|
||||
:key="index"
|
||||
class="mt-10"
|
||||
>
|
||||
<CardBox :title="item.name" :description="item.desc" class="cursor">
|
||||
<template #mouseEnter>
|
||||
<div class="delete-button">
|
||||
<el-button type="primary" link @click.stop="deleteDateset(item)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="footer-content">
|
||||
{{ item?.document_count || 0 }}文档数 {{ item?.char_length || 0 }}字符数
|
||||
{{ item?.char_length || 0 }}关联应用
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="dataset-list-container p-24">
|
||||
<div class="flex-between">
|
||||
<h3>数据集</h3>
|
||||
<el-input
|
||||
v-model="pageConfig.name"
|
||||
@change="search"
|
||||
placeholder="按 名称 搜索"
|
||||
prefix-icon="Search"
|
||||
class="w-240"
|
||||
/>
|
||||
</div>
|
||||
</LayoutContent>
|
||||
<div v-loading.fullscreen.lock="loading">
|
||||
<el-row
|
||||
:gutter="15"
|
||||
v-infinite-scroll="loadDataset"
|
||||
:infinite-scroll-disabled="disabledScroll"
|
||||
class="app-list-row"
|
||||
>
|
||||
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mt-8">
|
||||
<CardAdd title="创建数据集" @click="router.push({ path: '/dataset/create' })" />
|
||||
</el-col>
|
||||
<el-col
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="8"
|
||||
:lg="6"
|
||||
:xl="4"
|
||||
v-for="(item, index) in datasetList"
|
||||
:key="index"
|
||||
class="mt-8"
|
||||
>
|
||||
<CardBox
|
||||
:title="item.name"
|
||||
:description="item.desc"
|
||||
class="cursor"
|
||||
@click="router.push({ path: `/dataset/${item.id}/document` })"
|
||||
>
|
||||
<template #mouseEnter>
|
||||
<el-tooltip effect="dark" content="删除" placement="top">
|
||||
<el-button text @click.stop="deleteDateset(item)" class="delete-button">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="footer-content">
|
||||
<span class="bold">{{ item?.document_count || 0 }}</span>
|
||||
文档<el-divider direction="vertical" />
|
||||
<span class="bold">{{ numberFormat(item?.char_length) || 0 }}</span>
|
||||
字符<el-divider direction="vertical" />
|
||||
<span class="bold">{{ item?.char_length || 0 }}</span>
|
||||
关联应用
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import datasetApi from '@/api/dataset'
|
||||
import type { datasetListRequest } from '@/api/type/dataset'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
import { useRouter } from 'vue-router'
|
||||
import { numberFormat } from '@/utils/utils'
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(false)
|
||||
const filterText = ref('')
|
||||
const datasetList = ref<any[]>([])
|
||||
const disabledScroll = ref(false)
|
||||
const pageConfig = ref<datasetListRequest>({
|
||||
const pageConfig = reactive<datasetListRequest>({
|
||||
current_page: 1,
|
||||
page_size: 20,
|
||||
search_text: ''
|
||||
name: ''
|
||||
})
|
||||
|
||||
function loadDataset() { }
|
||||
function loadDataset() {}
|
||||
|
||||
function search() {
|
||||
pageConfig.current_page = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
function deleteDateset(row: any) {
|
||||
MsgConfirm({
|
||||
title: `是否删除数据集:${row.name}?`,
|
||||
decription: '此数据集关联2个应用,删除后无法恢复,请谨慎操作。',
|
||||
confirmButtonText: '删除',
|
||||
}, {
|
||||
confirmButtonClass: 'danger',
|
||||
})
|
||||
MsgConfirm(
|
||||
`是否删除数据集:${row.name} ?`,
|
||||
`此数据集关联 ${row.char_length} 个应用,删除后无法恢复,请谨慎操作。`,
|
||||
{
|
||||
confirmButtonText: '删除',
|
||||
confirmButtonClass: 'danger'
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
loading.value = true
|
||||
datasetApi.delDateset(row.id)
|
||||
datasetApi
|
||||
.delDateset(row.id)
|
||||
.then(() => {
|
||||
MsgSuccess('删除成功')
|
||||
getList()
|
||||
|
|
@ -96,9 +112,9 @@ function deleteDateset(row: any) {
|
|||
function getList() {
|
||||
loading.value = true
|
||||
datasetApi
|
||||
.getDateset(pageConfig.value)
|
||||
.getDateset(pageConfig)
|
||||
.then((res) => {
|
||||
datasetList.value = res.data
|
||||
datasetList.value = res.data?.records
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
@ -114,8 +130,14 @@ onMounted(() => {
|
|||
.dataset-list-container {
|
||||
.delete-button {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
right: 12px;
|
||||
top: 18px;
|
||||
height: auto;
|
||||
}
|
||||
.footer-content {
|
||||
.bold {
|
||||
color: var(--app-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<el-scrollbar>
|
||||
<div class="upload-document p-24">
|
||||
<!-- 基本信息 -->
|
||||
<BaseForm ref="BaseFormRef" v-if="isCreate" />
|
||||
<!-- 上传文档 -->
|
||||
<UploadComponent ref="UploadComponentRef" />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import BaseForm from '@/views/dataset/component/BaseForm.vue'
|
||||
import UploadComponent from '@/views/dataset/component/UploadComponent.vue'
|
||||
|
||||
import useStore from '@/stores'
|
||||
const { dataset } = useStore()
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { type }
|
||||
} = route
|
||||
const isCreate = type === 'create'
|
||||
const BaseFormRef = ref()
|
||||
const UploadComponentRef = ref()
|
||||
|
||||
// submit
|
||||
const onSubmit = async () => {
|
||||
if (isCreate) {
|
||||
if ((await BaseFormRef.value?.validate()) && (await UploadComponentRef.value.validate())) {
|
||||
// stores保存数据
|
||||
dataset.saveBaseInfo(BaseFormRef.value.form)
|
||||
dataset.saveDocumentsFile(UploadComponentRef.value.form.fileList)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (await UploadComponentRef.value.validate()) {
|
||||
// stores保存数据
|
||||
dataset.saveDocumentsFile(UploadComponentRef.value.form.fileList)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
|
||||
defineExpose({
|
||||
onSubmit
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.upload-document {
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
<template>
|
||||
<div class="set-rules">
|
||||
<el-row class="set-rules-height">
|
||||
<el-col :span="12" class="p-24">
|
||||
<h4 class="title-decoration-1 mb-8">设置分段规则</h4>
|
||||
<div>
|
||||
<el-scrollbar>
|
||||
<div class="left-height">
|
||||
<el-radio-group v-model="radio" class="set-rules__radio">
|
||||
<el-radio label="1" size="large" border class="mb-16">
|
||||
<p>智能分段(推荐)</p>
|
||||
<el-text type="info">不了解如何设置分段规则推荐使用智能分段</el-text>
|
||||
</el-radio>
|
||||
<el-radio label="2" size="large" border class="mb-16">
|
||||
<p>高级分段</p>
|
||||
<el-text type="info"
|
||||
>用户可根据文档规范自行设置分段标识符、分段长度以及清洗规则
|
||||
</el-text>
|
||||
<el-card shadow="never" class="card-never mt-16" v-if="radio === '2'">
|
||||
<div class="set-rules__form">
|
||||
<div class="form-item mb-16">
|
||||
<div class="title flex align-center mb-8">
|
||||
<span style="margin-right: 4px">分段标识</span>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="按照所选符号先后顺序做递归分割,分割结果超出分段长度将截取至分段长度。"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon style="font-size: 16px"><Warning /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<el-select v-model="form.patterns" multiple placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in patternsList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
multiple
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="form-item mb-16">
|
||||
<div class="title mb-8">分段长度</div>
|
||||
<el-slider
|
||||
v-model="form.limit"
|
||||
show-input
|
||||
:show-input-controls="false"
|
||||
:min="10"
|
||||
:max="1024"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-item mb-16">
|
||||
<div class="title mb-8">自动清洗</div>
|
||||
<el-switch v-model="form.with_filter" />
|
||||
<div style="margin-top: 4px">
|
||||
<el-text type="info">去掉重复多余符号空格、空行、制表符</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div class="text-right">
|
||||
<el-button @click="splitDocument">生成预览</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="p-24 border-l">
|
||||
<div v-loading="loading">
|
||||
<h4 class="title-decoration-1 mb-8">分段预览</h4>
|
||||
<ParagraphPreview v-model:data="paragraphList" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, reactive } from 'vue'
|
||||
import ParagraphPreview from '@/views/dataset/component/ParagraphPreview.vue'
|
||||
import DatasetApi from '@/api/dataset'
|
||||
import useStore from '@/stores'
|
||||
const { dataset } = useStore()
|
||||
const documentsFiles = computed(() => dataset.documentsFiles)
|
||||
const patternType = ['空行', '#', '##', '###', '####', '-', '空格', '回车', '句号', '逗号', '分号']
|
||||
|
||||
const marks = reactive({
|
||||
10: '10',
|
||||
1024: '1024'
|
||||
})
|
||||
|
||||
const radio = ref('1')
|
||||
const loading = ref(false)
|
||||
const paragraphList = ref<any[]>([])
|
||||
|
||||
const form = reactive<any>({
|
||||
patterns: [] as any,
|
||||
limit: 0,
|
||||
with_filter: false
|
||||
})
|
||||
|
||||
const patternsList = ref<string[]>(patternType)
|
||||
|
||||
function splitDocument() {
|
||||
loading.value = true
|
||||
let fd = new FormData()
|
||||
documentsFiles.value.forEach((item) => {
|
||||
if (item?.raw) {
|
||||
fd.append('file', item?.raw)
|
||||
}
|
||||
})
|
||||
if (radio.value === '2') {
|
||||
Object.keys(form).forEach((key) => {
|
||||
fd.append(key, form[key])
|
||||
})
|
||||
}
|
||||
DatasetApi.postSplitDocument(fd)
|
||||
.then((res: any) => {
|
||||
paragraphList.value = res.data
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
splitDocument()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
paragraphList
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.set-rules {
|
||||
width: 100%;
|
||||
.set-rules-height {
|
||||
height: var(--create-dataset-height);
|
||||
}
|
||||
|
||||
.left-height {
|
||||
max-height: calc(var(--create-dataset-height) - 105px);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
&__radio {
|
||||
display: block;
|
||||
.el-radio {
|
||||
white-space: break-spaces;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: calc(var(--app-base-px) * 2);
|
||||
line-height: 22px;
|
||||
color: var(--app-text-color);
|
||||
}
|
||||
:deep(.el-radio__label) {
|
||||
padding-left: 32px;
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.el-radio__input) {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
}
|
||||
}
|
||||
&__form {
|
||||
.el-select {
|
||||
width: 100%;
|
||||
}
|
||||
.title {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<LayoutContainer header="设置">
|
||||
<div class="dataset-setting">
|
||||
<div class="p-24" v-loading="loading">
|
||||
<BaseForm ref="BaseFormRef" :data="detail" />
|
||||
<div class="text-right">
|
||||
<el-button @click="submit" type="primary"> 保存 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LayoutContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import BaseForm from '@/views/dataset/component/BaseForm.vue'
|
||||
import datasetApi from '@/api/dataset'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { datasetId }
|
||||
} = route as any
|
||||
|
||||
const BaseFormRef = ref()
|
||||
const loading = ref(false)
|
||||
const detail = ref({})
|
||||
|
||||
async function submit() {
|
||||
if (await BaseFormRef.value?.validate()) {
|
||||
loading.value = true
|
||||
datasetApi
|
||||
.postDocument(datasetId, BaseFormRef.value.form)
|
||||
.then((res) => {
|
||||
MsgSuccess('保存成功')
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getDetail() {
|
||||
loading.value = true
|
||||
datasetApi
|
||||
.getDatesetDetail(datasetId)
|
||||
.then((res) => {
|
||||
detail.value = res.data
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.dataset-setting {
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
<template>
|
||||
<LayoutContainer header="文档">
|
||||
<div class="main-calc-height">
|
||||
<div class="p-24">
|
||||
<div class="flex-between">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="router.push({ path: '/dataset/upload', query: { id: datasetId } })"
|
||||
>上传文档</el-button
|
||||
>
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
placeholder="按 文档名称 搜索"
|
||||
prefix-icon="Search"
|
||||
class="w-240"
|
||||
@change="getList"
|
||||
/>
|
||||
</div>
|
||||
<app-table
|
||||
class="mt-16"
|
||||
:data="documentData"
|
||||
:pagination-config="paginationConfig"
|
||||
quick-create
|
||||
@sizeChange="handleSizeChange"
|
||||
@changePage="handleCurrentChange"
|
||||
@cell-mouse-enter="cellMouseEnter"
|
||||
@cell-mouse-leave="cellMouseLeave"
|
||||
@creatQuick="creatQuickHandle"
|
||||
@row-click="rowClickHandle"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-table-column prop="name" label="文件名称" min-width="280">
|
||||
<template #default="{ row }">
|
||||
<ReadWrite
|
||||
@change="editName"
|
||||
:data="row.name"
|
||||
:showEditIcon="row.id === currentMouseId"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="char_length" label="字符数" align="right">
|
||||
<template #default="{ row }">
|
||||
{{ toThousands(row.char_length) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="paragraph_count" label="分段" align="right" />
|
||||
<el-table-column prop="status" label="文件状态" min-width="90">
|
||||
<template #default="{ row }">
|
||||
<el-text v-if="row.status === '1'">
|
||||
<el-icon class="success"><SuccessFilled /></el-icon> 成功
|
||||
</el-text>
|
||||
<el-text v-else-if="row.status === '2'">
|
||||
<el-icon class="danger"><CircleCloseFilled /></el-icon> 失败
|
||||
</el-text>
|
||||
<el-text v-else-if="row.status === '0'">
|
||||
<el-icon class="is-loading primary"><Loading /></el-icon> 导入中
|
||||
</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="启动状态">
|
||||
<template #default="{ row }">
|
||||
<div @click.stop>
|
||||
<el-switch v-model="row.is_active" @change="changeState($event, row)" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="创建时间" width="170">
|
||||
<template #default="{ row }">
|
||||
{{ datetimeFormat(row.create_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="update_time" label="更新时间" width="170">
|
||||
<template #default="{ row }">
|
||||
{{ datetimeFormat(row.update_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="操作" align="center">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.status === 2">
|
||||
<el-tooltip effect="dark" content="刷新" placement="top">
|
||||
<el-button type="primary" text>
|
||||
<el-icon><RefreshRight /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<span class="ml-4">
|
||||
<el-tooltip effect="dark" content="删除" placement="top">
|
||||
<el-button type="primary" text @click.stop="deleteDocument(row)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</app-table>
|
||||
</div>
|
||||
</div>
|
||||
</LayoutContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import datasetApi from '@/api/dataset'
|
||||
import { toThousands } from '@/utils/utils'
|
||||
import { datetimeFormat } from '@/utils/time'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { datasetId }
|
||||
} = route as any
|
||||
|
||||
const loading = ref(false)
|
||||
const filterText = ref('')
|
||||
const documentData = ref<any[]>([])
|
||||
const currentMouseId = ref(null)
|
||||
|
||||
const paginationConfig = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
function rowClickHandle(row: any) {
|
||||
router.push({ path: `/dataset/${datasetId}/${row.id}` })
|
||||
}
|
||||
|
||||
// 快速创建空白文档
|
||||
function creatQuickHandle(val: string) {
|
||||
loading.value = true
|
||||
const obj = { name: val }
|
||||
datasetApi
|
||||
.postDocument(datasetId, obj)
|
||||
.then((res) => {
|
||||
getList()
|
||||
MsgSuccess('创建成功')
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function deleteDocument(row: any) {
|
||||
MsgConfirm(
|
||||
`是否删除文档:${row.name} ?`,
|
||||
`此文档下的 ${row.paragraph_count} 个分段都会被删除,请谨慎操作。`,
|
||||
{
|
||||
confirmButtonText: '删除',
|
||||
confirmButtonClass: 'danger'
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
loading.value = true
|
||||
datasetApi
|
||||
.delDocument(datasetId, row.id)
|
||||
.then(() => {
|
||||
MsgSuccess('删除成功')
|
||||
getList()
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
// 更新名称或状态
|
||||
function updateData(documentId: string, data: any) {
|
||||
loading.value = true
|
||||
datasetApi
|
||||
.putDocument(datasetId, documentId, data)
|
||||
.then((res) => {
|
||||
const index = documentData.value.findIndex((v) => v.id === documentId)
|
||||
documentData.value.splice(index, 1, res.data)
|
||||
MsgSuccess('修改成功')
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function changeState(bool: Boolean, row: any) {
|
||||
const obj = {
|
||||
is_active: bool
|
||||
}
|
||||
currentMouseId.value && updateData(row.id, obj)
|
||||
}
|
||||
|
||||
function editName(val: string) {
|
||||
const obj = {
|
||||
name: val
|
||||
}
|
||||
currentMouseId.value && updateData(currentMouseId.value, obj)
|
||||
}
|
||||
|
||||
function cellMouseEnter(row: any) {
|
||||
currentMouseId.value = row.id
|
||||
}
|
||||
function cellMouseLeave() {
|
||||
currentMouseId.value = null
|
||||
}
|
||||
|
||||
function handleSizeChange(val: number) {
|
||||
console.log(`${val} items per page`)
|
||||
}
|
||||
function handleCurrentChange(val: number) {
|
||||
console.log(`current page: ${val}`)
|
||||
}
|
||||
|
||||
function getList() {
|
||||
loading.value = true
|
||||
datasetApi
|
||||
.getDocument(datasetId as string, filterText.value)
|
||||
.then((res) => {
|
||||
documentData.value = res.data
|
||||
paginationConfig.total = res.data.length
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<login-layout>
|
||||
<LoginContainer>
|
||||
<h4 class="mb-20">忘记密码</h4>
|
||||
<h4 class="mb-16">忘记密码</h4>
|
||||
<el-form
|
||||
class="register-form"
|
||||
ref="resetPasswordFormRef"
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
</el-input>
|
||||
<el-button
|
||||
size="large"
|
||||
class="send-email-button ml-10"
|
||||
class="send-email-button ml-16"
|
||||
@click="sendEmail"
|
||||
:loading="loading"
|
||||
>获取验证码</el-button
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
<el-button type="primary" class="login-submit-button w-full" @click="checkCode"
|
||||
>立即验证</el-button
|
||||
>
|
||||
<div class="operate-container mt-10">
|
||||
<div class="operate-container mt-8">
|
||||
<el-button
|
||||
class="register"
|
||||
@click="router.push('/login')"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<login-layout v-loading="loading">
|
||||
<LoginContainer subTitle="欢迎使用智能客服管理平台">
|
||||
<LoginContainer subTitle="欢迎使用 MaxKB 管理平台">
|
||||
<el-form class="login-form" :rules="rules" :model="loginForm" ref="loginFormRef">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<login-layout>
|
||||
<LoginContainer>
|
||||
<h4 class="mb-20">注册</h4>
|
||||
<h4 class="mb-16">注册</h4>
|
||||
<el-form class="register-form" :model="registerForm" :rules="rules" ref="registerFormRef">
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
</el-input>
|
||||
<el-button
|
||||
size="large"
|
||||
class="send-email-button ml-10"
|
||||
class="send-email-button ml-16"
|
||||
@click="sendEmail"
|
||||
:loading="sendEmailLoading"
|
||||
>获取验证码</el-button
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
<el-button type="primary" class="login-submit-button w-full" @click="register"
|
||||
>注册</el-button
|
||||
>
|
||||
<div class="operate-container mt-10">
|
||||
<div class="operate-container mt-8">
|
||||
<el-button
|
||||
class="register"
|
||||
@click="router.push('/login')"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<login-layout>
|
||||
<LoginContainer>
|
||||
<h4 class="mb-20">修改密码</h4>
|
||||
<h4 class="mb-16">修改密码</h4>
|
||||
<el-form
|
||||
class="reset-password-form"
|
||||
ref="resetPasswordFormRef"
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
<el-button type="primary" class="login-submit-button w-full" @click="resetPassword"
|
||||
>确认修改</el-button
|
||||
>
|
||||
<div class="operate-container mt-10">
|
||||
<div class="operate-container mt-8">
|
||||
<el-button
|
||||
class="register"
|
||||
@click="router.push('/login')"
|
||||
|
|
@ -63,6 +63,9 @@ import type { FormInstance, FormRules } from 'element-plus'
|
|||
import UserApi from '@/api/user'
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { code, email }
|
||||
} = route
|
||||
const resetPasswordForm = ref<ResetPasswordRequest>({
|
||||
password: '',
|
||||
re_password: '',
|
||||
|
|
@ -71,8 +74,6 @@ const resetPasswordForm = ref<ResetPasswordRequest>({
|
|||
})
|
||||
|
||||
onMounted(() => {
|
||||
const code = route.params.code
|
||||
const email = route.params.email
|
||||
if (code && email) {
|
||||
resetPasswordForm.value.code = code as string
|
||||
resetPasswordForm.value.email = email as string
|
||||
|
|
@ -133,4 +134,4 @@ const resetPassword = () => {
|
|||
</script>
|
||||
<style lang="scss" scope>
|
||||
@import '../index.scss';
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="title"
|
||||
v-model="dialogVisible"
|
||||
width="800"
|
||||
class="paragraph-dialog"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="16" class="p-24">
|
||||
<el-scrollbar>
|
||||
<div style="height: 350px">
|
||||
<div class="flex-between mb-16">
|
||||
<div class="bold title align-center">分段内容</div>
|
||||
<el-button text @click="isEdit = true" v-if="problemId && !isEdit">
|
||||
<el-icon><EditPen /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<ParagraphForm ref="paragraphFormRef" :data="detail" :isEdit="isEdit" />
|
||||
</div>
|
||||
|
||||
<div class="text-right" v-if="problemId && isEdit">
|
||||
<el-button @click.prevent="dialogVisible = false"> 取消 </el-button>
|
||||
<el-button type="primary" :disabled="loading" @click="submitHandle"> 保存 </el-button>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-col>
|
||||
<el-col :span="8" class="border-l p-24">
|
||||
<!-- 关联问题 -->
|
||||
<ProblemComponent :problemId="problemId" ref="ProblemRef" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer v-if="!problemId">
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> 取消 </el-button>
|
||||
<el-button type="primary" @click="submitHandle"> 提交 </el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import ParagraphForm from '@/views/paragraph/component/ParagraphForm.vue'
|
||||
import ProblemComponent from '@/views/paragraph/component/ProblemComponent.vue'
|
||||
import datasetApi from '@/api/dataset'
|
||||
import useStore from '@/stores'
|
||||
|
||||
const props = defineProps({
|
||||
title: String
|
||||
})
|
||||
|
||||
const { paragraph } = useStore()
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { datasetId, documentId }
|
||||
} = route as any
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const ProblemRef = ref()
|
||||
const paragraphFormRef = ref<FormInstance>()
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
const loading = ref(false)
|
||||
const problemId = ref('')
|
||||
const detail = ref<any>({})
|
||||
const isEdit = ref(false)
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
problemId.value = ''
|
||||
detail.value = {}
|
||||
isEdit.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const open = (data: any) => {
|
||||
if (data) {
|
||||
detail.value.title = data.title
|
||||
detail.value.content = data.content
|
||||
problemId.value = data.id
|
||||
} else {
|
||||
isEdit.value = true
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const submitHandle = async () => {
|
||||
if (await paragraphFormRef.value?.validate()) {
|
||||
loading.value = true
|
||||
if (problemId.value) {
|
||||
paragraph
|
||||
.asyncPutParagraph(datasetId, documentId, problemId.value, paragraphFormRef.value?.form)
|
||||
.then(() => {
|
||||
emit('refresh')
|
||||
loading.value = false
|
||||
dialogVisible.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
} else {
|
||||
const obj =
|
||||
ProblemRef.value.problemList.length > 0
|
||||
? {
|
||||
problem_list: ProblemRef.value.problemList,
|
||||
...paragraphFormRef.value?.form
|
||||
}
|
||||
: paragraphFormRef.value?.form
|
||||
datasetApi
|
||||
.postParagraph(datasetId, documentId, obj)
|
||||
.then((res) => {
|
||||
emit('refresh')
|
||||
loading.value = false
|
||||
dialogVisible.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.paragraph-dialog {
|
||||
.el-dialog__header {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
.el-dialog__body {
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
padding: 0 !important;
|
||||
}
|
||||
.el-dialog__footer {
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
}
|
||||
.title {
|
||||
color: var(--app-text-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<el-form ref="paragraphFormRef" :model="form" label-position="top" :rules="rules" @submit.prevent>
|
||||
<el-form-item label="分段标题">
|
||||
<el-input v-if="isEdit" v-model="form.title" placeholder="请输入分段标题"> </el-input>
|
||||
<span v-else>{{ form.title }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="分段内容" prop="content">
|
||||
<el-input
|
||||
v-if="isEdit"
|
||||
v-model="form.content"
|
||||
placeholder="请输入分段内容"
|
||||
maxlength="1024"
|
||||
show-word-limit
|
||||
:rows="8"
|
||||
type="textarea"
|
||||
>
|
||||
</el-input>
|
||||
<span v-else>{{ form.content }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onUnmounted, watch } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
isEdit: Boolean
|
||||
})
|
||||
|
||||
const form = ref<any>({
|
||||
title: '',
|
||||
content: ''
|
||||
})
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
content: [{ required: true, message: '请输入分段内容', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const paragraphFormRef = ref<FormInstance>()
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(value) => {
|
||||
if (value && JSON.stringify(value) !== '{}') {
|
||||
form.value.title = value.title
|
||||
form.value.content = value.content
|
||||
}
|
||||
},
|
||||
{
|
||||
// 初始化立即执行
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
// 表单校验
|
||||
function validate() {
|
||||
if (!paragraphFormRef.value) return
|
||||
return paragraphFormRef.value.validate((valid: any) => {
|
||||
return valid
|
||||
})
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
form.value = {
|
||||
title: '',
|
||||
content: ''
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
form
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<template>
|
||||
<p class="bold title mb-16">
|
||||
关联问题 <el-divider direction="vertical" />
|
||||
<el-button text @click="addProblem">
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-button>
|
||||
</p>
|
||||
<div v-loading="loading" style="height: 350px">
|
||||
<el-scrollbar>
|
||||
<el-input
|
||||
ref="inputRef"
|
||||
v-if="isAddProblem"
|
||||
v-model="problemValue"
|
||||
@change="addProblemHandle"
|
||||
placeholder="请输入问题,回车保存"
|
||||
class="mb-8"
|
||||
autofocus
|
||||
/>
|
||||
|
||||
<template v-for="(item, index) in problemList" :key="index">
|
||||
<TagEllipsis
|
||||
@close="delProblemHandle(item, index)"
|
||||
class="question-tag"
|
||||
type="info"
|
||||
effect="plain"
|
||||
closable
|
||||
>
|
||||
{{ item.content }}
|
||||
</TagEllipsis>
|
||||
</template>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import datasetApi from '@/api/dataset'
|
||||
|
||||
const props = defineProps({
|
||||
problemId: String
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { datasetId, documentId }
|
||||
} = route as any
|
||||
|
||||
const inputRef = ref()
|
||||
const loading = ref(false)
|
||||
const isAddProblem = ref(false)
|
||||
|
||||
const problemValue = ref('')
|
||||
const problemList = ref<any[]>([])
|
||||
|
||||
watch(
|
||||
() => props.problemId,
|
||||
(value) => {
|
||||
if (value) {
|
||||
getProblemList()
|
||||
}
|
||||
},
|
||||
{
|
||||
// 初始化立即执行
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
function delProblemHandle(item: any, index: number) {
|
||||
loading.value = true
|
||||
if (item.id) {
|
||||
datasetApi
|
||||
.delProblem(datasetId, documentId, props.problemId || '', item.id)
|
||||
.then((res) => {
|
||||
getProblemList()
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
} else {
|
||||
problemList.value.splice(index, 1)
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getProblemList() {
|
||||
loading.value = true
|
||||
datasetApi
|
||||
.getProblem(datasetId, documentId, props.problemId || '')
|
||||
.then((res) => {
|
||||
problemList.value = res.data
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function addProblem() {
|
||||
isAddProblem.value = true
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus()
|
||||
})
|
||||
}
|
||||
function addProblemHandle(val: string) {
|
||||
if (val) {
|
||||
const obj = {
|
||||
content: val
|
||||
}
|
||||
loading.value = true
|
||||
if (props.problemId) {
|
||||
datasetApi
|
||||
.postProblem(datasetId, documentId, props.problemId, obj)
|
||||
.then((res) => {
|
||||
getProblemList()
|
||||
problemValue.value = ''
|
||||
isAddProblem.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
} else {
|
||||
problemList.value.unshift(obj)
|
||||
problemValue.value = ''
|
||||
isAddProblem.value = false
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
onUnmounted(() => {
|
||||
problemList.value = []
|
||||
problemValue.value = ''
|
||||
isAddProblem.value = false
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
problemList
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.question-tag {
|
||||
width: 217px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
<template>
|
||||
<LayoutContainer :header="documentDetail?.name" back-to="-1" class="document-detail">
|
||||
<template #header>
|
||||
<div class="document-detail__header">
|
||||
<el-button @click="addParagraph" type="primary" :disabled="loading"> 添加分段 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div class="document-detail__main p-16" v-loading="loading">
|
||||
<div class="flex-between p-8">
|
||||
<span>{{ paragraphDetail.length }} 段落</span>
|
||||
<el-input
|
||||
v-model="search"
|
||||
placeholder="搜索"
|
||||
class="input-with-select"
|
||||
style="width: 260px"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-select v-model="searchType" placeholder="Select" style="width: 80px">
|
||||
<el-option label="标题" value="title" />
|
||||
<el-option label="内容" value="content" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<el-scrollbar>
|
||||
<div class="document-detail-height">
|
||||
<el-row>
|
||||
<el-col
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="8"
|
||||
:lg="6"
|
||||
:xl="4"
|
||||
v-for="(item, index) in paragraphDetail"
|
||||
:key="index"
|
||||
class="p-8"
|
||||
>
|
||||
<CardBox
|
||||
shadow="hover"
|
||||
:title="item.title"
|
||||
:description="item.content"
|
||||
class="document-card cursor"
|
||||
:class="item.is_active ? '' : 'disabled'"
|
||||
:showIcon="false"
|
||||
@click="editParagraph(item)"
|
||||
>
|
||||
<div class="active-button">
|
||||
<el-switch v-model="item.is_active" @change="changeState($event, item)" />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="footer-content flex-between">
|
||||
<span> {{ numberFormat(item?.content.length) || 0 }} 个 字符 </span>
|
||||
<el-tooltip effect="dark" content="删除" placement="top">
|
||||
<el-button text @click.stop="deleteParagraph(item)" class="delete-button">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<ParagraphDialog ref="ParagraphDialogRef" :title="title" @refresh="refresh" />
|
||||
</LayoutContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import datasetApi from '@/api/dataset'
|
||||
import ParagraphDialog from './component/ParagraphDialog.vue'
|
||||
import { numberFormat } from '@/utils/utils'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import useStore from '@/stores'
|
||||
const { paragraph } = useStore()
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { datasetId, documentId }
|
||||
} = route as any
|
||||
|
||||
const ParagraphDialogRef = ref()
|
||||
const loading = ref(false)
|
||||
const documentDetail = ref<any>({})
|
||||
const paragraphDetail = ref<any[]>([])
|
||||
const title = ref('')
|
||||
const search = ref('')
|
||||
const searchType = ref('title')
|
||||
|
||||
function changeState(bool: Boolean, row: any) {
|
||||
const obj = {
|
||||
is_active: bool
|
||||
}
|
||||
loading.value = true
|
||||
paragraph
|
||||
.asyncPutParagraph(datasetId, documentId, row.id, obj)
|
||||
.then((res) => {
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function deleteParagraph(row: any) {
|
||||
MsgConfirm(`是否删除段落:${row.title} ?`, `删除后无法恢复,请谨慎操作。`, {
|
||||
confirmButtonText: '删除',
|
||||
confirmButtonClass: 'danger'
|
||||
})
|
||||
.then(() => {
|
||||
loading.value = true
|
||||
datasetApi
|
||||
.delParagraph(datasetId, documentId, row.id)
|
||||
.then(() => {
|
||||
MsgSuccess('删除成功')
|
||||
getParagraphDetail()
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function addParagraph() {
|
||||
title.value = '添加分段'
|
||||
ParagraphDialogRef.value.open()
|
||||
}
|
||||
function editParagraph(row: any) {
|
||||
title.value = '分段详情'
|
||||
ParagraphDialogRef.value.open(row)
|
||||
}
|
||||
|
||||
function getDetail() {
|
||||
loading.value = true
|
||||
datasetApi
|
||||
.getDocumentDetail(datasetId, documentId)
|
||||
.then((res) => {
|
||||
documentDetail.value = res.data
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function getParagraphDetail() {
|
||||
loading.value = true
|
||||
datasetApi
|
||||
.getParagraph(datasetId, documentId)
|
||||
.then((res) => {
|
||||
paragraphDetail.value = res.data
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
getParagraphDetail()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
getParagraphDetail()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.document-detail {
|
||||
&__header {
|
||||
position: absolute;
|
||||
right: calc(var(--app-base-px) * 3);
|
||||
}
|
||||
|
||||
.document-detail-height {
|
||||
height: calc(var(--app-main-height) - 75px);
|
||||
}
|
||||
.document-card {
|
||||
height: 210px;
|
||||
background: var(--app-layout-bg-color);
|
||||
border: 1px solid var(--app-layout-bg-color);
|
||||
&:hover {
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--el-border-color);
|
||||
}
|
||||
&.disabled {
|
||||
background: var(--app-layout-bg-color);
|
||||
border: 1px solid var(--app-layout-bg-color);
|
||||
:deep(.description) {
|
||||
color: var(--app-border-color-dark);
|
||||
}
|
||||
:deep(.title) {
|
||||
color: var(--app-border-color-dark);
|
||||
}
|
||||
}
|
||||
:deep(.description) {
|
||||
-webkit-line-clamp: 5 !important;
|
||||
height: 110px;
|
||||
}
|
||||
.active-button {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,26 +1,28 @@
|
|||
<template>
|
||||
<el-input v-model="filterText" placeholder="搜索" prefix-icon="Search" class="mb-16" />
|
||||
|
||||
<el-table :data="data" :max-height="tableHeight">
|
||||
<el-table-column prop="name" label="数据集名称" />
|
||||
<el-table-column label="管理" align="center">
|
||||
<template #header>
|
||||
<!-- <template #header>
|
||||
<el-checkbox
|
||||
v-model="allChecked[MANAGE]"
|
||||
label="管理"
|
||||
@change="handleCheckAllChange($event, MANAGE)"
|
||||
/>
|
||||
</template>
|
||||
</template> -->
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.operate[MANAGE]" @change="checkedOperateChange(MANAGE, row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="使用" align="center">
|
||||
<template #header>
|
||||
<!-- <template #header>
|
||||
<el-checkbox
|
||||
v-model="allChecked[USE]"
|
||||
label="使用"
|
||||
@change="handleCheckAllChange($event, USE)"
|
||||
/>
|
||||
</template>
|
||||
</template> -->
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.operate[USE]" @change="checkedOperateChange(USE, row)" />
|
||||
</template>
|
||||
|
|
@ -49,6 +51,8 @@ const allChecked: any = ref({
|
|||
|
||||
const tableHeight = ref(0)
|
||||
|
||||
const filterText = ref('')
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(val) => {
|
||||
|
|
@ -74,7 +78,7 @@ function handleCheckAllChange(val: string | number | boolean, Name: string | num
|
|||
}
|
||||
}
|
||||
function checkedOperateChange(Name: string | number, row: any) {
|
||||
if (Name === MANAGE) {
|
||||
if (Name === MANAGE && row.operate[MANAGE]) {
|
||||
props.data.map((item: any) => {
|
||||
if (item.id === row.id) {
|
||||
item.operate[USE] = true
|
||||
|
|
|
|||
|
|
@ -1,55 +1,49 @@
|
|||
<template>
|
||||
<LayoutContent header="团队管理">
|
||||
<LayoutContainer header="团队管理">
|
||||
<div class="team-manage flex main-calc-height">
|
||||
<div class="team-member p-15 border-r">
|
||||
<h4>团队成员</h4>
|
||||
<div class="text-right">
|
||||
<div class="team-member p-8 border-r">
|
||||
<div class="flex-between p-16">
|
||||
<h4>成员</h4>
|
||||
<el-button type="primary" link @click="addMember">
|
||||
<AppIcon iconName="app-add-users" class="add-user-icon" />添加成员
|
||||
<AppIcon iconName="app-add-users" class="add-user-icon" />
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="mt-10">
|
||||
<el-input v-model="filterText" placeholder="请输入用户名搜索" suffix-icon="Search" />
|
||||
<div class="team-member-input">
|
||||
<el-input v-model="filterText" placeholder="请输入用户名搜索" prefix-icon="Search" />
|
||||
</div>
|
||||
<div class="member-list mt-10" v-loading="loading">
|
||||
<el-scrollbar>
|
||||
<ul v-if="filterMember.length > 0">
|
||||
<template v-for="(item, index) in filterMember" :key="index">
|
||||
<li
|
||||
@click.prevent="clickMemberHandle(item.id)"
|
||||
:class="currentUser === item.id ? 'active' : ''"
|
||||
class="border-b-light flex-between p-15 cursor"
|
||||
>
|
||||
<div>
|
||||
<span class="mr-10">{{ item.username }}</span>
|
||||
<el-tag effect="dark" v-if="isManage(item.type)">所有者</el-tag>
|
||||
<el-tag effect="dark" type="warning" v-else>用户</el-tag>
|
||||
</div>
|
||||
<span @click.stop>
|
||||
<el-dropdown trigger="click" v-if="!isManage(item.type)">
|
||||
<span class="cursor">
|
||||
<el-icon><MoreFilled /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click.prevent="deleteMember(item)"
|
||||
>移除</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<common-list
|
||||
:data="filterMember"
|
||||
class="mt-8"
|
||||
v-loading="loading"
|
||||
@click="clickMemberHandle"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div class="flex-between">
|
||||
<div>
|
||||
<span class="mr-8">{{ row.username }}</span>
|
||||
<el-tag v-if="isManage(row.type)" class="default-tag">所有者</el-tag>
|
||||
<el-tag type="warning" v-else>用户</el-tag>
|
||||
</div>
|
||||
<div @click.stop style="margin-top: 5px">
|
||||
<el-dropdown trigger="click" v-if="!isManage(row.type)">
|
||||
<span class="cursor">
|
||||
<el-icon class="rotate-90"><MoreFilled /></el-icon>
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<el-empty description="暂无数据" v-else />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click.prevent="deleteMember(row)">移除</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</common-list>
|
||||
</div>
|
||||
<div class="permission-setting flex" v-loading="rLoading">
|
||||
<div class="team-manage__table p-15">
|
||||
<div class="team-manage__table p-24">
|
||||
<h4>权限设置</h4>
|
||||
<el-tabs v-model="activeName" class="demo-tabs">
|
||||
<el-tabs v-model="activeName" class="team-manage__tabs">
|
||||
<el-tab-pane
|
||||
v-for="item in settingTags"
|
||||
:key="item.value"
|
||||
|
|
@ -61,13 +55,13 @@
|
|||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<div class="team-manage__footer border-t p-15 flex">
|
||||
<div class="team-manage__footer border-t p-16 flex">
|
||||
<el-button type="primary" @click="submitPermissions">保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CreateMemberDialog ref="CreateMemberRef" @refresh="refresh" />
|
||||
</LayoutContent>
|
||||
</LayoutContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -154,13 +148,15 @@ function MemberPermissions(id: String) {
|
|||
}
|
||||
|
||||
function deleteMember(row: TeamMember) {
|
||||
MsgConfirm({
|
||||
title: `是否移除成员:${row.username}`,
|
||||
decription: '移除后将会取消成员拥有的数据集和应用权限。',
|
||||
confirmButtonText: '移除',
|
||||
}, {
|
||||
confirmButtonClass: 'danger',
|
||||
})
|
||||
MsgConfirm(
|
||||
`是否移除成员:${row.username}?`,
|
||||
'移除后将会取消成员拥有的数据集和应用权限。',
|
||||
|
||||
{
|
||||
confirmButtonText: '移除',
|
||||
confirmButtonClass: 'danger'
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
loading.value = true
|
||||
TeamApi.delTeamMember(row.id)
|
||||
|
|
@ -179,9 +175,9 @@ function isManage(type: String) {
|
|||
return type === 'manage'
|
||||
}
|
||||
|
||||
function clickMemberHandle(id: String) {
|
||||
currentUser.value = id
|
||||
MemberPermissions(id)
|
||||
function clickMemberHandle(item: any) {
|
||||
currentUser.value = item.id
|
||||
MemberPermissions(item.id)
|
||||
}
|
||||
function addMember() {
|
||||
CreateMemberRef.value?.open()
|
||||
|
|
@ -214,30 +210,30 @@ onMounted(() => {
|
|||
<style lang="scss" scoped>
|
||||
.team-manage {
|
||||
.add-user-icon {
|
||||
margin-right: 5px;
|
||||
font-size: 20px;
|
||||
font-size: 17px;
|
||||
}
|
||||
.team-member-input {
|
||||
padding: 0 calc(var(--app-base-px) * 2);
|
||||
}
|
||||
.team-member {
|
||||
box-sizing: border-box;
|
||||
width: var(--team-manage-left-width);
|
||||
min-width: var(--team-manage-left-width);
|
||||
.member-list {
|
||||
li {
|
||||
&.active {
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
}
|
||||
width: var(--setting-left-width);
|
||||
min-width: var(--setting-left-width);
|
||||
}
|
||||
|
||||
.permission-setting {
|
||||
box-sizing: border-box;
|
||||
width: calc(100% - var(--team-manage-left-width) - 5px);
|
||||
width: calc(100% - var(--setting-left-width));
|
||||
flex-direction: column;
|
||||
}
|
||||
.team-manage__table {
|
||||
|
||||
&__tabs {
|
||||
margin-top: 10px;
|
||||
}
|
||||
&__table {
|
||||
flex: 1;
|
||||
}
|
||||
.team-manage__footer {
|
||||
&__footer {
|
||||
flex: 0 0 auto;
|
||||
justify-content: right;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<LayoutContainer header="模版管理">
|
||||
<div class="template-manage flex main-calc-height">
|
||||
<div class="template-manage__left p-8 border-r">
|
||||
<h4 class="p-16">供应商</h4>
|
||||
<common-list :data="list" class="mt-8" v-loading="loading" @click="clickHandle">
|
||||
<template #default="{ row }">
|
||||
<div class="flex">
|
||||
<img src="@/assets/icon_document.svg" alt="" class="mr-8" />
|
||||
<span>{{ row.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</common-list>
|
||||
</div>
|
||||
<div class="template-manage__right p-24">
|
||||
<h4>全部模型</h4>
|
||||
</div>
|
||||
</div>
|
||||
</LayoutContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, reactive, watch } from 'vue'
|
||||
const loading = ref(false)
|
||||
const list = ref([
|
||||
{
|
||||
name: '1111'
|
||||
}
|
||||
])
|
||||
|
||||
function clickHandle(row: any) {}
|
||||
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.template-manage {
|
||||
&__left {
|
||||
box-sizing: border-box;
|
||||
width: var(--setting-left-width);
|
||||
min-width: var(--setting-left-width);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue