feat: layout
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run

This commit is contained in:
wangdan-fit2cloud 2025-04-28 18:14:16 +08:00
parent ab46d7ab61
commit 69e35b36aa
91 changed files with 6691 additions and 2089 deletions

View File

@ -13,11 +13,14 @@
"format": "prettier --write src/"
},
"dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/theme-one-dark": "^6.1.2",
"axios": "^1.8.4",
"element-plus": "^2.9.7",
"nprogress": "^0.2.0",
"pinia": "^3.0.1",
"vue": "^3.5.13",
"vue-codemirror": "^6.1.1",
"vue-i18n": "^11.1.3",
"vue-router": "^4.5.0"
},

209
ui/src/api/model/model.ts Normal file
View File

@ -0,0 +1,209 @@
import { request } from '../../request/index'
import { Result } from '@/request/Result'
import { get, post, del, put } from '@/request/index'
import { type Ref } from 'vue'
import type {
modelRequest,
Provider,
ListModelRequest,
Model,
BaseModel,
CreateModelRequest,
EditModelRequest
} from '@/api/type/model'
import type { FormField } from '@/components/dynamics-form/type'
import type { KeyValue } from '../type/common'
const prefix = '/model'
const prefix_provider = '/provider'
/**
*
* @params name, model_type, model_name
*/
const getModel: (
request?: ListModelRequest,
loading?: Ref<boolean>
) => Promise<Result<Array<Model>>> = (data, loading) => {
return get(`${prefix}`, data, loading)
}
/**
*
*/
const getProvider: (loading?: Ref<boolean>) => Promise<Result<Array<Provider>>> = (loading) => {
return get(`${prefix_provider}`, {}, loading)
}
/**
*
*/
const getProviderByModelType: (model_type: string, loading?: Ref<boolean>) => Promise<Result<Array<Provider>>> = (model_type, loading) => {
return get(`${prefix_provider}`, {model_type}, loading)
}
/**
*
* @param provider
* @param model_type
* @param model_name
* @param loading
* @returns
*/
const getModelCreateForm: (
provider: string,
model_type: string,
model_name: string,
loading?: Ref<boolean>
) => Promise<Result<Array<FormField>>> = (provider, model_type, model_name, loading) => {
return get(`${prefix_provider}/model_form`, { provider, model_type, model_name }, loading)
}
/**
*
* @param model_id id
* @param loading
* @returns
*/
const getModelParamsForm: (
model_id: string,
loading?: Ref<boolean>
) => Promise<Result<Array<FormField>>> = (model_id, loading) => {
return get(`model/${model_id}/model_params_form`, {}, loading)
}
/**
*
* @param provider
* @param loading
* @returns
*/
const listModelType: (
provider: string,
loading?: Ref<boolean>
) => Promise<Result<Array<KeyValue<string, string>>>> = (provider, loading?: Ref<boolean>) => {
return get(`${prefix_provider}/model_type_list`, { provider }, loading)
}
/**
*
* @param provider
* @param model_type
* @param loading
* @returns
*/
const listBaseModel: (
provider: string,
model_type: string,
loading?: Ref<boolean>
) => Promise<Result<Array<BaseModel>>> = (provider, model_type, loading) => {
return get(`${prefix_provider}/model_list`, { provider, model_type }, loading)
}
const listBaseModelParamsForm: (
provider: string,
model_type: string,
model_name: string,
loading?: Ref<boolean>
) => Promise<Result<Array<BaseModel>>> = (provider, model_type, model_name, loading) => {
return get(`${prefix_provider}/model_params_form`, { provider, model_type, model_name}, loading)
}
/**
*
* @param request
* @param loading
* @returns
*/
const createModel: (
request: CreateModelRequest,
loading?: Ref<boolean>
) => Promise<Result<Model>> = (request, loading) => {
return post(`${prefix}`, request, {}, loading)
}
/**
*
* @param request
* @param loading
* @returns
*/
const updateModel: (
model_id: string,
request: EditModelRequest,
loading?: Ref<boolean>
) => Promise<Result<Model>> = (model_id, request, loading) => {
return put(`${prefix}/${model_id}`, request, {}, loading)
}
/**
*
* @param request
* @param loading
* @returns
*/
const updateModelParamsForm: (
model_id: string,
request: any[],
loading?: Ref<boolean>
) => Promise<Result<Model>> = (model_id, request, loading) => {
return put(`${prefix}/${model_id}/model_params_form`, request, {}, loading)
}
/**
* id
* @param model_id id
* @param loading
* @returns
*/
const getModelById: (model_id: string, loading?: Ref<boolean>) => Promise<Result<Model>> = (
model_id,
loading
) => {
return get(`${prefix}/${model_id}`, {}, loading)
}
/**
* id
* @param model_id id
* @param loading
* @returns
*/
const getModelMetaById: (model_id: string, loading?: Ref<boolean>) => Promise<Result<Model>> = (
model_id,
loading
) => {
return get(`${prefix}/${model_id}/meta`, {}, loading)
}
/**
*
* @param model_id id
* @param loading
* @returns
*/
const pauseDownload: (model_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (
model_id,
loading
) => {
return put(`${prefix}/${model_id}/pause_download`, undefined, {}, loading)
}
const deleteModel: (model_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (
model_id,
loading
) => {
return del(`${prefix}/${model_id}`, undefined, {}, loading)
}
export default {
getModel,
getProvider,
getModelCreateForm,
listModelType,
listBaseModel,
listBaseModelParamsForm,
createModel,
updateModel,
deleteModel,
getModelById,
getModelMetaById,
pauseDownload,
getModelParamsForm,
updateModelParamsForm,
getProviderByModelType
}

View File

@ -0,0 +1,12 @@
import { Result } from '@/request/Result'
import { get, post } from '@/request/index'
import type { Ref } from 'vue'
const trigger: (
provider: string,
method: string,
request_body: any,
loading?: Ref<boolean>
) => Promise<Result<Array<any> | string>> = (provider, method, request_body, loading) => {
return post(`provider/${provider}/${method}`, {}, request_body, loading)
}
export default { trigger, get }

14
ui/src/api/type/common.ts Normal file
View File

@ -0,0 +1,14 @@
interface KeyValue<K, V> {
key: K
value: V
}
interface Dict<V> {
[propName: string]: V
}
interface pageRequest {
current_page: number
page_size: number
}
export type { KeyValue, Dict, pageRequest }

147
ui/src/api/type/model.ts Normal file
View File

@ -0,0 +1,147 @@
import type { Dict } from './common'
interface modelRequest {
name: string
model_type: string
model_name: string
}
interface Provider {
/**
*
*/
provider: string
/**
*
*/
name: string
/**
* icon
*/
icon: string
}
interface ListModelRequest {
/**
*
*/
name?: string
/**
*
*/
model_type?: string
/**
*
*/
model_name?: string
/**
*
*/
provider?: string
}
interface Model {
/**
* id
*/
id: string
/**
*
*/
name: string
/**
*
*/
model_type: string
user_id: string
username: string
permission_type: 'PUBLIC' | 'PRIVATE'
/**
*
*/
model_name: string
/**
*
*/
credential: any
/**
*
*/
provider: string
/**
*
*/
status: 'SUCCESS' | 'DOWNLOAD' | 'ERROR' | 'PAUSE_DOWNLOAD'
/**
*
*/
meta: Dict<any>
/**
*
*/
model_params_form: Dict<any>[]
}
interface CreateModelRequest {
/**
*
*/
name: string
/**
*
*/
model_type: string
/**
*
*/
model_name: string
/**
*
*/
credential: any
/**
*
*/
provider: string
}
interface EditModelRequest {
/**
*
*/
name: string
/**
*
*/
model_type: string
/**
*
*/
model_name: string
/**
*
*/
credential: any
}
interface BaseModel {
/**
*
*/
name: string
/**
*
*/
desc: string
/**
*
*/
model_type: string
}
export type {
modelRequest,
Provider,
ListModelRequest,
Model,
BaseModel,
CreateModelRequest,
EditModelRequest
}

View File

@ -7,6 +7,7 @@ interface User {
*
*/
username: string
nick_name: string
/**
*
*/
@ -14,7 +15,7 @@ interface User {
/**
*
*/
role: string
role: Array<string>
/**
*
*/
@ -25,7 +26,7 @@ interface User {
is_edit_password?: boolean
IS_XPACK?: boolean
XPACK_LICENSE_IS_VALID?: boolean
language: string
language?: string
}
interface LoginRequest {
@ -117,5 +118,5 @@ export type {
CheckCodeRequest,
ResetPasswordRequest,
User,
ResetCurrentUserPasswordRequest
ResetCurrentUserPasswordRequest,
}

View File

@ -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

View File

@ -0,0 +1,32 @@
<template>
<component
v-if="isIconfont"
:is="
Object.keys(iconMap).includes(iconName)
? iconMap[iconName].iconReader()
: iconMap['404'].iconReader()
"
class="el-icon app-icon"
>
</component>
<el-icon v-else-if="iconName">
<component :is="iconName" />
</el-icon>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { iconMap } from '@/components/app-icon/index'
defineOptions({ name: 'AppIcon' })
const props = withDefaults(
defineProps<{
iconName?: string
}>(),
{
iconName: '404'
}
)
const isIconfont = computed(() => props.iconName?.includes('app-'))
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,70 @@
import { h } from 'vue'
export default {
'app-github': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
},
[
h('path', {
d: 'M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9 23.5 23.2 38.1 55.4 38.1 91v112.5c0.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z',
fill: 'currentColor',
}),
],
),
])
},
},
'app-help': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
},
[
h('path', {
d: 'M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768z m0 85.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z m-21.333333-298.666666h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333zM343.466667 396.032c0.554667-4.778667 1.109333-8.746667 1.664-11.946667 8.32-46.293333 29.397333-80.341333 63.189333-102.144 26.453333-17.28 59.008-25.941333 97.621333-25.941333 50.730667 0 92.842667 12.288 126.378667 36.864 33.578667 24.533333 50.346667 60.928 50.346667 109.141333 0 29.568-7.253333 54.485333-21.888 74.752-8.533333 12.245333-24.917333 27.946667-49.152 47.061334l-23.893334 18.773333c-13.013333 10.24-21.632 22.186667-25.898666 35.84-1.152 3.712-2.176 10.624-3.072 20.736a21.333333 21.333333 0 0 1-21.248 19.498667h-47.786667a21.333333 21.333333 0 0 1-21.248-23.296c2.773333-29.696 5.717333-48.469333 8.832-56.362667 5.845333-14.677333 20.906667-31.573333 45.141333-50.688l24.533334-19.413333c8.106667-6.144 49.749333-35.456 49.749333-61.44 0-25.941333-4.522667-35.498667-17.578667-49.749334-13.013333-14.208-42.368-18.773333-68.864-18.773333-26.026667 0-48.256 6.869333-59.136 24.405333-5.034667 8.106667-9.173333 16.768-12.117333 25.6a89.472 89.472 0 0 0-3.114667 13.098667 21.333333 21.333333 0 0 1-21.034666 17.706667H364.672a21.333333 21.333333 0 0 1-21.205333-23.722667z',
fill: 'currentColor',
}),
],
),
])
},
},
'app-user-manual': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
},
[
h('path', {
d: 'M768 128H256a85.333333 85.333333 0 0 0-85.333333 85.333333v426.666667h512V64h85.333333v640a21.333333 21.333333 0 0 1-21.333333 21.333333H256a85.333333 85.333333 0 0 0-0.128 170.666667H832a21.333333 21.333333 0 0 0 21.333333-21.333333V341.333333h85.333334v597.333334a42.666667 42.666667 0 0 1-42.666667 42.666666H256c-94.293333 0-170.666667-76.16-170.666667-170.410666V213.248C85.333333 119.04 161.706667 42.666667 256 42.666667h469.333333a42.666667 42.666667 0 0 1 42.666667 42.666666v42.666667z',
fill: 'currentColor',
}),
h('path', {
d: 'M277.333333 768a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h469.333334a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333h-469.333334z',
fill: 'currentColor',
}),
],
),
])
},
},
}

View File

@ -0,0 +1,80 @@
import { h } from 'vue'
const iconsImport: any = import.meta.glob('./icons/*.ts', { eager: true, import: 'default' })
const dynamicIcons = Object.values(iconsImport).reduce(
(acc: Record<string, any>, module) => ({
...acc,
...(typeof module === 'object' && module !== null ? module : {}),
}),
{} as Record<string, any>,
)
export const iconMap: any = {
'app-warning': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
},
[
h('path', {
d: 'M512 234.666667A53.333333 53.333333 0 1 1 512 341.333333a53.333333 53.333333 0 0 1 0-106.666666zM522.666667 384h-64a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h21.333333v213.333334H426.666667a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h192a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333h-53.333334v-256a42.666667 42.666667 0 0 0-42.666666-42.666667z',
fill: 'currentColor',
}),
h('path', {
d: 'M512 981.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z m0-85.333333a384 384 0 1 0 0-768 384 384 0 0 0 0 768z',
fill: 'currentColor',
}),
],
),
])
},
},
'app-warning-colorful': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
},
[
h('path', {
d: 'M42.666667 512c0 259.2 210.133333 469.333333 469.333333 469.333333s469.333333-210.133333 469.333333-469.333333S771.2 42.666667 512 42.666667 42.666667 252.8 42.666667 512z m469.333333-277.333333A53.333333 53.333333 0 1 1 512 341.333333a53.333333 53.333333 0 0 1 0-106.666666zM458.666667 384h64a42.666667 42.666667 0 0 1 42.666666 42.666667v256h53.333334a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333H426.666667a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333h53.333333v-213.333334h-21.333333a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333z',
fill: '#3370FF',
}),
],
),
])
},
},
'app-copy': {
iconReader: () => {
return h('i', [
h(
'svg',
{
style: { height: '100%', width: '100%' },
viewBox: '0 0 1024 1024',
version: '1.1',
xmlns: 'http://www.w3.org/2000/svg',
},
[
h('path', {
d: 'M213.333333 341.333333v512h426.666667V341.333333H213.333333z m512-42.666666v602.069333c0 20.949333-17.834667 37.930667-39.808 37.930667H167.808C145.834667 938.666667 128 921.685333 128 900.736V293.973333C128 272.981333 145.834667 256 167.808 256H682.666667a42.666667 42.666667 0 0 1 42.666666 42.666667z m158.165334-200.832A42.538667 42.538667 0 0 1 896 128v533.333333a21.333333 21.333333 0 0 1-21.333333 21.333334h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333334V170.666667H405.333333a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334H853.333333c11.776 0 22.442667 4.778667 30.165334 12.501334z',
fill: 'currentColor',
}),
],
),
])
},
},
// 动态加载的图标
...dynamicIcons,
}

View File

@ -0,0 +1,299 @@
<template>
<div style="width: 1024px">
<DynamicsForm
v-model="form_data"
:model="form_data"
:render_data="damo_data"
ref="dynamicsFormRef"
>
<template #default="scope">
<el-form-item label="其他字段">
<el-input v-model="scope.form_value['zha']" /> </el-form-item
></template>
</DynamicsForm>
<el-button @click="click">点我校验</el-button>
</div>
</template>
<script setup lang="ts">
import type { FormField } from '@/components/dynamics-form/type'
import DynamicsForm from '@/components/dynamics-form/index.vue'
import { ref } from 'vue'
import type { Dict } from '@/api/type/common'
const damo_data: Array<FormField> = [
{ field: 'name', input_type: 'PasswordInput', label: '用戶名', required: false },
{ field: 'json_text', input_type: 'JsonInput', label: 'aa', required: false },
{
field: 'array_object_card_field',
input_type: 'ArrayObjectCard',
label: '測試',
trigger_type: 'CHILD_FORMS',
attrs: { 'label-width': '120px', 'label-suffix': ':ssss', 'label-position': 'top' },
required: false,
children: [
{ field: 'name1', input_type: 'TextInput', label: '用戶名1' },
{ field: 'name2', input_type: 'TextInput', label: '用戶名2' },
{ field: 'name3', input_type: 'TextInput', label: '用戶名3' }
]
},
{
field: 'maxkb_tokens',
input_type: 'Slider',
default_value: 1,
attrs: {
min: 0,
max: 10,
step: 1,
precision: 1,
'show-input-controls': false,
'show-input': true
},
label: { label: '温度', attrs: { tooltip: 'sss' }, input_type: 'TooltipLabel' }
},
{
field: 'object_card_field',
input_type: 'ObjectCard',
label: '測試',
trigger_type: 'CHILD_FORMS',
attrs: { 'label-width': '120px', 'label-suffix': ':ssss', 'label-position': 'left' },
required: false,
children: [
{ field: 'name1', input_type: 'TextInput', label: '用戶名1' },
{ field: 'name2', input_type: 'TextInput', label: '用戶名2' },
{ field: 'name3', input_type: 'TextInput', label: '用戶名3' }
]
},
{
field: 'tab_card_field',
input_type: 'TabCard',
label: '測試',
trigger_type: 'CHILD_FORMS',
attrs: { 'label-width': '120px', 'label-suffix': ':ssss', 'label-position': 'left' },
required: false,
relation_trigger_field_dict: {
'array_object_card_field.0.name1': ['111']
},
props_info: { tabs_label: '用户' },
children: [
{ field: 'name1', input_type: 'TextInput', label: '用戶名1' },
{ field: 'name2', input_type: 'TextInput', label: '用戶名2' },
{ field: 'name3', input_type: 'TextInput', label: '用戶名3' }
]
},
{
field: 'single_select_field',
input_type: 'SingleSelect',
label: '测试单选',
required: true,
attrs: { placeholder: '请选择' },
option_list: [
{
key: '测试',
value: 'test'
},
{
key: '测试1',
value: 'test1'
}
]
},
{
field: 'multi_select_field',
input_type: 'MultiSelect',
default_value: ['test1'],
relation_show_field_dict: {
'object_card_field.name1': []
},
label: '测试多选下拉',
required: true,
attrs: { placeholder: '请选择' },
option_list: [
{
key: '测试',
value: 'test'
},
{
key: '测试1',
value: 'test1'
}
]
},
{
field: 'radio_field',
input_type: 'Radio',
label: '测试单选',
required: true,
attrs: { placeholder: '请选择' },
option_list: [
{
key: '测试',
value: 'test'
},
{
key: '测试1',
value: 'test1'
}
]
},
{
field: 'radio_button_field',
input_type: 'RadioButton',
label: '测试单选',
required: true,
attrs: { placeholder: '请选择' },
option_list: [
{
key: '测试',
value: 'test'
},
{
key: '测试1',
value: 'test1'
}
]
},
{
field: 'radio_card_field',
input_type: 'RadioCard',
label: '测试单选1',
required: true,
attrs: { placeholder: '请选择' },
option_list: [
{
key: '测试',
value: 'test'
},
{
key: '测试111111',
value: 'test1'
}
]
},
{
field: 'table_radio_field',
input_type: 'TableRadio',
label: '表格单选',
required: true,
attrs: { placeholder: '请选择' },
props_info: {
active_msg: '当前选中',
table_columns: [
{
property: '`${row.key}${row.number}`',
label: '名称',
type: 'eval'
},
{
property: 'ProgressTableItem',
label: '数值',
type: 'component',
value_field: 'number',
attrs: {
color: [
{ color: '#f56c6c', percentage: 20 },
{ color: '#e6a23c', percentage: 40 },
{ color: '#5cb87a', percentage: 60 },
{ color: '#1989fa', percentage: 80 },
{ color: '#6f7ad3', percentage: 100 }
]
},
props_info: {
view_card: [
{
type: 'eval',
title: '测试',
value_field:
'`${parseFloat(row.number).toLocaleString("zh-CN",{style: "decimal",maximumFractionDigits:1})}%&nbsp;&nbsp;&nbsp;`'
},
{
type: 'eval',
title: '名称',
value_field: '`${row.key}&nbsp;&nbsp;&nbsp;`'
}
]
}
}
],
style: { width: '500px' }
},
option_list: [
{
key: '测试',
value: 'test',
number: 10
},
{
key: '测试111111',
value: 'test1',
number: 100
}
]
},
{
field: 'table_checkbox_field',
input_type: 'TableCheckbox',
label: '表格多选',
required: true,
attrs: { placeholder: '请选择' },
props_info: {
active_msg: '当前选中',
table_columns: [
{
property: '`${row.key}${row.number}`',
label: '名称',
type: 'eval'
},
{
property: 'ProgressTableItem',
label: '数值',
type: 'component',
value_field: 'number',
attrs: {
color: [
{ color: '#f56c6c', percentage: 20 },
{ color: '#e6a23c', percentage: 40 },
{ color: '#5cb87a', percentage: 60 },
{ color: '#1989fa', percentage: 80 },
{ color: '#6f7ad3', percentage: 100 }
]
},
props_info: {
view_card: [
{
type: 'eval',
title: '测试',
value_field:
'`${parseFloat(row.number).toLocaleString("zh-CN",{style: "decimal",maximumFractionDigits:1})}%&nbsp;&nbsp;&nbsp;`'
},
{
type: 'eval',
title: '名称',
value_field: '`${row.key}&nbsp;&nbsp;&nbsp;`'
}
]
}
}
],
style: { width: '500px' }
},
option_list: [
{
key: '测试',
value: 'test',
number: 10
},
{
key: '测试111111',
value: 'test1',
number: 100
}
]
}
]
const form_data = ref<Dict<any>>({})
const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()
const click = () => {
dynamicsFormRef.value?.validate()
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,55 @@
<template>
<el-row :gutter="12">
<el-col :span="12">
<el-card shadow="never">
<DynamicsFormConstructor
v-model="item"
label-position="top"
require-asterisk-position="right"
ref="DynamicsFormConstructorRef"
></DynamicsFormConstructor>
<el-button @click="add_field">添加</el-button>
</el-card></el-col
>
<el-col :span="12">
<el-card shadow="never">
<DynamicsForm
label-position="top"
require-asterisk-position="right"
v-model="form_data"
:model="form_data"
:render_data="form_item_list"
ref="dynamicsFormRef"
>
</DynamicsForm>
<el-button @click="validate">校验</el-button>
</el-card></el-col
>
</el-row>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import DynamicsFormConstructor from '@/components/dynamics-form/constructor/index.vue'
import DynamicsForm from '@/components/dynamics-form/index.vue'
const DynamicsFormConstructorRef = ref<InstanceType<typeof DynamicsFormConstructor>>()
const form_item_list = ref<Array<any>>([])
const add_field = () => {
if (DynamicsFormConstructorRef.value) {
DynamicsFormConstructorRef.value.validate().then(() => {
form_item_list.value.push(DynamicsFormConstructorRef.value?.getData())
})
}
}
const form_data = ref({})
const item = ref({})
const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()
const validate = () => {
dynamicsFormRef.value
?.validate()
.then((ok) => {})
.catch((e) => {})
}
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,184 @@
<template>
<el-form-item
v-loading="loading"
:style="formItemStyle"
:prop="formfield.field"
:key="formfield.field"
:rules="rules"
>
<template #label v-if="formfield.label">
<FormItemLabel v-if="isString(formfield.label)" :form-field="formfield"></FormItemLabel>
<component
v-else
:is="formfield.label.input_type"
:label="formfield.label.label"
v-bind="label_attrs"
></component>
</template>
<component
ref="componentFormRef"
:view="view"
v-model="itemValue"
:is="formfield.input_type"
:form-field="formfield"
:other-params="otherParams"
:style="componentStyle"
:field="formfield.field"
v-bind="attrs"
:formfield-list="formfieldList"
></component>
</el-form-item>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, type Ref } from 'vue'
import type { FormField } from '@/components/dynamics-form/type'
import FormItemLabel from './FormItemLabel.vue'
import type { Dict } from '@/api/type/common'
import bus from '@/utils/bus'
import { t } from '@/locales'
const props = defineProps<{
//
modelValue: any
// Item
formfield: FormField
//
view: boolean
//
otherParams: any
// Options
trigger: (formItem: FormField, loading: Ref<boolean>) => Promise<any>
//
initDefaultData: (formItem: FormField) => void
//
defaultItemWidth: string
//
formValue: Dict<any>
formfieldList: Array<FormField>
parent_field?: string
}>()
const emit = defineEmits(['change'])
const loading = ref<boolean>(false)
const isString = (value: any) => {
return typeof value === 'string'
}
const itemValue = computed({
get: () => {
return props.modelValue
},
set: (value: any) => {
emit('change', value)
if (props.parent_field) {
bus.emit(props.parent_field + '.' + props.formfield.field, value)
} else {
bus.emit(props.formfield.field, value)
}
}
})
const componentFormRef = ref<any>()
const label_attrs = computed(() => {
return props.formfield.label &&
typeof props.formfield.label !== 'string' &&
props.formfield.label.attrs
? props.formfield.label.attrs
: {}
})
const props_info = computed(() => {
return props.formfield.props_info ? props.formfield.props_info : {}
})
/**
* 表单 item style
*/
const formItemStyle = computed(() => {
return props_info.value.item_style ? props_info.value.item_style : {}
})
/**
* 表单错误Msg
*/
const errMsg = computed(() => {
return props_info.value.err_msg
? props_info.value.err_msg
: isString(props.formfield.label)
? props.formfield.label + ' ' + t('dynamicsForm.tip.requiredMessage')
: props.formfield.label.label + ' ' + t('dynamicsForm.tip.requiredMessage')
})
/**
* 反序列化
* @param rule
*/
const to_rule = (rule: any) => {
if (rule.validator) {
let validator = (rule: any, value: string, callback: any) => {}
eval(rule.validator)
return { ...rule, validator }
}
return rule
}
/**
* 校验
*/
const rules = computed(() => {
return props_info.value.rules
? props_info.value.rules.map(to_rule)
: {
message: errMsg.value,
trigger: props.formfield.input_type === 'Slider' ? 'blur' : ['blur', 'change'],
required: props.formfield.required === false ? false : true
}
})
/**
* 组件样式
*/
const componentStyle = computed(() => {
return props_info.value.style ? props_info.value.style : {}
})
/**
* 组件attrs
*/
const attrs = computed(() => {
return props.formfield.attrs ? props.formfield.attrs : {}
})
onMounted(() => {
props.initDefaultData(props.formfield)
if (props.formfield.provider && props.formfield.method) {
props.trigger(props.formfield, loading)
}
//
const trigger_field_dict = props.formfield.relation_trigger_field_dict
if (trigger_field_dict) {
const keys = Object.keys(trigger_field_dict)
keys.forEach((key) => {
const value = trigger_field_dict[key]
//
bus.on(key, (v: any) => {
if (value && value.length > 0) {
if (value.includes(v)) {
props.trigger(props.formfield, loading)
}
} else {
props.trigger(props.formfield, loading)
}
})
})
}
})
const validate = () => {
if (props.formfield.trigger_type === 'CHILD_FORMS' && componentFormRef.value) {
return componentFormRef.value.validate()
}
return Promise.resolve()
}
defineExpose({ validate })
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,11 @@
<template>
{{ formField.label }}
</template>
<script setup lang="ts">
import type { FormField } from '@/components/dynamics-form/type'
const props = defineProps<{
// Item
formField: FormField
}>()
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,44 @@
import { t } from '@/locales'
const input_type_list = [
{
label: t('dynamicsForm.input_type_list.TextInput'),
value: 'TextInput'
},
{
label: t('dynamicsForm.input_type_list.PasswordInput'),
value: 'PasswordInput'
},
{
label: t('dynamicsForm.input_type_list.Slider'),
value: 'Slider'
},
{
label: t('dynamicsForm.input_type_list.SwitchInput'),
value: 'SwitchInput'
},
{
label: t('dynamicsForm.input_type_list.SingleSelect'),
value: 'SingleSelect'
},
{
label: t('dynamicsForm.input_type_list.MultiSelect'),
value: 'MultiSelect'
},
{
label: t('dynamicsForm.input_type_list.DatePicker'),
value: 'DatePicker'
},
{
label: t('dynamicsForm.input_type_list.JsonInput'),
value: 'JsonInput'
},
{
label: t('dynamicsForm.input_type_list.RadioCard'),
value: 'RadioCard'
},
{
label: t('dynamicsForm.input_type_list.RadioRow'),
value: 'RadioRow'
}
]
export { input_type_list }

