mirror of
https://gitee.com/myxzgzs/boyue-ui-admin-vue3
synced 2025-08-08 08:22:41 +08:00
Merge branch 'yudaocode:master' into master
This commit is contained in:
commit
6289bbd3e1
Binary file not shown.
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 32 KiB |
BIN
.image/demo/vue3-ep.png
Normal file
BIN
.image/demo/vue3-ep.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 242 KiB |
18
README.md
18
README.md
@ -11,7 +11,7 @@
|
||||
|
||||
* nodejs > 16.18.0 && pnpm > 8.6.0 (强制使用pnpm)
|
||||
* 演示地址【Vue3 + element-plus】:<http://dashboard-vue3.yudao.iocoder.cn>
|
||||
* 演示地址【Vue3 + vben(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
|
||||
* 演示地址【Vue3 + vben5.0(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
|
||||
* 演示地址【Vue2 + element-ui】:<http://dashboard.yudao.iocoder.cn>
|
||||
* 启动文档:<https://doc.iocoder.cn/quick-start/>
|
||||
* 视频教程:<https://doc.iocoder.cn/video/>
|
||||
@ -24,7 +24,7 @@
|
||||
* 改换 saas,自动引入等功能
|
||||
* 使用 Element Plus 免费开源的中后台模版,具备如下特性:
|
||||
|
||||

|
||||

|
||||
|
||||
* **最新技术栈**:使用 Vue3、Vite4 等前端前沿技术开发
|
||||
* **TypeScript**: 应用程序级 JavaScript 的语言
|
||||
@ -38,15 +38,15 @@
|
||||
|
||||
| 框架 | 说明 | 版本 |
|
||||
|----------------------------------------------------------------------|------------------|--------|
|
||||
| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.3.8 |
|
||||
| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.3.8 |
|
||||
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.5.0 |
|
||||
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.4.2 |
|
||||
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.4.2 |
|
||||
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 5.2.2 |
|
||||
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.1.7 |
|
||||
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.1.7 |
|
||||
| [vueuse](https://vueuse.org/) | 常用工具集 | 10.6.1 |
|
||||
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.6.5 |
|
||||
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.2.5 |
|
||||
| [unocss](https://uno.antfu.me/) | 原子 css | 0.57.4 |
|
||||
| [unocss](https://uno.antfu.me/) | 原子 css | 0.57.4 |
|
||||
| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 3.1.1 |
|
||||
| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.23 |
|
||||
|
||||
@ -121,9 +121,9 @@
|
||||
|
||||
基于 Flowable 构建,可支持信创(国产)数据库,满足中国特色流程操作:
|
||||
|
||||
| BPMN 设计器 | 钉钉/飞书设计器 |
|
||||
|------------------------------|--------------------------------|
|
||||
|  |  |
|
||||
| BPMN 设计器 | 钉钉/飞书设计器 |
|
||||
|-----------------------------|-------------------------------|
|
||||
|  |  |
|
||||
|
||||
> 历经头部企业生产验证,工作流引擎须标配仿钉钉/飞书 + BPMN 双设计器!!!
|
||||
>
|
||||
|
@ -13,7 +13,7 @@ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||
import viteCompression from 'vite-plugin-compression'
|
||||
import topLevelAwait from 'vite-plugin-top-level-await'
|
||||
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons-ng'
|
||||
import UnoCSS from 'unocss/vite'
|
||||
|
||||
export function createVitePlugins() {
|
||||
@ -78,7 +78,6 @@ export function createVitePlugins() {
|
||||
createSvgIconsPlugin({
|
||||
iconDirs: [pathResolve('src/assets/svgs')],
|
||||
symbolId: 'icon-[dir]-[name]',
|
||||
svgoOptions: true
|
||||
}),
|
||||
viteCompression({
|
||||
verbose: true, // 是否在控制台输出压缩结果
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yudao-ui-admin-vue3",
|
||||
"version": "2.4.1-snapshot",
|
||||
"version": "2.4.2-snapshot",
|
||||
"description": "基于vue3、vite4、element-plus、typesScript",
|
||||
"author": "xingyu",
|
||||
"private": false,
|
||||
@ -133,7 +133,7 @@
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-progress": "^0.0.7",
|
||||
"vite-plugin-purge-icons": "^0.10.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-plugin-svg-icons-ng": "^1.3.1",
|
||||
"vite-plugin-top-level-await": "^1.4.4",
|
||||
"vue-eslint-parser": "^9.3.2",
|
||||
"vue-tsc": "^1.8.27"
|
||||
|
2042
pnpm-lock.yaml
generated
2042
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
public/home.png
BIN
public/home.png
Binary file not shown.
Before Width: | Height: | Size: 73 KiB |
@ -20,6 +20,6 @@ export const deleteWorkflow = async (id) => {
|
||||
return await request.delete({ url: '/ai/workflow/delete?id=' + id })
|
||||
}
|
||||
|
||||
export const updateWorkflowModel = async (data) => {
|
||||
return await request.put({ url: '/ai/workflow/updateWorkflowModel', data })
|
||||
export const testWorkflow = async (data) => {
|
||||
return await request.post({ url: '/ai/workflow/test', data })
|
||||
}
|
||||
|
@ -46,11 +46,6 @@ export type DatabaseTableVO = {
|
||||
comment: string
|
||||
}
|
||||
|
||||
export type CodegenDetailVO = {
|
||||
table: CodegenTableVO
|
||||
columns: CodegenColumnVO[]
|
||||
}
|
||||
|
||||
export type CodegenPreviewVO = {
|
||||
filePath: string
|
||||
code: string
|
||||
@ -61,11 +56,6 @@ export type CodegenUpdateReqVO = {
|
||||
columns: CodegenColumnVO[]
|
||||
}
|
||||
|
||||
export type CodegenCreateListReqVO = {
|
||||
dataSourceConfigId: number
|
||||
tableNames: string[]
|
||||
}
|
||||
|
||||
// 查询列表代码生成表定义
|
||||
export const getCodegenTableList = (dataSourceConfigId: number) => {
|
||||
return request.get({ url: '/infra/codegen/table/list?dataSourceConfigId=' + dataSourceConfigId })
|
||||
@ -81,11 +71,6 @@ export const getCodegenTable = (id: number) => {
|
||||
return request.get({ url: '/infra/codegen/detail?tableId=' + id })
|
||||
}
|
||||
|
||||
// 新增代码生成表定义
|
||||
export const createCodegenTable = (data: CodegenCreateListReqVO) => {
|
||||
return request.post({ url: '/infra/codegen/create', data })
|
||||
}
|
||||
|
||||
// 修改代码生成表定义
|
||||
export const updateCodegenTable = (data: CodegenUpdateReqVO) => {
|
||||
return request.put({ url: '/infra/codegen/update', data })
|
||||
|
@ -1,11 +1,5 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export interface FilePageReqVO extends PageParam {
|
||||
path?: string
|
||||
type?: string
|
||||
createTime?: Date[]
|
||||
}
|
||||
|
||||
// 文件预签名地址 Response VO
|
||||
export interface FilePresignedUrlRespVO {
|
||||
// 文件配置编号
|
||||
@ -17,7 +11,7 @@ export interface FilePresignedUrlRespVO {
|
||||
}
|
||||
|
||||
// 查询文件列表
|
||||
export const getFilePage = (params: FilePageReqVO) => {
|
||||
export const getFilePage = (params: PageParam) => {
|
||||
return request.get({ url: '/infra/file/page', params })
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ export interface FileClientConfig {
|
||||
bucket?: string
|
||||
accessKey?: string
|
||||
accessSecret?: string
|
||||
enablePathStyleAccess?: boolean
|
||||
domain: string
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import request from '@/config/axios'
|
||||
import { getRefreshToken } from '@/utils/auth'
|
||||
import type { RegisterVO, UserLoginVO } from './types'
|
||||
|
||||
export interface SmsCodeVO {
|
||||
@ -72,7 +71,6 @@ export const socialAuthRedirect = (type: number, redirectUri: string) => {
|
||||
}
|
||||
// 获取验证图片以及 token
|
||||
export const getCode = (data: any) => {
|
||||
debugger
|
||||
return request.postOriginal({ url: 'system/captcha/get', data })
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,11 @@ export const getTenant = (id: number) => {
|
||||
return request.get({ url: '/system/tenant/get?id=' + id })
|
||||
}
|
||||
|
||||
// 获取租户精简信息列表
|
||||
export const getTenantList = () => {
|
||||
return request.get({ url: '/system/tenant/simple-list' })
|
||||
}
|
||||
|
||||
// 新增租户
|
||||
export const createTenant = (data: TenantVO) => {
|
||||
return request.post({ url: '/system/tenant/create', data })
|
||||
|
@ -22,11 +22,6 @@ export const getUserPage = (params: PageParam) => {
|
||||
return request.get({ url: '/system/user/page', params })
|
||||
}
|
||||
|
||||
// 查询所有用户列表
|
||||
export const getAllUser = () => {
|
||||
return request.get({ url: '/system/user/all' })
|
||||
}
|
||||
|
||||
// 查询用户详情
|
||||
export const getUser = (id: number) => {
|
||||
return request.get({ url: '/system/user/get?id=' + id })
|
||||
@ -48,7 +43,7 @@ export const deleteUser = (id: number) => {
|
||||
}
|
||||
|
||||
// 导出用户
|
||||
export const exportUser = (params) => {
|
||||
export const exportUser = (params: any) => {
|
||||
return request.download({ url: '/system/user/export', params })
|
||||
}
|
||||
|
||||
@ -58,7 +53,7 @@ export const importUserTemplate = () => {
|
||||
}
|
||||
|
||||
// 用户密码重置
|
||||
export const resetUserPwd = (id: number, password: string) => {
|
||||
export const resetUserPassword = (id: number, password: string) => {
|
||||
const data = {
|
||||
id,
|
||||
password
|
||||
|
@ -32,10 +32,11 @@ export interface ProfileVO {
|
||||
}
|
||||
|
||||
export interface UserProfileUpdateReqVO {
|
||||
nickname: string
|
||||
email: string
|
||||
mobile: string
|
||||
sex: number
|
||||
nickname?: string
|
||||
email?: string
|
||||
mobile?: string
|
||||
sex?: number
|
||||
avatar?: string
|
||||
}
|
||||
|
||||
// 查询用户个人信息
|
||||
@ -58,8 +59,3 @@ export const updateUserPassword = (oldPassword: string, newPassword: string) =>
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 用户头像上传
|
||||
export const uploadAvatar = (data) => {
|
||||
return request.upload({ url: '/system/user/profile/update-avatar', data: data })
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div @click.stop>
|
||||
<Dialog
|
||||
v-model="dialogVisible"
|
||||
:canFullscreen="false"
|
||||
@ -181,6 +181,7 @@ function openModal() {
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
debugger
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ const dialogStyle = computed(() => {
|
||||
icon="ep:close"
|
||||
hover-color="var(--el-color-primary)"
|
||||
color="var(--el-color-info)"
|
||||
@click="close"
|
||||
@click.stop="close"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -13,7 +13,7 @@ export const CouponDiscount = defineComponent({
|
||||
setup(props) {
|
||||
const coupon = props.coupon as CouponTemplateApi.CouponTemplateVO
|
||||
// 折扣
|
||||
let value = coupon.discountPercent + ''
|
||||
let value = coupon.discountPercent / 10 + ''
|
||||
let suffix = ' 折'
|
||||
// 满减
|
||||
if (coupon.discountType === PromotionDiscountTypeEnum.PRICE.type) {
|
||||
@ -43,7 +43,7 @@ export const CouponDiscountDesc = defineComponent({
|
||||
const discountDesc =
|
||||
coupon.discountType === PromotionDiscountTypeEnum.PRICE.type
|
||||
? `减${floatToFixed2(coupon.discountPrice)}元`
|
||||
: `打${coupon.discountPercent}折`
|
||||
: `打${coupon.discountPercent / 10.0}折`
|
||||
return () => (
|
||||
<div>
|
||||
<span>{useCondition}</span>
|
||||
|
@ -49,7 +49,13 @@
|
||||
<div class="flex flex-col justify-evenly gap-4px">
|
||||
<!-- 优惠值 -->
|
||||
<CouponDiscount :coupon="coupon" />
|
||||
<div>{{ coupon.name }}</div>
|
||||
<!-- 优惠描述 -->
|
||||
<CouponDiscountDesc :coupon="coupon" />
|
||||
<!-- 领取说明 -->
|
||||
<div v-if="coupon.totalCount >= 0">
|
||||
仅剩:{{ coupon.totalCount - coupon.takeCount }}张
|
||||
</div>
|
||||
<div v-else-if="coupon.totalCount === -1">仅剩:不限制</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
@ -67,7 +73,8 @@
|
||||
<div v-else class="flex flex-col items-center justify-around gap-4px p-4px">
|
||||
<!-- 优惠值 -->
|
||||
<CouponDiscount :coupon="coupon" />
|
||||
<div>{{ coupon.name }}</div>
|
||||
<!-- 优惠描述 -->
|
||||
<CouponDiscountDesc :coupon="coupon" />
|
||||
<div
|
||||
class="rounded-20px p-x-8px p-y-2px"
|
||||
:style="{
|
||||
@ -124,7 +131,7 @@ watch(
|
||||
() => {
|
||||
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
|
||||
couponWidth.value =
|
||||
(phoneWidth.value * 0.95 - props.property.space * (props.property.columns - 1)) /
|
||||
(phoneWidth.value - props.property.space * (props.property.columns - 1)) /
|
||||
props.property.columns
|
||||
// 显示滚动条
|
||||
scrollbarWidth.value = `${
|
||||
|
@ -1,16 +1,19 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative"
|
||||
:style="{ height: `${rowCount * CUBE_SIZE}px`, width: `${4 * CUBE_SIZE}px` }"
|
||||
:style="{
|
||||
height: `${rowCount * CUBE_SIZE}px`,
|
||||
width: `${4 * CUBE_SIZE}px`,
|
||||
padding: `${property.space}px`
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in property.list"
|
||||
:key="index"
|
||||
class="absolute"
|
||||
:style="{
|
||||
width: `${item.width * CUBE_SIZE - property.space * 2}px`,
|
||||
height: `${item.height * CUBE_SIZE - property.space * 2}px`,
|
||||
margin: `${property.space}px`,
|
||||
width: `${item.width * CUBE_SIZE - property.space}px`,
|
||||
height: `${item.height * CUBE_SIZE - property.space}px`,
|
||||
top: `${item.top * CUBE_SIZE}px`,
|
||||
left: `${item.left * CUBE_SIZE}px`
|
||||
}"
|
||||
@ -63,10 +66,10 @@ const rowCount = computed(() => {
|
||||
let count = 0
|
||||
if (props.property.list.length > 0) {
|
||||
// 最大行号
|
||||
count = Math.max(...props.property.list.map((item) => item.bottom))
|
||||
count = Math.max(...props.property.list.map((item) => item.top + item.height))
|
||||
}
|
||||
// 行号从 0 开始,所以加 1
|
||||
return count + 1
|
||||
// 保证至少有一行
|
||||
return count == 0 ? 1 : count
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -39,7 +39,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-carousel-item>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</template>
|
||||
|
||||
@ -51,7 +51,7 @@ const props = defineProps<{ property: MenuSwiperProperty }>()
|
||||
// 标题的高度
|
||||
const TITLE_HEIGHT = 20
|
||||
// 图标的高度
|
||||
const ICON_SIZE = 42
|
||||
const ICON_SIZE = 32
|
||||
// 垂直间距:一行上下的间距
|
||||
const SPACE_Y = 16
|
||||
|
||||
|
@ -29,7 +29,10 @@
|
||||
<ColorInput v-model="formData.bgColor" />
|
||||
</el-form-item>
|
||||
<el-form-item label="背景图片" prop="bgImg" v-else>
|
||||
<UploadImg v-model="formData.bgImg" :limit="1" width="56px" height="56px" />
|
||||
<div class="flex items-center">
|
||||
<UploadImg v-model="formData.bgImg" :limit="1" width="56px" height="56px" />
|
||||
<span class="text-xs text-gray-400 ml-2 mb-2">建议宽度:750</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-card class="property-group" shadow="never">
|
||||
<template #header>
|
||||
@ -39,8 +42,9 @@
|
||||
<el-checkbox
|
||||
v-model="formData._local.previewMp"
|
||||
@change="formData._local.previewOther = !formData._local.previewMp"
|
||||
>预览</el-checkbox
|
||||
>
|
||||
预览
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
@ -54,8 +58,9 @@
|
||||
<el-checkbox
|
||||
v-model="formData._local.previewOther"
|
||||
@change="formData._local.previewMp = !formData._local.previewOther"
|
||||
>预览</el-checkbox
|
||||
>
|
||||
预览
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -82,8 +82,8 @@ export const component = {
|
||||
bgEndColor: '#FE832A',
|
||||
imgUrl: ''
|
||||
},
|
||||
borderRadiusTop: 8,
|
||||
borderRadiusBottom: 8,
|
||||
borderRadiusTop: 6,
|
||||
borderRadiusBottom: 6,
|
||||
space: 8,
|
||||
spuIds: [],
|
||||
style: {
|
||||
|
@ -14,7 +14,10 @@
|
||||
:key="index"
|
||||
>
|
||||
<!-- 角标 -->
|
||||
<div v-if="property.badge.show" class="absolute left-0 top-0 z-1 items-center justify-center">
|
||||
<div
|
||||
v-if="property.badge.show && property.badge.imgUrl"
|
||||
class="absolute left-0 top-0 z-1 items-center justify-center"
|
||||
>
|
||||
<el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
|
||||
</div>
|
||||
<!-- 商品封面图 -->
|
||||
|
@ -3,7 +3,7 @@
|
||||
<!-- 表单 -->
|
||||
<el-form label-width="80px" :model="formData" class="m-t-8px">
|
||||
<el-card header="搜索热词" class="property-group" shadow="never">
|
||||
<Draggable v-model="formData.hotKeywords" :empty-item="''">
|
||||
<Draggable v-model="formData.hotKeywords" :empty-item="''" :min="0">
|
||||
<template #default="{ index }">
|
||||
<el-input v-model="formData.hotKeywords[index]" placeholder="请输入热词" />
|
||||
</template>
|
||||
@ -61,6 +61,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { SearchProperty } from '@/components/DiyEditor/components/mobile/SearchBar/config'
|
||||
import { isString } from '@/utils/is'
|
||||
|
||||
/** 搜索框属性面板 */
|
||||
defineOptions({ name: 'SearchProperty' })
|
||||
@ -68,6 +69,19 @@ defineOptions({ name: 'SearchProperty' })
|
||||
const props = defineProps<{ modelValue: SearchProperty }>()
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const formData = useVModel(props, 'modelValue', emit)
|
||||
|
||||
// 监听热词数组变化
|
||||
watch(
|
||||
() => formData.value.hotKeywords,
|
||||
(newVal) => {
|
||||
// 找到非字符串项的索引
|
||||
const nonStringIndex = newVal.findIndex((item) => !isString(item))
|
||||
if (nonStringIndex !== -1) {
|
||||
formData.value.hotKeywords[nonStringIndex] = ''
|
||||
}
|
||||
},
|
||||
{ deep: true, flush: 'post' }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import {ComponentStyle, DiyComponent} from '@/components/DiyEditor/util'
|
||||
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||
|
||||
/** 标题栏属性 */
|
||||
export interface TitleBarProperty {
|
||||
// 背景图
|
||||
bgImgUrl: string
|
||||
// 偏移
|
||||
marginLeft: number
|
||||
// 显示位置
|
||||
@ -22,6 +24,8 @@ export interface TitleBarProperty {
|
||||
titleColor: string
|
||||
// 描述颜色
|
||||
descriptionColor: string
|
||||
// 高度
|
||||
height: number
|
||||
// 查看更多
|
||||
more: {
|
||||
// 是否显示查看更多
|
||||
@ -52,6 +56,8 @@ export const component = {
|
||||
descriptionWeight: 200,
|
||||
titleColor: 'rgba(50, 50, 51, 10)',
|
||||
descriptionColor: 'rgba(150, 151, 153, 10)',
|
||||
marginLeft: 0,
|
||||
height: 40,
|
||||
more: {
|
||||
//查看更多
|
||||
show: false,
|
||||
|
@ -1,55 +1,49 @@
|
||||
<template>
|
||||
<div
|
||||
:style="{
|
||||
background:
|
||||
property.style.bgType === 'color' ? property.style.bgColor : `url(${property.style.bgImg})`,
|
||||
backgroundSize: '100% 100%',
|
||||
backgroundRepeat: 'no-repeat'
|
||||
}"
|
||||
class="title-bar"
|
||||
>
|
||||
<!-- 内容 -->
|
||||
<div>
|
||||
<div class="title-bar" :style="{ height: `${property.height}px` }">
|
||||
<el-image v-if="property.bgImgUrl" :src="property.bgImgUrl" fit="cover" class="w-full" />
|
||||
<div class="absolute left-0 top-0 w-full h-full flex flex-col justify-center">
|
||||
<!-- 标题 -->
|
||||
<div
|
||||
v-if="property.title"
|
||||
:style="{
|
||||
fontSize: `${property.titleSize}px`,
|
||||
fontWeight: property.titleWeight,
|
||||
color: property.titleColor,
|
||||
textAlign: property.textAlign
|
||||
textAlign: property.textAlign,
|
||||
marginLeft: `${property.marginLeft}px`,
|
||||
marginBottom: '4px'
|
||||
}"
|
||||
v-if="property.title"
|
||||
>
|
||||
{{ property.title }}
|
||||
</div>
|
||||
<!-- 副标题 -->
|
||||
<div
|
||||
v-if="property.description"
|
||||
:style="{
|
||||
fontSize: `${property.descriptionSize}px`,
|
||||
fontWeight: property.descriptionWeight,
|
||||
color: property.descriptionColor,
|
||||
textAlign: property.textAlign
|
||||
textAlign: property.textAlign,
|
||||
marginLeft: `${property.marginLeft}px`
|
||||
}"
|
||||
class="m-t-8px"
|
||||
v-if="property.description"
|
||||
>
|
||||
{{ property.description }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 更多 -->
|
||||
<div
|
||||
class="more"
|
||||
v-show="property.more.show"
|
||||
:style="{
|
||||
color: property.descriptionColor
|
||||
}"
|
||||
class="more"
|
||||
>
|
||||
<span v-if="property.more.type !== 'icon'"> {{ property.more.text }} </span>
|
||||
<Icon v-if="property.more.type !== 'text'" icon="ep:arrow-right" />
|
||||
<Icon icon="ep:arrow-right" v-if="property.more.type !== 'text'" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
<script setup lang="ts">
|
||||
import { TitleBarProperty } from './config'
|
||||
|
||||
/** 标题栏 */
|
||||
@ -57,7 +51,7 @@ defineOptions({ name: 'TitleBar' })
|
||||
|
||||
defineProps<{ property: TitleBarProperty }>()
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
<style scoped lang="scss">
|
||||
.title-bar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<ComponentContainerProperty v-model="formData.style">
|
||||
<el-form :model="formData" :rules="rules" label-width="85px">
|
||||
<el-card class="property-group" header="风格" shadow="never">
|
||||
<el-form label-width="85px" :model="formData" :rules="rules">
|
||||
<el-card header="风格" class="property-group" shadow="never">
|
||||
<el-form-item label="背景图片" prop="bgImgUrl">
|
||||
<UploadImg v-model="formData.bgImgUrl" width="100%" height="40px">
|
||||
<template #tip>建议尺寸 750*80</template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
<el-form-item label="标题位置" prop="textAlign">
|
||||
<el-radio-group v-model="formData!.textAlign">
|
||||
<el-tooltip content="居左" placement="top">
|
||||
@ -16,66 +21,84 @@
|
||||
</el-tooltip>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="偏移量" prop="marginLeft" label-width="70px">
|
||||
<el-slider
|
||||
v-model="formData.marginLeft"
|
||||
:max="100"
|
||||
:min="0"
|
||||
show-input
|
||||
input-size="small"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="高度" prop="height" label-width="70px">
|
||||
<el-slider
|
||||
v-model="formData.height"
|
||||
:max="200"
|
||||
:min="20"
|
||||
show-input
|
||||
input-size="small"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card class="property-group" header="主标题" shadow="never">
|
||||
<el-form-item label="文字" label-width="40px" prop="title">
|
||||
<el-card header="主标题" class="property-group" shadow="never">
|
||||
<el-form-item label="文字" prop="title" label-width="40px">
|
||||
<InputWithColor
|
||||
v-model="formData.title"
|
||||
v-model:color="formData.titleColor"
|
||||
maxlength="20"
|
||||
show-word-limit
|
||||
maxlength="20"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="大小" label-width="40px" prop="titleSize">
|
||||
<el-form-item label="大小" prop="titleSize" label-width="40px">
|
||||
<el-slider
|
||||
v-model="formData.titleSize"
|
||||
:max="60"
|
||||
:min="10"
|
||||
input-size="small"
|
||||
show-input
|
||||
input-size="small"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="粗细" label-width="40px" prop="titleWeight">
|
||||
<el-form-item label="粗细" prop="titleWeight" label-width="40px">
|
||||
<el-slider
|
||||
v-model="formData.titleWeight"
|
||||
:max="900"
|
||||
:min="100"
|
||||
:max="900"
|
||||
:step="100"
|
||||
input-size="small"
|
||||
show-input
|
||||
input-size="small"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card class="property-group" header="副标题" shadow="never">
|
||||
<el-form-item label="文字" label-width="40px" prop="description">
|
||||
<el-card header="副标题" class="property-group" shadow="never">
|
||||
<el-form-item label="文字" prop="description" label-width="40px">
|
||||
<InputWithColor
|
||||
v-model="formData.description"
|
||||
v-model:color="formData.descriptionColor"
|
||||
maxlength="50"
|
||||
show-word-limit
|
||||
maxlength="50"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="大小" label-width="40px" prop="descriptionSize">
|
||||
<el-form-item label="大小" prop="descriptionSize" label-width="40px">
|
||||
<el-slider
|
||||
v-model="formData.descriptionSize"
|
||||
:max="60"
|
||||
:min="10"
|
||||
input-size="small"
|
||||
show-input
|
||||
input-size="small"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="粗细" label-width="40px" prop="descriptionWeight">
|
||||
<el-form-item label="粗细" prop="descriptionWeight" label-width="40px">
|
||||
<el-slider
|
||||
v-model="formData.descriptionWeight"
|
||||
:max="900"
|
||||
:min="100"
|
||||
:max="900"
|
||||
:step="100"
|
||||
input-size="small"
|
||||
show-input
|
||||
input-size="small"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card class="property-group" header="查看更多" shadow="never">
|
||||
<el-card header="查看更多" class="property-group" shadow="never">
|
||||
<el-form-item label="是否显示" prop="more.show">
|
||||
<el-checkbox v-model="formData.more.show" />
|
||||
</el-form-item>
|
||||
@ -88,7 +111,7 @@
|
||||
<el-radio value="all">文字+图标</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-show="formData.more.type !== 'icon'" label="更多文字" prop="more.text">
|
||||
<el-form-item label="更多文字" prop="more.text" v-show="formData.more.type !== 'icon'">
|
||||
<el-input v-model="formData.more.text" />
|
||||
</el-form-item>
|
||||
<el-form-item label="跳转链接" prop="more.url">
|
||||
@ -99,7 +122,7 @@
|
||||
</el-form>
|
||||
</ComponentContainerProperty>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
<script setup lang="ts">
|
||||
import { TitleBarProperty } from './config'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
// 导航栏属性面板
|
||||
@ -113,4 +136,4 @@ const formData = useVModel(props, 'modelValue', emit)
|
||||
const rules = {}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style scoped lang="scss"></style>
|
||||
|
@ -28,7 +28,7 @@
|
||||
<Icon
|
||||
icon="ep:delete"
|
||||
class="cursor-pointer text-red-5"
|
||||
v-if="formData.length > 1"
|
||||
v-if="formData.length > min"
|
||||
@click="handleDelete(index)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
@ -69,7 +69,9 @@ const props = defineProps({
|
||||
// 空的元素:点击添加按钮时,创建元素并添加到列表;默认为空对象
|
||||
emptyItem: any<unknown>().def({}),
|
||||
// 数量限制:默认为0,表示不限制
|
||||
limit: propTypes.number.def(0)
|
||||
limit: propTypes.number.def(0),
|
||||
// 最小数量:默认为1
|
||||
min: propTypes.number.def(1)
|
||||
})
|
||||
// 定义事件
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
@ -69,11 +69,18 @@ export const useApiSelect = (option: ApiSelectProps) => {
|
||||
if (isEmpty(props.url)) {
|
||||
return
|
||||
}
|
||||
|
||||
switch (props.method) {
|
||||
case 'GET':
|
||||
let url: string = props.url
|
||||
if (props.remote) {
|
||||
url = `${url}?${props.remoteField}=${queryParam.value}`
|
||||
if (queryParam.value != undefined) {
|
||||
if (url.includes('?')) {
|
||||
url = `${url}&${props.remoteField}=${queryParam.value}`
|
||||
} else {
|
||||
url = `${url}?${props.remoteField}=${queryParam.value}`
|
||||
}
|
||||
}
|
||||
}
|
||||
parseOptions(await request.get({ url: url }))
|
||||
break
|
||||
|
@ -17,6 +17,7 @@ export const useSelectRule = (option: SelectRuleOption) => {
|
||||
icon: option.icon,
|
||||
label,
|
||||
name,
|
||||
event: option.event,
|
||||
rule() {
|
||||
return {
|
||||
type: name,
|
||||
|
@ -46,5 +46,6 @@ export interface SelectRuleOption {
|
||||
label: string // label 名称
|
||||
name: string // 组件名称
|
||||
icon: string // 组件图标
|
||||
props?: any[] // 组件规则
|
||||
props?: any[], // 组件规则
|
||||
event?: any[] // 事件配置
|
||||
}
|
||||
|
@ -63,7 +63,8 @@ export const useFormCreateDesigner = async (designer: Ref) => {
|
||||
name: 'ApiSelect',
|
||||
label: '接口选择器',
|
||||
icon: 'icon-server',
|
||||
props: [...apiSelectRule]
|
||||
props: [...apiSelectRule],
|
||||
event: ['click', 'change', 'visibleChange', 'clear', 'blur', 'focus']
|
||||
})
|
||||
|
||||
/**
|
||||
|
@ -35,13 +35,13 @@
|
||||
>
|
||||
<!-- 右上角热区删除按钮 -->
|
||||
<div
|
||||
v-if="selectedHotAreaIndex === index"
|
||||
v-if="selectedHotAreaIndex === index && hotArea.width && hotArea.height"
|
||||
class="btn-delete"
|
||||
@click="handleDeleteHotArea(index)"
|
||||
>
|
||||
<Icon icon="ep:circle-close-filled" />
|
||||
</div>
|
||||
{{ `${hotArea.width}×${hotArea.height}` }}
|
||||
<span v-if="hotArea.width">{{ `${hotArea.width}×${hotArea.height}` }}</span>
|
||||
</div>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -237,7 +237,7 @@ const props = defineProps({
|
||||
const prefix = inject('prefix')
|
||||
const width = inject('width')
|
||||
|
||||
const formKey = ref('')
|
||||
const formKey = ref(undefined)
|
||||
const businessKey = ref('')
|
||||
const optionModelTitle = ref('')
|
||||
const fieldList = ref<any[]>([])
|
||||
@ -462,6 +462,7 @@ const updateElementExtensions = () => {
|
||||
const formList = ref([]) // 流程表单的下拉框的数据
|
||||
onMounted(async () => {
|
||||
formList.value = await FormApi.getFormSimpleList()
|
||||
formKey.value = parseInt(formKey.value)
|
||||
})
|
||||
|
||||
watch(
|
||||
|
@ -370,7 +370,6 @@ const removeListenerField = (index) => {
|
||||
}
|
||||
// 移除监听器
|
||||
const removeListener = (index) => {
|
||||
debugger
|
||||
ElMessageBox.confirm('确认移除该监听器吗?', '提示', {
|
||||
confirmButtonText: '确 认',
|
||||
cancelButtonText: '取 消'
|
||||
|
@ -2,7 +2,6 @@ import { toRaw } from 'vue'
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
// 创建监听器实例
|
||||
export function createListenerObject(options, isTask, prefix) {
|
||||
debugger
|
||||
const listenerObj = Object.create(null)
|
||||
listenerObj.event = options.event
|
||||
isTask && (listenerObj.id = options.id) // 任务监听器特有的 id 字段
|
||||
|
@ -3,7 +3,14 @@ import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestCo
|
||||
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
|
||||
import qs from 'qs'
|
||||
import { config } from '@/config/axios/config'
|
||||
import { getAccessToken, getRefreshToken, getTenantId, removeToken, setToken } from '@/utils/auth'
|
||||
import {
|
||||
getAccessToken,
|
||||
getRefreshToken,
|
||||
getTenantId,
|
||||
getVisitTenantId,
|
||||
removeToken,
|
||||
setToken
|
||||
} from '@/utils/auth'
|
||||
import errorCode from './errorCode'
|
||||
|
||||
import { resetRouter } from '@/router'
|
||||
@ -24,7 +31,7 @@ export const isRelogin = { show: false }
|
||||
let requestList: any[] = []
|
||||
// 是否正在刷新中
|
||||
let isRefreshToken = false
|
||||
// 请求白名单,无须token的接口
|
||||
// 请求白名单,无须 token 的接口
|
||||
const whiteList: string[] = ['/login', '/refresh-token']
|
||||
|
||||
// 创建axios实例
|
||||
@ -55,6 +62,11 @@ service.interceptors.request.use(
|
||||
if (tenantEnable && tenantEnable === 'true') {
|
||||
const tenantId = getTenantId()
|
||||
if (tenantId) config.headers['tenant-id'] = tenantId
|
||||
// 只有登录时,才设置 visit-tenant-id 访问租户
|
||||
const visitTenantId = getVisitTenantId()
|
||||
if (config.headers.Authorization && visitTenantId) {
|
||||
config.headers['visit-tenant-id'] = visitTenantId
|
||||
}
|
||||
}
|
||||
const method = config.method?.toUpperCase()
|
||||
// 防止 GET 请求缓存
|
||||
|
@ -10,6 +10,7 @@ export const CACHE_KEY = {
|
||||
// 用户相关
|
||||
ROLE_ROUTERS: 'roleRouters',
|
||||
USER: 'user',
|
||||
VisitTenantId: 'visitTenantId',
|
||||
// 系统设置
|
||||
IS_DARK: 'isDark',
|
||||
LANG: 'lang',
|
||||
@ -35,5 +36,6 @@ export const deleteUserCache = () => {
|
||||
const { wsCache } = useCache()
|
||||
wsCache.delete(CACHE_KEY.USER)
|
||||
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
|
||||
wsCache.delete(CACHE_KEY.VisitTenantId)
|
||||
// 注意,不要清理 LoginForm 登录表单
|
||||
}
|
||||
|
@ -1,8 +1,14 @@
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { watch } from 'vue'
|
||||
|
||||
const domSymbol = Symbol('watermark-dom')
|
||||
|
||||
export function useWatermark(appendEl: HTMLElement | null = document.body) {
|
||||
let func: Fn = () => {}
|
||||
const id = domSymbol.toString()
|
||||
const appStore = useAppStore()
|
||||
let watermarkStr = ''
|
||||
|
||||
const clear = () => {
|
||||
const domId = document.getElementById(id)
|
||||
if (domId) {
|
||||
@ -22,7 +28,7 @@ export function useWatermark(appendEl: HTMLElement | null = document.body) {
|
||||
if (cans) {
|
||||
cans.rotate((-20 * Math.PI) / 120)
|
||||
cans.font = '15px Vedana'
|
||||
cans.fillStyle = 'rgba(0, 0, 0, 0.15)'
|
||||
cans.fillStyle = appStore.getIsDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.15)'
|
||||
cans.textAlign = 'left'
|
||||
cans.textBaseline = 'middle'
|
||||
cans.fillText(str, can.width / 20, can.height)
|
||||
@ -44,6 +50,7 @@ export function useWatermark(appendEl: HTMLElement | null = document.body) {
|
||||
}
|
||||
|
||||
function setWatermark(str: string) {
|
||||
watermarkStr = str
|
||||
createWatermark(str)
|
||||
func = () => {
|
||||
createWatermark(str)
|
||||
@ -51,5 +58,15 @@ export function useWatermark(appendEl: HTMLElement | null = document.body) {
|
||||
window.addEventListener('resize', func)
|
||||
}
|
||||
|
||||
// 监听主题变化
|
||||
watch(
|
||||
() => appStore.getIsDark,
|
||||
() => {
|
||||
if (watermarkStr) {
|
||||
createWatermark(watermarkStr)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return { setWatermark, clear }
|
||||
}
|
||||
|
46
src/layout/components/TenantVisit/index.vue
Normal file
46
src/layout/components/TenantVisit/index.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-select
|
||||
filterable
|
||||
placeholder="请选择租户"
|
||||
class="!w-180px"
|
||||
v-model="value"
|
||||
@change="handleChange"
|
||||
clearable
|
||||
>
|
||||
<el-option v-for="item in tenants" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import * as TenantApi from '@/api/system/tenant'
|
||||
import { getVisitTenantId, setVisitTenantId } from '@/utils/auth'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { useTagsView } from '@/hooks/web/useTagsView'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const tagsView = useTagsView() // 标签页操作
|
||||
|
||||
const value = ref(getVisitTenantId()) // 当前选中的租户 ID
|
||||
const tenants = ref<any[]>([]) // 租户列表
|
||||
|
||||
const handleChange = (id: number) => {
|
||||
// 设置访问租户 ID
|
||||
setVisitTenantId(id)
|
||||
// 关闭其他标签页,只保留当前页
|
||||
tagsView.closeOther()
|
||||
// 刷新当前页面
|
||||
tagsView.refreshPage()
|
||||
// 提示切换成功
|
||||
const tenant = tenants.value.find((item) => item.id === id)
|
||||
if (tenant) {
|
||||
message.success(`切换当前租户为: ${tenant.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
tenants.value = await TenantApi.getTenantList()
|
||||
})
|
||||
</script>
|
@ -8,8 +8,10 @@ import { Breadcrumb } from '@/layout/components/Breadcrumb'
|
||||
import { SizeDropdown } from '@/layout/components/SizeDropdown'
|
||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||
import RouterSearch from '@/components/RouterSearch/index.vue'
|
||||
import TenantVisit from '@/layout/components/TenantVisit/index.vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
|
||||
const { getPrefixCls, variables } = useDesign()
|
||||
|
||||
@ -41,6 +43,9 @@ const locale = computed(() => appStore.getLocale)
|
||||
// 消息图标
|
||||
const message = computed(() => appStore.getMessage)
|
||||
|
||||
// 租户切换权限
|
||||
const hasTenantVisitPermission = computed(() => checkPermi(['system:tenant:visit']))
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ToolHeader',
|
||||
setup() {
|
||||
@ -62,6 +67,7 @@ export default defineComponent({
|
||||
</div>
|
||||
) : undefined}
|
||||
<div class="h-full flex items-center">
|
||||
{hasTenantVisitPermission.value ? <TenantVisit /> : undefined}
|
||||
{screenfull.value ? (
|
||||
<Screenfull class="custom-hover" color="var(--top-header-text-color)"></Screenfull>
|
||||
) : undefined}
|
||||
|
@ -142,9 +142,9 @@ export default {
|
||||
qrcode: '扫描二维码登录',
|
||||
btnRegister: '注册',
|
||||
SmsSendMsg: '验证码已发送',
|
||||
resetPassword: "重置密码",
|
||||
resetPasswordSuccess: "重置密码成功",
|
||||
invalidTenantName: "无效的租户名称"
|
||||
resetPassword: '重置密码',
|
||||
resetPasswordSuccess: '重置密码成功',
|
||||
invalidTenantName: '无效的租户名称'
|
||||
},
|
||||
captcha: {
|
||||
verification: '请完成安全验证',
|
||||
@ -416,9 +416,9 @@ export default {
|
||||
},
|
||||
info: {
|
||||
title: '基本信息',
|
||||
basicInfo: '基本资料',
|
||||
resetPwd: '修改密码',
|
||||
userSocial: '社交信息'
|
||||
basicInfo: '基本设置',
|
||||
resetPwd: '密码设置',
|
||||
userSocial: '社交绑定'
|
||||
},
|
||||
rules: {
|
||||
nickname: '请输入用户昵称',
|
||||
|
@ -476,9 +476,9 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
name: 'DiyTemplateDecorate',
|
||||
meta: {
|
||||
title: '模板装修',
|
||||
noCache: true,
|
||||
noCache: false,
|
||||
hidden: true,
|
||||
activeMenu: '/mall/promotion/diy/template'
|
||||
activeMenu: '/mall/promotion/diy-template/diy-template'
|
||||
},
|
||||
component: () => import('@/views/mall/promotion/diy/template/decorate.vue')
|
||||
},
|
||||
@ -487,9 +487,9 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
name: 'DiyPageDecorate',
|
||||
meta: {
|
||||
title: '页面装修',
|
||||
noCache: true,
|
||||
noCache: false,
|
||||
hidden: true,
|
||||
activeMenu: '/mall/promotion/diy/page'
|
||||
activeMenu: '/mall/promotion/diy-template/diy-page'
|
||||
},
|
||||
component: () => import('@/views/mall/promotion/diy/page/decorate.vue')
|
||||
}
|
||||
|
@ -56,6 +56,11 @@ export const useUserStore = defineStore('admin-user', {
|
||||
let userInfo = wsCache.get(CACHE_KEY.USER)
|
||||
if (!userInfo) {
|
||||
userInfo = await getInfo()
|
||||
} else {
|
||||
// 特殊:在有缓存的情况下,进行加载。但是即使加载失败,也不影响后续的操作,保证可以进入系统
|
||||
try {
|
||||
userInfo = await getInfo()
|
||||
} catch (error) {}
|
||||
}
|
||||
this.permissions = new Set(userInfo.permissions)
|
||||
this.roles = userInfo.roles
|
||||
|
@ -67,6 +67,14 @@ export const getTenantId = () => {
|
||||
return wsCache.get(CACHE_KEY.TenantId)
|
||||
}
|
||||
|
||||
export const setTenantId = (username: string) => {
|
||||
wsCache.set(CACHE_KEY.TenantId, username)
|
||||
export const setTenantId = (tenantId: number) => {
|
||||
wsCache.set(CACHE_KEY.TenantId, tenantId)
|
||||
}
|
||||
|
||||
export const getVisitTenantId = () => {
|
||||
return wsCache.get(CACHE_KEY.VisitTenantId)
|
||||
}
|
||||
|
||||
export const setVisitTenantId = (visitTenantId: number) => {
|
||||
wsCache.set(CACHE_KEY.VisitTenantId, visitTenantId)
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ export const SystemUserSocialTypeEnum = {
|
||||
export const InfraCodegenTemplateTypeEnum = {
|
||||
CRUD: 1, // 基础 CRUD
|
||||
TREE: 2, // 树形 CRUD
|
||||
SUB: 3 // 主子表 CRUD
|
||||
SUB: 15 // 主子表 CRUD
|
||||
}
|
||||
|
||||
/**
|
||||
@ -461,5 +461,5 @@ export const BpmProcessInstanceStatus = {
|
||||
export const BpmAutoApproveType = {
|
||||
NONE: 0, // 不自动通过
|
||||
APPROVE_ALL: 1, // 仅审批一次,后续重复的审批节点均自动通过
|
||||
APPROVE_SEQUENT: 2, // 仅针对连续审批的节点自动通过
|
||||
APPROVE_SEQUENT: 2 // 仅针对连续审批的节点自动通过
|
||||
}
|
||||
|
@ -517,8 +517,8 @@ export function jsonParse(str: string) {
|
||||
try {
|
||||
return JSON.parse(str)
|
||||
} catch (e) {
|
||||
console.log(`str[${str}] 不是一个 JSON 字符串`)
|
||||
return ''
|
||||
console.warn(`str[${str}] 不是一个 JSON 字符串`)
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,12 +83,16 @@
|
||||
:sm="24"
|
||||
:xs="24"
|
||||
>
|
||||
<el-card shadow="hover" class="mr-5px mt-5px">
|
||||
<el-card
|
||||
shadow="hover"
|
||||
class="mr-5px mt-5px cursor-pointer"
|
||||
@click="handleProjectClick(item.message)"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<Icon :icon="item.icon" :size="25" class="mr-8px" />
|
||||
<Icon :icon="item.icon" :size="25" class="mr-8px" :style="{ color: item.color }" />
|
||||
<span class="text-16px">{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="mt-12px text-9px text-gray-400">{{ t(item.message) }}</div>
|
||||
<div class="mt-12px text-12px text-gray-400">{{ t(item.message) }}</div>
|
||||
<div class="mt-12px flex justify-between text-12px text-gray-400">
|
||||
<span>{{ item.personal }}</span>
|
||||
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
|
||||
@ -131,8 +135,8 @@
|
||||
<el-row>
|
||||
<el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-8px">
|
||||
<div class="flex items-center">
|
||||
<Icon :icon="item.icon" class="mr-8px" />
|
||||
<el-link type="default" :underline="false" @click="setWatermark(item.name)">
|
||||
<Icon :icon="item.icon" class="mr-8px" :style="{ color: item.color }" />
|
||||
<el-link type="default" :underline="false" @click="handleShortcutClick(item.url)">
|
||||
{{ item.name }}
|
||||
</el-link>
|
||||
</div>
|
||||
@ -180,10 +184,12 @@ import { useUserStore } from '@/store/modules/user'
|
||||
import { useWatermark } from '@/hooks/web/useWatermark'
|
||||
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
|
||||
import { pieOptions, barOptions } from './echarts-data'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
defineOptions({ name: 'Home' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const { setWatermark } = useWatermark()
|
||||
const loading = ref(true)
|
||||
@ -212,45 +218,51 @@ const getProject = async () => {
|
||||
const data = [
|
||||
{
|
||||
name: 'ruoyi-vue-pro',
|
||||
icon: 'akar-icons:github-fill',
|
||||
message: 'https://github.com/YunaiV/ruoyi-vue-pro',
|
||||
icon: 'simple-icons:springboot',
|
||||
message: 'github.com/YunaiV/ruoyi-vue-pro',
|
||||
personal: 'Spring Boot 单体架构',
|
||||
time: new Date()
|
||||
time: new Date('2025-01-02'),
|
||||
color: '#6DB33F'
|
||||
},
|
||||
{
|
||||
name: 'yudao-ui-admin-vue3',
|
||||
icon: 'logos:vue',
|
||||
message: 'https://github.com/yudaocode/yudao-ui-admin-vue3',
|
||||
personal: 'Vue3 + element-plus',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'yudao-ui-admin-vben',
|
||||
icon: 'logos:vue',
|
||||
message: 'https://github.com/yudaocode/yudao-ui-admin-vben',
|
||||
personal: 'Vue3 + vben(antd)',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'yudao-cloud',
|
||||
icon: 'akar-icons:github',
|
||||
message: 'https://github.com/YunaiV/yudao-cloud',
|
||||
personal: 'Spring Cloud 微服务架构',
|
||||
time: new Date()
|
||||
icon: 'ep:element-plus',
|
||||
message: 'github.com/yudaocode/yudao-ui-admin-vue3',
|
||||
personal: 'Vue3 + element-plus 管理后台',
|
||||
time: new Date('2025-02-03'),
|
||||
color: '#409EFF'
|
||||
},
|
||||
{
|
||||
name: 'yudao-ui-mall-uniapp',
|
||||
icon: 'logos:vue',
|
||||
message: 'https://github.com/yudaocode/yudao-ui-admin-uniapp',
|
||||
personal: 'Vue3 + uniapp',
|
||||
time: new Date()
|
||||
icon: 'icon-park-outline:mall-bag',
|
||||
message: 'github.com/yudaocode/yudao-ui-mall-uniapp',
|
||||
personal: 'Vue3 + uniapp 商城手机端',
|
||||
time: new Date('2025-03-04'),
|
||||
color: '#ff4d4f'
|
||||
},
|
||||
{
|
||||
name: 'yudao-ui-admin-vue2',
|
||||
icon: 'logos:vue',
|
||||
message: 'https://github.com/yudaocode/yudao-ui-admin-vue2',
|
||||
personal: 'Vue2 + element-ui',
|
||||
time: new Date()
|
||||
name: 'yudao-cloud',
|
||||
icon: 'material-symbols:cloud-outline',
|
||||
message: 'github.com/YunaiV/yudao-cloud',
|
||||
personal: 'Spring Cloud 微服务架构',
|
||||
time: new Date('2025-04-05'),
|
||||
color: '#1890ff'
|
||||
},
|
||||
{
|
||||
name: 'yudao-ui-admin-vben',
|
||||
icon: 'devicon:antdesign',
|
||||
message: 'github.com/yudaocode/yudao-ui-admin-vben',
|
||||
personal: 'Vue3 + vben5(antd) 管理后台',
|
||||
time: new Date('2025-05-06'),
|
||||
color: '#e18525'
|
||||
},
|
||||
{
|
||||
name: 'yudao-ui-admin-uniapp',
|
||||
icon: 'ant-design:mobile',
|
||||
message: 'github.com/yudaocode/yudao-ui-admin-uniapp',
|
||||
personal: 'Vue3 + uniapp 管理手机端',
|
||||
time: new Date('2025-06-01'),
|
||||
color: '#2979ff'
|
||||
}
|
||||
]
|
||||
projects = Object.assign(projects, data)
|
||||
@ -262,26 +274,26 @@ const getNotice = async () => {
|
||||
const data = [
|
||||
{
|
||||
title: '系统支持 JDK 8/17/21,Vue 2/3',
|
||||
type: '通知',
|
||||
keys: ['通知', '8', '17', '21', '2', '3'],
|
||||
type: '技术兼容性',
|
||||
keys: ['JDK', 'Vue'],
|
||||
date: new Date()
|
||||
},
|
||||
{
|
||||
title: '后端提供 Spring Boot 2.7/3.2 + Cloud 双架构',
|
||||
type: '公告',
|
||||
keys: ['公告', 'Boot', 'Cloud'],
|
||||
type: '架构灵活性',
|
||||
keys: ['Boot', 'Cloud'],
|
||||
date: new Date()
|
||||
},
|
||||
{
|
||||
title: '全部开源,个人与企业可 100% 直接使用,无需授权',
|
||||
type: '通知',
|
||||
keys: ['通知', '无需授权'],
|
||||
type: '开源免授权',
|
||||
keys: ['无需授权'],
|
||||
date: new Date()
|
||||
},
|
||||
{
|
||||
title: '国内使用最广泛的快速开发平台,超 300+ 人贡献',
|
||||
type: '公告',
|
||||
keys: ['公告', '最广泛'],
|
||||
title: '国内使用最广泛的快速开发平台,远超 10w+ 企业使用',
|
||||
type: '广泛企业认可',
|
||||
keys: ['最广泛', '10w+'],
|
||||
date: new Date()
|
||||
}
|
||||
]
|
||||
@ -294,34 +306,40 @@ let shortcut = reactive<Shortcut[]>([])
|
||||
const getShortcut = async () => {
|
||||
const data = [
|
||||
{
|
||||
name: 'Github',
|
||||
icon: 'akar-icons:github-fill',
|
||||
url: 'github.io'
|
||||
name: '首页',
|
||||
icon: 'ion:home-outline',
|
||||
url: '/',
|
||||
color: '#1fdaca'
|
||||
},
|
||||
{
|
||||
name: 'Vue',
|
||||
icon: 'logos:vue',
|
||||
url: 'vuejs.org'
|
||||
name: '商城中心',
|
||||
icon: 'ep:shop',
|
||||
url: '/mall/home',
|
||||
color: '#ff6b6b'
|
||||
},
|
||||
{
|
||||
name: 'Vite',
|
||||
icon: 'vscode-icons:file-type-vite',
|
||||
url: 'https://vitejs.dev/'
|
||||
name: 'AI 大模型',
|
||||
icon: 'tabler:ai',
|
||||
url: '/ai/chat',
|
||||
color: '#7c3aed'
|
||||
},
|
||||
{
|
||||
name: 'Angular',
|
||||
icon: 'logos:angular-icon',
|
||||
url: 'github.io'
|
||||
name: 'ERP 系统',
|
||||
icon: 'simple-icons:erpnext',
|
||||
url: '/erp/home',
|
||||
color: '#3fb27f'
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
icon: 'logos:react',
|
||||
url: 'github.io'
|
||||
name: 'CRM 系统',
|
||||
icon: 'simple-icons:civicrm',
|
||||
url: '/crm/backlog',
|
||||
color: '#4daf1bc9'
|
||||
},
|
||||
{
|
||||
name: 'Webpack',
|
||||
icon: 'logos:webpack',
|
||||
url: 'github.io'
|
||||
name: 'IoT 物联网',
|
||||
icon: 'fa-solid:hdd',
|
||||
url: '/iot/home',
|
||||
color: '#1a73e8'
|
||||
}
|
||||
]
|
||||
shortcut = Object.assign(shortcut, data)
|
||||
@ -387,5 +405,13 @@ const getAllApi = async () => {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const handleProjectClick = (message: string) => {
|
||||
window.open(`https://${message}`, '_blank')
|
||||
}
|
||||
|
||||
const handleShortcutClick = (url: string) => {
|
||||
router.push(url)
|
||||
}
|
||||
|
||||
getAllApi()
|
||||
</script>
|
||||
|
@ -10,6 +10,7 @@ export type Project = {
|
||||
message: string
|
||||
personal: string
|
||||
time: Date | number | string
|
||||
color: string
|
||||
}
|
||||
|
||||
export type Notice = {
|
||||
@ -23,6 +24,7 @@ export type Shortcut = {
|
||||
name: string
|
||||
icon: string
|
||||
url: string
|
||||
color: string
|
||||
}
|
||||
|
||||
export type RadarData = {
|
||||
|
@ -312,8 +312,8 @@ const doSocialLogin = async (type: number) => {
|
||||
}
|
||||
}
|
||||
// 计算 redirectUri
|
||||
// tricky: type、redirect需要先encode一次,否则钉钉回调会丢失。
|
||||
// 配合 Login/SocialLogin.vue#getUrlValue() 使用
|
||||
// 注意: type、redirect 需要先 encode 一次,否则钉钉回调会丢失。
|
||||
// 配合 social-login.vue#getUrlValue() 使用
|
||||
const redirectUri =
|
||||
location.origin +
|
||||
'/social-login?' +
|
||||
|
@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<!-- TODO @芋艿:可优化,对标 vben 版本 -->
|
||||
<div class="flex">
|
||||
<el-card class="user w-1/3" shadow="hover">
|
||||
<template #header>
|
||||
@ -9,11 +10,6 @@
|
||||
<ProfileUser />
|
||||
</el-card>
|
||||
<el-card class="user ml-3 w-2/3" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ t('profile.info.title') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-tabs v-model="activeName" class="profile-tabs" style="height: 400px" tab-position="top">
|
||||
<el-tab-pane :label="t('profile.info.basicInfo')" name="basicInfo">
|
||||
|
@ -12,11 +12,13 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { uploadAvatar } from '@/api/system/user/profile'
|
||||
import { updateUserProfile } from '@/api/system/user/profile'
|
||||
import { CropperAvatar } from '@/components/Cropper'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
||||
import { UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
|
||||
|
||||
|
||||
// TODO @芋艿:合并到 ProfileUser 组件中,更简洁一点
|
||||
defineOptions({ name: 'UserAvatar' })
|
||||
|
||||
defineProps({
|
||||
@ -25,12 +27,18 @@ defineProps({
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
|
||||
const cropperRef = ref()
|
||||
const handelUpload = async ({ data }) => {
|
||||
const res = await uploadAvatar({ avatarFile: data })
|
||||
const { httpRequest } = useUpload()
|
||||
const avatar = ((await httpRequest({
|
||||
file: data,
|
||||
filename: 'avatar.png',
|
||||
} as UploadRequestOptions)) as unknown as { data: string }).data
|
||||
await updateUserProfile({ avatar })
|
||||
|
||||
// 关闭弹窗,并更新 userStore
|
||||
cropperRef.value.close()
|
||||
userStore.setUserAvatarAction(res.data)
|
||||
await userStore.setUserAvatarAction(avatar)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -462,6 +462,8 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
|
||||
(error) => {
|
||||
message.alert(`对话异常! ${error}`)
|
||||
stopStream()
|
||||
// 需要抛出异常,禁止重试
|
||||
throw error
|
||||
},
|
||||
() => {
|
||||
stopStream()
|
||||
|
@ -80,6 +80,8 @@ const submit = (data: AiMindMapGenerateReqVO) => {
|
||||
onError(err) {
|
||||
console.error('生成思维导图失败', err)
|
||||
stopStream()
|
||||
// 需要抛出异常,禁止重试
|
||||
throw error
|
||||
},
|
||||
ctrl: ctrl.value
|
||||
})
|
||||
|
@ -4,7 +4,7 @@
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
label-width="130px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="所属平台" prop="platform">
|
||||
@ -146,7 +146,10 @@ const formRules = reactive({
|
||||
platform: [{ required: true, message: '所属平台不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '模型类型不能为空', trigger: 'blur' }],
|
||||
sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
temperature: [{ required: true, message: '温度参数不能为空', trigger: 'blur' }],
|
||||
maxTokens: [{ required: true, message: '回复数 Token 数不能为空', trigger: 'blur' }],
|
||||
maxContexts: [{ required: true, message: '上下文数量不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const apiKeyList = ref([] as ApiKeyVO[]) // API 密钥列表
|
||||
|
@ -13,11 +13,58 @@
|
||||
测试
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 测试窗口 -->
|
||||
<el-drawer v-model="showTestDrawer" title="工作流测试" :modal="false">
|
||||
<fieldset>
|
||||
<legend class="ml-15px"><h3>运行参数配置</h3></legend>
|
||||
<div class="p-20px">
|
||||
<div
|
||||
class="flex justify-around mb-10px"
|
||||
v-for="(param, index) in params4Test"
|
||||
:key="index"
|
||||
>
|
||||
<el-select class="w-200px!" v-model="param.key" placeholder="参数名">
|
||||
<el-option
|
||||
v-for="(value, key) in paramsOfStartNode"
|
||||
:key="key"
|
||||
:label="value?.description || key"
|
||||
:value="key"
|
||||
:disabled="!!value?.disabled"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input class="w-200px!" v-model="param.value" placeholder="参数值" />
|
||||
<el-button type="danger" plain :icon="Delete" circle @click="removeParam(index)" />
|
||||
</div>
|
||||
<!-- TODO @lesan:是不是不用添加和删除参数,直接把必填和选填列出来,然后加上参数校验? -->
|
||||
<el-button type="primary" plain @click="addParam">添加参数</el-button>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="mt-20px bg-#f8f9fa">
|
||||
<legend class="ml-15px"><h3>运行结果</h3></legend>
|
||||
<div class="p-20px">
|
||||
<div v-if="loading"> <el-text type="primary">执行中...</el-text></div>
|
||||
<div v-else-if="error">
|
||||
<el-text type="danger">{{ error }}</el-text>
|
||||
</div>
|
||||
<pre v-else-if="testResult" class="result-content"
|
||||
>{{ JSON.stringify(testResult, null, 2) }}
|
||||
</pre>
|
||||
<div v-else> <el-text type="info">点击运行查看结果</el-text> </div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<el-button class="mt-20px w-100%" size="large" type="success" @click="goRun">
|
||||
运行流程
|
||||
</el-button>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Tinyflow from '@/components/Tinyflow/Tinyflow.vue'
|
||||
import * as WorkflowApi from '@/api/ai/workflow'
|
||||
// TODO @lesan:要不使用 ICon 哪个组件哈
|
||||
import { Delete } from '@element-plus/icons-vue'
|
||||
|
||||
defineProps<{
|
||||
provider: any
|
||||
@ -25,9 +72,149 @@ defineProps<{
|
||||
|
||||
const tinyflowRef = ref()
|
||||
const workflowData = inject('workflowData') as Ref
|
||||
const showTestDrawer = ref(false)
|
||||
const params4Test = ref([])
|
||||
const paramsOfStartNode = ref({})
|
||||
const testResult = ref(null)
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
|
||||
/** 展示工作流测试抽屉 */
|
||||
const testWorkflowModel = () => {
|
||||
// TODO @lesan 测试
|
||||
showTestDrawer.value = !showTestDrawer.value
|
||||
}
|
||||
|
||||
/** 运行流程 */
|
||||
const goRun = async () => {
|
||||
try {
|
||||
const val = tinyflowRef.value.getData()
|
||||
loading.value = true
|
||||
error.value = null
|
||||
testResult.value = null
|
||||
/// 查找start节点
|
||||
const startNode = getStartNode()
|
||||
|
||||
// 获取参数定义
|
||||
const parameters = startNode.data?.parameters || []
|
||||
const paramDefinitions = {}
|
||||
parameters.forEach((param) => {
|
||||
paramDefinitions[param.name] = param.dataType
|
||||
})
|
||||
|
||||
// 参数类型转换
|
||||
const convertedParams = {}
|
||||
for (const { key, value } of params4Test.value) {
|
||||
const paramKey = key.trim()
|
||||
if (!paramKey) continue
|
||||
|
||||
let dataType = paramDefinitions[paramKey]
|
||||
if (!dataType) {
|
||||
dataType = 'String'
|
||||
}
|
||||
|
||||
try {
|
||||
convertedParams[paramKey] = convertParamValue(value, dataType)
|
||||
} catch (e) {
|
||||
throw new Error(`参数 ${paramKey} 转换失败: ${e.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
graph: JSON.stringify(val),
|
||||
params: convertedParams
|
||||
}
|
||||
|
||||
const response = await WorkflowApi.testWorkflow(data)
|
||||
testResult.value = response
|
||||
} catch (err) {
|
||||
error.value = err.response?.data?.message || '运行失败,请检查参数和网络连接'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听测试抽屉的开启,获取开始节点参数列表 */
|
||||
watch(showTestDrawer, (value) => {
|
||||
if (!value) return
|
||||
|
||||
/// 查找start节点
|
||||
const startNode = getStartNode()
|
||||
|
||||
// 获取参数定义
|
||||
const parameters = startNode.data?.parameters || []
|
||||
const paramDefinitions = {}
|
||||
|
||||
// 加入参数选项方便用户添加非必须参数
|
||||
parameters.forEach((param) => {
|
||||
paramDefinitions[param.name] = param
|
||||
})
|
||||
|
||||
function mergeIfRequiredButNotSet(target) {
|
||||
let needPushList = []
|
||||
for (let key in paramDefinitions) {
|
||||
let param = paramDefinitions[key]
|
||||
|
||||
if (param.required) {
|
||||
let item = target.find((item) => item.key === key)
|
||||
|
||||
if (!item) {
|
||||
needPushList.push({ key: param.name, value: param.defaultValue || '' })
|
||||
}
|
||||
}
|
||||
}
|
||||
target.push(...needPushList)
|
||||
}
|
||||
// 自动装载需必填的参数
|
||||
mergeIfRequiredButNotSet(params4Test.value)
|
||||
|
||||
paramsOfStartNode.value = paramDefinitions
|
||||
})
|
||||
|
||||
/** 获取开始节点 */
|
||||
const getStartNode = () => {
|
||||
const val = tinyflowRef.value.getData()
|
||||
const startNode = val.nodes.find((node) => node.type === 'startNode')
|
||||
if (!startNode) {
|
||||
throw new Error('流程缺少开始节点')
|
||||
}
|
||||
return startNode
|
||||
}
|
||||
|
||||
/** 添加参数项 */
|
||||
const addParam = () => {
|
||||
params4Test.value.push({ key: '', value: '' })
|
||||
}
|
||||
|
||||
/** 删除参数项 */
|
||||
const removeParam = (index) => {
|
||||
params4Test.value.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 类型转换函数 */
|
||||
const convertParamValue = (value, dataType) => {
|
||||
if (value === '') return null // 空值处理
|
||||
|
||||
switch (dataType) {
|
||||
case 'String':
|
||||
return String(value)
|
||||
case 'Number':
|
||||
const num = Number(value)
|
||||
if (isNaN(num)) throw new Error('非数字格式')
|
||||
return num
|
||||
case 'Boolean':
|
||||
if (value.toLowerCase() === 'true') return true
|
||||
if (value.toLowerCase() === 'false') return false
|
||||
throw new Error('必须为 true/false')
|
||||
case 'Object':
|
||||
case 'Array':
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (e) {
|
||||
throw new Error(`JSON格式错误: ${e.message}`)
|
||||
}
|
||||
default:
|
||||
throw new Error(`不支持的类型: ${dataType}`)
|
||||
}
|
||||
}
|
||||
|
||||
/** 表单校验 */
|
||||
@ -47,3 +234,17 @@ defineExpose({
|
||||
validate
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.result-content {
|
||||
background: white;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
font-family: Monaco, Consolas, monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
|
@ -59,7 +59,7 @@
|
||||
<WorkflowDesign
|
||||
v-if="currentStep === 1"
|
||||
v-model="formData"
|
||||
:provider="provider"
|
||||
:provider="llmProvider"
|
||||
ref="workflowDesignRef"
|
||||
/>
|
||||
</div>
|
||||
@ -73,7 +73,8 @@ import { CommonStatusEnum } from '@/utils/constants'
|
||||
import * as WorkflowApi from '@/api/ai/workflow'
|
||||
import BasicInfo from './BasicInfo.vue'
|
||||
import WorkflowDesign from './WorkflowDesign.vue'
|
||||
import { ApiKeyApi } from '@/api/ai/model/apiKey'
|
||||
import { ModelApi } from '@/api/ai/model/model'
|
||||
import { AiModelTypeEnum } from '@/views/ai/utils/constants'
|
||||
|
||||
const router = useRouter()
|
||||
const { delView } = useTagsViewStore()
|
||||
@ -104,31 +105,35 @@ const formData: any = ref({
|
||||
graph: '',
|
||||
status: CommonStatusEnum.ENABLE
|
||||
})
|
||||
// TODO @lesan:待接入
|
||||
const provider = ref<any>()
|
||||
const llmProvider = ref<any>([])
|
||||
const workflowData = ref<any>({})
|
||||
provide('workflowData', workflowData)
|
||||
|
||||
/** 初始化数据 */
|
||||
const actionType = route.params.type as string
|
||||
const initData = async () => {
|
||||
// 编辑情况下,需要加载工作流配置
|
||||
if (actionType === 'update') {
|
||||
const workflowId = route.params.id as string
|
||||
formData.value = await WorkflowApi.getWorkflow(workflowId)
|
||||
workflowData.value = JSON.parse(formData.value.graph)
|
||||
}
|
||||
|
||||
const apiKeys = await ApiKeyApi.getApiKeySimpleList()
|
||||
provider.value = {
|
||||
// 加载模型列表
|
||||
const models = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT)
|
||||
llmProvider.value = {
|
||||
llm: () =>
|
||||
apiKeys.map(({ id, name }) => ({
|
||||
models.map(({ id, name }) => ({
|
||||
value: id,
|
||||
label: name
|
||||
})),
|
||||
knowledge: () => [],
|
||||
internal: () => []
|
||||
}
|
||||
// TODO @lesan:知识库(可以看下 knowledge)
|
||||
// TODO @lesan:搜索引擎(这个之前有个 pr 搞了,,,可能来接下)
|
||||
|
||||
// 设置当前步骤
|
||||
currentStep.value = 0
|
||||
}
|
||||
|
||||
@ -164,17 +169,17 @@ const handleSave = async () => {
|
||||
|
||||
// 更新表单数据
|
||||
const data = {
|
||||
...formData.value
|
||||
...formData.value,
|
||||
graph: JSON.stringify(workflowData.value)
|
||||
}
|
||||
|
||||
data.graph = JSON.stringify(workflowData.value)
|
||||
|
||||
if (actionType === 'update') {
|
||||
await WorkflowApi.updateWorkflow(data)
|
||||
} else {
|
||||
await WorkflowApi.createWorkflow(data)
|
||||
}
|
||||
|
||||
// 保存成功,提示并跳转到列表页
|
||||
message.success('保存成功')
|
||||
delView(unref(router.currentRoute))
|
||||
await router.push({ name: 'AiWorkflow' })
|
||||
} catch (error: any) {
|
||||
|
@ -57,9 +57,11 @@ const submit = (data: WriteVO) => {
|
||||
},
|
||||
ctrl: abortController.value,
|
||||
onClose: stopStream,
|
||||
onError: (...err) => {
|
||||
console.error('写作异常', ...err)
|
||||
onError: (error) => {
|
||||
console.error('写作异常', error)
|
||||
stopStream()
|
||||
// 需要抛出异常,禁止重试
|
||||
throw error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -449,7 +449,6 @@ const handleChangeState = async (row: any) => {
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const id = row.id
|
||||
debugger
|
||||
const statusState = state === 1 ? '停用' : '启用'
|
||||
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
|
||||
await message.confirm(content)
|
||||
|
@ -62,7 +62,11 @@
|
||||
</el-row>
|
||||
<!-- 操作 -->
|
||||
<template #footer>
|
||||
<el-button :disabled="tableList.length === 0" type="primary" @click="handleImportTable">
|
||||
<el-button
|
||||
:disabled="tableList.length === 0 || dbTableLoading"
|
||||
type="primary"
|
||||
@click="handleImportTable"
|
||||
>
|
||||
导入
|
||||
</el-button>
|
||||
<el-button @click="close">关闭</el-button>
|
||||
@ -139,13 +143,18 @@ const handleSelectionChange = (selection) => {
|
||||
|
||||
/** 导入按钮操作 */
|
||||
const handleImportTable = async () => {
|
||||
await CodegenApi.createCodegenList({
|
||||
dataSourceConfigId: queryParams.dataSourceConfigId,
|
||||
tableNames: tableList.value
|
||||
})
|
||||
message.success('导入成功')
|
||||
emit('success')
|
||||
close()
|
||||
dbTableLoading.value = true
|
||||
try {
|
||||
await CodegenApi.createCodegenList({
|
||||
dataSourceConfigId: queryParams.dataSourceConfigId,
|
||||
tableNames: tableList.value
|
||||
})
|
||||
message.success('导入成功')
|
||||
emit('success')
|
||||
close()
|
||||
} finally {
|
||||
dbTableLoading.value = false
|
||||
}
|
||||
}
|
||||
const emit = defineEmits(['success'])
|
||||
</script>
|
||||
|
@ -5,7 +5,7 @@
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
label-width="130px"
|
||||
>
|
||||
<el-form-item label="配置名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入配置名" />
|
||||
@ -83,6 +83,16 @@
|
||||
<el-form-item v-if="formData.storage === 20" label="accessSecret" prop="config.accessSecret">
|
||||
<el-input v-model="formData.config.accessSecret" placeholder="请输入 accessSecret" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.storage === 20"
|
||||
label="是否 Path Style"
|
||||
prop="config.enablePathStyleAccess"
|
||||
>
|
||||
<el-radio-group v-model="formData.config.enablePathStyleAccess">
|
||||
<el-radio key="true" :value="true">启用</el-radio>
|
||||
<el-radio key="false" :value="false">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- 通用 -->
|
||||
<el-form-item v-if="formData.storage === 20" label="自定义域名">
|
||||
<!-- 无需参数校验,所以去掉 prop -->
|
||||
@ -133,6 +143,9 @@ const formRules = reactive<FormRules>({
|
||||
bucket: [{ required: true, message: '存储 bucket 不能为空', trigger: 'blur' }],
|
||||
accessKey: [{ required: true, message: 'accessKey 不能为空', trigger: 'blur' }],
|
||||
accessSecret: [{ required: true, message: 'accessSecret 不能为空', trigger: 'blur' }],
|
||||
enablePathStyleAccess: [
|
||||
{ required: true, message: '是否 PathStyle 访问不能为空', trigger: 'change' }
|
||||
],
|
||||
domain: [{ required: true, message: '自定义域名不能为空', trigger: 'blur' }]
|
||||
} as FormRules
|
||||
})
|
||||
|
@ -132,7 +132,6 @@ defineExpose({ open, close: () => (dialogVisible.value = false) })
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success'])
|
||||
const submitForm = async () => {
|
||||
debugger
|
||||
await formRef.value.validate()
|
||||
formLoading.value = true
|
||||
try {
|
||||
|
@ -62,20 +62,13 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="模板名称" align="center" prop="name" />
|
||||
<el-table-column label="模板名称" align="center" prop="name" min-width="180" />
|
||||
<el-table-column label="是否使用" align="center" prop="used">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.used" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="使用时间"
|
||||
align="center"
|
||||
prop="usedTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180px"
|
||||
/>
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="备注" align="center" prop="remark" min-width="180" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
|
@ -16,7 +16,6 @@ import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
|
||||
import { getRefreshToken } from '@/utils/auth'
|
||||
import { useWebSocket } from '@vueuse/core'
|
||||
import { useMallKefuStore } from '@/store/modules/mall/kefu'
|
||||
import { jsonParse } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'KeFu' })
|
||||
|
||||
@ -66,7 +65,8 @@ watch(
|
||||
// 2.3 消息类型:KEFU_MESSAGE_ADMIN_READ
|
||||
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
|
||||
// 更新会话已读
|
||||
kefuStore.updateConversationStatus(jsonParse(jsonMessage.content))
|
||||
const message = JSON.parse(jsonMessage.content)
|
||||
kefuStore.updateConversationStatus(message.conversationId)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
@ -120,7 +120,7 @@
|
||||
v-if="scope.row.deliveryType === DeliveryTypeEnum.EXPRESS.type"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<span>买家:{{ scope.row.user.nickname }}</span>
|
||||
<span>买家:{{ scope.row.user?.nickname }}</span>
|
||||
<span>
|
||||
收货人:{{ scope.row.receiverName }} {{ scope.row.receiverMobile }}
|
||||
{{ scope.row.receiverAreaName }} {{ scope.row.receiverDetailAddress }}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import type { UploadProps, UploadRawFile } from 'element-plus'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import { getRefreshToken } from '@/utils/auth'
|
||||
import { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'
|
||||
|
||||
const HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 请求头
|
||||
const HEADERS = { Authorization: 'Bearer ' + getRefreshToken() } // 请求头(解决 el-upload 上传过程中,无法刷新令牌的问题)
|
||||
const UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传地址
|
||||
|
||||
interface UploadData {
|
||||
|
@ -6,7 +6,11 @@
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { getAccessToken, getRefreshToken } from '@/utils/auth'
|
||||
|
||||
defineOptions({ name: 'GoView' })
|
||||
|
||||
const src = ref(import.meta.env.VITE_GOVIEW_URL)
|
||||
const src = ref(
|
||||
`${import.meta.env.VITE_GOVIEW_URL}?accessToken=${getAccessToken()}&refreshToken=${getRefreshToken()}`
|
||||
)
|
||||
</script>
|
||||
|
15
src/views/report/jmreport/bi.vue
Normal file
15
src/views/report/jmreport/bi.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<doc-alert title="大屏设计器" url="https://doc.iocoder.cn/screen/" />
|
||||
|
||||
<ContentWrap :bodyStyle="{ padding: '0px' }" class="!mb-0">
|
||||
<IFrame :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { getRefreshToken } from '@/utils/auth'
|
||||
|
||||
defineOptions({ name: 'JimuBI' })
|
||||
|
||||
// 使用 getRefreshToken() 方法,而不使用 getAccessToken() 方法的原因:积木报表无法方便的刷新访问令牌
|
||||
const src = ref(import.meta.env.VITE_BASE_URL + '/drag/list?token=' + getRefreshToken())
|
||||
</script>
|
@ -147,6 +147,7 @@ const formData = ref({
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '菜单类型不能为空', trigger: 'blur' }],
|
||||
sort: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
|
||||
path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||
|
@ -20,7 +20,7 @@
|
||||
</el-form>
|
||||
<el-form-item
|
||||
v-if="formData.dataScope === SystemDataScopeEnum.DEPT_CUSTOM"
|
||||
label="权限范围"
|
||||
label="部门范围"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-card class="w-full h-400px !overflow-y-scroll" shadow="never">
|
||||
|
@ -41,7 +41,7 @@
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="用户状态"
|
||||
placeholder="请选择用户状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
@ -345,7 +345,7 @@ const handleResetPwd = async (row: UserApi.UserVO) => {
|
||||
)
|
||||
const password = result.value
|
||||
// 发起重置
|
||||
await UserApi.resetUserPwd(row.id, password)
|
||||
await UserApi.resetUserPassword(row.id, password)
|
||||
message.success('修改成功,新密码是:' + password)
|
||||
} catch {}
|
||||
}
|
||||
|
@ -37,7 +37,6 @@ ${selector}:before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: var(--el-border-color);
|
||||
z-index: 3;
|
||||
|
Loading…
x
Reference in New Issue
Block a user