View File

@ -0,0 +1,144 @@
<template>
<el-form
@submit.prevent
ref="ruleFormRef"
class="mb-24"
label-width="auto"
:model="form_data"
v-bind="$attrs"
>
<el-form-item :label="$t('dynamicsForm.paramForm.field.label')" :required="true" prop="field" :rules="rules.field">
<el-input
v-model="form_data.field"
:maxlength="64"
:placeholder="$t('dynamicsForm.paramForm.field.placeholder')"
show-word-limit
/>
</el-form-item>
<el-form-item :label="$t('dynamicsForm.paramForm.name.label')" :required="true" prop="label" :rules="rules.label">
<el-input
v-model="form_data.label"
:maxlength="64"
show-word-limit
:placeholder="$t('dynamicsForm.paramForm.name.placeholder')"
/>
</el-form-item>
<el-form-item :label="$t('dynamicsForm.paramForm.tooltip.label')">
<el-input
v-model="form_data.tooltip"
:maxlength="128"
show-word-limit
:placeholder="$t('dynamicsForm.paramForm.tooltip.placeholder')"
/>
</el-form-item>
<el-form-item :label="$t('dynamicsForm.paramForm.required.label')" :required="true" prop="required" :rules="rules.required">
<el-switch v-model="form_data.required" :active-value="true" :inactive-value="false" />
</el-form-item>
<el-form-item :label="$t('dynamicsForm.paramForm.input_type.label')" :required="true" prop="input_type" :rules="rules.input_type">
<el-select v-model="form_data.input_type" :placeholder="$t('dynamicsForm.paramForm.input_type.placeholder')">
<el-option
v-for="input_type in input_type_list"
:key="input_type.value"
:label="input_type.label"
:value="input_type.value"
/>
</el-select>
</el-form-item>
<component
v-if="form_data.input_type"
ref="componentFormRef"
v-model="form_data"
:is="form_data.input_type"
></component>
</el-form>
</template>
<script setup lang="ts">
import { onMounted, ref, nextTick } from 'vue'
import type { FormInstance } from 'element-plus'
import _ from 'lodash'
import { input_type_list as input_type_list_data } from '@/components/dynamics-form/constructor/data'
import { t } from '@/locales'
const props = withDefaults(
defineProps<{
modelValue?: any
input_type_list?: Array<{ label: string; value: string }>
}>(),
{
input_type_list: () =>
input_type_list_data.map((item) => ({ label: item.label, value: item.value + 'Constructor' }))
}
)
const emit = defineEmits(['update:modelValue'])
const ruleFormRef = ref<FormInstance>()
const componentFormRef = ref<any>()
const form_data = ref<any>({
label: '',
field: '',
tooltip: '',
required: false,
input_type: ''
})
const rules = {
label: [{ required: true, message: t('dynamicsForm.paramForm.name.requiredMessage') }],
field: [{ required: true, message: t('dynamicsForm.paramForm.field.requiredMessage') }],
required: [{ required: true, message: t('dynamicsForm.paramForm.required.requiredMessage') }],
input_type: [{ required: true, message: t('dynamicsForm.paramForm.input_type.requiredMessage') }]
}
const getData = () => {
let label: string | any = form_data.value.label
if (form_data.value.tooltip) {
label = {
input_type: 'TooltipLabel',
label: form_data.value.label,
attrs: { tooltip: form_data.value.tooltip },
props_info: {}
}
}
return {
label: label,
required: form_data.value.required,
field: form_data.value.field,
default_value: form_data.value.default_value,
show_default_value: form_data.value.show_default_value,
...componentFormRef.value.getData()
}
}
const validate = () => {
if (ruleFormRef.value) {
return ruleFormRef.value?.validate()
}
return Promise.resolve()
}
onMounted(() => {
if (props.modelValue) {
rander(props.modelValue)
}
})
const rander = (data: any) => {
form_data.value.required = data.required ? data.required : false
form_data.value.field = data.field
if (data.show_default_value !== undefined) {
form_data.value.show_default_value = data.show_default_value
}
if (data.input_type) {
form_data.value.input_type = data.input_type + 'Constructor'
}
if (data.label && data.label.input_type === 'TooltipLabel') {
form_data.value.tooltip = data.label.attrs.tooltip
form_data.value.label = data.label.label
} else {
form_data.value.label = data.label
}
nextTick(() => {
componentFormRef.value?.rander(data)
})
}
defineExpose({ getData, validate, rander })
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,138 @@
<template>
<el-form-item :label="$t('dynamicsForm.DatePicker.dataType.label')" required>
<el-select
@change="type_change"
v-model="formValue.type"
:placeholder="$t('dynamicsForm.DatePicker.dataType.placeholder')"
>
<el-option
v-for="input_type in type_list"
:key="input_type.value"
:label="input_type.label"
:value="input_type.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="$t('dynamicsForm.DatePicker.format.label')" required>
<el-select
v-model="formValue.format"
filterable
default-first-option
allow-create
:placeholder="$t('dynamicsForm.DatePicker.format.placeholder')"
>
<el-option
v-for="input_type in type_dict[formValue.type]"
:key="input_type.value"
:label="input_type.value"
:value="input_type.value"
/>
</el-select>
</el-form-item>
<el-form-item
class="defaultValueItem"
:required="formValue.required"
prop="default_value"
:label="$t('dynamicsForm.default.label')"
:rules="
formValue.required
? [{ required: true, message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}` }]
: []
"
>
<div class="defaultValueCheckbox">
<el-checkbox
v-model="formValue.show_default_value"
:label="$t('dynamicsForm.default.show')"
/>
</div>
<el-date-picker
v-model="formValue.default_value"
:type="formValue.type"
:placeholder="$t('dynamicsForm.DatePicker.placeholder')"
:format="formValue.format"
:value-format="formValue.format"
/>
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onBeforeMount } from 'vue'
import { t } from '@/locales'
const type_list = [
{
label: t('dynamicsForm.DatePicker.year'),
value: 'year'
},
{
label: t('dynamicsForm.DatePicker.month'),
value: 'month'
},
{
label: t('dynamicsForm.DatePicker.date'),
value: 'date'
},
{
label: t('dynamicsForm.DatePicker.datetime'),
value: 'datetime'
}
]
const type_dict: any = {
year: [{ value: 'YYYY' }],
month: [{ value: 'YYYY-MM' }],
date: [{ value: 'YYYY-MM-DD' }],
datetime: [{ value: 'YYYY-MM-DD HH:mm:ss' }]
}
const type_change = () => {
formValue.value.format = type_dict[formValue.value.type][0].value
formValue.value.default_value = ''
}
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
const getData = () => {
return {
input_type: 'DatePicker',
attrs: {
type: formValue.value.type,
format: formValue.value.format,
'value-format': formValue.value.format
},
default_value: formValue.value.default_value,
show_default_value: formValue.value.show_default_value
}
}
const rander = (form_data: any) => {
formValue.value.type = form_data.attrs.type
formValue.value.format = form_data.attrs?.format
formValue.value.default_value = form_data.default_value || ''
}
defineExpose({ getData, rander })
onBeforeMount(() => {
formValue.value.type = 'datetime'
formValue.value.format = 'YYYY-MM-DD HH:mm:ss'
formValue.value.default_value = ''
if (formValue.value.show_default_value === undefined) {
formValue.value.show_default_value = true
}
})
</script>
<style lang="scss" scoped>
.defaultValueItem {
position: relative;
.defaultValueCheckbox {
position: absolute;
right: 0;
top: -35px;
}
}
</style>

View File

@ -0,0 +1,84 @@
<template>
<el-form-item
class="defaultValueItem"
:label="$t('dynamicsForm.default.label')"
:required="formValue.required"
prop="default_value"
:rules="[default_value_rule]"
>
<div class="defaultValueCheckbox">
<el-checkbox
v-model="formValue.show_default_value"
:label="$t('dynamicsForm.default.show')"
/>
</div>
<JsonInput ref="jsonInputRef" v-model="formValue.default_value"> </JsonInput>
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import JsonInput from '@/components/dynamics-form/items/JsonInput.vue'
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
const jsonInputRef = ref<InstanceType<typeof JsonInput>>()
const getData = () => {
return {
input_type: 'JsonInput',
attrs: {},
props_info: {
rules: [
{
required: formValue.value.required,
validator: `validator = (rule, value, callback) => {
return componentFormRef.value?.validate_rules(rule, value, callback);
}`,
trigger: 'blur'
}
]
},
default_value: formValue.value.default_value,
show_default_value: formValue.value.show_default_value
}
}
const default_value_rule = {
required: true,
validator: (rule: any, value: any, callback: any) => {
jsonInputRef.value?.validate_rules(rule, value, callback)
return true
},
trigger: 'blur'
}
const rander = (form_data: any) => {
formValue.value.default_value = form_data.default_value
}
defineExpose({ getData, rander })
onMounted(() => {
formValue.value.default_value = {}
if (formValue.value.show_default_value === undefined) {
formValue.value.show_default_value = true
}
})
</script>
<style lang="scss" scoped>
.defaultValueItem {
position: relative;
.defaultValueCheckbox {
position: absolute;
right: 0;
top: -35px;
}
}
</style>

View File

@ -0,0 +1,156 @@
<template>
<el-form-item>
<template #label>
<div class="flex-between">
{{ $t('dynamicsForm.Select.label') }}
<el-button link type="primary" @click.stop="addOption()">
<el-icon class="mr-4">
<Plus />
</el-icon>
{{ $t('common.add') }}
</el-button>
</div>
</template>
<el-row style="width: 100%" :gutter="10">
<el-col :span="10">
<div class="grid-content ep-bg-purple" />
{{ $t('dynamicsForm.tag.label') }}
</el-col>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
{{ $t('dynamicsForm.Select.label') }}</el-col
>
</el-row>
<el-row
style="width: 100%"
v-for="(option, $index) in formValue.option_list"
:key="$index"
:gutter="10"
class="mb-8"
>
<el-col :span="10"
><div class="grid-content ep-bg-purple" />
<el-input v-model="formValue.option_list[$index].label" :placeholder="$t('dynamicsForm.tag.placeholder')"
/></el-col>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
<el-input
v-model="formValue.option_list[$index].value"
:placeholder="$t('dynamicsForm.Select.label')"
/></el-col>
<el-col :span="1"
><div class="grid-content ep-bg-purple" />
<el-button link class="ml-8" @click.stop="delOption($index)">
<el-icon>
<Delete />
</el-icon> </el-button
></el-col>
</el-row>
</el-form-item>
<el-form-item
class="defaultValueItem"
:label="$t('dynamicsForm.default.label')"
:required="formValue.required"
prop="default_value"
:rules="
formValue.required
? [{ required: true, message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}` }]
: []
"
>
<div class="defaultValueCheckbox">
<el-checkbox
v-model="formValue.show_default_value"
:label="$t('dynamicsForm.default.show')"
/>
</div>
<el-select
class="m-2"
multiple
collapse-tags
filterable
clearable
v-model="formValue.default_value"
:teleported="false"
popper-class="default-select"
>
<el-option
v-for="(option, index) in formValue.option_list"
:key="index"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onMounted } from 'vue'
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
const addOption = () => {
formValue.value.option_list.push({ value: '', label: '' })
}
const delOption = (index: number) => {
const option = formValue.value.option_list[index]
if (option.value && formValue.value.default_value == option.value) {
formValue.value.default_value = ''
}
formValue.value.option_list.splice(index, 1)
}
const getData = () => {
return {
input_type: 'MultiSelect',
attrs: {},
default_value: formValue.value.default_value,
show_default_value: formValue.value.show_default_value,
text_field: 'label',
value_field: 'value',
option_list: formValue.value.option_list
}
}
const rander = (form_data: any) => {
formValue.value.option_list = form_data.option_list || []
formValue.value.default_value = form_data.default_value
}
defineExpose({ getData, rander })
onMounted(() => {
formValue.value.option_list = []
formValue.value.default_value = ''
if (formValue.value.show_default_value === undefined) {
formValue.value.show_default_value = true
}
addOption()
})
</script>
<style lang="scss" scoped>
.defaultValueItem {
position: relative;
.defaultValueCheckbox {
position: absolute;
right: 0;
top: -35px;
}
}
:deep(.el-form-item__label) {
display: block;
}
:deep(.el-select-dropdown) {
max-width: 400px;
}
</style>

View File

@ -0,0 +1,194 @@
<template>
<el-form-item :label="$t('dynamicsForm.TextInput.length.label')" required>
<el-row class="w-full">
<el-col :span="11">
<el-form-item
:rules="[
{
required: true,
message: $t('dynamicsForm.TextInput.length.minRequired'),
trigger: 'change'
}
]"
prop="minlength"
>
<el-input-number
style="width: 100%"
:min="1"
:step="1"
step-strictly
v-model="formValue.minlength"
controls-position="right"
/>
</el-form-item>
</el-col>
<el-col :span="2" class="text-center">
<span>-</span>
</el-col>
<el-col :span="11">
<el-form-item
:rules="[
{
required: true,
message: $t('dynamicsForm.TextInput.length.maxRequired'),
trigger: 'change'
}
]"
prop="maxlength"
>
<el-input-number
style="width: 100%"
:min="formValue.minlength > formValue.maxlength ? formValue.minlength : 1"
step-strictly
:step="1"
v-model="formValue.maxlength"
controls-position="right"
/>
</el-form-item>
</el-col>
</el-row>
</el-form-item>
<el-form-item
class="defaultValueItem"
:required="formValue.required"
prop="default_value"
:label="$t('dynamicsForm.default.label')"
:rules="
formValue.required ? [{ required: true, message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}` }, ...rules] : rules
"
>
<div class="defaultValueCheckbox">
<el-checkbox
v-model="formValue.show_default_value"
:label="$t('dynamicsForm.default.show')"
/>
</div>
<el-input
v-model="formValue.default_value"
:maxlength="formValue.maxlength"
:minlength="formValue.minlength"
:placeholder="$t('dynamicsForm.default.placeholder')"
show-word-limit
type="password"
show-password
/>
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onMounted, watch } from 'vue'
import { t } from '@/locales'
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
watch(
() => formValue.value.minlength,
() => {
if (formValue.value.minlength > formValue.value.maxlength) {
formValue.value.maxlength = formValue.value.minlength
}
}
)
const getData = () => {
return {
input_type: 'PasswordInput',
attrs: {
maxlength: formValue.value.maxlength,
minlength: formValue.value.minlength,
'show-word-limit': true,
type: 'password',
'show-password': true
},
default_value: formValue.value.default_value,
show_default_value: formValue.value.show_default_value,
props_info: {
rules: formValue.value.required
? [
{
required: true,
message: `${formValue.value.label} ${t('dynamicsForm.default.requiredMessage')}`
},
{
min: formValue.value.minlength,
max: formValue.value.maxlength,
message: `${formValue.value.label}${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,
trigger: 'blur'
}
]
: [
{
min: formValue.value.minlength,
max: formValue.value.maxlength,
message: `${formValue.value.label}${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,
trigger: 'blur'
}
]
}
}
}
const rander = (form_data: any) => {
const attrs = form_data.attrs || {}
formValue.value.minlength = attrs.minlength
formValue.value.maxlength = attrs.maxlength
formValue.value.default_value = form_data.default_value
formValue.value.show_default_value = form_data.show_default_value
formValue.value.show_password = attrs['show-password']
}
const rangeRules = [
{
required: true,
validator: (rule: any, value: any, callback: any) => {
if (!formValue.value.minlength) {
callback(new Error(t('dynamicsForm.TextInput.length.requiredMessage4')))
}
if (!formValue.value.maxlength) {
callback(new Error(t('dynamicsForm.TextInput.length.requiredMessage4')))
}
return true
},
message: `${formValue.value.label} ${t('dynamicsForm.default.requiredMessage')}`
}
]
const rules = computed(() => [
{
min: formValue.value.minlength,
max: formValue.value.maxlength,
message: `${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,
trigger: 'blur'
}
])
defineExpose({ getData, rander })
onMounted(() => {
formValue.value.minlength = 0
formValue.value.maxlength = 200
formValue.value.default_value = ''
formValue.value.show_password = true
if (formValue.value.show_default_value === undefined) {
formValue.value.show_default_value = true
}
})
</script>
<style lang="scss" scoped>
.defaultValueItem {
position: relative;
.defaultValueCheckbox {
position: absolute;
right: 0;
top: -35px;
}
}
</style>

View File

@ -0,0 +1,149 @@
<template>
<el-form-item>
<template #label>
<div class="flex-between">
{{ $t('dynamicsForm.Select.label') }}
<el-button link type="primary" @click.stop="addOption()">
<el-icon class="mr-4">
<Plus />
</el-icon>
{{ $t('common.add') }}
</el-button>
</div>
</template>
<el-row style="width: 100%" :gutter="10">
<el-col :span="10">
<div class="grid-content ep-bg-purple" />
{{ $t('dynamicsForm.tag.label') }}
</el-col>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
{{ $t('dynamicsForm.Select.label') }}</el-col
>
</el-row>
<el-row
style="width: 100%"
v-for="(option, $index) in formValue.option_list"
:key="$index"
:gutter="10"
class="mb-8"
>
<el-col :span="10"
><div class="grid-content ep-bg-purple" />
<el-input v-model="formValue.option_list[$index].label" :placeholder="$t('dynamicsForm.tag.placeholder')"
/></el-col>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
<el-input
v-model="formValue.option_list[$index].value"
:placeholder="$t('dynamicsForm.Select.label')"
/></el-col>
<el-col :span="1"
><div class="grid-content ep-bg-purple" />
<el-button link class="ml-8" @click.stop="delOption($index)">
<el-icon>
<Delete />
</el-icon> </el-button
></el-col>
</el-row>
</el-form-item>
<el-form-item
class="defaultValueItem"
:label="$t('dynamicsForm.default.label')"
:required="formValue.required"
prop="default_value"
:rules="
formValue.required
? [{ required: true, message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}` }]
: []
"
>
<div class="defaultValueCheckbox">
<el-checkbox
v-model="formValue.show_default_value"
:label="$t('dynamicsForm.default.show')"
/>
</div>
<RadioCard
:form-field="formField"
v-model="formValue.default_value"
:other-params="{}"
field="default_value"
>
</RadioCard>
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import RadioCard from '@/components/dynamics-form/items/radio/RadioCard.vue'
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
const addOption = () => {
formValue.value.option_list.push({ value: '', label: '' })
}
const delOption = (index: number) => {
const option = formValue.value.option_list[index]
if (option.value && formValue.value.default_value == option.value) {
formValue.value.default_value = ''
}
formValue.value.option_list.splice(index, 1)
}
const formField = computed(() => {
return { field: '', ...getData() }
})
const getData = () => {
return {
input_type: 'RadioCard',
attrs: {},
default_value: formValue.value.default_value,
show_default_value: formValue.value.show_default_value,
text_field: 'label',
value_field: 'value',
option_list: formValue.value.option_list
}
}
const rander = (form_data: any) => {
formValue.value.option_list = form_data.option_list || []
formValue.value.default_value = form_data.default_value
}
defineExpose({ getData, rander })
onMounted(() => {
formValue.value.option_list = []
formValue.value.default_value = ''
if (formValue.value.show_default_value === undefined) {
formValue.value.show_default_value = true
}
addOption()
})
</script>
<style lang="scss" scoped>
.defaultValueItem {
position: relative;
.defaultValueCheckbox {
position: absolute;
right: 0;
top: -35px;
}
}
:deep(.el-form-item__label) {
display: block;
}
:deep(.el-select-dropdown) {
max-width: 400px;
}
</style>

View File

@ -0,0 +1,151 @@
<template>
<el-form-item>
<template #label>
<div class="flex-between">
{{ $t('dynamicsForm.Select.label') }}
<el-button link type="primary" @click.stop="addOption()">
<el-icon class="mr-4">
<Plus />
</el-icon>
{{ $t('common.add') }}
</el-button>
</div>
</template>
<el-row style="width: 100%" :gutter="10">
<el-col :span="10">
<div class="grid-content ep-bg-purple" />
{{ $t('dynamicsForm.tag.label') }}
</el-col>
<el-col :span="12">
<div class="grid-content ep-bg-purple" />
{{ $t('dynamicsForm.Select.label') }}
</el-col>
</el-row>
<el-row
style="width: 100%"
v-for="(option, $index) in formValue.option_list"
:key="$index"
:gutter="10"
class="mb-8"
>
<el-col :span="10"
><div class="grid-content ep-bg-purple" />
<el-input
v-model="formValue.option_list[$index].label"
:placeholder="$t('dynamicsForm.tag.placeholder')"
/></el-col>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
<el-input
v-model="formValue.option_list[$index].value"
:placeholder="$t('dynamicsForm.Select.label')"
/></el-col>
<el-col :span="1"
><div class="grid-content ep-bg-purple" />
<el-button link class="ml-8" @click.stop="delOption($index)">
<el-icon>
<Delete />
</el-icon> </el-button
></el-col>
</el-row>
</el-form-item>
<el-form-item
class="defaultValueItem"
:label="$t('dynamicsForm.default.label')"
:required="formValue.required"
prop="default_value"
:rules="
formValue.required
? [{ required: true, message:`${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}` }]
: []
"
>
<div class="defaultValueCheckbox">
<el-checkbox
v-model="formValue.show_default_value"
:label="$t('dynamicsForm.default.show')"
/>
</div>
<RadioRow
:form-field="formField"
v-model="formValue.default_value"
:other-params="{}"
field="default_value"
>
</RadioRow>
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import RadioRow from '@/components/dynamics-form/items/radio/RadioRow.vue'
import type { FormField } from '@/components/dynamics-form/type'
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
const addOption = () => {
formValue.value.option_list.push({ value: '', label: '' })
}
const delOption = (index: number) => {
const option = formValue.value.option_list[index]
if (option.value && formValue.value.default_value == option.value) {
formValue.value.default_value = ''
}
formValue.value.option_list.splice(index, 1)
}
const formField = computed<FormField>(() => {
return { field: '', ...getData() }
})
const getData = () => {
return {
input_type: 'RadioRow',
attrs: {},
default_value: formValue.value.default_value,
text_field: 'label',
value_field: 'value',
option_list: formValue.value.option_list
}
}
const rander = (form_data: any) => {
formValue.value.option_list = form_data.option_list || []
formValue.value.default_value = form_data.default_value
}
defineExpose({ getData, rander })
onMounted(() => {
formValue.value.option_list = []
formValue.value.default_value = ''
if (formValue.value.show_default_value === undefined) {
formValue.value.show_default_value = true
}
addOption()
})
</script>
<style lang="scss" scoped>
.defaultValueItem {
position: relative;
.defaultValueCheckbox {
position: absolute;
right: 0;
top: -35px;
}
}
:deep(.el-form-item__label) {
display: block;
}
:deep(.el-select-dropdown) {
max-width: 400px;
}
</style>

View File

@ -0,0 +1,150 @@
<template>
<el-form-item>
<template #label>
<div class="flex-between">
{{ $t('dynamicsForm.Select.label') }}
<el-button link type="primary" @click.stop="addOption()">
<el-icon class="mr-4">
<Plus />
</el-icon>
{{ $t('common.add') }}
</el-button>
</div>
</template>
<el-row style="width: 100%" :gutter="10">
<el-col :span="10"
><div class="grid-content ep-bg-purple" />
{{ $t('dynamicsForm.tag.label') }}</el-col
>
<el-col :span="12">
<div class="grid-content ep-bg-purple" />
{{ $t('dynamicsForm.Select.label') }}
</el-col>
</el-row>
<el-row
style="width: 100%"
v-for="(option, $index) in formValue.option_list"
:key="$index"
:gutter="10"
class="mb-8"
>
<el-col :span="10"
><div class="grid-content ep-bg-purple" />
<el-input v-model="formValue.option_list[$index].label" :placeholder="$t('dynamicsForm.tag.placeholder')"
/></el-col>
<el-col :span="12"
><div class="grid-content ep-bg-purple" />
<el-input
v-model="formValue.option_list[$index].value"
:placeholder="$t('dynamicsForm.Select.label')"
/></el-col>
<el-col :span="1"
><div class="grid-content ep-bg-purple" />
<el-button link class="ml-8" @click.stop="delOption($index)">
<el-icon>
<Delete />
</el-icon> </el-button
></el-col>
</el-row>
</el-form-item>
<el-form-item
class="defaultValueItem"
:required="formValue.required"
prop="default_value"
:label="$t('dynamicsForm.default.label')"
:rules="
formValue.required
? [{ required: true, message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}` }]
: []
"
>
<div class="defaultValueCheckbox">
<el-checkbox
v-model="formValue.show_default_value"
:label="$t('dynamicsForm.default.show')"
/>
</div>
<el-select v-model="formValue.default_value" :teleported="false" popper-class="default-select">
<el-option
v-for="(option, index) in formValue.option_list"
:key="index"
:label="option.label"
:value="option.value"
/>
</el-select>
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onMounted } from 'vue'
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
const addOption = () => {
formValue.value.option_list.push({ value: '', label: '' })
}
const delOption = (index: number) => {
const option = formValue.value.option_list[index]
if (option.value && formValue.value.default_value == option.value) {
formValue.value.default_value = ''
}
formValue.value.option_list.splice(index, 1)
}
const getData = () => {
return {
input_type: 'SingleSelect',
attrs: {},
default_value: formValue.value.default_value,
show_default_value: formValue.value.show_default_value,
text_field: 'label',
value_field: 'value',
option_list: formValue.value.option_list
}
}
const rander = (form_data: any) => {
formValue.value.option_list = form_data.option_list || []
formValue.value.default_value = form_data.default_value
formValue.value.show_default_value = form_data.show_default_value
}
defineExpose({ getData, rander })
onMounted(() => {
formValue.value.option_list = []
formValue.value.default_value = ''
if (formValue.value.show_default_value === undefined) {
formValue.value.show_default_value = true
}
addOption()
})
</script>
<style lang="scss" scoped>
.defaultValueItem {
position: relative;
.defaultValueCheckbox {
position: absolute;
right: 0;
top: -35px;
}
}
:deep(.el-form-item__label) {
display: block;
}
:deep(.el-select-dropdown) {
max-width: 400px;
}
</style>

View File

@ -0,0 +1,162 @@
<template>
<el-form-item :label="$t('dynamicsForm.Slider.showInput.label')" required prop="showInput">
<el-switch v-model="formValue.showInput" />
</el-form-item>
<el-form-item :label="$t('dynamicsForm.Slider.valueRange.label')" required>
<el-col :span="11" style="padding-left: 0">
<el-form-item
:rules="[
{
required: true,
message: $t('dynamicsForm.Slider.valueRange.minRequired'),
trigger: 'change'
}
]"
prop="min"
>
<el-input-number style="width: 100%" v-model="formValue.min" controls-position="right"
/></el-form-item>
</el-col>
<el-col :span="2" class="text-center">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<el-form-item
:rules="[
{
required: true,
message: $t('dynamicsForm.Slider.valueRange.maxRequired'),
trigger: 'change'
}
]"
prop="max"
><el-input-number
prop="max"
style="width: 100%"
v-model="formValue.max"
:min="formValue.min > formValue.max ? formValue.min : undefined"
controls-position="right"
/></el-form-item>
</el-col>
</el-form-item>
<el-col :span="11" style="padding-left: 0">
<el-form-item
:label="$t('dynamicsForm.Slider.step.label')"
required
prop="step"
:rules="step_rules"
>
<el-input-number
style="width: 100%"
v-model="formValue.step"
:min="0"
controls-position="right"
/>
</el-form-item>
</el-col>
<el-form-item
:label="$t('dynamicsForm.default.label')"
:required="formValue.required"
prop="default_value"
:rules="
formValue.required
? [{ required: true, message: $t('dynamicsForm.default.requiredMessage') }]
: []
"
>
<el-slider
v-model="formValue.default_value"
:show-input="formValue.showInput"
:show-input-controls="false"
:max="formValue.max"
:min="formValue.min"
:step="formValue.step == 0 ? 0.1 : formValue.step"
:precision="formValue.precision"
/>
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onBeforeMount, watch } from 'vue'
import { t } from '@/locales'
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
const getData = () => {
return {
input_type: 'Slider',
attrs: {
min: formValue.value.min,
max: formValue.value.max,
step: formValue.value.step,
precision: formValue.value.precision,
'show-input-controls': false,
'show-input': formValue.value.showInput
},
props_info: {
rules: [
{
message: formValue.value.label + ' ' + t('dynamicsForm.tip.requiredMessage'),
trigger: 'blur',
required: formValue.value.required
}
]
},
show_default_value: true,
default_value: formValue.value.default_value
}
}
watch(
() => formValue.value.min,
() => {
if (formValue.value.min > formValue.value.max) {
formValue.value.max = formValue.value.min
}
}
)
const rander = (form_data: any) => {
const attrs = form_data.attrs
formValue.value.option_list = form_data.option_list
formValue.value.min = attrs.min
formValue.value.max = attrs.max
formValue.value.step = attrs.step
formValue.value.showInput = attrs['show-input']
formValue.value.default_value = form_data.default_value
}
const step_rules = [
{
required: true,
validator: (rule: any, value: any, callback: any) => {
if (!value) {
callback(new Error(t('dynamicsForm.Slider.step.requiredMessage1')))
return false
}
if (value === 0) {
callback(new Error(t('dynamicsForm.Slider.step.requiredMessage2')))
return false
}
return true
},
trigger: 'blur'
}
]
defineExpose({ getData, rander })
onBeforeMount(() => {
formValue.value.min = 0
formValue.value.max = 20
formValue.value.step = 0.1
formValue.value.default_value = 1
formValue.value.showInput = true
})
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,48 @@
<template>
<el-form-item
:label="$t('dynamicsForm.default.label')"
:required="formValue.required"
prop="default_value"
:rules="
formValue.required
? [{ required: true, message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}` }]
: []
"
>
<el-switch v-model="formValue.default_value" />
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onMounted } from 'vue'
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
const getData = () => {
return {
input_type: 'SwitchInput',
show_default_value: true,
attrs: {},
default_value: formValue.value.default_value
}
}
const rander = (form_data: any) => {
formValue.value.default_value = form_data.default_value || false
}
defineExpose({ getData, rander })
onMounted(() => {
formValue.value.default_value = false
})
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,183 @@
<template>
<el-form-item :label="$t('dynamicsForm.TextInput.length.label')" required>
<el-row class="w-full">
<el-col :span="11">
<el-form-item
:rules="[
{
required: true,
message: $t('dynamicsForm.TextInput.length.minRequired'),
trigger: 'change'
}
]"
prop="minlength"
>
<el-input-number
style="width: 100%"
:min="1"
:step="1"
step-strictly
v-model="formValue.minlength"
controls-position="right"
/>
</el-form-item>
</el-col>
<el-col :span="2" class="text-center">
<span>-</span>
</el-col>
<el-col :span="11">
<el-form-item
:rules="[
{
required: true,
message: $t('dynamicsForm.TextInput.length.maxRequired'),
trigger: 'change'
}
]"
prop="maxlength"
>
<el-input-number
style="width: 100%"
:min="formValue.minlength > formValue.maxlength ? formValue.minlength : 1"
step-strictly
:step="1"
v-model="formValue.maxlength"
controls-position="right"
/></el-form-item>
</el-col>
</el-row>
</el-form-item>
<el-form-item
class="defaultValueItem"
:required="formValue.required"
prop="default_value"
:label="$t('dynamicsForm.default.label')"
:rules="
formValue.required ? [{ required: true, message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}` }, ...rules] : rules
"
>
<div class="defaultValueCheckbox">
<el-checkbox
v-model="formValue.show_default_value"
:label="$t('dynamicsForm.default.show')"
/>
</div>
<el-input
v-model="formValue.default_value"
:maxlength="formValue.maxlength"
:minlength="formValue.minlength"
:placeholder="$t('dynamicsForm.default.placeholder')"
show-word-limit
type="text"
/>
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onMounted, watch } from 'vue'
import { t } from '@/locales'
const props = defineProps<{
modelValue: any
}>()
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
})
watch(
() => formValue.value.minlength,
() => {
if (formValue.value.minlength > formValue.value.maxlength) {
formValue.value.maxlength = formValue.value.minlength
}
}
)
const getData = () => {
return {
input_type: 'TextInput',
attrs: {
maxlength: formValue.value.maxlength,
minlength: formValue.value.minlength,
'show-word-limit': true
},
default_value: formValue.value.default_value,
show_default_value: formValue.value.show_default_value,
props_info: {
rules: formValue.value.required
? [
{ required: true, message: `${formValue.value.label} ${t('dynamicsForm.default.requiredMessage')}` },
{
min: formValue.value.minlength,
max: formValue.value.maxlength,
message: `${formValue.value.label}${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,
trigger: 'blur'
}
]
: [
{
min: formValue.value.minlength,
max: formValue.value.maxlength,
message: `${formValue.value.label}${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,
trigger: 'blur'
}
]
}
}
}
const rander = (form_data: any) => {
const attrs = form_data.attrs || {}
formValue.value.minlength = attrs.minlength
formValue.value.maxlength = attrs.maxlength
formValue.value.default_value = form_data.default_value
formValue.value.show_default_value = form_data.show_default_value
}
const rangeRules = [
{
required: true,
validator: (rule: any, value: any, callback: any) => {
if (!formValue.value.minlength) {
callback(new Error(t('dynamicsForm.TextInput.length.requiredMessage4')))
}
if (!formValue.value.maxlength) {
callback(new Error(t('dynamicsForm.TextInput.length.requiredMessage4')))
}
return true
},
message: `${formValue.value.label} ${t('dynamicsForm.default.requiredMessage')}`
}
]
const rules = computed(() => [
{
min: formValue.value.minlength,
max: formValue.value.maxlength,
message: `${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,
trigger: 'blur'
}
])
defineExpose({ getData, rander })
onMounted(() => {
formValue.value.minlength = 0
formValue.value.maxlength = 200
formValue.value.default_value = ''
// console.log(formValue.value.show_default_value)
if (formValue.value.show_default_value === undefined) {
formValue.value.show_default_value = true
}
})
</script>
<style lang="scss" scoped>
.defaultValueItem {
position: relative;
.defaultValueCheckbox {
position: absolute;
right: 0;
top: -35px;
}
}
</style>

View File

@ -0,0 +1,25 @@
import type { App } from 'vue'
import type { Dict } from '@/api/type/common'
import DynamicsForm from '@/components/dynamics-form/index.vue'
let components: Dict<any> = import.meta.glob('@/components/dynamics-form/**/**.vue', {
eager: true
})
components = {
...components,
...import.meta.glob('@/components/dynamics-form/**/**/**.vue', {
eager: true
})
}
const install = (app: App) => {
Object.keys(components).forEach((key: string) => {
const commentName: string = key
.substring(key.lastIndexOf('/') + 1, key.length)
.replace('.vue', '')
if (key !== '/src/components/dynamics-form/constructor/index.vue') {
app.component(commentName, components[key].default)
}
})
app.component('DynamicsForm', DynamicsForm)
}
export default { install }

View File

@ -0,0 +1,228 @@
<template>
<el-form
@submit.prevent
ref="ruleFormRef"
label-width="130px"
label-suffix=":"
v-loading="loading"
v-bind="$attrs"
>
<slot :form_value="formValue"></slot>
<template v-for="item in formFieldList" :key="item.field">
<FormItem
ref="formFieldRef"
:key="item.field"
v-if="show(item)"
@change="change(item, $event)"
v-bind:modelValue="formValue[item.field]"
:formfield="item"
:trigger="trigger"
:view="view"
:initDefaultData="initDefaultData"
:defaultItemWidth="defaultItemWidth"
:other-params="otherParams"
:form-value="formValue"
:formfield-list="formFieldList"
:parent_field="parent_field"
>
</FormItem>
</template>
</el-form>
</template>
<script lang="ts" setup>
import type { Dict } from '@/api/type/common'
import FormItem from '@/components/dynamics-form/FormItem.vue'
import type { FormField } from '@/components/dynamics-form/type'
import { ref, onBeforeMount, watch, type Ref } from 'vue'
import type { FormInstance } from 'element-plus'
import triggerApi from '@/api/model/provider'
import type Result from '@/request/Result'
import _ from 'lodash'
defineOptions({ name: 'dynamicsForm' })
const props = withDefaults(
defineProps<{
//
render_data: Promise<Result<Array<FormField>>> | string | Array<FormField>
//
otherParams?: any
//
view?: boolean
//
defaultItemWidth?: string
parent_field?: string
modelValue?: Dict<any>
}>(),
{ view: false, defaultItemWidth: '75%', otherParams: () => {} }
)
const formValue = ref<Dict<any>>({})
const loading = ref<boolean>(false)
const formFieldList = ref<Array<FormField>>([])
const ruleFormRef = ref<FormInstance>()
const formFieldRef = ref<Array<InstanceType<typeof FormItem>>>([])
/**
* 当前 field是否展示
* @param field
*/
const show = (field: FormField) => {
if (field.relation_show_field_dict) {
let keys = Object.keys(field.relation_show_field_dict)
for (const index in keys) {
const key = keys[index]
let v = _.get(formValue.value, key)
if (v && v !== undefined && v !== null) {
let values = field.relation_show_field_dict[key]
if (values && values.length > 0) {
return values.includes(v)
} else {
return true
}
} else {
return false
}
}
}
return true
}
const emit = defineEmits(['update:modelValue'])
/**
* 表单字段修改
* @param field
* @param value
*/
const change = (field: FormField, value: any) => {
formValue.value[field.field] = value
}
watch(
formValue,
() => {
emit('update:modelValue', formValue.value)
},
{ deep: true }
)
/**
* 触发器,用户获取子表单 或者 下拉选项
* @param field
* @param loading
*/
const trigger = (field: FormField, loading: Ref<boolean>) => {
if (field.provider && field.method) {
return triggerApi
.trigger(
field.provider,
field.method,
{
...props.otherParams,
...formValue.value
},
loading
)
.then((ok) => {
if (field.trigger_type === 'CHILD_FORMS') {
field.children = ok.data as Array<FormField>
} else {
field.option_list = ok.data as Array<any>
}
})
}
return Promise.resolve([])
}
/**
* 初始化默认数据
*/
const initDefaultData = (formField: FormField) => {
if (
formField.default_value &&
(formValue.value[formField.field] === undefined ||
formValue.value[formField.field] === null ||
!formValue.value[formField.field]) &&
formValue.value[formField.field] != false
) {
if (formField.show_default_value === true) {
formValue.value[formField.field] = formField.default_value
}
}
}
onBeforeMount(() => {
render(props.render_data, props.modelValue)
})
const render = (
render_data: string | Array<FormField> | Promise<Result<Array<FormField>>>,
data?: Dict<any>
) => {
if (typeof render_data == 'string') {
triggerApi.get(render_data, {}, loading).then((ok) => {
formFieldList.value = ok.data
})
} else if (render_data instanceof Array) {
formFieldList.value = render_data
} else {
render_data.then((ok) => {
formFieldList.value = ok.data
})
}
const form_data = data ? data : {}
if (form_data) {
const value = formFieldList.value
.map((item) => {
if (form_data[item.field] !== undefined) {
if (item.value_field && item.option_list && item.option_list.length > 0) {
const value_field = item.value_field
const find = item.option_list?.find((i) => {
if (typeof form_data[item.field] === 'string') {
return i[value_field] === form_data[item.field]
} else {
return form_data[item.field].indexOf([value_field]) === -1
}
})
if (find) {
return { [item.field]: form_data[item.field] }
}
if (item.show_default_value === true || item.show_default_value === undefined) {
return { [item.field]: item.default_value }
}
} else {
return { [item.field]: form_data[item.field] }
}
}
if (item.show_default_value === true || item.show_default_value === undefined) {
return { [item.field]: item.default_value }
}
return {}
})
.reduce((x, y) => ({ ...x, ...y }), {})
formValue.value = _.cloneDeep(value)
}
}
/**
* 校验函数
*/
const validate = () => {
return Promise.all([
...formFieldRef.value.map((item) => item.validate()),
ruleFormRef.value ? ruleFormRef.value.validate() : Promise.resolve()
])
}
//
defineExpose({
initDefaultData,
validate,
render,
ruleFormRef
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,5 @@
<template>
<el-date-picker v-bind="$attrs" />
</template>
<script setup lang="ts"></script>
<style lang="scss"></style>

View File

@ -0,0 +1,143 @@
<template>
<div style="width: 100%" class="function-CodemirrorEditor">
<Codemirror
v-bind="$attrs"
ref="cmRef"
v-model="model_value"
:extensions="extensions"
:style="codemirrorStyle"
:tab-size="4"
:autofocus="true"
/>
<div class="function-CodemirrorEditor__format">
<el-button text type="info" @click="format" class="magnify">
<el-icon><DocumentChecked /></el-icon>
</el-button>
</div>
<div class="function-CodemirrorEditor__footer">
<el-button text type="info" @click="openCodemirrorDialog" class="magnify">
<AppIcon iconName="app-magnify" style="font-size: 16px"></AppIcon>
</el-button>
</div>
<!-- Codemirror 弹出层 -->
<el-dialog
v-model="dialogVisible"
:title="$t('dynamicsForm.default.label')"
append-to-body
fullscreen
>
<Codemirror
v-model="cloneContent"
:extensions="extensions"
:style="codemirrorStyle"
:tab-size="4"
:autofocus="true"
style="
height: calc(100vh - 160px) !important;
border: 1px solid #bbbfc4;
border-radius: 4px;
"
/>
<template #footer>
<div class="dialog-footer mt-24">
<el-button type="primary" @click="submitDialog"> {{ $t('common.confirm') }}</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { json, jsonParseLinter } from '@codemirror/lang-json'
import { oneDark } from '@codemirror/theme-one-dark'
import { Codemirror } from 'vue-codemirror'
import { linter } from '@codemirror/lint'
import { computed, ref } from 'vue'
import { t } from '@/locales'
const props = withDefaults(defineProps<{ modelValue?: any }>(), { modelValue: () => {} })
const emit = defineEmits(['update:modelValue'])
const cache_model_value_str = ref<string>()
const model_value = computed({
get: () => {
if (cache_model_value_str.value) {
return cache_model_value_str.value
}
return JSON.stringify(props.modelValue, null, 4)
},
set: (v: string) => {
if (!v) {
emit('update:modelValue', JSON.parse('{}'))
} else {
try {
cache_model_value_str.value = v
const result = JSON.parse(v)
emit('update:modelValue', result)
} catch (e) {}
}
}
})
const extensions = [json(), linter(jsonParseLinter()), oneDark]
const codemirrorStyle = {
height: '210px!important',
width: '100%'
}
//
const dialogVisible = ref<boolean>(false)
const cloneContent = ref<string>('')
const openCodemirrorDialog = () => {
cloneContent.value = model_value.value
dialogVisible.value = true
}
const format = () => {
try {
const json_str = JSON.parse(model_value.value)
model_value.value = JSON.stringify(json_str, null, 4)
} catch (e) {}
}
function submitDialog() {
model_value.value = cloneContent.value
dialogVisible.value = false
}
/**
* 校验格式
* @param rule
* @param value
* @param callback
*/
const validate_rules = (rule: any, value: any, callback: any) => {
if (model_value.value) {
try {
JSON.parse(model_value.value)
} catch (e) {
callback(new Error(t('dynamicsForm.tip.requiredMessage')))
return false
}
}
return true
}
defineExpose({ validate_rules: validate_rules })
</script>
<style lang="scss">
.function-CodemirrorEditor__footer {
position: absolute;
bottom: 10px;
right: 10px;
}
.function-CodemirrorEditor {
position: relative;
}
.function-CodemirrorEditor__format {
position: absolute;
top: 10px;
right: 10px;
}
</style>

View File

@ -0,0 +1,5 @@
<template>
<el-input v-bind="$attrs" :show-password="true"></el-input>
</template>
<script setup lang="ts"></script>
<style lang="scss"></style>

View File

@ -0,0 +1,5 @@
<template>
<el-input v-bind="$attrs" />
</template>
<script setup lang="ts"></script>
<style lang="scss"></style>

View File

@ -0,0 +1,156 @@
<template v-loading="_loading">
<div class="arrt-object-card flex w-full">
<el-card class="box-card" :style="style" v-for="(item, index) in _data" :key="index">
<DynamicsForm
:style="formStyle"
:view="view"
label-position="top"
require-asterisk-position="right"
ref="ceFormRef"
v-model="_data[index]"
:model="_data[index]"
:other-params="other"
:render_data="render_data()"
v-bind="attr"
:parent_field="formField.field + '.' + index"
></DynamicsForm>
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
<el-button text @click.stop="deleteDataset(item)" class="delete-button">
<el-icon><Delete /></el-icon>
</el-button>
</el-tooltip>
</el-card>
<el-card shadow="never" class="card-add box-card" @click="add_card">
<div class="flex-center">
<AppIcon iconName="Plus" class="add-icon layout-bg p-8 border-r-4" />
<span>{{ add_msg }}</span>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import _ from 'lodash'
import type { FormField } from '@/components/dynamics-form/type'
import DynamicsForm from '@/components/dynamics-form/index.vue'
import Result from '@/request/Result'
const props = defineProps<{
modelValue?: Array<any>
formValue?: any
formfieldList?: Array<FormField>
field: string
otherParams: any
formField: FormField
view?: boolean
}>()
const render_data = () => {
return Promise.resolve(Result.success(props.formField.children as Array<FormField>))
}
const deleteDataset = (item: any) => {
_data.value = _data.value.filter((row) => row !== item)
}
const emit = defineEmits(['update:modelValue', 'change'])
//
const dynamicsFormRef = ref<Array<InstanceType<typeof DynamicsForm>>>([])
const _data = computed<Array<any>>({
get() {
if (props.modelValue) {
return props.modelValue
} else {
emit('update:modelValue', [{}])
return []
}
},
set(value) {
emit('update:modelValue', value)
}
})
const props_info = computed(() => {
return props.formField.props_info ? props.formField.props_info : {}
})
const add_msg = computed(() => {
return props_info.value.add_msg ? props_info.value.add_msg : '添加'
})
/**
* 添加一个card
*/
const add_card = () => {
_data.value = [..._data.value, {}]
}
/**
* 组件样式
*/
const formStyle = computed(() => {
return props_info.value.form_style ? props_info.value.form_style : {}
})
const style = computed(() => {
return props_info.value.style ? props_info.value.style : {}
})
const attr = computed(() => {
if (props.formField.attrs) {
return props.formField.attrs
}
return {}
})
/**
* 校验方法
*/
function validate() {
return Promise.all(dynamicsFormRef.value.map((item) => item.validate()))
}
const other = computed(() => {
return { ...(props.formValue ? props.formValue : {}), ...props.otherParams }
})
defineExpose({
validate,
field: props.field
})
</script>
<style lang="scss" scoped>
.arrt-object-card {
.box-card {
width: 30%;
position: relative;
margin: 10px;
padding-top: 20px;
}
.card-add {
display: inline-flex;
justify-content: center;
align-items: center;
font-size: 16px;
cursor: pointer;
min-height: var(--card-min-height);
border: 1px dashed var(--el-color-primary);
background: var(--el-disabled-bg-color);;
padding-bottom: 20px;
.add-icon {
font-size: 14px;
border: 1px solid var(--app-border-color-dark);
margin-right: 12px;
}
&:hover {
color: var(--el-color-primary);
background: #ffffff;
.add-icon {
background: #ffffff;
border-color: var(--el-color-primary);
}
}
}
.delete-button {
position: absolute;
right: 12px;
top: 10px;
height: auto;
}
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<el-card :style="style">
<DynamicsForm
:read-only="view"
:style="formStyle"
label-position="top"
require-asterisk-position="right"
ref="dynamicsFormRef"
v-model="data"
:other-params="other"
:render_data="formField.children ? formField.children : []"
v-bind="$attrs"
:parent_field="formField.field"
></DynamicsForm>
</el-card>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import type { FormField } from '@/components/dynamics-form/type'
import DynamicsForm from '@/components/dynamics-form/index.vue'
const emit = defineEmits(['update:modelValue', 'change'])
const props = defineProps<{
modelValue?: any
formValue?: any
formfieldList?: Array<FormField>
otherParams: any
formField: FormField
view?: boolean
}>()
const data = computed({
get: () => {
if (props.modelValue) {
return props.modelValue
}
return {}
},
set: ($event) => {
emit('update:modelValue', $event)
}
})
const other = computed(() => {
return { ...(props.formfieldList ? props.formfieldList : {}), ...props.otherParams }
})
//
const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()
/**
* 组件样式
*/
const formStyle = computed(() => {
return props_info.value.form_style ? props_info.value.form_style : {}
})
const props_info = computed(() => {
return props.formField.props_info ? props.formField.props_info : {}
})
const style = computed(() => {
return props_info.value.style ? props_info.value.style : {}
})
/**
* 校验方法
*/
function validate() {
if (dynamicsFormRef.value) {
return dynamicsFormRef.value.validate()
}
return Promise.resolve()
}
defineExpose({
validate
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,123 @@
<template v-loading="_loading">
<div style="width: 100%">
<el-tabs v-model="activeTab" editable @edit="handleTabsEdit" type="card">
<el-tab-pane
v-for="(item, index) in _data"
:key="index"
:label="tabs_label + (index + 1)"
:name="index"
>
<template v-if="formField.children">
<el-card :style="style">
<DynamicsForm
:style="formStyle"
:view="view"
label-position="top"
require-asterisk-position="right"
ref="ceFormRef"
v-model="_data[index]"
:model="_data[index]"
:other-params="other"
:render_data="render_data()"
v-bind="attr"
:parent_field="formField.field + '.' + index"
></DynamicsForm>
</el-card>
</template>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import _ from 'lodash'
import type { FormField } from '@/components/dynamics-form/type'
import DynamicsForm from '@/components/dynamics-form/index.vue'
import Result from '@/request/Result'
import type { TabPaneName } from 'element-plus'
const props = defineProps<{
modelValue?: Array<any>
formValue?: any
formfieldList?: Array<FormField>
field: string
otherParams: any
formField: FormField
view?: boolean
}>()
const render_data = () => {
return Promise.resolve(Result.success(props.formField.children as Array<FormField>))
}
const emit = defineEmits(['update:modelValue', 'change'])
//
const dynamicsFormRef = ref<Array<InstanceType<typeof DynamicsForm>>>([])
const _data = computed<Array<any>>({
get() {
if (props.modelValue) {
return props.modelValue
} else {
emit('update:modelValue', [{}])
return []
}
},
set(value) {
emit('update:modelValue', value)
}
})
const props_info = computed(() => {
return props.formField.props_info ? props.formField.props_info : {}
})
const tabs_label = computed(() => {
return props_info.value.tabs_label ? props_info.value.tabs_label : 'label'
})
/**
* 组件样式
*/
const formStyle = computed(() => {
return props_info.value.form_style ? props_info.value.form_style : {}
})
const attr = computed(() => {
if (props.formField.attrs) {
return props.formField.attrs
}
return {}
})
const activeTab = ref(0)
/**
* 校验方法
*/
function validate() {
return Promise.all(dynamicsFormRef.value.map((item) => item.validate()))
}
const other = computed(() => {
return { ...(props.formValue ? props.formValue : {}), ...props.otherParams }
})
const style = computed(() => {
return props_info.value.style ? props_info.value.style : {}
})
const handleTabsEdit = (targetName: TabPaneName | undefined, action: 'remove' | 'add') => {
if (action === 'add') {
_data.value = [..._data.value, {}]
activeTab.value = _data.value.length
} else if (action === 'remove') {
const update_value = _data.value.filter((item, index) => index != targetName)
_data.value = update_value
activeTab.value = update_value.length - 1
}
}
defineExpose({
validate,
field: props.field
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,20 @@
<template>
<div class="flex align-center" style="display: inline-flex">
<div class="flex-between mr-4">
<span>{{ label }}</span>
</div>
<el-tooltip effect="dark" placement="right">
<template #content
><div style="max-width: 200px">{{ tooltip }}</div></template
>
<AppIcon iconName="app-warning" class="app-warning-icon" style="flex-shrink: 0"></AppIcon>
</el-tooltip>
</div>
</template>
<script setup lang="ts">
defineProps<{
label: string
tooltip: string
}>()
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,38 @@
<template>
<el-radio-group v-bind="$attrs">
<el-radio v-for="(item, index) in option_list" :key="index" :label="item[valueField]">
<div v-html="label(item)"></div>
</el-radio>
</el-radio-group>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { FormField } from '@/components/dynamics-form/type'
import _ from 'lodash'
const props = defineProps<{
formValue?: any
formfieldList?: Array<FormField>
field: string
otherParams: any
formField: FormField
view?: boolean
}>()
const textField = computed(() => {
return props.formField.text_field ? props.formField.text_field : 'key'
})
const valueField = computed(() => {
return props.formField.value_field ? props.formField.value_field : 'value'
})
const option_list = computed(() => {
return props.formField.option_list ? props.formField.option_list : []
})
const label = (option: any) => {
return option[textField.value]
}
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,38 @@
<template>
<el-radio-group v-bind="$attrs">
<el-radio-button v-for="(item, index) in option_list" :key="index" :label="item[valueField]">
<div v-html="label(item)"></div>
</el-radio-button>
</el-radio-group>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { FormField } from '@/components/dynamics-form/type'
import _ from 'lodash'
const props = defineProps<{
formValue?: any
formfieldList?: Array<FormField>
field: string
otherParams: any
formField: FormField
view?: boolean
}>()
const textField = computed(() => {
return props.formField.text_field ? props.formField.text_field : 'key'
})
const valueField = computed(() => {
return props.formField.value_field ? props.formField.value_field : 'value'
})
const option_list = computed(() => {
return props.formField.option_list ? props.formField.option_list : []
})
const label = (option: any) => {
return option[textField.value]
}
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,106 @@
<template>
<div class="radio_content" :style="radioContentStyle">
<el-row :gutter="12" class="w-full">
<template v-for="(item, index) in option_list" :key="index">
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
<el-card
:key="item.value"
class="item break-all"
shadow="never"
style="--el-card-padding: 12px 16px"
:class="[
inputDisabled ? 'is-disabled' : '',
modelValue == item[valueField] ? 'active' : ''
]"
@click="inputDisabled ? () => {} : selected(item[valueField])"
>
{{ item[textField] }}
</el-card>
</el-col>
</template>
</el-row>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, inject } from 'vue'
import type { FormField } from '@/components/dynamics-form/type'
import { useFormDisabled, formItemContextKey } from 'element-plus'
const inputDisabled = useFormDisabled()
const props = defineProps<{
formValue?: any
formfieldList?: Array<FormField>
field: string
otherParams: any
formField: FormField
view?: boolean
//
modelValue?: any
disabled?: boolean
}>()
const elFormItem = inject(formItemContextKey, void 0)
const selected = (activeValue: string | number) => {
emit('update:modelValue', activeValue)
if (elFormItem?.validate) {
elFormItem.validate('change')
}
}
const emit = defineEmits(['update:modelValue', 'change'])
const width = ref<number>()
const radioContentStyle = computed(() => {
if (width.value) {
if (width.value < 350) {
return { '--maxkb-radio-card-width': '316px' }
} else if (width.value > 770) {
return { '--maxkb-radio-card-width': '378px' }
} else {
return { '--maxkb-radio-card-width': '100%' }
}
}
return {}
})
const textField = computed(() => {
return props.formField.text_field ? props.formField.text_field : 'key'
})
const valueField = computed(() => {
return props.formField.value_field ? props.formField.value_field : 'value'
})
const option_list = computed(() => {
return props.formField.option_list ? props.formField.option_list : []
})
</script>
<style lang="scss" scoped>
.radio_content {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
width: 100%;
.is-disabled {
border: 1px solid var(--el-card-border-color);
background-color: var(--el-fill-color-light);
color: var(--el-text-color-placeholder);
cursor: not-allowed;
&:hover {
cursor: not-allowed;
}
}
.active {
border: 1px solid var(--el-color-primary);
color: var(--el-color-primary);
}
.item {
line-height: 22px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
width: var(--maxkb-radio-card-width, 100%);
margin: 4px;
}
}
</style>

View File

@ -0,0 +1,92 @@
<template>
<div class="radio_content">
<div
v-for="item in option_list"
:key="item.value"
class="item"
:class="[inputDisabled ? 'is-disabled' : '', modelValue == item[valueField] ? 'active' : '']"
@click="selected(item[valueField])"
>
{{ item[textField] }}
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, inject } from 'vue'
import type { FormField } from '@/components/dynamics-form/type'
import { useFormDisabled, formItemContextKey } from 'element-plus'
const inputDisabled = useFormDisabled()
const props = defineProps<{
formValue?: any
formfieldList?: Array<FormField>
field: string
otherParams: any
formField: FormField
view?: boolean
//
modelValue?: any
}>()
const elFormItem = inject(formItemContextKey, void 0)
const selected = (activeValue: string | number) => {
emit('update:modelValue', activeValue)
if (elFormItem?.validate) {
elFormItem.validate('change')
}
}
const emit = defineEmits(['update:modelValue'])
const textField = computed(() => {
return props.formField.text_field ? props.formField.text_field : 'key'
})
const valueField = computed(() => {
return props.formField.value_field ? props.formField.value_field : 'value'
})
const option_list = computed(() => {
return props.formField.option_list ? props.formField.option_list : []
})
</script>
<style lang="scss" scoped>
.radio_content {
height: 32px;
display: inline-flex;
border: 1px solid #bbbfc4;
border-radius: 4px;
font-weight: 400;
font-size: 14px;
color: #1f2329;
padding: 3px 4px;
box-sizing: border-box;
white-space: nowrap;
.is-disabled {
border: 1px solid var(--el-card-border-color);
background-color: var(--el-fill-color-light);
color: var(--el-text-color-placeholder);
cursor: not-allowed;
&:hover {
cursor: not-allowed;
}
}
.active {
border-radius: 4px;
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
.item {
cursor: pointer;
margin: 0px 2px;
padding: 2px 8px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
&:last-child {
margin: 0 4px 0 2px;
}
&:first-child {
margin: 0 2px 0 4px;
}
}
}
</style>

View File

@ -0,0 +1,64 @@
<template>
<el-select
class="m-2"
multiple
filterable
clearable
v-bind="$attrs"
v-model="_modelValue"
>
<el-option
v-for="(item, index) in option_list"
:key="index"
:label="label(item)"
:value="item[valueField]"
>
</el-option>
</el-select>
</template>
<script setup lang="ts">
import type { FormField } from '@/components/dynamics-form/type'
import { computed, ref } from 'vue'
import _ from 'lodash'
const rowTemp = ref<any>()
const props = defineProps<{
modelValue?: Array<any>
formValue?: any
formfieldList?: Array<FormField>
field: string
otherParams: any
formField: FormField
view?: boolean
}>()
const emit = defineEmits(['update:modelValue', 'change'])
const _modelValue = computed({
get() {
if (props.modelValue) {
return props.modelValue
}
return []
},
set($event) {
emit('update:modelValue', $event)
}
})
const textField = computed(() => {
return props.formField.text_field ? props.formField.text_field : 'key'
})
const valueField = computed(() => {
return props.formField.value_field ? props.formField.value_field : 'value'
})
const option_list = computed(() => {
return props.formField.option_list ? props.formField.option_list : []
})
const label = (option: any) => {
return option[textField.value]
}
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,77 @@
<template>
<el-select
filterable
:teleported="true"
popper-class="dynamics-single-select"
clearable
v-bind="$attrs"
v-model="_modelValue"
>
<el-option
v-for="(item, index) in option_list"
:key="index"
teleported
:label="label(item)"
:value="item[valueField]"
>
</el-option>
</el-select>
</template>
<script setup lang="ts">
import type { FormField } from '@/components/dynamics-form/type'
import { computed, ref } from 'vue'
import _ from 'lodash'
const rowTemp = ref<any>()
const props = defineProps<{
modelValue?: string
formValue?: any
formfieldList?: Array<FormField>
field: string
otherParams: any
formField: FormField
view?: boolean
}>()
const emit = defineEmits(['update:modelValue', 'change'])
const _modelValue = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
emit('change', props.formField)
}
})
const textField = computed(() => {
return props.formField.text_field ? props.formField.text_field : 'key'
})
const valueField = computed(() => {
return props.formField.value_field ? props.formField.value_field : 'value'
})
const option_list = computed(() => {
return props.formField.option_list ? props.formField.option_list : []
})
const label = (option: any) => {
//
if (props.modelValue && option_list.value) {
const oldItem = option_list.value.find((item) => item[valueField.value] === props.modelValue)
if (!oldItem) {
emit('update:modelValue', undefined)
}
}
return option[textField.value]
}
</script>
<style lang="scss">
.dynamics-single-select {
.el-select-dropdown {
max-width: 1px;
}
}
</style>

View File

@ -0,0 +1,11 @@
<template>
<el-slider v-bind="$attrs" show-input :show-input-controls="false" class="custom-slider" />
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped>
.custom-slider {
.el-input-number.is-without-controls .el-input__wrapper {
padding: 0 !important;
}
}
</style>

View File

@ -0,0 +1,7 @@
<template>
<el-switch v-bind="$attrs" />
</template>
<script setup lang="ts"></script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,70 @@
<template>
<div class="progress-table-item">
<el-popover placement="top-start" :title="row[text_field]" :width="200" trigger="hover">
<template #reference>
<el-progress v-bind="$attrs" :percentage="row[value_field]"></el-progress
></template>
<div>
<el-row v-for="(item, index) in view_card" :key="index">
<el-col :span="6">{{ item.title }}</el-col>
<el-col :span="18"> <span class="value" :innerHTML="value_html(item)"> </span></el-col>
</el-row>
</div>
</el-popover>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
const props = defineProps<{
/**
*表单渲染Item column
*/
column: any
/**
* 这一行数据
*/
row: any
}>()
const rowRef = ref<any>()
function evalF(text: string, row: any) {
rowRef.value = row
return eval(text)
}
const props_info = computed(() => {
return props.column.props_info ? props.column.props_info : {}
})
const text_field = computed(() => {
return props.column.text_field ? props.column.text_field : 'key'
})
const value_field = computed(() => {
return props.column.value_field ? props.column.value_field : 'value'
})
const value_html = (view_card_item: any) => {
if (view_card_item.type === 'eval') {
return evalF(view_card_item.value_field, props.row)
} else {
return props.row[view_card_item.value_field]
}
}
const view_card = computed(() => {
return props_info.value.view_card ? props_info.value.view_card : []
})
</script>
<style lang="scss" scoped>
@mixin valueScss() {
color: rgba(31, 35, 41, 1);
font-weight: 500;
font-size: 12px;
line-height: 22px;
height: 22px;
}
.progress-table-item {
.value {
float: right;
@include valueScss;
}
}
</style>

View File

@ -0,0 +1,214 @@
<template>
<div class="table-checkbox">
<div class="header">
<div class="title">{{ title }}</div>
<el-input
v-model="filterText"
:validate-event="false"
:placeholder="$t('dynamicsForm.searchBar.placeholder')"
class="input-with-select"
style="--el-color-danger: #c0c4cc"
clearable
>
<template #prepend>
<el-button :icon="Search" />
</template>
</el-input>
</div>
<el-table
ref="multipleTableRef"
:data="tableData"
highlight-current-row
style="width: 100%; height: 100%; --el-bg-color: #f5f6f7"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column
v-for="(column, index) in tableColumns"
:key="index"
v-bind="column"
:label="column.label"
>
<template #default="scope">
<template v-if="column.type === 'component'">
<TableColumn :column="column" :row="scope.row"></TableColumn>
</template>
<template v-else-if="column.type === 'eval'">
<span v-html="evalF(column.property, scope.row)"></span
></template>
<template v-else>
<span>{{ scope.row[column.property] }}</span></template
>
</template>
</el-table-column>
</el-table>
<div class="msg" v-show="props.modelValue">
{{ activeMsg }}
<span class="active">
{{ activeText }}
</span>
</div>
</div>
</template>
<script setup lang="ts">
import type { FormField } from '@/components/dynamics-form/type'
import { computed, ref, watch } from 'vue'
import { Search } from '@element-plus/icons-vue'
import type { ElTable } from 'element-plus'
import _ from 'lodash'
import TableColumn from '@/components/dynamics-form/items/table/TableColumn.vue'
const filterText = ref<string>('')
const props = defineProps<{
formValue?: any
formfieldList?: Array<FormField>
field: string
otherParams: any
formField: FormField
view?: boolean
//
modelValue?: Array<any>
}>()
const rowTemp = ref<any>()
const evalF: (text: string, row: any) => string = (text: string, row: any) => {
rowTemp.value = row
return eval(text)
}
const emit = defineEmits(['update:modelValue', 'change'])
const multipleTableRef = ref<InstanceType<typeof ElTable>>()
const _data = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
emit('change', props.formField)
}
})
const handleSelectionChange = (val: any[]) => {
_data.value = val.map((row) => row[valueField.value])
}
const propsInfo = computed(() => {
return props.formField.props_info ? props.formField.props_info : {}
})
const activeMsg = computed(() => {
return propsInfo.value.active_msg ? propsInfo.value.active_msg : ''
})
const title = computed(() => {
return propsInfo.value.title ? propsInfo.value.title : ''
})
const tableColumns = computed(() => {
return propsInfo.value.table_columns ? propsInfo.value.table_columns : []
})
const option_list = computed(() => {
return props.formField.option_list ? props.formField.option_list : []
})
const textField = computed(() => {
return props.formField.text_field ? props.formField.text_field : 'key'
})
const valueField = computed(() => {
return props.formField.value_field ? props.formField.value_field : 'value'
})
const tableData = computed(() => {
if (option_list.value) {
if (filterText.value) {
return option_list.value.filter((item: any) =>
tableColumns.value.some((c: any) => {
let v = ''
if (c.type === 'eval') {
v = evalF(c.property, item)
} else if (c.type === 'component') {
return false
} else {
v = item[c.property]
}
return typeof v == 'string' ? v.indexOf(filterText.value) >= 0 : false
})
)
} else {
return option_list.value.filter((item: any) => item[valueField.value])
}
}
return []
})
/**
* 监听表格数据设置默认值
*/
watch(
() => tableData.value,
() => {
if (tableData.value && tableData.value.length > 0) {
const defaultItem = _.head(tableData.value)
let defaultItemValue = _.get(defaultItem, valueField.value)
if (props.modelValue) {
const row = option_list.value.find((f: any) => f[valueField.value] === props.modelValue)
if (row) {
defaultItemValue = row[valueField.value]
}
}
emit('update:modelValue', defaultItemValue)
} else {
emit('update:modelValue', undefined)
}
emit('change', props.formField)
}
)
const activeText = computed(() => {
if (props.modelValue) {
const rows = option_list.value.filter((f: any) =>
props.modelValue?.includes(f[valueField.value])
)
if (rows) {
if (rows.length > 3) {
return (
rows
.map((row) => row[textField.value])
.splice(0, 3)
.join(',') + '...'
)
} else {
return rows.map((row) => row[textField.value]).join(',')
}
}
}
return props.modelValue
})
</script>
<style lang="scss" scoped>
.table-checkbox {
.header {
display: flex;
justify-content: space-between;
margin-bottom: 16px;
.title {
color: #1f2329;
font-weight: 400;
font-size: 14px;
line-height: 22px;
}
.input-with-select {
width: 45%;
}
}
.msg {
margin-top: 12px;
color: rgba(100, 106, 115, 1);
.active {
margin-left: 3px;
color: var(--el-color-primary);
}
}
}
</style>

View File

@ -0,0 +1,22 @@
<template>
<component :is="column.property" v-bind="attrs" :column="column" :row="row"></component>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import _ from 'lodash'
const props = defineProps<{
/**
*表单渲染Item column
*/
column: any
/**
* 这一行数据
*/
row: any
}>()
const attrs = computed(() => {
return props.column.attrs ? props.column.attrs : {}
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,202 @@
<template>
<div class="table-radio">
<div class="header">
<div class="title">{{ title }}</div>
<el-input
v-model="filterText"
:validate-event="false"
:placeholder="$t('dynamicsForm.searchBar.placeholder')"
class="input-with-select"
style="--el-color-danger: #c0c4cc"
clearable
>
<template #prepend>
<el-button :icon="Search" />
</template>
</el-input>
</div>
<el-table
ref="singleTableRef"
:data="tableData"
highlight-current-row
style="width: 100%; height: 100%; --el-bg-color: #f5f6f7"
@current-change="_data = $event[valueField]"
>
<el-table-column width="50px">
<template #default="scope">
<input type="radio" :checked="_data === scope.row[valueField]" />
</template>
</el-table-column>
<el-table-column
v-for="(column, index) in tableColumns"
v-bind="column"
:label="column.label"
:key="index"
>
<template #default="scope">
<template v-if="column.type === 'component'">
<TableColumn :column="column" :row="scope.row"></TableColumn>
</template>
<template v-else-if="column.type === 'eval'">
<span v-html="evalF(column.property, scope.row)"></span
></template>
<template v-else>
<span>{{ scope.row[column.property] }}</span></template
>
</template>
</el-table-column>
</el-table>
<div class="msg" v-show="props.modelValue">
{{ activeMsg }}
<span class="active">
{{ activeText }}
</span>
</div>
</div>
</template>
<script setup lang="ts">
import type { FormField } from '@/components/dynamics-form/type'
import { computed, ref, watch } from 'vue'
import { Search } from '@element-plus/icons-vue'
import type { ElTable } from 'element-plus'
import _ from 'lodash'
import TableColumn from '@/components/dynamics-form/items/table/TableColumn.vue'
const filterText = ref<string>('')
const props = defineProps<{
formValue?: any
formfieldList?: Array<FormField>
field: string
otherParams: any
formField: FormField
view?: boolean
//
modelValue?: any
}>()
const rowTemp = ref<any>()
const evalF = (text: string, row: any) => {
rowTemp.value = row
return eval(text)
}
const emit = defineEmits(['update:modelValue', 'change'])
const singleTableRef = ref<InstanceType<typeof ElTable>>()
const _data = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue', value)
emit('change', props.formField)
}
})
const propsInfo = computed(() => {
return props.formField.props_info ? props.formField.props_info : {}
})
const activeMsg = computed(() => {
return propsInfo.value.active_msg ? propsInfo.value.active_msg : ''
})
const title = computed(() => {
return propsInfo.value.title ? propsInfo.value.title : ''
})
const tableColumns = computed(() => {
return propsInfo.value.table_columns ? propsInfo.value.table_columns : []
})
const option_list = computed(() => {
return props.formField.option_list ? props.formField.option_list : []
})
const textField = computed(() => {
return props.formField.text_field ? props.formField.text_field : 'key'
})
const valueField = computed(() => {
return props.formField.value_field ? props.formField.value_field : 'value'
})
const tableData = computed(() => {
if (option_list.value) {
if (filterText.value) {
return option_list.value.filter((item: any) =>
tableColumns.value.some((c: any) => {
let v = ''
if (c.type === 'eval') {
v = evalF(c.property, item)
} else if (c.type === 'component') {
return false
} else {
v = item[c.property]
}
return typeof v == 'string' ? v.indexOf(filterText.value) >= 0 : false
})
)
} else {
return option_list.value.filter((item: any) => item[valueField.value])
}
}
return []
})
/**
* 监听表格数据设置默认值
*/
watch(
() => tableData.value,
() => {
if (tableData.value && tableData.value.length > 0) {
const defaultItem = _.head(tableData.value)
let defaultItemValue = _.get(defaultItem, valueField.value)
if (props.modelValue) {
const row = option_list.value.find((f: any) => f[valueField.value] === props.modelValue)
if (row) {
defaultItemValue = row[valueField.value]
}
}
emit('update:modelValue', defaultItemValue)
} else {
emit('update:modelValue', undefined)
}
emit('change', props.formField)
}
)
const activeText = computed(() => {
if (props.modelValue) {
const row = option_list.value.find((f: any) => f[valueField.value] === props.modelValue)
return row[textField.value]
}
return props.modelValue
})
</script>
<style lang="scss" scoped>
.table-radio {
.header {
display: flex;
justify-content: space-between;
margin-bottom: 16px;
.title {
color: #1f2329;
font-weight: 400;
font-size: 14px;
line-height: 22px;
}
.input-with-select {
width: 45%;
}
}
.msg {
margin-top: 12px;
color: rgba(100, 106, 115, 1);
.active {
margin-left: 3px;
color: var(--el-color-primary);
}
}
}
</style>

View File

@ -0,0 +1,176 @@
import type { Dict } from '@/api/type/common'
interface ViewCardItem {
/**
*
*/
type: 'eval' | 'default'
/**
*
*/
title: string
/**
* default= row[value_field] eval `${parseFloat(row.number).toLocaleString("zh-CN",{style: "decimal",maximumFractionDigits:1})}%&nbsp;&nbsp;&nbsp;`
*/
value_field: string
}
interface TableColumn {
/**
* ||
*/
property: string
/**
*
*/
label: string
/**
*
*/
value_field?: string
attrs?: Attrs
/**
*
*/
type: 'eval' | 'component' | 'default'
props_info?: PropsInfo
}
interface ColorItem {
/**
* #f56c6c
*/
color: string
/**
*
*/
percentage: number
}
interface Attrs {
/**
*
*/
placeholder?: string
/**
* '50px' Form form-item 使 auto
*/
labelWidth?: string
/**
*
*/
labelSuffix?: string
/**
*
*/
requireAsteriskPosition?: 'left' | 'right'
color?: Array<ColorItem>
[propName: string]: any
}
interface PropsInfo {
/**
* card
*/
view_card?: Array<ViewCardItem>
/**
*
*/
table_columns?: Array<TableColumn>
/**
* message
*/
active_msg?: string
/**
*
*/
style?: Dict<any>
/**
* el-form-item
*/
item_style?: Dict<any>
/**
* element校验一样
*/
rules?: Dict<any>
/**
*
*/
err_msg?: string
/**
*tabs的时候使用
*/
tabs_label?: string
[propName: string]: any
}
interface FormField {
field: string
/**
*
*/
input_type: string
/**
*
*/
label?: string | any
/**
*
*/
required?: boolean
/**
*
*/
default_value?: any
/**
*
*/
show_default_value?: boolean
/**
* {field:field_value_list} field有值 ,field_value_list中才显示
*/
relation_show_field_dict?: Dict<Array<any>>
/**
* {field:field_value_list} field有值 ,field_value_list中才
*/
relation_trigger_field_dict?: Dict<Array<any>>
/**
* OPTION_LIST请求Option_list数据 CHILD_FORMS请求子表单
*/
trigger_type?: 'OPTION_LIST' | 'CHILD_FORMS'
/**
* attr数据
*/
attrs?: Attrs
/**
*
*/
props_info?: PropsInfo
/**
* field
*/
text_field?: string
/**
* value
*/
value_field?: string
/**
*
*/
option_list?: Array<any>
/**
*
*/
provider?: string
/**
*
*/
method?: string
children?: Array<FormField>
}
export type { FormField }

View File

@ -2,10 +2,14 @@ import { type App } from 'vue'
import LogoFull from './logo/LogoFull.vue'
import LogoIcon from './logo/LogoIcon.vue'
import SendIcon from './logo/SendIcon.vue'
import dynamicsForm from './dynamics-form'
import AppIcon from './app-icon/AppIcon.vue'
export default {
install(app: App) {
app.component('LogoFull', LogoFull)
app.component('LogoIcon', LogoIcon)
app.component('SendIcon', SendIcon)
app.use(dynamicsForm)
app.component('AppIcon', AppIcon)
},
}

View File

@ -129,15 +129,15 @@
</template>
</el-dropdown>
</div>
<CreateApplicationDialog ref="CreateApplicationDialogRef" @refresh="refresh" />
<CreateDatasetDialog ref="CreateDatasetDialogRef" @refresh="refresh" />
<!-- <CreateApplicationDialog ref="CreateApplicationDialogRef" @refresh="refresh" />
<CreateDatasetDialog ref="CreateDatasetDialogRef" @refresh="refresh" /> -->
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { onBeforeRouteLeave, useRouter, useRoute } from 'vue-router'
import CreateApplicationDialog from '@/views/application/component/CreateApplicationDialog.vue'
import CreateDatasetDialog from '@/views/dataset/component/CreateDatasetDialog.vue'
// import CreateApplicationDialog from '@/views/application/component/CreateApplicationDialog.vue'
// import CreateDatasetDialog from '@/views/dataset/component/CreateDatasetDialog.vue'
import { isAppIcon, isWorkFlow } from '@/utils/application'
import useStore from '@/stores'
const { common, dataset, application } = useStore()

View File

@ -1,4 +0,0 @@
export { default as Sidebar } from './sidebar/index.vue'
export { default as AppMain } from './app-main/index.vue'
export { default as TopBar } from './top-bar/index.vue'
export { default as AppHeader } from './app-header/index.vue'

View File

@ -1,8 +1,8 @@
<template>
<div class="sidebar p-8">
<div v-if="showBreadcrumb">
<!-- <div v-if="showBreadcrumb">
<AppBreadcrumb />
</div>
</div> -->
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu :default-active="activeMenu" router>
<sidebar-item
@ -23,7 +23,7 @@ import { computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { getChildRouteListByPathAndName } from '@/router/index'
import SidebarItem from './SidebarItem.vue'
import AppBreadcrumb from './../breadcrumb/index.vue'
// import AppBreadcrumb from './../breadcrumb/index.vue'
const route = useRoute()

View File

@ -4,10 +4,10 @@
<div class="logo">
<LogoFull />
</div>
<div class="flex-between">
<div class="flex-between w-full">
<div></div>
<TopMenu></TopMenu>
<TopUrlMenu></TopUrlMenu>
<TopAbout></TopAbout>
</div>
<Avatar></Avatar>
</div>
@ -15,9 +15,15 @@
<script setup lang="ts">
import TopMenu from './top-menu/index.vue'
import Avatar from './avatar/index.vue'
import TopUrlMenu from './top-url-menu/index.vue'
import TopAbout from './top-about/index.vue'
import { useRouter } from 'vue-router'
const router = useRouter()
</script>
<style lang="scss"></style>
<style lang="scss" scoped>
.app-top-bar-container {
height: var(--app-header-height);
box-sizing: border-box;
padding: var(--app-header-padding);
}
</style>

View File

@ -69,19 +69,19 @@
</el-dropdown-menu>
</template>
</el-dropdown>
<ResetPassword ref="resetPasswordRef"></ResetPassword>
<AboutDialog ref="AboutDialogRef"></AboutDialog>
<APIKeyDialog :user-id="user.userInfo?.id" ref="APIKeyDialogRef" />
<UserPwdDialog ref="UserPwdDialogRef" />
<!-- <ResetPassword ref="resetPasswordRef"></ResetPassword> -->
<!-- <AboutDialog ref="AboutDialogRef"></AboutDialog>
<APIKeyDialog :user-id="user.userInfo?.id" ref="APIKeyDialogRef" /> -->
<!-- <UserPwdDialog ref="UserPwdDialogRef" /> -->
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import useStore from '@/stores'
import { useRouter } from 'vue-router'
import ResetPassword from './ResetPassword.vue'
import AboutDialog from './AboutDialog.vue'
import UserPwdDialog from '@/views/user-manage/component/UserPwdDialog.vue'
import APIKeyDialog from './APIKeyDialog.vue'
// import ResetPassword from './ResetPassword.vue'
// import AboutDialog from './AboutDialog.vue'
// import UserPwdDialog from '@/views/user-manage/component/UserPwdDialog.vue'
// import APIKeyDialog from './APIKeyDialog.vue'
import { ComplexPermission } from '@/utils/permission/type'
import { langList } from '@/locales/index'
import { useLocale } from '@/locales/useLocale'
@ -118,7 +118,7 @@ const logout = () => {
onMounted(() => {
if (user.userInfo?.is_edit_password) {
UserPwdDialogRef.value.open(user.userInfo)
// UserPwdDialogRef.value.open(user.userInfo)
}
})
</script>

View File

@ -0,0 +1,50 @@
<template>
<div>
<el-tooltip
effect="dark"
:content="$t('layout.github')"
placement="top"
v-if="user.themeInfo?.showProject"
>
<AppIcon
iconName="app-github"
class="cursor color-secondary mr-8 ml-8"
style="font-size: 20px"
@click="toUrl(user.themeInfo?.projectUrl)"
></AppIcon>
</el-tooltip>
<el-tooltip
effect="dark"
:content="$t('layout.wiki')"
placement="top"
v-if="user.themeInfo?.showUserManual"
>
<AppIcon
iconName="app-user-manual"
class="cursor color-secondary mr-8 ml-8"
style="font-size: 20px"
@click="toUrl(user.themeInfo?.userManualUrl)"
></AppIcon>
</el-tooltip>
<el-tooltip
effect="dark"
:content="$t('layout.forum')"
placement="top"
v-if="user.themeInfo?.showForum"
>
<AppIcon
iconName="app-help"
class="cursor color-secondary mr-16 ml-8"
style="font-size: 20px"
@click="toUrl(user.themeInfo?.forumUrl)"
></AppIcon>
</el-tooltip>
</div>
</template>
<script setup lang="ts">
import useStore from '@/stores'
const { user } = useStore()
function toUrl(url: string) {
window.open(url, '_blank')
}
</script>

View File

@ -1,48 +0,0 @@
<template>
<el-tooltip
effect="dark"
:content="$t('layout.github')"
placement="top"
v-if="user.themeInfo?.showProject"
>
<AppIcon
iconName="app-github"
class="cursor color-secondary mr-8 ml-8"
style="font-size: 20px"
@click="toUrl(user.themeInfo?.projectUrl)"
></AppIcon>
</el-tooltip>
<el-tooltip
effect="dark"
:content="$t('layout.wiki')"
placement="top"
v-if="user.themeInfo?.showUserManual"
>
<AppIcon
iconName="app-reading"
class="cursor color-secondary mr-8 ml-8"
style="font-size: 20px"
@click="toUrl(user.themeInfo?.userManualUrl)"
></AppIcon>
</el-tooltip>
<el-tooltip
effect="dark"
:content="$t('layout.forum')"
placement="top"
v-if="user.themeInfo?.showForum"
>
<AppIcon
iconName="app-help"
class="cursor color-secondary mr-16 ml-8"
style="font-size: 20px"
@click="toUrl(user.themeInfo?.forumUrl)"
></AppIcon>
</el-tooltip>
</template>
<script setup lang="ts">
import useStore from '@/stores'
const { user } = useStore()
function toUrl(url: string) {
window.open(url, '_blank')
}
</script>

View File

@ -1,19 +0,0 @@
<script setup lang="ts">
import UserHeader from './layout-header/UserHeader.vue'
import useStore from '@/stores'
const { user } = useStore()
</script>
<template>
<div class="app-layout">
<div class="app-header">
<UserHeader />
</div>
<div class="app-main">
<AppMain />
</div>
</div>
</template>
<style lang="scss">
@import './index.scss';
</style>

View File

@ -1,18 +0,0 @@
<template>
<div class="main-layout h-full flex">
<div class="sidebar-container">
<Sidebar />
</div>
<div class="view-container">
<AppMain />
</div>
</div>
</template>
<script setup lang="ts">
import { Sidebar, AppMain } from '../components'
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>

View File

@ -1,6 +1,9 @@
<template>
<div class="app-layout">
<AppHeader />
<div class="app-header">
<UserHeader />
</div>
<div class="app-main">
<div class="main-layout h-full flex">
<div class="sidebar-container">
@ -15,7 +18,9 @@
</template>
<script setup lang="ts">
import { AppHeader, Sidebar, AppMain } from '../components'
import UserHeader from '@/layout/layout-header/UserHeader.vue'
import Sidebar from '@/layout/components/sidebar/index.vue'
import AppMain from '@/layout/app-main/index.vue'
import useStore from '@/stores'
const { user } = useStore()
</script>

View File

@ -6,7 +6,7 @@ import system from './system'
import functionLib from './function-lib'
import user from './user'
import team from './team'
import template from './template'
import model from './model'
import document from './document'
import paragraph from './paragraph'
import problem from './problem'
@ -22,7 +22,7 @@ export default {
functionLib,
user,
team,
template,
model,
dataset,
applicationWorkflow,
document,

View File

@ -33,7 +33,7 @@ export default {
IMAGE: 'Vision Model',
TTI: 'Image Generation'
},
templateForm: {
modelForm: {
title: {
baseInfo: 'Basic Information',
advancedInfo: 'Advanced Settings',

View File

@ -0,0 +1,102 @@
export default {
input_type_list: {
TextInput: '文本框',
PasswordInput: '密码框',
Slider: '滑块',
SwitchInput: '开关',
SingleSelect: '单选框',
MultiSelect: '多选框',
DatePicker: '日期',
JsonInput: 'JSON文本框',
RadioCard: '选项卡',
RadioRow: '单行选项卡'
},
default: {
label: '默认值',
placeholder: '请输入默认值',
requiredMessage: '为必填属性',
show: '显示默认值'
},
tip: {
requiredMessage: '不能为空',
jsonMessage: 'JSON格式不正确'
},
searchBar: {
placeholder: '请输入关键字搜索'
},
paramForm: {
field: {
label: '参数',
placeholder: '请输入参数',
requiredMessage: '参数 为必填属性',
requiredMessage2: '只能输入字母数字和下划线'
},
name: {
label: '显示名称',
placeholder: '请输入显示名称',
requiredMessage: '显示名称 为必填属性'
},
tooltip: {
label: '参数提示说明',
placeholder: '请输入参数提示说明'
},
required: {
label: '是否必填',
requiredMessage: '是否必填 为必填属性'
},
input_type: {
label: '组件类型',
placeholder: '请选择组件类型',
requiredMessage: '组建类型 为必填属性'
}
},
DatePicker: {
placeholder: '选择日期',
year: '年',
month: '月',
date: '日期',
datetime: '日期时间',
dataType: {
label: '时间类型',
placeholder: '请选择时间类型'
},
format: {
label: '格式',
placeholder: '请选择格式'
}
},
Select: {
label: '选项值',
placeholder: '请输入选项值'
},
tag: {
label: '标签',
placeholder: '请输入选项标签'
},
Slider: {
showInput: {
label: '是否带输入框'
},
valueRange: {
label: '取值范围',
minRequired: '最小值必填',
maxRequired: '最大值必填'
},
step: {
label: '步长值',
requiredMessage1: '步长值必填',
requiredMessage2: '步长不能为0'
}
},
TextInput: {
length: {
label: '文本长度',
minRequired: '最小长度必填',
maxRequired: '最大长度必填',
requiredMessage1: '长度在',
requiredMessage2: '到',
requiredMessage3: '个字符',
requiredMessage4: '文本长度为必填参数'
}
}
}

View File

@ -2,12 +2,15 @@ import zhCn from 'element-plus/es/locale/lang/zh-cn'
// import components from './components'
import views from './views'
import theme from './theme'
import layout from './layout'
import dynamicsForm from './dynamics-form'
// import common from './common'
// import dynamicsForm from './dynamics-form'
// import chat from './ai-chat'
export default {
lang: '简体中文',
zhCn,
views,
theme
theme,
layout,
dynamicsForm,
}

View File

@ -0,0 +1,33 @@
export default {
github: '项目地址',
wiki: '用户手册',
forum: '论坛求助',
logout: '退出',
apiKey: 'API Key 管理',
apiServiceAddress: 'API 服务地址',
language: '语言',
isExpire: '未上传 License 或 License 已过期。',
about: {
title: '关于',
expiredTime: '到期时间',
edition: {
label: '版本',
community: '社区版',
professional: '专业版'
},
version: '版本号',
serialNo: '序列号',
remark: '备注',
update: '更新',
authorize: '授权给'
},
time: {
daysLater: '天后',
hoursLater: '小时后',
expired: '已过期',
expiringSoon: '即将到期'
},
copyright: '版权所有 © 2014-2025 杭州飞致云信息科技有限公司',
userManualUrl: 'https://maxkb.cn/docs/',
forumUrl: 'https://bbs.fit2cloud.com/c/mk/11'
}

View File

@ -1,3 +1,5 @@
import login from './login'
import model from './model'
// import notFound from './404'
// import application from './application'
// import applicationOverview from './application-overview'
@ -6,15 +8,17 @@
// import functionLib from './function-lib'
// import user from './user'
// import team from './team'
// import template from './template'
// import document from './document'
// import paragraph from './paragraph'
// import problem from './problem'
// import log from './log'
// import applicationWorkflow from './application-workflow'
import login from './login'
// import operateLog from './operate-log'
export default {
login,
model,
// notFound,
// application,
// applicationOverview,
@ -23,12 +27,11 @@ export default {
// functionLib,
// user,
// team,
// template,
// document,
// paragraph,
// problem,
// log,
// applicationWorkflow,
login,
// operateLog
}

View File

@ -0,0 +1,83 @@
export default {
title: '模型',
provider: '供应商',
providerPlaceholder: '选择供应商',
addModel: '添加模型',
searchBar: {
placeholder: '按名称搜索'
},
delete: {
confirmTitle: '删除模型',
confirmMessage: '是否删除模型:'
},
tip: {
createSuccessMessage: '创建模型成功',
createErrorMessage: '基础信息有填写错误',
errorMessage: '变量已存在: ',
emptyMessage1: '请先选择基础信息的模型类型和基础模型',
emptyMessage2: '所选模型不支持参数设置',
updateSuccessMessage: '修改模型成功',
saveSuccessMessage: '模型参数保存成功',
downloadError: '下载失败',
noModel: '模型在Ollama不存在'
},
modelType: {
allModel: '全部模型',
publicModel: '公有模型',
privateModel: '私有模型',
LLM: '大语言模型',
EMBEDDING: '向量模型',
RERANKER: '重排模型',
STT: '语音识别',
TTS: '语音合成',
IMAGE: '视觉模型',
TTI: '图片生成'
},
modelForm: {
title: {
baseInfo: '基础信息',
advancedInfo: '高级设置',
modelParams: '模型参数',
editParam: '编辑参数',
addParam: '添加参数',
paramSetting: '模型参数设置',
apiParamPassing: '接口传参'
},
form: {
templateName: {
label: '模型名称',
placeholder: '请给基础模型设置一个名称',
tooltip: 'MaxKB 中自定义的模型名称',
requiredMessage: '模型名称不能为空'
},
permissionType: {
label: '权限',
privateDesc: '仅当前用户使用',
publicDesc: '所有用户都可使用',
requiredMessage: '权限不能为空'
},
model_type: {
label: '模型类型',
placeholder: '请选择模型类型',
tooltip1: '大语言模型在应用中与AI对话的推理模型。',
tooltip2: '向量模型:在知识库中对文档内容进行向量化的模型。',
tooltip3: '语音识别:在应用中开启语音识别后用于语音转文字的模型。',
tooltip4: '语音合成:在应用中开启语音播放后用于文字转语音的模型。',
tooltip5: '重排模型:在高级编排应用中使用多路召回时,对候选分段进行重新排序的模型。',
tooltip6: '视觉模型:在高级编排应用中用于图片理解的视觉模型。',
tooltip7: '图片生成:在高级编排应用中用于图片生成的视觉模型。',
requiredMessage: '模型类型不能为空'
},
base_model: {
label: '基础模型',
tooltip: '列表中未列出的模型,直接输入模型名称,回车即可添加',
placeholder: '自定义输入基础模型后回车即可',
requiredMessage: '基础模型不能为空'
}
}
},
download: {
downloading: '正在下载中',
cancelDownload: '取消下载'
}
}

View File

@ -6,7 +6,7 @@ import system from './system'
import functionLib from './function-lib'
import user from './user'
import team from './team'
import template from './template'
import model from './model'
import document from './document'
import paragraph from './paragraph'
import problem from './problem'
@ -22,7 +22,7 @@ export default {
functionLib,
user,
team,
template,
model,
dataset,
applicationWorkflow,
document,

View File

@ -1,5 +1,5 @@
export default {
title: '模型設定',
title: '模型',
provider: '供應商',
providerPlaceholder: '選擇供應商',
addModel: '新增模型',
@ -33,7 +33,7 @@ export default {
IMAGE: '圖片理解',
TTI: '圖片生成'
},
templateForm: {
modelForm: {
title: {
baseInfo: '基礎資訊',
advancedInfo: '進階設定',

View File

@ -3,7 +3,7 @@ const ModelRouter = {
name: 'model',
meta: { title: 'views.model.title', permission: 'MODEL:READ' },
redirect: '/model',
component: () => import('@/layout/layout-template/AppLayout.vue'),
component: () => import('@/layout/layout-template/MainLayout.vue'),
children: [
{
path: '/model',

View File

@ -36,10 +36,9 @@ const useLoginStore = defineStore('user', {
return !this.themeInfo?.theme || this.themeInfo?.theme === '#3370FF'
},
async profile(loading?: Ref<boolean>) {
return UserApi.getUserProfile(loading).then((ok: { data: User }) => {
this.userInfo = ok.data
useLocalStorage<string>(localeConfigKey, 'en-US').value =
ok.data?.language || this.getLanguage()
return UserApi.getUserProfile(loading).then((ok) => {
this.userInfo = ok
useLocalStorage<string>(localeConfigKey, 'en-US').value = ok?.language || this.getLanguage()
// return this.asyncGetProfile()
})
},

View File

@ -0,0 +1,10 @@
export const defaultIcon = '/ui/favicon.ico'
// 是否显示字母 / icon
export function isAppIcon(url: String | undefined) {
return url === defaultIcon ? '' : url
}
export function isWorkFlow(type: string | undefined) {
return type === 'WORK_FLOW'
}

8
ui/src/utils/bus.ts Normal file
View File

@ -0,0 +1,8 @@
import mitt from "mitt";
const bus: any = {};
const emitter = mitt();
bus.on = emitter.on;
bus.off = emitter.off;
bus.emit = emitter.emit;
export default bus;

16
ui/src/utils/clipboard.ts Normal file
View File

@ -0,0 +1,16 @@
import Clipboard from 'vue-clipboard3'
import { MsgSuccess, MsgError } from '@/utils/message'
import { t } from '@/locales'
/*
*/
export async function copyClick(info: string) {
const { toClipboard } = Clipboard()
try {
await toClipboard(info)
MsgSuccess(t('common.copySuccess'))
} catch (e) {
console.error(e)
MsgError(t('common.copyError'))
}
}

24
ui/src/utils/common.ts Normal file
View File

@ -0,0 +1,24 @@
/**
* n个拆分为一个数组
* @param sourceDataList
* @param splitNum
* @returns
*/
export function splitArray<T>(sourceDataList: Array<T>, splitNum: number) {
const count =
sourceDataList.length % splitNum == 0
? sourceDataList.length / splitNum
: sourceDataList.length / splitNum + 1
const arrayList: Array<Array<T>> = []
for (let i = 0; i < count; i++) {
let index = i * splitNum
const list: Array<T> = []
let j = 0
while (j < splitNum && index < sourceDataList.length) {
list.push(sourceDataList[index++])
j++
}
arrayList.push(list)
}
return arrayList
}

View File

@ -1,88 +0,0 @@
<template>
<el-dialog
:title="$t('views.functionLib.functionForm.form.functionName.name')"
v-model="dialogVisible"
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
append-to-body
width="450"
>
<el-form
label-position="top"
ref="fieldFormRef"
:rules="rules"
:model="form"
require-asterisk-position="right"
>
<el-form-item prop="name">
<el-input v-model="form.name" maxlength="64" show-word-limit></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
<el-button type="primary" @click="submit(fieldFormRef)" :loading="loading">
{{ isEdit ? $t('common.save') : $t('common.add') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import type { FormInstance } from 'element-plus'
import { cloneDeep } from 'lodash'
import { t } from '@/locales'
const emit = defineEmits(['refresh'])
const fieldFormRef = ref()
const loading = ref<boolean>(false)
const isEdit = ref<boolean>(false)
const form = ref<any>({
name: ''
})
const rules = reactive({
name: [
{
required: true,
message: t('views.functionLib.functionForm.form.functionName.placeholder'),
trigger: 'blur'
}
]
})
const dialogVisible = ref<boolean>(false)
watch(dialogVisible, (bool) => {
if (!bool) {
form.value = {
name: ''
}
}
})
const open = (row: any, edit: boolean) => {
if (row) {
form.value = cloneDeep(row)
}
isEdit.value = edit || false
dialogVisible.value = true
}
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
emit('refresh', form.value, isEdit.value)
dialogVisible.value = false
}
})
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,85 @@
<template>
<el-drawer
v-model="drawer"
:direction="direction"
size="600"
:destroy-on-close="true"
:before-close="cancelClick"
>
<template #header>
<h4>
{{
isEdit
? $t('views.model.modelForm.title.editParam')
: $t('views.model.modelForm.title.addParam')
}}
</h4>
</template>
<template #default>
<DynamicsFormConstructor
v-model="currentItem"
label-position="top"
require-asterisk-position="right"
ref="DynamicsFormConstructorRef"
></DynamicsFormConstructor>
</template>
<template #footer>
<div style="flex: auto">
<el-button @click="cancelClick">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="confirmClick()">{{
isEdit ? $t('common.save') : $t('common.add')
}}</el-button>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { DrawerProps } from 'element-plus'
import { cloneDeep } from 'lodash'
import DynamicsFormConstructor from '@/components/dynamics-form/constructor/index.vue'
const drawer = ref(false)
const direction = ref<DrawerProps['direction']>('rtl')
const isEdit = ref(false)
const DynamicsFormConstructorRef = ref<InstanceType<typeof DynamicsFormConstructor>>()
const currentItem = ref(null)
const currentIndex = ref(null)
const emit = defineEmits(['refresh'])
const open = (row: any, index: any) => {
if (row) {
currentItem.value = cloneDeep(row)
currentIndex.value = index
isEdit.value = true
}
drawer.value = true
}
function cancelClick() {
drawer.value = false
isEdit.value = false
currentItem.value = null
currentIndex.value = null
}
function confirmClick() {
const formEl = DynamicsFormConstructorRef.value
formEl?.validate().then((valid) => {
if (valid) {
emit('refresh', formEl?.getData(), currentIndex.value)
drawer.value = false
isEdit.value = false
currentItem.value = null
currentIndex.value = null
}
})
}
defineExpose({ open })
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,498 @@
<template>
<el-dialog
v-model="dialogVisible"
width="600px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
:before-close="close"
append-to-body
>
<template #header="{ close, titleId, titleClass }">
<el-breadcrumb separator=">">
<el-breadcrumb-item>
<span @click="toSelectProvider" class="select-provider">
{{ $t('views.model.providerPlaceholder') }}
</span>
</el-breadcrumb-item>
<el-breadcrumb-item
><span class="active-breadcrumb">{{
`${$t('common.add')} ${providerValue?.name}`
}}</span></el-breadcrumb-item
>
</el-breadcrumb>
</template>
<el-tabs v-model="activeName">
<el-tab-pane :label="$t('views.model.modelForm.title.baseInfo')" name="base-info">
<DynamicsForm
v-model="form_data"
:render_data="model_form_field"
:model="form_data"
ref="dynamicsFormRef"
label-position="top"
require-asterisk-position="right"
class="mb-24"
label-width="auto"
>
<template #default>
<el-form-item prop="name" :rules="base_form_data_rule.name">
<template #label>
<div class="flex align-center" style="display: inline-flex">
<div class="mr-4">
<span> {{ $t('views.model.modelForm.form.templateName.label') }} </span>
</div>
<el-tooltip effect="dark" placement="right">
<template #content>
<p>{{ $t('views.model.modelForm.form.templateName.tooltip') }}</p>
</template>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
</div>
</template>
<el-input
v-model="base_form_data.name"
maxlength="64"
show-word-limit
:placeholder="$t('views.model.modelForm.form.templateName.placeholder')"
/>
</el-form-item>
<el-form-item prop="permission_type" :rules="base_form_data_rule.permission_type">
<template #label>
<span>{{ $t('views.model.modelForm.form.permissionType.label') }}</span>
</template>
<el-radio-group v-model="base_form_data.permission_type" class="card__radio">
<el-row :gutter="16">
<template v-for="(value, key) of PermissionType" :key="key">
<el-col :span="12">
<el-card
shadow="never"
class="mb-16"
:class="base_form_data.permission_type === key ? 'active' : ''"
>
<el-radio :value="key" size="large">
<p class="mb-4">{{ $t(value) }}</p>
<el-text type="info">
{{ $t(PermissionDesc[key]) }}
</el-text>
</el-radio>
</el-card>
</el-col>
</template>
</el-row>
</el-radio-group>
</el-form-item>
<el-form-item prop="model_type" :rules="base_form_data_rule.model_type">
<template #label>
<div class="flex align-center" style="display: inline-flex">
<span class="mr-4"
>{{ $t('views.model.modelForm.form.model_type.label') }}
</span>
<el-tooltip effect="dark" placement="right">
<template #content>
<p>{{ $t('views.model.modelForm.form.model_type.tooltip1') }}</p>
<p>{{ $t('views.model.modelForm.form.model_type.tooltip2') }}</p>
<p>{{ $t('views.model.modelForm.form.model_type.tooltip3') }}</p>
<p>{{ $t('views.model.modelForm.form.model_type.tooltip4') }}</p>
<p>{{ $t('views.model.modelForm.form.model_type.tooltip5') }}</p>
<p>{{ $t('views.model.modelForm.form.model_type.tooltip6') }}</p>
<p>{{ $t('views.model.modelForm.form.model_type.tooltip7') }}</p>
</template>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
</div>
</template>
<el-select
v-loading="model_type_loading"
@change="list_base_model($event, true)"
v-model="base_form_data.model_type"
class="w-full m-2"
:placeholder="$t('views.model.modelForm.form.model_type.placeholder')"
>
<el-option
v-for="item in model_type_list"
:key="item.value"
:label="item.key"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item prop="model_name" :rules="base_form_data_rule.model_name">
<template #label>
<div class="flex align-center" style="display: inline-flex">
<div class="mr-4">
<span>{{ $t('views.model.modelForm.form.base_model.label') }} </span>
<span class="danger ml-4">{{
$t('views.model.modelForm.form.base_model.tooltip')
}}</span>
</div>
</div>
</template>
<el-select
@change="getModelForm($event)"
v-loading="base_model_loading"
v-model="base_form_data.model_name"
class="w-full m-2"
:placeholder="$t('views.model.modelForm.form.base_model.placeholder')"
filterable
allow-create
default-first-option
>
<el-option v-for="item in base_model_list" :key="item.name" :value="item.name">
<template #default>
<div class="flex align-center" style="display: inline-flex">
<div class="flex-between mr-4">
<span>{{ item.name }} </span>
</div>
<el-tooltip effect="dark" placement="right" v-if="item.desc">
<template #content>
<p class="w-280">{{ item.desc }}</p>
</template>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
</div>
</template>
</el-option>
</el-select>
</el-form-item>
</template>
</DynamicsForm>
</el-tab-pane>
<el-tab-pane
:label="$t('views.model.modelForm.title.advancedInfo')"
name="advanced-info"
>
<el-empty
v-if="!base_form_data.model_type || !base_form_data.model_name"
:description="$t('views.model.tip.emptyMessage1')"
/>
<el-empty
v-else-if="
base_form_data.model_type === 'RERANKER' ||
base_form_data.model_type === 'EMBEDDING' ||
base_form_data.model_type === 'STT'
"
:description="$t('views.model.tip.emptyMessage2')"
/>
<div class="flex-between mb-8" v-else>
<h5>{{ $t('views.model.modelForm.title.modelParams') }}</h5>
<el-button
type="text"
@click.stop="openAddDrawer()"
:disabled="
base_form_data.model_type !== 'TTS' &&
base_form_data.model_type !== 'LLM' &&
base_form_data.model_type !== 'IMAGE' &&
base_form_data.model_type !== 'TTI'
"
>
<AppIcon iconName="Plus" class="add-icon" />{{ $t('common.add') }}
</el-button>
</div>
<el-table
:data="base_form_data.model_params_form"
v-if="base_form_data.model_params_form?.length > 0"
class="mb-16"
>
<el-table-column
prop="label"
:label="$t('dynamicsForm.paramForm.name.label')"
show-overflow-tooltip
>
<template #default="{ row }">
<span v-if="row.label && row.label.input_type === 'TooltipLabel'">{{
row.label.label
}}</span>
<span v-else>{{ row.label }}</span>
</template>
</el-table-column>
<el-table-column
prop="field"
:label="$t('dynamicsForm.paramForm.field.label')"
show-overflow-tooltip
width="95px"
/>
<el-table-column :label="$t('dynamicsForm.paramForm.input_type.label')" width="110px">
<template #default="{ row }">
<el-tag type="info" class="info-tag">{{
input_type_list.find((item) => item.value === row.input_type)?.label
}}</el-tag>
</template>
</el-table-column>
<el-table-column
prop="default_value"
:label="$t('dynamicsForm.default.label')"
show-overflow-tooltip
/>
<el-table-column :label="$t('common.required')">
<template #default="{ row }">
<div @click.stop>
<el-switch disabled size="small" v-model="row.required" />
</div>
</template>
</el-table-column>
<el-table-column :label="$t('common.operation')" align="left" width="90">
<template #default="{ row, $index }">
<span class="mr-4">
<el-tooltip effect="dark" :content="$t('common.modify')" placement="top">
<el-button type="primary" text @click.stop="openAddDrawer(row, $index)">
<el-icon><EditPen /></el-icon>
</el-button>
</el-tooltip>
</span>
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
<el-button type="primary" text @click="deleteParam($index)">
<el-icon>
<Delete />
</el-icon>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
<template #footer>
<span class="dialog-footer">
<el-button @click="close">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="submit" :loading="loading">
{{ $t('common.save') }}
</el-button>
</span>
</template>
</el-dialog>
<AddParamDrawer ref="AddParamRef" @refresh="refresh" />
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { Provider, BaseModel } from '@/api/type/model'
import type { Dict, KeyValue } from '@/api/type/common'
import ModelApi from '@/api/model/model'
import type { FormField } from '@/components/dynamics-form/type'
import DynamicsForm from '@/components/dynamics-form/index.vue'
import type { FormRules } from 'element-plus'
import { MsgError, MsgSuccess, MsgWarning } from '@/utils/message'
import { PermissionType, PermissionDesc } from '@/enums/model'
import { input_type_list } from '@/components/dynamics-form/constructor/data'
import AddParamDrawer from '@/views/template/component/AddParamDrawer.vue'
import { t } from '@/locales'
const providerValue = ref<Provider>()
const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()
const emit = defineEmits(['change', 'submit'])
const loading = ref<boolean>(false)
const model_type_loading = ref<boolean>(false)
const base_model_loading = ref<boolean>(false)
const model_type_list = ref<Array<KeyValue<string, string>>>([])
const base_model_list = ref<Array<BaseModel>>()
const model_form_field = ref<Array<FormField>>([])
const dialogVisible = ref<boolean>(false)
const activeName = ref('base-info')
const AddParamRef = ref()
const base_form_data_rule = ref<FormRules>({
name: {
required: true,
trigger: 'blur',
message: t('views.model.modelForm.form.templateName.requiredMessage')
},
permission_type: {
required: true,
trigger: 'change',
message: t('views.model.modelForm.form.permissionType.requiredMessage')
},
model_type: {
required: true,
trigger: 'change',
message: t('views.model.modelForm.form.model_type.requiredMessage')
},
model_name: {
required: true,
trigger: 'change',
message: t('views.model.modelForm.form.base_model.requiredMessage')
}
})
const base_form_data = ref<{
name: string
permission_type: string
model_type: string
model_name: string
model_params_form: any
}>({ name: '', model_type: '', model_name: '', permission_type: 'PRIVATE', model_params_form: [] })
const credential_form_data = ref<Dict<any>>({})
const form_data = computed({
get: () => {
return {
...credential_form_data.value,
name: base_form_data.value.name,
model_type: base_form_data.value.model_type,
model_name: base_form_data.value.model_name,
permission_type: base_form_data.value.permission_type,
model_params_form: base_form_data.value.model_params_form
}
},
set: (event: any) => {
credential_form_data.value = event
}
})
const getModelForm = (model_name: string) => {
if (!form_data.value.model_type) {
MsgWarning(t('views.model.modelForm.form.model_type.requiredMessage'))
base_form_data.value.model_name = ''
return
}
if (providerValue.value) {
ModelApi.getModelCreateForm(
providerValue.value.provider,
form_data.value.model_type,
model_name
).then((ok) => {
model_form_field.value = ok.data
//
dynamicsFormRef.value?.render(model_form_field.value, undefined)
})
ModelApi.listBaseModelParamsForm(
providerValue.value.provider,
form_data.value.model_type,
model_name,
base_model_loading
).then((ok) => {
base_form_data.value.model_params_form = ok.data
})
}
}
const open = (provider: Provider, model_type?: string) => {
ModelApi.listModelType(provider.provider, model_type_loading).then((ok) => {
model_type_list.value = ok.data
})
providerValue.value = provider
dialogVisible.value = true
base_form_data.value.model_type = model_type || ''
activeName.value = 'base-info'
if (model_type) {
list_base_model(model_type)
}
}
const list_base_model = (model_type: any, change?: boolean) => {
if (change) {
base_form_data.value.model_name = ''
base_form_data.value.model_params_form = []
}
if (providerValue.value) {
ModelApi.listBaseModel(providerValue.value.provider, model_type, base_model_loading).then(
(ok) => {
base_model_list.value = ok.data
}
)
}
}
const close = () => {
base_form_data.value = {
name: '',
model_type: '',
model_name: '',
permission_type: 'PRIVATE',
model_params_form: []
}
credential_form_data.value = {}
model_form_field.value = []
base_model_list.value = []
loading.value = false
dialogVisible.value = false
}
const submit = () => {
dynamicsFormRef.value
?.validate()
.then(() => {
if (providerValue.value) {
ModelApi.createModel(
{
...base_form_data.value,
credential: credential_form_data.value,
provider: providerValue.value.provider
},
loading
).then((ok) => {
close()
MsgSuccess(t('views.model.tip.createSuccessMessage'))
emit('submit')
})
}
})
.catch(() => {
MsgError(t('views.model.tip.createErrorMessage'))
})
}
function openAddDrawer(data?: any, index?: any) {
AddParamRef.value?.open(data, index)
}
function deleteParam(index: any) {
base_form_data.value.model_params_form.splice(index, 1)
}
function refresh(data: any, index: any) {
for (let i = 0; i < base_form_data.value.model_params_form.length; i++) {
let field = base_form_data.value.model_params_form[i].field
let label = base_form_data.value.model_params_form[i].label
if (label && label.input_type === 'TooltipLabel') {
label = label.label
}
let label2 = data.label
if (label2 && label2.input_type === 'TooltipLabel') {
label2 = label2.label
}
if (field === data.field && index !== i) {
MsgError(t('views.model.tip.errorMessage') + data.field)
return
}
if (label === label2 && index !== i) {
MsgError(t('views.model.tip.errorMessage') + label)
return
}
}
if (index !== null) {
base_form_data.value.model_params_form.splice(index, 1, data)
} else {
base_form_data.value.model_params_form.push(data)
}
}
const toSelectProvider = () => {
close()
emit('change')
}
defineExpose({ open, close })
</script>
<style lang="scss" scoped>
.select-provider {
font-size: 16px;
color: rgba(100, 106, 115, 1);
font-weight: 400;
line-height: 24px;
cursor: pointer;
&:hover {
color: var(--el-color-primary);
}
}
.active-breadcrumb {
font-size: 16px;
color: rgba(31, 35, 41, 1);
font-weight: 500;
line-height: 24px;
}
</style>

View File

@ -1,127 +0,0 @@
<template>
<el-dialog
:title="`Logo ${$t('common.setting')}`"
v-model="dialogVisible"
:close-on-click-modal="false"
:close-on-press-escape="false"
width="550"
>
<el-radio-group v-model="radioType" class="radio-block mb-16">
<el-radio value="default">
<p>{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.default') }}</p>
<AppAvatar
v-if="detail?.name"
:name="detail?.name"
pinyinColor
class="mt-8 mb-8"
shape="square"
:size="32"
/>
</el-radio>
<el-radio value="custom">
<p>{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.customizeUpload') }}</p>
<div class="flex mt-8">
<AppAvatar
v-if="fileURL"
shape="square"
:size="32"
style="background: none"
class="mr-16"
>
<img :src="fileURL" alt="" />
</AppAvatar>
<el-upload
ref="uploadRef"
action="#"
:auto-upload="false"
:show-file-list="false"
accept="image/jpeg, image/png, image/gif"
:on-change="onChange"
>
<el-button icon="Upload" :disabled="radioType !== 'custom'"
>{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.upload') }}
</el-button>
</el-upload>
</div>
<div class="el-upload__tip info mt-8">
{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.sizeTip') }}
</div>
</el-radio>
</el-radio-group>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="submit" :loading="loading">
{{ $t('common.save') }}</el-button
>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import functionLibApi from '@/api/function-lib'
import { cloneDeep } from 'lodash'
import { MsgError, MsgSuccess } from '@/utils/message'
import { defaultIcon, isAppIcon } from '@/utils/application'
import { t } from '@/locales'
const emit = defineEmits(['refresh'])
const iconFile = ref<any>(null)
const fileURL = ref<any>(null)
const dialogVisible = ref<boolean>(false)
const loading = ref(false)
const detail = ref<any>(null)
const radioType = ref('default')
watch(dialogVisible, (bool) => {
if (!bool) {
iconFile.value = null
fileURL.value = null
}
})
const open = (data: any) => {
radioType.value = isAppIcon(data.icon) ? 'custom' : 'default'
fileURL.value = isAppIcon(data.icon) ? data.icon : null
detail.value = cloneDeep(data)
dialogVisible.value = true
}
const onChange = (file: any) => {
//110MB
const isLimit = file?.size / 1024 / 1024 < 10
if (!isLimit) {
// @ts-ignore
MsgError(t('views.applicationOverview.appInfo.EditAvatarDialog.fileSizeExceeded'))
return false
} else {
iconFile.value = file
fileURL.value = URL.createObjectURL(file.raw)
}
}
function submit() {
if (radioType.value === 'default') {
emit('refresh', '/ui/favicon.ico')
dialogVisible.value = false
} else if (radioType.value === 'custom' && iconFile.value) {
let fd = new FormData()
fd.append('file', iconFile.value.raw)
functionLibApi.putFunctionLibIcon(detail.value.id, fd, loading).then((res: any) => {
emit('refresh', res.data)
dialogVisible.value = false
})
} else {
MsgError(t('views.applicationOverview.appInfo.EditAvatarDialog.uploadImagePrompt'))
}
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,307 @@
<template>
<el-dialog
v-model="dialogVisible"
width="600px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
:before-close="close"
>
<template #header="{ close, titleId, titleClass }">
<el-breadcrumb separator=">">
<el-breadcrumb-item
><span class="active-breadcrumb">{{
`${$t('common.edit')} ${providerValue?.name}`
}}</span></el-breadcrumb-item
>
</el-breadcrumb>
</template>
<DynamicsForm
v-loading="formLoading"
v-model="form_data"
:render_data="model_form_field"
:model="form_data"
ref="dynamicsFormRef"
label-position="top"
require-asterisk-position="right"
>
<template #default>
<el-form-item prop="name" :rules="base_form_data_rule.name">
<template #label>
<div class="flex align-center" style="display: inline-flex">
<div class="mr-4">
<span>{{ $t('views.model.modelForm.form.templateName.label') }} </span>
</div>
<el-tooltip effect="dark" placement="right">
<template #content>
<p>{{ $t('views.model.modelForm.form.templateName.tooltip') }}</p>
</template>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
</div>
</template>
<el-input
v-model="base_form_data.name"
maxlength="64"
show-word-limit
:placeholder="$t('views.model.modelForm.form.templateName.placeholder')"
/>
</el-form-item>
<el-form-item prop="permission_type" :rules="base_form_data_rule.permission_type">
<template #label>
<span>{{ $t('views.model.modelForm.form.permissionType.label') }}</span>
</template>
<el-radio-group v-model="base_form_data.permission_type" class="card__radio">
<el-row :gutter="16">
<template v-for="(value, key) of PermissionType" :key="key">
<el-col :span="12">
<el-card
shadow="never"
class="mb-16"
:class="base_form_data.permission_type === key ? 'active' : ''"
>
<el-radio :value="key" size="large">
<p class="mb-4">{{ $t(value) }}</p>
<el-text type="info">
{{ $t(PermissionDesc[key]) }}
</el-text>
</el-radio>
</el-card>
</el-col>
</template>
</el-row>
</el-radio-group>
</el-form-item>
<el-form-item prop="model_type" :rules="base_form_data_rule.model_type">
<template #label>
<span>{{ $t('views.model.modelForm.form.model_type.label') }}</span>
</template>
<el-select
disabled
v-loading="model_type_loading"
@change="list_base_model($event, true)"
v-model="base_form_data.model_type"
class="w-full m-2"
:placeholder="$t('views.model.modelForm.form.model_type.placeholder')"
>
<el-option
v-for="item in model_type_list"
:key="item.value"
:label="item.key"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item prop="model_name" :rules="base_form_data_rule.model_name">
<template #label>
<div class="flex align-center" style="display: inline-flex">
<div class="mr-4">
<span>{{ $t('views.model.modelForm.form.base_model.label') }} </span>
<span class="danger ml-4">{{
$t('views.model.modelForm.form.base_model.tooltip')
}}</span>
</div>
</div>
</template>
<el-select
@change="getModelForm($event)"
v-loading="base_model_loading"
v-model="base_form_data.model_name"
class="w-full m-2"
:placeholder="$t('views.model.modelForm.form.base_model.requiredMessage')"
filterable
allow-create
default-first-option
>
<el-option v-for="item in base_model_list" :key="item.name" :value="item.name">
<template #default>
<div class="flex align-center" style="display: inline-flex">
<div class="flex-between mr-4">
<span>{{ item.name }} </span>
</div>
<el-tooltip effect="dark" placement="right" v-if="item.desc">
<template #content>
<p>{{ item.desc }}</p>
</template>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
</div>
</template>
</el-option>
</el-select>
</el-form-item>
</template>
</DynamicsForm>
<template #footer>
<span class="dialog-footer">
<el-button @click="close">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="submit" :loading="loading">
{{ $t('common.modify') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { Provider, BaseModel, Model } from '@/api/type/model'
import type { Dict, KeyValue } from '@/api/type/common'
import ModelApi from '@/api/model/model'
import type { FormField } from '@/components/dynamics-form/type'
import DynamicsForm from '@/components/dynamics-form/index.vue'
import type { FormRules } from 'element-plus'
import { MsgSuccess } from '@/utils/message'
import { PermissionType, PermissionDesc } from '@/enums/model'
import { t } from '@/locales'
const providerValue = ref<Provider>()
const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()
const emit = defineEmits(['change', 'submit'])
const loading = ref<boolean>(false)
const formLoading = ref<boolean>(false)
const model_type_loading = ref<boolean>(false)
const base_model_loading = ref<boolean>(false)
const model_type_list = ref<Array<KeyValue<string, string>>>([])
const modelValue = ref<Model>()
const base_model_list = ref<Array<BaseModel>>([])
const model_form_field = ref<Array<FormField>>([])
const dialogVisible = ref<boolean>(false)
const base_form_data_rule = ref<FormRules>({
name: {
required: true,
trigger: 'blur',
message: t('views.model.modelForm.form.templateName.requiredMessage')
},
model_type: {
required: true,
trigger: 'change',
message: t('views.model.modelForm.form.model_type.requiredMessage')
},
model_name: {
required: true,
trigger: 'change',
message: t('views.model.modelForm.form.base_model.requiredMessage')
}
})
const base_form_data = ref<{
name: string
permission_type: string
model_type: string
model_name: string
}>({ name: '', model_type: '', model_name: '', permission_type: 'PRIVATE' })
const credential_form_data = ref<Dict<any>>({})
const form_data = computed({
get: () => {
return { ...credential_form_data.value, ...base_form_data.value }
},
set: (event: any) => {
credential_form_data.value = event
}
})
const getModelForm = (model_name: string) => {
if (providerValue.value) {
ModelApi.getModelCreateForm(
providerValue.value.provider,
form_data.value.model_type,
model_name
).then((ok) => {
model_form_field.value = ok.data
if (modelValue.value) {
//
dynamicsFormRef.value?.render(model_form_field.value, modelValue.value.credential)
}
})
}
}
const list_base_model = (model_type: any, change?: boolean) => {
if (change) {
base_form_data.value.model_name = ''
}
if (providerValue.value) {
ModelApi.listBaseModel(providerValue.value.provider, model_type, base_model_loading).then(
(ok) => {
base_model_list.value = ok.data
}
)
}
}
const open = (provider: Provider, model: Model) => {
modelValue.value = model
ModelApi.getModelById(model.id, formLoading).then((ok) => {
modelValue.value = ok.data
ModelApi.listModelType(model.provider, model_type_loading).then((ok) => {
model_type_list.value = ok.data
list_base_model(model.model_type)
})
providerValue.value = provider
base_form_data.value = {
name: model.name,
permission_type: model.permission_type,
model_type: model.model_type,
model_name: model.model_name
}
form_data.value = model.credential
getModelForm(model.model_name)
})
dialogVisible.value = true
}
const close = () => {
base_form_data.value = { name: '', model_type: '', model_name: '', permission_type: '' }
dynamicsFormRef.value?.ruleFormRef?.resetFields()
credential_form_data.value = {}
model_form_field.value = []
base_model_list.value = []
dialogVisible.value = false
}
const submit = () => {
dynamicsFormRef.value?.validate().then(() => {
if (modelValue.value) {
ModelApi.updateModel(
modelValue.value.id,
{
...base_form_data.value,
credential: credential_form_data.value
},
loading
).then((ok) => {
MsgSuccess(t('views.model.tip.updateSuccessMessage'))
close()
emit('submit')
})
}
})
}
defineExpose({ open, close })
</script>
<style lang="scss" scoped>
.select-provider {
font-size: 16px;
color: rgba(100, 106, 115, 1);
font-weight: 400;
line-height: 24px;
cursor: pointer;
&:hover {
color: var(--el-color-primary);
}
}
.active-breadcrumb {
font-size: 16px;
color: rgba(31, 35, 41, 1);
font-weight: 500;
line-height: 24px;
}
</style>

View File

@ -1,129 +0,0 @@
<template>
<el-dialog
:title="
isEdit
? $t('views.template.templateForm.title.editParam')
: $t('views.template.templateForm.title.addParam')
"
v-model="dialogVisible"
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
append-to-body
>
<el-form
label-position="top"
ref="fieldFormRef"
:rules="rules"
:model="form"
require-asterisk-position="right"
>
<el-form-item :label="$t('views.functionLib.functionForm.form.paramName.label')" prop="name">
<el-input
v-model="form.name"
:placeholder="$t('views.functionLib.functionForm.form.paramName.placeholder')"
maxlength="64"
show-word-limit
@blur="form.name = form.name.trim()"
/>
</el-form-item>
<el-form-item :label="$t('views.functionLib.functionForm.form.dataType.label')">
<el-select v-model="form.type">
<el-option v-for="item in typeOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item :label="$t('views.functionLib.functionForm.form.source.label')">
<el-select v-model="form.source">
<el-option
:label="$t('views.functionLib.functionForm.form.source.reference')"
value="reference"
/>
<el-option
:label="$t('views.functionLib.functionForm.form.source.custom')"
value="custom"
/>
</el-select>
</el-form-item>
<el-form-item
:label="$t('views.functionLib.functionForm.form.required.label')"
@click.prevent
>
<el-switch size="small" v-model="form.is_required"></el-switch>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
<el-button type="primary" @click="submit(fieldFormRef)" :loading="loading">
{{ isEdit ? $t('common.save') : $t('common.add') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import type { FormInstance } from 'element-plus'
import { cloneDeep } from 'lodash'
import { t } from '@/locales'
const typeOptions = ['string', 'int', 'dict', 'array', 'float']
const emit = defineEmits(['refresh'])
const fieldFormRef = ref()
const loading = ref<boolean>(false)
const isEdit = ref(false)
const form = ref<any>({
name: '',
type: typeOptions[0],
source: 'reference',
is_required: true
})
const rules = reactive({
name: [
{
required: true,
message: t('views.functionLib.functionForm.form.paramName.placeholder'),
trigger: 'blur'
}
]
})
const dialogVisible = ref<boolean>(false)
watch(dialogVisible, (bool) => {
if (!bool) {
form.value = {
name: '',
type: typeOptions[0],
source: 'reference',
is_required: true
}
isEdit.value = false
}
})
const open = (row: any) => {
if (row) {
form.value = cloneDeep(row)
isEdit.value = true
}
dialogVisible.value = true
}
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
emit('refresh', form.value)
dialogVisible.value = false
}
})
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@ -1,184 +0,0 @@
<template>
<el-drawer v-model="debugVisible" size="60%" :append-to-body="true">
<template #header>
<div class="flex align-center" style="margin-left: -8px">
<el-button class="cursor mr-4" link @click.prevent="debugVisible = false">
<el-icon :size="20">
<Back />
</el-icon>
</el-button>
<h4>{{ $t('common.debug') }}</h4>
</div>
</template>
<div>
<div v-if="form.init_field_list.length > 0">
<h4 class="title-decoration-1 mb-16">
{{ $t('common.param.initParam') }}
</h4>
<el-card shadow="never" class="card-never" style="--el-card-padding: 12px">
<DynamicsForm
v-model="form.init_params"
:model="form.init_params"
label-position="top"
require-asterisk-position="right"
:render_data="form.init_field_list"
ref="dynamicsFormRef"
>
</DynamicsForm>
</el-card>
</div>
<div v-if="form.debug_field_list.length > 0" class="mb-16">
<h4 class="title-decoration-1 mb-16">
{{ $t('common.param.inputParam') }}
</h4>
<el-card shadow="never" class="card-never" style="--el-card-padding: 12px">
<el-form
ref="FormRef"
:model="form"
label-position="top"
require-asterisk-position="right"
hide-required-asterisk
v-loading="loading"
@submit.prevent
>
<template v-for="(item, index) in form.debug_field_list" :key="index">
<el-form-item
:label="item.name"
:prop="'debug_field_list.' + index + '.value'"
:rules="{
required: item.is_required,
message: $t('views.functionLib.functionForm.form.param.inputPlaceholder'),
trigger: 'blur'
}"
>
<template #label>
<div class="flex">
<span
>{{ item.name }} <span class="danger" v-if="item.is_required">*</span></span
>
<el-tag type="info" class="info-tag ml-4">{{ item.type }}</el-tag>
</div>
</template>
<el-input
v-model="item.value"
:placeholder="$t('views.functionLib.functionForm.form.param.inputPlaceholder')"
/>
</el-form-item>
</template>
</el-form>
</el-card>
</div>
<el-button type="primary" @click="submit(FormRef)" :loading="loading">
{{ $t('views.functionLib.functionForm.form.debug.run') }}
</el-button>
<div v-if="showResult" class="mt-8">
<h4 class="title-decoration-1 mb-16 mt-16">
{{ $t('views.functionLib.functionForm.form.debug.runResult') }}
</h4>
<div class="mb-16">
<el-alert
v-if="isSuccess"
:title="$t('views.functionLib.functionForm.form.debug.runSuccess')"
type="success"
show-icon
:closable="false"
/>
<el-alert
v-else
:title="$t('views.functionLib.functionForm.form.debug.runFailed')"
type="error"
show-icon
:closable="false"
/>
</div>
<p class="lighter mb-8">{{ $t('views.functionLib.functionForm.form.debug.output') }}</p>
<el-card
:class="isSuccess ? '' : 'danger'"
class="pre-wrap"
shadow="never"
style="max-height: 350px; overflow: scroll"
>
{{ String(result) == '0' ? 0 : result || '-' }}
</el-card>
</div>
</div>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
import functionLibApi from '@/api/function-lib'
import type { FormInstance } from 'element-plus'
import DynamicsForm from '@/components/dynamics-form/index.vue'
const FormRef = ref()
const dynamicsFormRef = ref()
const loading = ref(false)
const debugVisible = ref(false)
const showResult = ref(false)
const isSuccess = ref(false)
const result = ref('')
const form = ref<any>({
debug_field_list: [],
code: '',
input_field_list: [],
init_field_list: [],
init_params: {}
})
watch(debugVisible, (bool) => {
if (!bool) {
showResult.value = false
isSuccess.value = false
result.value = ''
form.value = {
debug_field_list: [],
code: '',
input_field_list: [],
init_field_list: [],
init_params: {}
}
}
})
const submit = async (formEl: FormInstance | undefined) => {
const validate = formEl ? formEl.validate() : Promise.resolve()
Promise.all([dynamicsFormRef.value?.validate(), validate]).then(() => {
functionLibApi.postFunctionLibDebug(form.value, loading).then((res) => {
if (res.code === 500) {
showResult.value = true
isSuccess.value = false
result.value = res.message
} else {
showResult.value = true
isSuccess.value = true
result.value = res.data
}
})
})
}
const open = (data: any) => {
if (data.input_field_list.length > 0) {
data.input_field_list.forEach((item: any) => {
form.value.debug_field_list.push({
value: '',
...item
})
})
}
form.value.code = data.code
form.value.input_field_list = data.input_field_list
form.value.init_field_list = data.init_field_list
debugVisible.value = true
}
defineExpose({
open
})
</script>
<style lang="scss"></style>

View File

@ -1,518 +0,0 @@
<template>
<el-drawer v-model="visible" size="60%" :before-close="close">
<template #header>
<h4>{{ title }}</h4>
</template>
<div>
<h4 class="title-decoration-1 mb-16">
{{ $t('views.functionLib.functionForm.title.baseInfo') }}
</h4>
<el-form
ref="FormRef"
:model="form"
:rules="rules"
label-position="top"
require-asterisk-position="right"
v-loading="loading"
@submit.prevent
>
<el-form-item
:label="$t('views.functionLib.functionForm.form.functionName.label')"
prop="name"
>
<div class="flex w-full">
<div
v-if="form.id"
class="edit-avatar mr-12"
@mouseenter="showEditIcon = true"
@mouseleave="showEditIcon = false"
>
<AppAvatar
v-if="isAppIcon(form.icon)"
:id="form.id"
shape="square"
:size="32"
style="background: none"
>
<img :src="String(form.icon)" alt="" />
</AppAvatar>
<AppAvatar
v-else-if="form.name"
:id="form.id"
:name="form.name"
pinyinColor
shape="square"
:size="32"
/>
<AppAvatar
v-if="showEditIcon"
:id="form.id"
shape="square"
class="edit-mask"
:size="32"
@click="openEditAvatar"
>
<el-icon><EditPen /></el-icon>
</AppAvatar>
</div>
<AppAvatar shape="square" style="background: #34c724" class="mr-12" v-else>
<img src="@/assets/icon_function_outlined.svg" style="width: 75%" alt="" />
</AppAvatar>
<el-input
v-model="form.name"
:placeholder="$t('views.functionLib.functionForm.form.functionName.placeholder')"
maxlength="64"
show-word-limit
@blur="form.name = form.name?.trim()"
/>
</div>
</el-form-item>
<el-form-item :label="$t('views.functionLib.functionForm.form.functionDescription.label')">
<el-input
v-model="form.desc"
type="textarea"
:placeholder="$t('views.functionLib.functionForm.form.functionDescription.placeholder')"
maxlength="128"
show-word-limit
:autosize="{ minRows: 3 }"
@blur="form.desc = form.desc?.trim()"
/>
</el-form-item>
<!--
<el-form-item prop="permission_type">
<template #label>
<span>{{ $t('views.functionLib.functionForm.form.permission_type.label') }}</span>
</template>
<el-radio-group v-model="form.permission_type" class="card__radio">
<el-row :gutter="16">
<template v-for="(value, key) of PermissionType" :key="key">
<el-col :span="12">
<el-card
shadow="never"
class="mb-16"
:class="form.permission_type === key ? 'active' : ''"
>
<el-radio :value="key" size="large">
<p class="mb-4">{{ $t(value) }}</p>
<el-text type="info">
{{ $t(PermissionDesc[key]) }}
</el-text>
</el-radio>
</el-card>
</el-col>
</template>
</el-row>
</el-radio-group>
</el-form-item>
-->
</el-form>
<div class="flex-between">
<h4 class="title-decoration-1 mb-16">
{{ $t('common.param.initParam') }}
</h4>
<el-button link type="primary" @click="openAddInitDialog()">
<el-icon class="mr-4"><Plus /></el-icon> {{ $t('common.add') }}
</el-button>
</div>
<el-table ref="initFieldTableRef" :data="form.init_field_list" class="mb-16">
<el-table-column prop="field" :label="$t('dynamicsForm.paramForm.field.label')">
<template #default="{ row }">
<span :title="row.field" class="ellipsis-1">{{ row.field }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('dynamicsForm.paramForm.input_type.label')">
<template #default="{ row }">
<el-tag type="info" class="info-tag" v-if="row.input_type === 'TextInput'">{{
$t('dynamicsForm.input_type_list.TextInput')
}}</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'PasswordInput'">{{
$t('dynamicsForm.input_type_list.PasswordInput')
}}</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'Slider'">{{
$t('dynamicsForm.input_type_list.Slider')
}}</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'SwitchInput'">{{
$t('dynamicsForm.input_type_list.SwitchInput')
}}</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'SingleSelect'">{{
$t('dynamicsForm.input_type_list.SingleSelect')
}}</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'MultiSelect'">{{
$t('dynamicsForm.input_type_list.MultiSelect')
}}</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'RadioCard'">{{
$t('dynamicsForm.input_type_list.RadioCard')
}}</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'DatePicker'">{{
$t('dynamicsForm.input_type_list.DatePicker')
}}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('common.required')">
<template #default="{ row }">
<div @click.stop>
<el-switch disabled size="small" v-model="row.required" />
</div>
</template>
</el-table-column>
<el-table-column :label="$t('common.operation')" align="left" width="90">
<template #default="{ row, $index }">
<span class="mr-4">
<el-tooltip effect="dark" :content="$t('common.modify')" placement="top">
<el-button type="primary" text @click.stop="openAddInitDialog(row, $index)">
<el-icon><EditPen /></el-icon>
</el-button>
</el-tooltip>
</span>
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
<el-button type="primary" text @click="deleteInitField($index)">
<el-icon>
<Delete />
</el-icon>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<div class="flex-between">
<h4 class="title-decoration-1 mb-16">
{{ $t('common.param.inputParam') }}
<el-text type="info" class="color-secondary">
{{ $t('views.functionLib.functionForm.form.param.paramInfo1') }}
</el-text>
</h4>
<el-button link type="primary" @click="openAddDialog()">
<el-icon class="mr-4"><Plus /></el-icon> {{ $t('common.add') }}
</el-button>
</div>
<el-table ref="inputFieldTableRef" :data="form.input_field_list" class="mb-16">
<el-table-column
prop="name"
:label="$t('views.functionLib.functionForm.form.paramName.label')"
/>
<el-table-column :label="$t('views.functionLib.functionForm.form.dataType.label')">
<template #default="{ row }">
<el-tag type="info" class="info-tag">{{ row.type }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('common.required')">
<template #default="{ row }">
<div @click.stop>
<el-switch size="small" v-model="row.is_required" />
</div>
</template>
</el-table-column>
<el-table-column
prop="source"
:label="$t('views.functionLib.functionForm.form.source.label')"
>
<template #default="{ row }">
{{
row.source === 'custom'
? $t('views.functionLib.functionForm.form.source.custom')
: $t('views.functionLib.functionForm.form.source.reference')
}}
</template>
</el-table-column>
<el-table-column :label="$t('common.operation')" align="left" width="90">
<template #default="{ row, $index }">
<span class="mr-4">
<el-tooltip effect="dark" :content="$t('common.modify')" placement="top">
<el-button type="primary" text @click.stop="openAddDialog(row, $index)">
<el-icon><EditPen /></el-icon>
</el-button>
</el-tooltip>
</span>
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
<el-button type="primary" text @click="deleteField($index)">
<el-icon>
<Delete />
</el-icon>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<h4 class="title-decoration-1 mb-16">
{{ $t('views.functionLib.functionForm.form.param.code') }}
<span style="color: red; margin-left: -10px">*</span>
<el-text type="info" class="color-secondary">
{{ $t('views.functionLib.functionForm.form.param.paramInfo2') }}
</el-text>
</h4>
<div class="mb-8" v-if="showEditor">
<CodemirrorEditor
:title="$t('views.functionLib.functionForm.form.param.code')"
v-model="form.code"
@submitDialog="submitCodemirrorEditor"
/>
</div>
<h4 class="title-decoration-1 mb-16 mt-16">
{{ $t('common.param.outputParam') }}
<el-text type="info" class="color-secondary">
{{ $t('views.functionLib.functionForm.form.param.paramInfo1') }}
</el-text>
</h4>
<div class="flex-between border-r-4 p-8-12 mb-8 layout-bg lighter">
<span>{{ $t('common.result') }} {result}</span>
</div>
</div>
<template #footer>
<div>
<el-button :loading="loading" @click="visible = false">{{ $t('common.cancel') }}</el-button>
<el-button :loading="loading" @click="openDebug">{{ $t('common.debug') }}</el-button>
<el-button type="primary" @click="submit(FormRef)" :loading="loading">
{{ isEdit ? $t('common.save') : $t('common.create') }}</el-button
>
</div>
</template>
<FunctionDebugDrawer ref="FunctionDebugDrawerRef" />
<FieldFormDialog ref="FieldFormDialogRef" @refresh="refreshFieldList" />
<UserFieldFormDialog ref="UserFieldFormDialogRef" @refresh="refreshInitFieldList" />
<EditAvatarDialog ref="EditAvatarDialogRef" @refresh="refreshFunctionLib" />
</el-drawer>
</template>
<script setup lang="ts">
import { ref, reactive, watch, nextTick } from 'vue'
import FieldFormDialog from './FieldFormDialog.vue'
import FunctionDebugDrawer from './FunctionDebugDrawer.vue'
import type { functionLibData } from '@/api/type/function-lib'
import functionLibApi from '@/api/function-lib'
import type { FormInstance } from 'element-plus'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { cloneDeep } from 'lodash'
import { PermissionType, PermissionDesc } from '@/enums/model'
import { t } from '@/locales'
import UserFieldFormDialog from '@/workflow/nodes/base-node/component/UserFieldFormDialog.vue'
import { isAppIcon } from '@/utils/application'
import EditAvatarDialog from './EditAvatarDialog.vue'
import Sortable from 'sortablejs'
const props = defineProps({
title: String
})
const emit = defineEmits(['refresh'])
const FieldFormDialogRef = ref()
const FunctionDebugDrawerRef = ref()
const UserFieldFormDialogRef = ref()
const EditAvatarDialogRef = ref()
const initFieldTableRef = ref()
const inputFieldTableRef = ref()
const FormRef = ref()
const isEdit = ref(false)
const loading = ref(false)
const visible = ref(false)
const showEditor = ref(false)
const currentIndex = ref<any>(null)
const showEditIcon = ref(false)
const form = ref<functionLibData>({
name: '',
desc: '',
code: '',
icon: '',
input_field_list: [],
init_field_list: [],
permission_type: 'PRIVATE'
})
watch(visible, (bool) => {
if (!bool) {
isEdit.value = false
showEditor.value = false
currentIndex.value = null
form.value = {
name: '',
desc: '',
code: '',
icon: '',
input_field_list: [],
init_field_list: [],
permission_type: 'PRIVATE'
}
FormRef.value?.clearValidate()
}
})
const rules = reactive({
name: [
{
required: true,
message: t('views.functionLib.functionForm.form.functionName.requiredMessage'),
trigger: 'blur'
}
],
permission_type: [
{
required: true,
message: t('views.functionLib.functionForm.form.permission_type.requiredMessage'),
trigger: 'change'
}
]
})
function onDragHandle() {
// For init_field_list table
if (initFieldTableRef.value) {
const el = initFieldTableRef.value.$el.querySelector('.el-table__body-wrapper tbody')
Sortable.create(el, {
animation: 150,
ghostClass: 'sortable-ghost',
onEnd: ({ newIndex, oldIndex }) => {
if (newIndex === undefined || oldIndex === undefined) return
if (newIndex !== oldIndex) {
const item = form.value.init_field_list?.splice(oldIndex, 1)[0]
form.value.init_field_list?.splice(newIndex, 0, item)
}
}
})
}
// For input_field_list table
if (inputFieldTableRef.value) {
const el = inputFieldTableRef.value.$el.querySelector('.el-table__body-wrapper tbody')
Sortable.create(el, {
animation: 150,
ghostClass: 'sortable-ghost',
onEnd: ({ newIndex, oldIndex }) => {
if (newIndex === undefined || oldIndex === undefined) return
if (newIndex !== oldIndex) {
const item = form.value.input_field_list?.splice(oldIndex, 1)[0]
form.value.input_field_list?.splice(newIndex, 0, item)
}
}
})
}
}
function submitCodemirrorEditor(val: string) {
form.value.code = val
}
function close() {
if (isEdit.value || !areAllValuesNonEmpty(form.value)) {
visible.value = false
} else {
MsgConfirm(t('common.tip'), t('views.functionLib.tip.saveMessage'), {
confirmButtonText: t('common.confirm'),
type: 'warning'
})
.then(() => {
visible.value = false
})
.catch(() => {})
}
}
function areAllValuesNonEmpty(obj: any) {
return Object.values(obj).some((value) => {
return Array.isArray(value)
? value.length !== 0
: value !== null && value !== undefined && value !== ''
})
}
function openDebug() {
FunctionDebugDrawerRef.value.open(form.value)
}
function deleteField(index: any) {
form.value.input_field_list?.splice(index, 1)
}
function openAddDialog(data?: any, index?: any) {
if (typeof index !== 'undefined') {
currentIndex.value = index
}
FieldFormDialogRef.value.open(data)
}
function refreshFieldList(data: any) {
if (currentIndex.value !== null) {
form.value.input_field_list?.splice(currentIndex.value, 1, data)
} else {
form.value.input_field_list?.push(data)
}
currentIndex.value = null
}
function openAddInitDialog(data?: any, index?: any) {
if (typeof index !== 'undefined') {
currentIndex.value = index
}
UserFieldFormDialogRef.value.open(data)
}
function refreshInitFieldList(data: any) {
if (currentIndex.value !== null) {
form.value.init_field_list?.splice(currentIndex.value, 1, data)
} else {
form.value.init_field_list?.push(data)
}
currentIndex.value = null
UserFieldFormDialogRef.value.close()
}
function refreshFunctionLib(data: any) {
form.value.icon = data
// console.log(data)
}
function deleteInitField(index: any) {
form.value.init_field_list?.splice(index, 1)
}
function openEditAvatar() {
EditAvatarDialogRef.value.open(form.value)
}
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid: any) => {
if (valid) {
// console.log(form.value)
if (isEdit.value) {
functionLibApi.putFunctionLib(form.value?.id as string, form.value, loading).then((res) => {
MsgSuccess(t('common.editSuccess'))
emit('refresh', res.data)
visible.value = false
})
} else {
functionLibApi.postFunctionLib(form.value, loading).then((res) => {
MsgSuccess(t('common.createSuccess'))
emit('refresh')
visible.value = false
})
}
}
})
}
const open = (data: any) => {
if (data) {
isEdit.value = data?.id ? true : false
form.value = cloneDeep(data)
}
visible.value = true
setTimeout(() => {
showEditor.value = true
}, 100)
}
defineExpose({
open
})
</script>
<style lang="scss" scoped></style>

View File

@ -1,97 +0,0 @@
<template>
<el-drawer v-model="debugVisible" size="60%" :append-to-body="true">
<template #header>
<div class="flex align-center" style="margin-left: -8px">
<el-button class="cursor mr-4" link @click.prevent="debugVisible = false">
<el-icon :size="20">
<Back />
</el-icon>
</el-button>
<h4>{{ $t('common.param.initParam') }}</h4>
</div>
</template>
<div>
<div v-if="form.init_field_list?.length > 0">
<DynamicsForm
v-model="form.init_params"
:model="form.init_params"
label-position="top"
require-asterisk-position="right"
:render_data="form.init_field_list"
ref="dynamicsFormRef"
>
</DynamicsForm>
</div>
</div>
<template #footer>
<div>
<el-button type="primary" @click="submit()" :loading="loading">
{{ $t('common.save') }}
</el-button
>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import functionLibApi from '@/api/function-lib'
import DynamicsForm from '@/components/dynamics-form/index.vue'
import { MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
import { cloneDeep } from 'lodash'
const emit = defineEmits(['refresh'])
const dynamicsFormRef = ref()
const loading = ref(false)
const debugVisible = ref(false)
const form = ref<any>({
init_params: {}
})
watch(debugVisible, (bool) => {
if (!bool) {
form.value = {
init_params: {},
is_active: false
}
}
})
const submit = async () => {
dynamicsFormRef.value.validate().then(() => {
functionLibApi.putFunctionLib(form.value?.id as string, form.value, loading)
.then((res) => {
MsgSuccess(t('common.editSuccess'))
emit('refresh')
debugVisible.value = false
})
})
}
const open = (data: any, is_active: boolean) => {
if (data) {
form.value = cloneDeep(data)
form.value.is_active = is_active
}
const init_params = form.value.init_field_list
.map((item: any) => {
if (item.show_default_value === false) {
return { [item.field]: undefined }
}
return { [item.field]: item.default_value }
})
.reduce((x: any, y: any) => ({ ...x, ...y }), {})
form.value.init_params = { ...init_params, ...form.value.init_params }
debugVisible.value = true
}
defineExpose({
open
})
</script>
<style lang="scss"></style>

View File

@ -1,100 +0,0 @@
<template>
<el-drawer v-model="visibleInternalDesc" size="60%" :append-to-body="true">
<template #header>
<div class="flex align-center" style="margin-left: -8px">
<el-button class="cursor mr-4" link @click.prevent="visibleInternalDesc = false">
<el-icon :size="20">
<Back />
</el-icon>
</el-button>
<h4>详情</h4>
</div>
</template>
<div>
<div class="card-header">
<div class="flex-between">
<div class="title flex align-center">
<AppAvatar
v-if="isAppIcon(functionDetail?.icon)"
shape="square"
:size="64"
style="background: none"
class="mr-8"
>
<img :src="functionDetail?.icon" alt="" />
</AppAvatar>
<AppAvatar
v-else-if="functionDetail?.name"
:name="functionDetail?.name"
pinyinColor
shape="square"
:size="64"
class="mr-8"
/>
<div class="ml-16">
<h3 class="mb-8">{{ functionDetail.name }}</h3>
<el-text type="info" v-if="functionDetail?.desc">
{{ functionDetail.desc }}
</el-text>
</div>
</div>
<div @click.stop>
<el-button type="primary" @click="addInternalFunction(functionDetail)">
{{ $t('common.add') }}
</el-button>
</div>
</div>
<div class="mt-16">
<el-text type="info">
<div>{{ $t('common.author') }}: MaxKB</div>
</el-text>
</div>
</div>
<MdPreview
ref="editorRef"
editorId="preview-only"
:modelValue="markdownContent"
style="background: none"
noImgZoomIn
/>
</div>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { cloneDeep } from 'lodash'
import { isAppIcon } from '@/utils/application'
const emit = defineEmits(['refresh', 'addFunction'])
const visibleInternalDesc = ref(false)
const markdownContent = ref('')
const functionDetail = ref<any>({})
watch(visibleInternalDesc, (bool) => {
if (!bool) {
markdownContent.value = ''
}
})
const open = (data: any, detail: any) => {
functionDetail.value = detail
if (data) {
markdownContent.value = cloneDeep(data)
}
visibleInternalDesc.value = true
}
const addInternalFunction = (data: any) => {
emit('addFunction', data)
visibleInternalDesc.value = false
}
defineExpose({
open
})
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,281 @@
<template>
<card-box :title="model.name" shadow="hover" class="model-card">
<template #header>
<div class="flex">
<span style="height: 32px; width: 32px" :innerHTML="icon" class="mr-12"></span>
<div style="width: calc(100% - 32px - 4px - var(--app-base-px))">
<div class="flex" style="height: 22px">
<auto-tooltip :content="model.name" style="max-width: 40%">
{{ model.name }}
</auto-tooltip>
<span v-if="currentModel.status === 'ERROR'">
<el-tooltip effect="dark" :content="errMessage" placement="top">
<el-icon class="danger ml-4" size="18"><Warning /></el-icon>
</el-tooltip>
</span>
<span v-if="currentModel.status === 'PAUSE_DOWNLOAD'">
<el-tooltip
effect="dark"
:content="`${$t('views.model.modelForm.form.base_model.label')}: ${props.model.model_name} ${$t('views.model.tip.downloadError')}`"
placement="top"
>
<el-icon class="danger ml-4" size="18"><Warning /></el-icon>
</el-tooltip>
</span>
</div>
<div class="mt-4">
<el-tag v-if="model.permission_type === 'PRIVATE'" type="danger" class="danger-tag">{{
$t('common.private')
}}</el-tag>
<el-tag v-else type="info" class="info-tag"> {{ $t('common.public') }}</el-tag>
</div>
</div>
</div>
</template>
<div class="mt-16">
<ul>
<li class="flex mt-16">
<el-text type="info">{{
$t('views.model.modelForm.form.model_type.label')
}}</el-text>
<span class="ellipsis ml-16">
{{ $t(modelType[model.model_type as keyof typeof modelType]) }}</span
>
</li>
<li class="flex mt-12">
<el-text type="info">{{
$t('views.model.modelForm.form.base_model.label')
}}</el-text>
<span class="ellipsis-1 ml-16" style="height: 20px; width: 70%">
{{ model.model_name }}</span
>
</li>
<li class="flex mt-12">
<el-text type="info">{{ $t('common.creator') }}</el-text>
<span class="ellipsis-1 ml-16" style="height: 20px; width: 70%">
{{ model.username }}</span
>
</li>
</ul>
</div>
<!-- progress -->
<div class="progress-mask" v-if="currentModel.status === 'DOWNLOAD'">
<DownloadLoading class="percentage" />
<div class="percentage-label flex-center">
{{ $t('views.model.download.downloading') }} <span class="dotting"></span>
<el-button
link
type="primary"
class="ml-16"
:disabled="!is_permisstion"
@click.stop="cancelDownload"
>{{ $t('views.model.download.cancelDownload') }}</el-button
>
</div>
</div>
<template #mouseEnter>
<div class="operation-button">
<el-tooltip effect="dark" :content="$t('common.modify')" placement="top">
<el-button text :disabled="!is_permisstion" @click.stop="openEditModel">
<el-icon>
<el-icon><EditPen /></el-icon>
</el-icon>
</el-button>
</el-tooltip>
<el-dropdown trigger="click">
<el-button text @click.stop>
<el-icon><MoreFilled /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-if="
currentModel.model_type === 'TTS' ||
currentModel.model_type === 'LLM' ||
currentModel.model_type === 'IMAGE' ||
currentModel.model_type === 'TTI'
"
:disabled="!is_permisstion"
icon="Setting"
@click.stop="openParamSetting"
>
{{ $t('views.model.modelForm.title.paramSetting') }}
</el-dropdown-item>
<el-dropdown-item
icon="Delete"
:disabled="!is_permisstion"
text
@click.stop="deleteModel"
>
{{ $t('common.delete') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<EditModel ref="editModelRef" @submit="emit('change')"></EditModel>
<ParamSettingDialog ref="paramSettingRef" :model="model" />
</card-box>
</template>
<script setup lang="ts">
import type { Provider, Model } from '@/api/type/model'
import ModelApi from '@/api/model/model'
import { computed, ref, onMounted, onBeforeUnmount } from 'vue'
import EditModel from '@/views/template/component/EditModel.vue'
import DownloadLoading from '@/components/loading/DownloadLoading.vue'
import { MsgConfirm } from '@/utils/message'
import { modelType } from '@/enums/model'
import useStore from '@/stores'
import ParamSettingDialog from './ParamSettingDialog.vue'
import { t } from '@/locales'
const props = defineProps<{
model: Model
provider_list: Array<Provider>
updateModelById: (model_id: string, model: Model) => void
}>()
const { user } = useStore()
const downModel = ref<Model>()
const is_permisstion = computed(() => {
return user.userInfo?.id == props.model.user_id
})
const currentModel = computed(() => {
if (downModel.value) {
return downModel.value
} else {
return props.model
}
})
const errMessage = computed(() => {
if (currentModel.value.meta && currentModel.value.meta.message) {
if (currentModel.value.meta.message === 'pull model manifest: file does not exist') {
return `${currentModel.value.model_name} ${t('views.model.tip.noModel')}`
}
return currentModel.value.meta.message
}
return ''
})
const emit = defineEmits(['change', 'update:model'])
const editModelRef = ref<InstanceType<typeof EditModel>>()
let interval: any
const deleteModel = () => {
MsgConfirm(
t('views.model.delete.confirmTitle'),
`${t('views.model.delete.confirmMessage')}${props.model.name} ?`,
{
confirmButtonText: t('common.confirm'),
confirmButtonClass: 'danger'
}
)
.then(() => {
ModelApi.deleteModel(props.model.id).then(() => {
emit('change')
})
})
.catch(() => {})
}
const cancelDownload = () => {
ModelApi.pauseDownload(props.model.id).then(() => {
downModel.value = undefined
emit('change')
})
}
const openEditModel = () => {
const provider = props.provider_list.find((p) => p.provider === props.model.provider)
if (provider) {
editModelRef.value?.open(provider, props.model)
}
}
const icon = computed(() => {
return props.provider_list.find((p) => p.provider === props.model.provider)?.icon
})
/**
* 初始化轮询
*/
const initInterval = () => {
interval = setInterval(() => {
if (currentModel.value.status === 'DOWNLOAD') {
ModelApi.getModelMetaById(props.model.id).then((ok) => {
downModel.value = ok.data
})
} else {
if (downModel.value) {
props.updateModelById(props.model.id, downModel.value)
downModel.value = undefined
}
}
}, 6000)
}
/**
* 关闭轮询
*/
const closeInterval = () => {
if (interval) {
clearInterval(interval)
}
}
const paramSettingRef = ref<InstanceType<typeof ParamSettingDialog>>()
const openParamSetting = () => {
paramSettingRef.value?.open()
}
onMounted(() => {
initInterval()
})
onBeforeUnmount(() => {
//
closeInterval()
})
</script>
<style lang="scss" scoped>
.model-card {
min-height: 135px;
min-width: auto;
.operation-button {
position: absolute;
right: 12px;
top: 18px;
height: auto;
.el-button + .el-button {
margin-left: 4px;
}
}
.progress-mask {
position: absolute;
top: 0;
left: 0;
background-color: rgba(255, 255, 255, 0.9);
width: 100%;
height: 100%;
z-index: 99;
text-align: center;
.percentage {
margin-top: 55px;
margin-bottom: 16px;
}
// .percentage-value {
// display: flex;
// font-size: 13px;
// align-items: center;
// color: var(--app-text-color-secondary);
// }
.percentage-label {
margin-top: 50px;
margin-left: 10px;
font-size: 13px;
color: var(--app-text-color-secondary);
}
}
}
</style>

View File

@ -0,0 +1,156 @@
<template>
<el-dialog
:title="$t('views.model.modelForm.title.paramSetting')"
v-model="dialogVisible"
width="800px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
:before-close="close"
>
<el-button type="primary" @click="openAddDrawer()" class="mb-12">
{{ $t('views.model.modelForm.title.addParam') }}
</el-button>
<el-table :data="modelParamsForm" class="mb-16">
<el-table-column prop="label" :label="$t('dynamicsForm.paramForm.name.label')" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.label && row.label.input_type === 'TooltipLabel'">{{
row.label.label
}}</span>
<span v-else>{{ row.label }}</span>
</template>
</el-table-column>
<el-table-column prop="field" :label="$t('dynamicsForm.paramForm.field.label')" show-overflow-tooltip />
<el-table-column :label="$t('dynamicsForm.paramForm.input_type.label')" width="110px">
<template #default="{ row }">
<el-tag type="info" class="info-tag">{{
input_type_list.find((item) => item.value === row.input_type)?.label
}}</el-tag>
</template>
</el-table-column>
<el-table-column
prop="default_value"
:label="$t('dynamicsForm.default.label')"
show-overflow-tooltip
/>
<el-table-column :label="$t('common.required')">
<template #default="{ row }">
<div @click.stop>
<el-switch disabled size="small" v-model="row.required" />
</div>
</template>
</el-table-column>
<el-table-column :label="$t('common.operation')" align="left" width="90">
<template #default="{ row, $index }">
<span class="mr-4">
<el-tooltip effect="dark" :content="$t('common.modify')" placement="top">
<el-button type="primary" text @click.stop="openAddDrawer(row, $index)">
<el-icon><EditPen /></el-icon>
</el-button>
</el-tooltip>
</span>
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
<el-button type="primary" text @click="deleteParam($index)">
<el-icon>
<Delete />
</el-icon>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<template #footer>
<span class="dialog-footer">
<el-button @click="close">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="submit" :loading="loading">
{{ $t('common.save') }}
</el-button>
</span>
</template>
</el-dialog>
<AddParamDrawer ref="AddParamRef" @refresh="refresh" />
</template>
<script setup lang="ts">
import type { Model } from '@/api/type/model'
import { ref } from 'vue'
import AddParamDrawer from './AddParamDrawer.vue'
import { MsgError, MsgSuccess } from '@/utils/message'
import ModelApi from '@/api/model/model'
import { input_type_list } from '@/components/dynamics-form/constructor/data'
import { t } from '@/locales'
const props = defineProps<{
model: Model
}>()
const loading = ref<boolean>(false)
const dialogVisible = ref<boolean>(false)
const modelParamsForm = ref<any[]>([])
const AddParamRef = ref()
const open = () => {
dialogVisible.value = true
loading.value = true
ModelApi.getModelParamsForm(props.model.id, loading)
.then((ok) => {
loading.value = false
modelParamsForm.value = ok.data
})
.catch(() => {
loading.value = false
})
}
const close = () => {
dialogVisible.value = false
}
function openAddDrawer(data?: any, index?: any) {
AddParamRef.value?.open(data, index)
}
function deleteParam(index: any) {
modelParamsForm.value.splice(index, 1)
}
function refresh(data: any, index: any) {
for (let i = 0; i < modelParamsForm.value.length; i++) {
let field = modelParamsForm.value[i].field
let label = modelParamsForm.value[i].label
if (label && label.input_type === 'TooltipLabel') {
label = label.label
}
let label2 = data.label
if (label2 && label2.input_type === 'TooltipLabel') {
label2 = label2.label
}
if (field === data.field && index !== i) {
MsgError(t('views.model.tip.errorMessage') + data.field)
return
}
if (label === label2 && index !== i) {
MsgError(t('views.model.tip.errorMessage') + label)
return
}
}
if (index !== null) {
modelParamsForm.value.splice(index, 1, data)
} else {
modelParamsForm.value.push(data)
}
}
function submit() {
ModelApi.updateModelParamsForm(props.model.id, modelParamsForm.value, loading).then((ok) => {
MsgSuccess(t('views.model.tip.saveSuccessMessage'))
close()
// emit('submit')
})
}
defineExpose({ open, close })
</script>
<style scoped lang="scss"></style>

View File

@ -1,106 +0,0 @@
<template>
<el-dialog
:title="$t('views.functionLib.functionForm.form.permission_type.label')"
v-model="dialogVisible"
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
append-to-body
width="450"
>
<el-form
label-position="top"
ref="fieldFormRef"
:rules="rules"
:model="form"
require-asterisk-position="right"
>
<el-radio-group v-model="form.permission_type" class="radio-block">
<el-radio value="PRIVATE" size="large">
{{ $t('common.private') }}
<el-text type="info">{{
$t('views.template.templateForm.form.permissionType.privateDesc')
}}</el-text>
</el-radio>
<el-radio value="PUBLIC" size="large">
{{ $t('common.public') }}
<el-text type="info">{{
$t('views.template.templateForm.form.permissionType.publicDesc')
}}</el-text>
</el-radio>
</el-radio-group>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
<el-button type="primary" @click="submit(fieldFormRef)" :loading="loading">
{{ isEdit ? $t('common.save') : $t('common.add') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import type { FormInstance } from 'element-plus'
import { cloneDeep } from 'lodash'
import { t } from '@/locales'
import functionLibApi from '@/api/function-lib'
import { MsgSuccess } from '@/utils/message'
const emit = defineEmits(['refresh'])
const fieldFormRef = ref()
const loading = ref<boolean>(false)
const isEdit = ref(false)
const form = ref<any>({
permission_type: 'PRIVATE'
})
const rules = reactive({
permission_type: [
{
required: true,
message: t('views.functionLib.functionForm.form.paramName.placeholder'),
trigger: 'blur'
}
]
})
const dialogVisible = ref<boolean>(false)
watch(dialogVisible, (bool) => {
if (!bool) {
form.value = {
permission_type: 'PRIVATE'
}
isEdit.value = false
}
})
const open = (row: any) => {
if (row) {
form.value = cloneDeep(row)
isEdit.value = true
}
dialogVisible.value = true
}
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
functionLibApi.putFunctionLib(form.value?.id as string, form.value, loading).then((res) => {
MsgSuccess(t('common.editSuccess'))
emit('refresh')
dialogVisible.value = false
})
}
})
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,88 @@
<template>
<el-dialog
v-model="dialogVisible"
width="600px"
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
:before-close="close"
append-to-body
>
<template #header>
<div class="flex-between">
<h4>{{ $t('views.model.providerPlaceholder') }}</h4>
<el-dropdown>
<span class="cursor">
{{ currentModelType || $t('views..model.modelType.allModel') }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="item in modelTypeOptions"
:key="item.value"
@click="checkModelType(item.value)"
>
<span>{{ item.text }}</span>
<el-icon v-if="currentModelType === item.text"><Check /></el-icon>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<el-row :gutter="12" v-loading="loading">
<el-col :span="12" class="mb-16" v-for="(data, index) in list_provider" :key="index">
<el-card shadow="hover" @click="go_create(data)">
<div class="flex align-center cursor">
<span :innerHTML="data.icon" alt="" style="height: 24px; width: 24px" class="mr-8" />
<span>{{ data.name }}</span>
</div>
</el-card>
</el-col>
</el-row>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import ModelApi from '@/api/model/model'
import type { Provider } from '@/api/type/model'
import { modelTypeList } from './data'
import { t } from '@/locales'
const loading = ref<boolean>(false)
const dialogVisible = ref<boolean>(false)
const list_provider = ref<Array<Provider>>([])
const currentModelType = ref('')
const selectModelType = ref('')
const modelTypeOptions = [{ text: t('views.model.modelType.allModel'), value: '' }, ...modelTypeList]
const open = (model_type?: string) => {
dialogVisible.value = true
const option = modelTypeOptions.find((item) => item.text === currentModelType.value)
checkModelType(model_type ? model_type : option ? option.value : '')
}
const close = () => {
dialogVisible.value = false
}
const checkModelType = (model_type: string) => {
selectModelType.value = model_type
currentModelType.value = modelTypeOptions.filter((item) => item.value === model_type)[0].text
ModelApi.getProviderByModelType(model_type, loading).then((ok) => {
list_provider.value = ok.data
list_provider.value.sort((a, b) => a.provider.localeCompare(b.provider))
})
}
const emit = defineEmits(['change'])
const go_create = (provider: Provider) => {
close()
emit('change', provider, selectModelType.value)
}
defineExpose({ open, close })
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,11 @@
import { modelType } from '@/enums/model'
import { t } from '@/locales'
export const modelTypeList = [
{ text: t(modelType['LLM']), value: 'LLM' },
{ text: t(modelType['EMBEDDING']), value: 'EMBEDDING' },
{ text: t(modelType['RERANKER']), value: 'RERANKER' },
{ text: t(modelType['STT']), value: 'STT' },
{ text: t(modelType['TTS']), value: 'TTS' },
{ text: t(modelType['IMAGE']), value: 'IMAGE' },
{ text: t(modelType['TTI']), value: 'TTI' }
]

View File

@ -1,623 +1,255 @@
<template>
<div class="function-lib-list-container p-24" style="padding-top: 16px">
<el-tabs v-model="functionType" @tab-change="tabChangeHandle">
<el-tab-pane :label="$t('views.functionLib.title')" name="PUBLIC"></el-tab-pane>
<el-tab-pane :label="$t('views.functionLib.internalTitle')" name="INTERNAL"></el-tab-pane>
</el-tabs>
<div class="flex-between mb-16">
<h4></h4>
<div class="flex-between">
<el-select
v-if="functionType === 'PUBLIC'"
v-model="selectUserId"
class="mr-12"
style="max-width: 240px; width: 150px"
@change="searchHandle"
>
<el-option
v-for="item in userOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-model="searchValue"
@change="searchHandle"
:placeholder="$t('views.functionLib.searchBar.placeholder')"
prefix-icon="Search"
class="w-240"
style="max-width: 240px"
clearable
/>
1111111111
<!-- <LayoutContainer :header="$t('views.model.title')">
<div class="model-manage flex main-calc-height">
<div class="model-manage__left p-8 border-r">
<h4 class="p-16">{{ $t('views.model.provider') }}</h4>
<div class="model-list-height-left">
<div
class="all-mode flex cursor"
@click="clickListHandle(allObj as Provider)"
:class="!active_provider?.provider ? 'all-mode-active' : ''"
>
<AppIcon
class="mr-8"
style="height: 20px; width: 20px"
:iconName="'app-all-menu-active'"
></AppIcon>
<span>{{ $t('views.model.modelType.allModel') }}</span>
</div>
<el-scrollbar>
<el-collapse class="model-collapse">
<el-collapse-item :title="$t('views.model.modelType.publicModel')" name="1">
<template #title>
<img src="@/assets/icon_file-folder_colorful.svg" class="mr-8" />
{{ $t('views.model.modelType.publicModel') }}
</template>
<common-list
:data="online_provider_list"
v-loading="loading"
@click="clickListHandle"
value-key="provider"
default-active=""
ref="commonList1"
>
<template #default="{ row }">
<div class="flex align-center">
<span
:innerHTML="row.icon"
alt=""
style="height: 20px; width: 20px"
class="mr-8"
/>
<span>{{ row.name }}</span>
</div>
</template>
</common-list>
</el-collapse-item>
<el-collapse-item :title="$t('views.model.modelType.privateModel')" name="2">
<template #title>
<img src="@/assets/icon_file-folder_colorful.svg" class="mr-8" />
{{ $t('views.model.modelType.privateModel') }}
</template>
<common-list
:data="local_provider_list"
v-loading="loading"
@click="clickListHandle"
value-key="provider"
default-active=""
ref="commonList2"
>
<template #default="{ row }">
<div class="flex align-center">
<span
:innerHTML="row.icon"
alt=""
style="height: 20px; width: 20px"
class="mr-8"
/>
<span>{{ row.name }}</span>
</div>
</template>
</common-list>
</el-collapse-item>
</el-collapse>
</el-scrollbar>
</div>
</div>
<div class="model-manage__right w-full" v-loading="list_model_loading">
<div class="p-24 pb-0">
<h4>{{ active_provider?.name }}</h4>
<div class="flex-between mt-16 mb-16">
<el-button type="primary" @click="openCreateModel(active_provider)">
{{ $t('views.model.addModel') }}</el-button
>
<div class="flex-between complex-search">
<el-select
class="complex-search__left"
v-model="search_type"
style="width: 120px"
@change="search_type_change"
>
<el-option :label="$t('common.creator')" value="create_user" />
<el-option
:label="$t('views.model.modelForm.form.permissionType.label')"
value="permission_type"
/>
<el-option
:label="$t('views.model.modelForm.form.model_type.label')"
value="model_type"
/>
<el-option
:label="$t('views.model.modelForm.form.templateName.label')"
value="name"
/>
</el-select>
<el-input
v-if="search_type === 'name'"
v-model="model_search_form.name"
@change="list_model"
:placeholder="$t('views.model.searchBar.placeholder')"
prefix-icon="Search"
style="width: 220px"
clearable
/>
<el-select
v-else-if="search_type === 'create_user'"
v-model="model_search_form.create_user"
@change="list_model"
clearable
style="width: 220px"
>
<el-option
v-for="u in user_options"
:key="u.id"
:value="u.id"
:label="u.username"
/>
</el-select>
<el-select
v-else-if="search_type === 'permission_type'"
v-model="model_search_form.permission_type"
clearable
@change="list_model"
style="width: 220px"
>
<el-option :label="$t('common.public')" value="PUBLIC" />
<el-option :label="$t('common.private')" value="PRIVATE" />
</el-select>
<el-select
v-else-if="search_type === 'model_type'"
v-model="model_search_form.model_type"
clearable
@change="list_model"
style="width: 220px"
>
<template v-for="item in modelTypeList" :key="item.value">
<el-option :label="item.text" :value="item.value" />
</template>
</el-select>
</div>
</div>
</div>
<div class="model-list-height">
<el-scrollbar>
<div class="p-24 pt-0">
<el-row v-if="model_split_list.length > 0" :gutter="15">
<template v-for="(row, index) in model_split_list" :key="index">
<el-col
:xs="24"
:sm="24"
:md="24"
:lg="12"
:xl="12"
class="mb-16"
v-for="(model, i) in row"
:key="i"
>
<ModelCard
@change="list_model"
:updateModelById="updateModelById"
:model="model"
:provider_list="provider_list"
>
</ModelCard>
</el-col>
</template>
</el-row>
<el-empty :description="$t('common.noData')" v-else />
</div>
</el-scrollbar>
</div>
</div>
</div>
<div
v-loading.fullscreen.lock="
(paginationConfig.current_page === 1 && loading) || changeStateloading
"
>
<InfiniteScroll
:size="functionLibList.length"
:total="paginationConfig.total"
:page_size="paginationConfig.page_size"
v-model:current_page="paginationConfig.current_page"
@load="getList"
:loading="loading"
>
<el-row :gutter="15">
<el-col
:xs="24"
:sm="12"
:md="8"
:lg="6"
:xl="6"
class="mb-16"
v-if="functionType === 'PUBLIC'"
>
<el-card shadow="hover" class="application-card-add" style="--el-card-padding: 8px">
<div class="card-add-button flex align-center cursor p-8" @click="openCreateDialog()">
<AppIcon iconName="app-add-application" class="mr-8"></AppIcon>
{{ $t('views.functionLib.createFunction') }}
</div>
<el-divider style="margin: 8px 0" />
<el-upload
ref="elUploadRef"
:file-list="[]"
action="#"
:auto-upload="false"
:show-file-list="false"
:limit="1"
:on-change="(file: any, fileList: any) => importFunctionLib(file)"
class="card-add-button"
>
<div class="flex align-center cursor p-8">
<AppIcon iconName="app-import" class="mr-8"></AppIcon>
{{ $t('views.functionLib.importFunction') }}
</div>
</el-upload>
</el-card>
</el-col>
<el-col
:xs="24"
:sm="12"
:md="8"
:lg="6"
:xl="6"
v-for="(item, index) in functionLibList"
:key="index"
class="mb-16"
>
<CardBox
v-if="functionType === 'PUBLIC'"
:title="item.name"
:description="item.desc"
class="function-lib-card"
@click="openCreateDialog(item)"
:class="item.permission_type === 'PUBLIC' && !canEdit(item) ? '' : 'cursor'"
>
<template #icon>
<AppAvatar
v-if="isAppIcon(item?.icon)"
shape="square"
:size="32"
style="background: none"
class="mr-8"
>
<img :src="item?.icon" alt="" />
</AppAvatar>
<AppAvatar
v-else-if="item?.name"
:name="item?.name"
pinyinColor
shape="square"
:size="32"
class="mr-8"
/>
</template>
<template #subTitle>
<el-text class="color-secondary" size="small">
<auto-tooltip :content="item.username">
{{ $t('common.creator') }}: {{ item.username }}
</auto-tooltip>
</el-text>
</template>
<div class="status-button">
<el-tag
class="info-tag"
v-if="item.permission_type === 'PUBLIC'"
style="height: 22px"
>
{{ $t('common.public') }}</el-tag
>
<el-tag
class="danger-tag"
v-else-if="item.permission_type === 'PRIVATE'"
style="height: 22px"
>
{{ $t('common.private') }}</el-tag
>
</div>
<template #footer>
<div class="footer-content flex-between">
<div>
<span v-if="item.template_id"> {{ $t('common.author') }}: MaxKB</span>
</div>
<div @click.stop>
<el-switch
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
v-model="item.is_active"
@change="changeState($event, item)"
size="small"
class="mr-4"
/>
<el-divider direction="vertical" />
<el-dropdown trigger="click">
<el-button text @click.stop>
<el-icon><MoreFilled /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-if="item.template_id"
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
@click.stop="addInternalFunction(item, true)"
>
<el-icon><EditPen /></el-icon>
{{ $t('common.edit') }}
</el-dropdown-item>
<el-dropdown-item
v-if="!item.template_id"
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
@click.stop="openCreateDialog(item)"
>
<el-icon><EditPen /></el-icon>
{{ $t('common.edit') }}
</el-dropdown-item>
<el-dropdown-item
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
v-if="!item.template_id"
@click.stop="copyFunctionLib(item)"
>
<AppIcon iconName="app-copy"></AppIcon>
{{ $t('common.copy') }}
</el-dropdown-item>
<el-dropdown-item
v-if="item.init_field_list?.length > 0"
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
@click.stop="configInitParams(item)"
>
<AppIcon iconName="app-operation" class="mr-4"></AppIcon>
{{ $t('common.param.initParam') }}
</el-dropdown-item>
<el-dropdown-item
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
@click.stop="configPermission(item)"
>
<el-icon><User /></el-icon>
{{ $t('views.functionLib.functionForm.form.permission_type.label') }}
</el-dropdown-item>
<el-dropdown-item
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
v-if="!item.template_id"
@click.stop="exportFunctionLib(item)"
>
<AppIcon iconName="app-export"></AppIcon>
{{ $t('common.export') }}
</el-dropdown-item>
<el-dropdown-item
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
@click.stop="deleteFunctionLib(item)"
>
<el-icon><Delete /></el-icon>
{{ $t('common.delete') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
</CardBox>
<CardBox
v-if="functionType === 'INTERNAL'"
:title="item.name"
:description="item.desc"
class="function-lib-card"
@click="openDescDrawer(item)"
:class="item.permission_type === 'PUBLIC' && !canEdit(item) ? '' : 'cursor'"
>
<template #icon>
<AppAvatar
v-if="isAppIcon(item?.icon)"
shape="square"
:size="32"
style="background: none"
class="mr-8"
>
<img :src="item?.icon" alt="" />
</AppAvatar>
<AppAvatar
v-else-if="item?.name"
:name="item?.name"
pinyinColor
shape="square"
:size="32"
class="mr-8"
/>
</template>
<div class="status-button"></div>
<template #footer>
<div class="footer-content flex-between">
<div>{{ $t('common.author') }}: MaxKB</div>
<div @click.stop>
<el-button type="primary" link @click="addInternalFunction(item)">
{{ $t('common.add') }}
</el-button>
</div>
</div>
</template>
</CardBox>
</el-col>
</el-row>
</InfiniteScroll>
</div>
<FunctionFormDrawer ref="FunctionFormDrawerRef" @refresh="refresh" :title="title" />
<PermissionDialog ref="PermissionDialogRef" @refresh="refresh" />
<AddInternalFunctionDialog
ref="AddInternalFunctionDialogRef"
@refresh="confirmAddInternalFunction"
/>
<InitParamDrawer ref="InitParamDrawerRef" @refresh="refresh" />
<InternalDescDrawer ref="InternalDescDrawerRef" @addFunction="addInternalFunction" />
</div>
<CreateModelDialog
ref="createModelRef"
@submit="list_model"
@change="openCreateModel($event)"
></CreateModelDialog>
<SelectProviderDialog
ref="selectProviderRef"
@change="(provider, modelType) => openCreateModel(provider, modelType)"
></SelectProviderDialog>
</LayoutContainer> -->
</template>
<script setup lang="ts">
import { ref, onMounted, reactive, watch, nextTick } from 'vue'
import { cloneDeep, get } from 'lodash'
import functionLibApi from '@/api/function-lib'
import FunctionFormDrawer from './component/FunctionFormDrawer.vue'
import { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'
import useStore from '@/stores'
import applicationApi from '@/api/application'
import { t } from '@/locales'
import PermissionDialog from '@/views/function-lib/component/PermissionDialog.vue'
import InitParamDrawer from '@/views/function-lib/component/InitParamDrawer.vue'
import InternalDescDrawer from '@/views/function-lib/component/InternalDescDrawer.vue'
import { isAppIcon } from '@/utils/application'
import InfiniteScroll from '@/components/infinite-scroll/index.vue'
import CardBox from '@/components/card-box/index.vue'
import AddInternalFunctionDialog from '@/views/function-lib/component/AddInternalFunctionDialog.vue'
const { user } = useStore()
<script lang="ts" setup>
const loading = ref(false)
const InternalDescDrawerRef = ref()
const FunctionFormDrawerRef = ref()
const PermissionDialogRef = ref()
const AddInternalFunctionDialogRef = ref()
const InitParamDrawerRef = ref()
const functionLibList = ref<any[]>([])
const paginationConfig = reactive({
current_page: 1,
page_size: 30,
total: 0
})
const searchValue = ref('')
const title = ref('')
const changeStateloading = ref(false)
interface UserOption {
label: string
value: string
}
const userOptions = ref<UserOption[]>([])
const selectUserId = ref('all')
const elUploadRef = ref<any>()
const functionType = ref('PUBLIC')
watch(
functionType,
(val) => {
paginationConfig.total = 0
paginationConfig.current_page = 1
functionLibList.value = []
getList()
},
{ immediate: true }
)
function tabChangeHandle() {
selectUserId.value = 'all'
searchValue.value = ''
}
const canEdit = (row: any) => {
return user.userInfo?.id === row?.user_id
}
function openCreateDialog(data?: any) {
// template_id
if (data?.template_id) {
return
}
// console.log(data)
title.value = data ? t('views.functionLib.editFunction') : t('views.functionLib.createFunction')
if (data) {
if (data?.permission_type !== 'PUBLIC' || canEdit(data)) {
functionLibApi.getFunctionLibById(data?.id, changeStateloading).then((res) => {
FunctionFormDrawerRef.value.open(res.data)
})
}
} else {
FunctionFormDrawerRef.value.open(data)
}
}
async function openDescDrawer(row: any) {
const index = row.icon.replace('icon.png', 'detail.md')
const response = await fetch(index)
const content = await response.text()
InternalDescDrawerRef.value.open(content, row)
}
function addInternalFunction(data?: any, isEdit?: boolean) {
AddInternalFunctionDialogRef.value.open(data, isEdit)
}
function confirmAddInternalFunction(data?: any, isEdit?: boolean) {
if (isEdit) {
functionLibApi.putFunctionLib(data?.id as string, data, loading).then((res) => {
MsgSuccess(t('common.saveSuccess'))
searchHandle()
})
} else {
functionLibApi
.addInternalFunction(data.id, { name: data.name }, changeStateloading)
.then((res) => {
MsgSuccess(t('common.addSuccess'))
searchHandle()
})
}
}
function searchHandle() {
if (user.userInfo) {
localStorage.setItem(user.userInfo.id + 'function', selectUserId.value)
}
paginationConfig.total = 0
paginationConfig.current_page = 1
functionLibList.value = []
getList()
}
async function changeState(bool: Boolean, row: any) {
if (!bool) {
MsgConfirm(
`${t('views.functionLib.disabled.confirmTitle')}${row.name} ?`,
t('views.functionLib.disabled.confirmMessage'),
{
confirmButtonText: t('views.functionLib.setting.disabled'),
confirmButtonClass: 'danger'
}
)
.then(() => {
const obj = {
is_active: bool
}
functionLibApi.putFunctionLib(row.id, obj, changeStateloading).then((res) => {})
})
.catch(() => {
row.is_active = true
})
} else {
const res = await functionLibApi.getFunctionLibById(row.id, changeStateloading)
if (
!res.data.init_params &&
res.data.init_field_list &&
res.data.init_field_list.length > 0 &&
res.data.init_field_list.filter((item: any) => item.default_value && item.show_default_value).length !==
res.data.init_field_list.length
) {
InitParamDrawerRef.value.open(res.data, bool)
row.is_active = false
return
}
const init_params = res.data.init_field_list.reduce((acc: any, item: any) => {
acc[item.field] = item.default_value
return acc
}, {})
const obj = {
is_active: bool,
init_params: init_params,
init_field_list: res.data.init_field_list
}
functionLibApi.putFunctionLib(row.id, obj, changeStateloading).then((res) => {})
}
}
function deleteFunctionLib(row: any) {
MsgConfirm(
`${t('views.functionLib.delete.confirmTitle')}${row.name} ?`,
t('views.functionLib.delete.confirmMessage'),
{
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
confirmButtonClass: 'danger'
}
)
.then(() => {
functionLibApi.delFunctionLib(row.id, loading).then(() => {
const index = functionLibList.value.findIndex((v) => v.id === row.id)
functionLibList.value.splice(index, 1)
MsgSuccess(t('common.deleteSuccess'))
})
})
.catch(() => {})
}
function copyFunctionLib(row: any) {
title.value = t('views.functionLib.copyFunction')
const obj = cloneDeep(row)
delete obj['id']
obj['name'] = obj['name'] + ` ${t('views.functionLib.functionForm.title.copy')}`
FunctionFormDrawerRef.value.open(obj)
}
function exportFunctionLib(row: any) {
functionLibApi.exportFunctionLib(row.id, row.name, loading).catch((e: any) => {
if (e.response.status !== 403) {
e.response.data.text().then((res: string) => {
MsgError(`${t('views.application.tip.ExportError')}:${JSON.parse(res).message}`)
})
}
})
}
function configPermission(item: any) {
PermissionDialogRef.value.open(item)
}
function configInitParams(item: any) {
functionLibApi.getFunctionLibById(item?.id, changeStateloading).then((res) => {
InitParamDrawerRef.value.open(res.data)
})
}
function importFunctionLib(file: any) {
const formData = new FormData()
formData.append('file', file.raw, file.name)
elUploadRef.value.clearFiles()
functionLibApi
.importFunctionLib(formData, loading)
.then(async (res: any) => {
if (res?.data) {
searchHandle()
}
})
.catch((e: any) => {
if (e.code === 400) {
MsgConfirm(t('common.tip'), t('views.application.tip.professionalMessage'), {
cancelButtonText: t('common.confirm'),
confirmButtonText: t('common.professional')
}).then(() => {
window.open('https://maxkb.cn/pricing.html', '_blank')
})
}
})
}
async function getList() {
if (userOptions.value?.length === 0) {
await getUserList()
}
const params = {
...(searchValue.value && { name: searchValue.value }),
...(functionType.value && { function_type: functionType.value }),
...(selectUserId.value &&
selectUserId.value !== 'all' && { select_user_id: selectUserId.value })
}
functionLibApi.getFunctionLib(paginationConfig, params, loading).then((res: any) => {
res.data.records.forEach((item: any) => {
if (user.userInfo && item.user_id === user.userInfo.id) {
item.username = user.userInfo.username
} else {
item.username = userOptions.value.find((v) => v.value === item.user_id)?.label
}
})
functionLibList.value = [...functionLibList.value, ...res.data.records]
paginationConfig.total = res.data.total
})
}
function refresh(data: any) {
if (data) {
const index = functionLibList.value.findIndex((v) => v.id === data.id)
if (user.userInfo && data.user_id === user.userInfo.id) {
data.username = user.userInfo.username
} else {
data.username = userOptions.value.find((v) => v.value === data.user_id)?.label
}
functionLibList.value.splice(index, 1, data)
}
paginationConfig.total = 0
paginationConfig.current_page = 1
functionLibList.value = []
getList()
}
async function getUserList() {
const res = await applicationApi.getUserList('FUNCTION', loading)
if (res.data) {
userOptions.value = res.data.map((item: any) => {
return {
label: item.username,
value: item.id
}
})
if (user.userInfo) {
const selectUserIdValue = localStorage.getItem(user.userInfo.id + 'function')
if (selectUserIdValue && userOptions.value.find((v) => v.value === selectUserIdValue)) {
selectUserId.value = selectUserIdValue
}
}
}
}
onMounted(() => {
})
</script>
<style lang="scss" scoped>
.application-card-add {
width: 100%;
font-size: 14px;
min-height: var(--card-min-height);
border: 1px dashed var(--el-border-color);
background: var(--el-disabled-bg-color);
border-radius: 8px;
box-sizing: border-box;
&:hover {
border: 1px solid var(--el-card-bg-color);
background-color: var(--el-card-bg-color);
.model-manage {
&__left {
box-sizing: border-box;
width: var(--setting-left-width);
min-width: var(--setting-left-width);
}
.card-add-button {
&:hover {
border-radius: 4px;
background: var(--app-text-color-light-1);
.model-list-height {
height: calc(var(--create-dataset-height) - 80px);
}
.model-list-height-left {
height: calc(var(--create-dataset-height) - 40px);
}
.all-mode {
padding: 10px 16px;
}
.all-mode-active {
background: var(--el-color-primary-light-9);
border-radius: 4px;
color: var(--el-color-primary);
font-weight: 500;
}
.template-collapse {
border-top: none !important;
border-bottom: none !important;
:deep(.el-collapse-item__header) {
border-bottom: none !important;
padding-left: 16px;
font-size: 14px;
height: 40px;
&:hover {
background: var(--app-text-color-light-1);
border-radius: 4px;
}
}
:deep(.el-upload) {
display: block;
width: 100%;
color: var(--el-text-color-regular);
:deep(.el-collapse-item) {
margin-top: 2px;
}
:deep(.common-list) {
li {
padding-left: 30px !important;
}
}
:deep(.el-collapse-item__wrap) {
border-bottom: none !important;
}
:deep(.el-collapse-item__content) {
padding-bottom: 0 !important;
}
}
}
.application-card {
.status-tag {
position: absolute;
right: 16px;
top: 15px;
}
}
.function-lib-list-container {
.status-button {
position: absolute;
right: 12px;
top: 15px;
height: auto;
}
}
</style>