修改Saas登录方式
parent
132d024848
commit
dc2017266d
|
|
@ -10,6 +10,8 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redis 配置类
|
* Redis 配置类
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import com.yanzhu.framework.common.enums.UserTypeEnum;
|
||||||
import com.yanzhu.framework.common.pojo.CommonResult;
|
import com.yanzhu.framework.common.pojo.CommonResult;
|
||||||
import com.yanzhu.framework.security.config.SecurityProperties;
|
import com.yanzhu.framework.security.config.SecurityProperties;
|
||||||
import com.yanzhu.framework.security.core.util.SecurityFrameworkUtils;
|
import com.yanzhu.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
|
import com.yanzhu.framework.tenant.core.aop.TenantIgnore;
|
||||||
|
import com.yanzhu.framework.tenant.core.context.TenantContextHolder;
|
||||||
import com.yanzhu.module.system.controller.admin.auth.vo.*;
|
import com.yanzhu.module.system.controller.admin.auth.vo.*;
|
||||||
import com.yanzhu.module.system.convert.auth.AuthConvert;
|
import com.yanzhu.module.system.convert.auth.AuthConvert;
|
||||||
import com.yanzhu.module.system.dal.dataobject.permission.MenuDO;
|
import com.yanzhu.module.system.dal.dataobject.permission.MenuDO;
|
||||||
|
|
@ -42,6 +44,7 @@ import static com.yanzhu.framework.security.core.util.SecurityFrameworkUtils.get
|
||||||
@Tag(name = "管理后台 - 认证")
|
@Tag(name = "管理后台 - 认证")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/system/auth")
|
@RequestMapping("/system/auth")
|
||||||
|
@TenantIgnore
|
||||||
@Validated
|
@Validated
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
@ -66,6 +69,11 @@ public class AuthController {
|
||||||
@PermitAll
|
@PermitAll
|
||||||
@Operation(summary = "使用账号密码登录")
|
@Operation(summary = "使用账号密码登录")
|
||||||
public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
|
public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
|
||||||
|
// 先根据用户名查询用户,获取租户ID
|
||||||
|
AdminUserDO user = userService.getUserByUsername(reqVO.getUsername());
|
||||||
|
if (user != null && user.getTenantId() != null) {
|
||||||
|
TenantContextHolder.setTenantId(user.getTenantId());
|
||||||
|
}
|
||||||
return success(authService.login(reqVO));
|
return success(authService.login(reqVO));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,9 +135,12 @@ public class AuthController {
|
||||||
@PostMapping("/sms-login")
|
@PostMapping("/sms-login")
|
||||||
@PermitAll
|
@PermitAll
|
||||||
@Operation(summary = "使用短信验证码登录")
|
@Operation(summary = "使用短信验证码登录")
|
||||||
// 可按需开启限流:https://github.com/YunaiV/ruoyi-vue-pro/issues/851
|
|
||||||
// @RateLimiter(time = 60, count = 6, keyResolver = ExpressionRateLimiterKeyResolver.class, keyArg = "#reqVO.mobile")
|
|
||||||
public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
|
public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
|
||||||
|
// 先根据手机号查询用户,获取租户ID
|
||||||
|
AdminUserDO user = userService.getUserByMobile(reqVO.getMobile());
|
||||||
|
if (user != null && user.getTenantId() != null) {
|
||||||
|
TenantContextHolder.setTenantId(user.getTenantId());
|
||||||
|
}
|
||||||
return success(authService.smsLogin(reqVO));
|
return success(authService.smsLogin(reqVO));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,6 +148,11 @@ public class AuthController {
|
||||||
@PermitAll
|
@PermitAll
|
||||||
@Operation(summary = "发送手机验证码")
|
@Operation(summary = "发送手机验证码")
|
||||||
public CommonResult<Boolean> sendLoginSmsCode(@RequestBody @Valid AuthSmsSendReqVO reqVO) {
|
public CommonResult<Boolean> sendLoginSmsCode(@RequestBody @Valid AuthSmsSendReqVO reqVO) {
|
||||||
|
// 先根据手机号查询用户,获取租户ID
|
||||||
|
AdminUserDO user = userService.getUserByMobile(reqVO.getMobile());
|
||||||
|
if (user != null && user.getTenantId() != null) {
|
||||||
|
TenantContextHolder.setTenantId(user.getTenantId());
|
||||||
|
}
|
||||||
authService.sendSmsCode(reqVO);
|
authService.sendSmsCode(reqVO);
|
||||||
return success(true);
|
return success(true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ import java.time.LocalDateTime;
|
||||||
@Builder
|
@Builder
|
||||||
public class AuthLoginRespVO {
|
public class AuthLoginRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "租户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
|
private Long tenantId;
|
||||||
|
|
||||||
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import com.yanzhu.framework.common.util.object.BeanUtils;
|
||||||
import com.yanzhu.framework.common.util.servlet.ServletUtils;
|
import com.yanzhu.framework.common.util.servlet.ServletUtils;
|
||||||
import com.yanzhu.framework.common.util.validation.ValidationUtils;
|
import com.yanzhu.framework.common.util.validation.ValidationUtils;
|
||||||
import com.yanzhu.framework.datapermission.core.annotation.DataPermission;
|
import com.yanzhu.framework.datapermission.core.annotation.DataPermission;
|
||||||
|
import com.yanzhu.framework.tenant.core.context.TenantContextHolder;
|
||||||
import com.yanzhu.module.system.api.logger.dto.LoginLogCreateReqDTO;
|
import com.yanzhu.module.system.api.logger.dto.LoginLogCreateReqDTO;
|
||||||
import com.yanzhu.module.system.api.sms.SmsCodeApi;
|
import com.yanzhu.module.system.api.sms.SmsCodeApi;
|
||||||
import com.yanzhu.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
import com.yanzhu.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
||||||
|
|
@ -216,7 +217,10 @@ public class AdminAuthServiceImpl implements AdminAuthService {
|
||||||
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(),
|
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(),
|
||||||
OAuth2ClientConstants.CLIENT_ID_DEFAULT, null);
|
OAuth2ClientConstants.CLIENT_ID_DEFAULT, null);
|
||||||
// 构建返回结果
|
// 构建返回结果
|
||||||
return BeanUtils.toBean(accessTokenDO, AuthLoginRespVO.class);
|
AuthLoginRespVO respVO = BeanUtils.toBean(accessTokenDO, AuthLoginRespVO.class);
|
||||||
|
// 设置租户编号
|
||||||
|
respVO.setTenantId(TenantContextHolder.getTenantId());
|
||||||
|
return respVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,13 @@ VITE_APP_DOCALERT_ENABLE=true
|
||||||
VITE_APP_BAIDU_CODE = a1ff8825baa73c3a78eb96aa40325abc
|
VITE_APP_BAIDU_CODE = a1ff8825baa73c3a78eb96aa40325abc
|
||||||
|
|
||||||
# 默认账户密码
|
# 默认账户密码
|
||||||
VITE_APP_DEFAULT_LOGIN_TENANT = 研筑科技
|
#VITE_APP_DEFAULT_LOGIN_TENANT = 研筑科技
|
||||||
VITE_APP_DEFAULT_LOGIN_USERNAME = admin
|
#VITE_APP_DEFAULT_LOGIN_USERNAME = admin
|
||||||
VITE_APP_DEFAULT_LOGIN_PASSWORD = admin123
|
#VITE_APP_DEFAULT_LOGIN_PASSWORD = admin123
|
||||||
|
|
||||||
|
VITE_APP_DEFAULT_LOGIN_TENANT =
|
||||||
|
VITE_APP_DEFAULT_LOGIN_USERNAME =
|
||||||
|
VITE_APP_DEFAULT_LOGIN_PASSWORD =
|
||||||
|
|
||||||
# API 加解密
|
# API 加解密
|
||||||
VITE_APP_API_ENCRYPT_ENABLE = true
|
VITE_APP_API_ENCRYPT_ENABLE = true
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,14 @@ VITE_APP_CAPTCHA_ENABLE=true
|
||||||
VITE_APP_TENANT_ENABLE=true
|
VITE_APP_TENANT_ENABLE=true
|
||||||
|
|
||||||
# 默认账户密码
|
# 默认账户密码
|
||||||
VITE_APP_DEFAULT_LOGIN_TENANT = 研筑科技
|
# 默认账户密码
|
||||||
VITE_APP_DEFAULT_LOGIN_USERNAME = admin
|
#VITE_APP_DEFAULT_LOGIN_TENANT = 研筑科技
|
||||||
VITE_APP_DEFAULT_LOGIN_PASSWORD = admin123
|
#VITE_APP_DEFAULT_LOGIN_USERNAME = admin
|
||||||
|
#VITE_APP_DEFAULT_LOGIN_PASSWORD = admin123
|
||||||
|
|
||||||
|
VITE_APP_DEFAULT_LOGIN_TENANT =
|
||||||
|
VITE_APP_DEFAULT_LOGIN_USERNAME =
|
||||||
|
VITE_APP_DEFAULT_LOGIN_PASSWORD =
|
||||||
|
|
||||||
# GoView域名
|
# GoView域名
|
||||||
VITE_GOVIEW_URL='http://127.0.0.1:3000'
|
VITE_GOVIEW_URL='http://127.0.0.1:3000'
|
||||||
|
|
|
||||||
|
|
@ -35,3 +35,6 @@
|
||||||
border-left-color: var(--el-color-primary);
|
border-left-color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.hidden{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ export const formatToken = (token: string): string => {
|
||||||
// ========== 账号相关 ==========
|
// ========== 账号相关 ==========
|
||||||
|
|
||||||
export type LoginFormType = {
|
export type LoginFormType = {
|
||||||
tenantName: string
|
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
rememberMe: boolean
|
rememberMe: boolean
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,5 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineOptions({ name: 'Error403' })
|
defineOptions({ name: 'Error403' })
|
||||||
|
|
||||||
const { push } = useRouter()
|
const { push } = useRouter()
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -4,4 +4,4 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineOptions({ name: 'Error404' })
|
defineOptions({ name: 'Error404' })
|
||||||
const { push } = useRouter()
|
const { push } = useRouter()
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -4,4 +4,4 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineOptions({ name: 'Error500' })
|
defineOptions({ name: 'Error500' })
|
||||||
const { push } = useRouter()
|
const { push } = useRouter()
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -55,7 +55,6 @@
|
||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-row class="mt-8px" :gutter="8" justify="space-between">
|
<el-row class="mt-8px" :gutter="8" justify="space-between">
|
||||||
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-8px">
|
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-8px">
|
||||||
<el-card shadow="never">
|
<el-card shadow="never">
|
||||||
|
|
@ -107,7 +106,6 @@
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card shadow="never" class="mt-8px">
|
<el-card shadow="never" class="mt-8px">
|
||||||
<el-skeleton :loading="loading" animated>
|
<el-skeleton :loading="loading" animated>
|
||||||
<el-row :gutter="20" justify="space-between">
|
<el-row :gutter="20" justify="space-between">
|
||||||
|
|
@ -184,15 +182,12 @@
|
||||||
import { set } from 'lodash-es'
|
import { set } from 'lodash-es'
|
||||||
import { EChartsOption } from 'echarts'
|
import { EChartsOption } from 'echarts'
|
||||||
import { formatTime } from '@/utils'
|
import { formatTime } from '@/utils'
|
||||||
|
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
// import { useWatermark } from '@/hooks/web/useWatermark'
|
// import { useWatermark } from '@/hooks/web/useWatermark'
|
||||||
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
|
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
|
||||||
import { pieOptions, barOptions } from './echarts-data'
|
import { pieOptions, barOptions } from './echarts-data'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
defineOptions({ name: 'Index' })
|
defineOptions({ name: 'Index' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
@ -207,7 +202,6 @@ let totalSate = reactive<WorkplaceTotal>({
|
||||||
access: 0,
|
access: 0,
|
||||||
todo: 0
|
todo: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
const getCount = async () => {
|
const getCount = async () => {
|
||||||
const data = {
|
const data = {
|
||||||
project: 40,
|
project: 40,
|
||||||
|
|
@ -216,7 +210,6 @@ const getCount = async () => {
|
||||||
}
|
}
|
||||||
totalSate = Object.assign(totalSate, data)
|
totalSate = Object.assign(totalSate, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取项目数
|
// 获取项目数
|
||||||
let projects = reactive<Project[]>([])
|
let projects = reactive<Project[]>([])
|
||||||
const getProject = async () => {
|
const getProject = async () => {
|
||||||
|
|
@ -272,7 +265,6 @@ const getProject = async () => {
|
||||||
]
|
]
|
||||||
projects = Object.assign(projects, data)
|
projects = Object.assign(projects, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取通知公告
|
// 获取通知公告
|
||||||
let notice = reactive<Notice[]>([])
|
let notice = reactive<Notice[]>([])
|
||||||
const getNotice = async () => {
|
const getNotice = async () => {
|
||||||
|
|
@ -304,10 +296,8 @@ const getNotice = async () => {
|
||||||
]
|
]
|
||||||
notice = Object.assign(notice, data)
|
notice = Object.assign(notice, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取快捷入口
|
// 获取快捷入口
|
||||||
let shortcut = reactive<Shortcut[]>([])
|
let shortcut = reactive<Shortcut[]>([])
|
||||||
|
|
||||||
const getShortcut = async () => {
|
const getShortcut = async () => {
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
|
|
@ -349,7 +339,6 @@ const getShortcut = async () => {
|
||||||
]
|
]
|
||||||
shortcut = Object.assign(shortcut, data)
|
shortcut = Object.assign(shortcut, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户来源
|
// 用户来源
|
||||||
const getUserAccessSource = async () => {
|
const getUserAccessSource = async () => {
|
||||||
const data = [
|
const data = [
|
||||||
|
|
@ -372,7 +361,6 @@ const getUserAccessSource = async () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
|
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
|
||||||
|
|
||||||
// 周活跃量
|
// 周活跃量
|
||||||
const getWeeklyUserActivity = async () => {
|
const getWeeklyUserActivity = async () => {
|
||||||
const data = [
|
const data = [
|
||||||
|
|
@ -397,7 +385,6 @@ const getWeeklyUserActivity = async () => {
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllApi = async () => {
|
const getAllApi = async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
getCount(),
|
getCount(),
|
||||||
|
|
@ -409,14 +396,11 @@ const getAllApi = async () => {
|
||||||
])
|
])
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleProjectClick = (message: string) => {
|
const handleProjectClick = (message: string) => {
|
||||||
window.open(`https://${message}`, '_blank')
|
window.open(`https://${message}`, '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleShortcutClick = (url: string) => {
|
const handleShortcutClick = (url: string) => {
|
||||||
router.push(url)
|
router.push(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllApi()
|
getAllApi()
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -28,7 +28,6 @@
|
||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
||||||
<el-card class="mb-20px" shadow="hover">
|
<el-card class="mb-20px" shadow="hover">
|
||||||
<el-skeleton :loading="loading" :rows="2" animated>
|
<el-skeleton :loading="loading" :rows="2" animated>
|
||||||
|
|
@ -57,7 +56,6 @@
|
||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
||||||
<el-card class="mb-20px" shadow="hover">
|
<el-card class="mb-20px" shadow="hover">
|
||||||
<el-skeleton :loading="loading" :rows="2" animated>
|
<el-skeleton :loading="loading" :rows="2" animated>
|
||||||
|
|
@ -86,7 +84,6 @@
|
||||||
</el-skeleton>
|
</el-skeleton>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
|
||||||
<el-card class="mb-20px" shadow="hover">
|
<el-card class="mb-20px" shadow="hover">
|
||||||
<el-skeleton :loading="loading" :rows="2" animated>
|
<el-skeleton :loading="loading" :rows="2" animated>
|
||||||
|
|
@ -143,26 +140,21 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { set } from 'lodash-es'
|
import { set } from 'lodash-es'
|
||||||
import { EChartsOption } from 'echarts'
|
import { EChartsOption } from 'echarts'
|
||||||
|
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import type { AnalysisTotalTypes } from './types'
|
import type { AnalysisTotalTypes } from './types'
|
||||||
import { barOptions, lineOptions, pieOptions } from './echarts-data'
|
import { barOptions, lineOptions, pieOptions } from './echarts-data'
|
||||||
|
|
||||||
defineOptions({ name: 'Home2' })
|
defineOptions({ name: 'Home2' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
const prefixCls = getPrefixCls('panel')
|
const prefixCls = getPrefixCls('panel')
|
||||||
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
|
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
|
||||||
|
|
||||||
let totalState = reactive<AnalysisTotalTypes>({
|
let totalState = reactive<AnalysisTotalTypes>({
|
||||||
users: 0,
|
users: 0,
|
||||||
messages: 0,
|
messages: 0,
|
||||||
moneys: 0,
|
moneys: 0,
|
||||||
shoppings: 0
|
shoppings: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
const getCount = async () => {
|
const getCount = async () => {
|
||||||
const data = {
|
const data = {
|
||||||
users: 102400,
|
users: 102400,
|
||||||
|
|
@ -172,7 +164,6 @@ const getCount = async () => {
|
||||||
}
|
}
|
||||||
totalState = Object.assign(totalState, data)
|
totalState = Object.assign(totalState, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户来源
|
// 用户来源
|
||||||
const getUserAccessSource = async () => {
|
const getUserAccessSource = async () => {
|
||||||
const data = [
|
const data = [
|
||||||
|
|
@ -190,7 +181,6 @@ const getUserAccessSource = async () => {
|
||||||
set(pieOptionsData, 'series.data', data)
|
set(pieOptionsData, 'series.data', data)
|
||||||
}
|
}
|
||||||
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
|
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
|
||||||
|
|
||||||
// 周活跃量
|
// 周活跃量
|
||||||
const getWeeklyUserActivity = async () => {
|
const getWeeklyUserActivity = async () => {
|
||||||
const data = [
|
const data = [
|
||||||
|
|
@ -215,9 +205,7 @@ const getWeeklyUserActivity = async () => {
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineOptionsData = reactive<EChartsOption>(lineOptions) as EChartsOption
|
const lineOptionsData = reactive<EChartsOption>(lineOptions) as EChartsOption
|
||||||
|
|
||||||
// 每月销售总额
|
// 每月销售总额
|
||||||
const getMonthlySales = async () => {
|
const getMonthlySales = async () => {
|
||||||
const data = [
|
const data = [
|
||||||
|
|
@ -259,61 +247,48 @@ const getMonthlySales = async () => {
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAllApi = async () => {
|
const getAllApi = async () => {
|
||||||
await Promise.all([getCount(), getUserAccessSource(), getWeeklyUserActivity(), getMonthlySales()])
|
await Promise.all([getCount(), getUserAccessSource(), getWeeklyUserActivity(), getMonthlySales()])
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllApi()
|
getAllApi()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$prefix-cls: #{$namespace}-panel;
|
$prefix-cls: #{$namespace}-panel;
|
||||||
|
|
||||||
.#{$prefix-cls} {
|
.#{$prefix-cls} {
|
||||||
&__item {
|
&__item {
|
||||||
&--peoples {
|
&--peoples {
|
||||||
color: #40c9c6;
|
color: #40c9c6;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--message {
|
&--message {
|
||||||
color: #36a3f7;
|
color: #36a3f7;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--money {
|
&--money {
|
||||||
color: #f4516c;
|
color: #f4516c;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--shopping {
|
&--shopping {
|
||||||
color: #34bfa3;
|
color: #34bfa3;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
:deep(.#{$namespace}-icon) {
|
:deep(.#{$namespace}-icon) {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$prefix-cls}__item--icon {
|
.#{$prefix-cls}__item--icon {
|
||||||
transition: all 0.38s ease-out;
|
transition: all 0.38s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$prefix-cls}__item--peoples {
|
.#{$prefix-cls}__item--peoples {
|
||||||
background: #40c9c6;
|
background: #40c9c6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$prefix-cls}__item--message {
|
.#{$prefix-cls}__item--message {
|
||||||
background: #36a3f7;
|
background: #36a3f7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$prefix-cls}__item--money {
|
.#{$prefix-cls}__item--money {
|
||||||
background: #f4516c;
|
background: #f4516c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$prefix-cls}__item--shopping {
|
.#{$prefix-cls}__item--shopping {
|
||||||
background: #34bfa3;
|
background: #34bfa3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -69,28 +69,21 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { underlineToHump } from '@/utils'
|
import { underlineToHump } from '@/utils'
|
||||||
|
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||||
|
|
||||||
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue, ForgetPasswordForm } from './components'
|
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue, ForgetPasswordForm } from './components'
|
||||||
|
|
||||||
defineOptions({ name: 'Login' })
|
defineOptions({ name: 'Login' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
const prefixCls = getPrefixCls('login')
|
const prefixCls = getPrefixCls('login')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$prefix-cls: #{$namespace}-login;
|
$prefix-cls: #{$namespace}-login;
|
||||||
|
|
||||||
.#{$prefix-cls} {
|
.#{$prefix-cls} {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
&__left {
|
&__left {
|
||||||
&::before {
|
&::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -107,15 +100,13 @@ $prefix-cls: #{$namespace}-login;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.dark .login-form {
|
.dark .login-form {
|
||||||
.el-divider__text {
|
.el-divider__text {
|
||||||
background-color: var(--login-bg-color);
|
background-color: var(--login-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-card {
|
.el-card {
|
||||||
background-color: var(--login-bg-color);
|
background-color: var(--login-bg-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -148,17 +148,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { underlineToHump } from '@/utils'
|
import { underlineToHump } from '@/utils'
|
||||||
|
|
||||||
import { ElLoading } from 'element-plus'
|
import { ElLoading } from 'element-plus'
|
||||||
|
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { useIcon } from '@/hooks/web/useIcon'
|
import { useIcon } from '@/hooks/web/useIcon'
|
||||||
import { usePermissionStore } from '@/store/modules/permission'
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
|
|
||||||
import * as LoginApi from '@/api/login'
|
import * as LoginApi from '@/api/login'
|
||||||
import * as authUtil from '@/utils/auth'
|
import * as authUtil from '@/utils/auth'
|
||||||
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||||
|
|
@ -166,12 +162,9 @@ import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||||
import { LoginStateEnum, useFormValid, useLoginState } from './components/useLogin'
|
import { LoginStateEnum, useFormValid, useLoginState } from './components/useLogin'
|
||||||
import LoginFormTitle from './components/LoginFormTitle.vue'
|
import LoginFormTitle from './components/LoginFormTitle.vue'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
|
|
||||||
defineOptions({ name: 'SocialLogin' })
|
defineOptions({ name: 'SocialLogin' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
const prefixCls = getPrefixCls('login')
|
const prefixCls = getPrefixCls('login')
|
||||||
|
|
@ -186,9 +179,7 @@ const permissionStore = usePermissionStore()
|
||||||
const loginLoading = ref(false)
|
const loginLoading = ref(false)
|
||||||
const verify = ref()
|
const verify = ref()
|
||||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
|
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
|
||||||
|
|
||||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
||||||
|
|
||||||
const LoginRules = {
|
const LoginRules = {
|
||||||
tenantName: [required],
|
tenantName: [required],
|
||||||
username: [required],
|
username: [required],
|
||||||
|
|
@ -206,7 +197,6 @@ const loginData = reactive({
|
||||||
rememberMe: false
|
rememberMe: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
const getCode = async () => {
|
const getCode = async () => {
|
||||||
// 情况一,未开启:则直接登录
|
// 情况一,未开启:则直接登录
|
||||||
|
|
@ -239,13 +229,11 @@ const getCookie = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const loading = ref() // ElLoading.service 返回的实例
|
const loading = ref() // ElLoading.service 返回的实例
|
||||||
|
|
||||||
// tricky: 配合LoginForm.vue中redirectUri需要对参数进行encode,需要在回调后进行decode
|
// tricky: 配合LoginForm.vue中redirectUri需要对参数进行encode,需要在回调后进行decode
|
||||||
function getUrlValue(key: string): string {
|
function getUrlValue(key: string): string {
|
||||||
const url = new URL(decodeURIComponent(location.href))
|
const url = new URL(decodeURIComponent(location.href))
|
||||||
return url.searchParams.get(key) ?? ''
|
return url.searchParams.get(key) ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试登录: 当账号已经绑定,socialLogin会直接获得token
|
// 尝试登录: 当账号已经绑定,socialLogin会直接获得token
|
||||||
const tryLogin = async () => {
|
const tryLogin = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -253,14 +241,11 @@ const tryLogin = async () => {
|
||||||
const redirect = getUrlValue('redirect')
|
const redirect = getUrlValue('redirect')
|
||||||
const code = route?.query?.code as string
|
const code = route?.query?.code as string
|
||||||
const state = route?.query?.state as string
|
const state = route?.query?.state as string
|
||||||
|
|
||||||
const res = await LoginApi.socialLogin(type, code, state)
|
const res = await LoginApi.socialLogin(type, code, state)
|
||||||
authUtil.setToken(res)
|
authUtil.setToken(res)
|
||||||
|
|
||||||
router.push({ path: redirect || '/' })
|
router.push({ path: redirect || '/' })
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
const handleLogin = async (params) => {
|
const handleLogin = async (params) => {
|
||||||
loginLoading.value = true
|
loginLoading.value = true
|
||||||
|
|
@ -270,13 +255,10 @@ const handleLogin = async (params) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let redirect = getUrlValue('redirect')
|
let redirect = getUrlValue('redirect')
|
||||||
|
|
||||||
const type = getUrlValue('type')
|
const type = getUrlValue('type')
|
||||||
const code = route?.query?.code as string
|
const code = route?.query?.code as string
|
||||||
const state = route?.query?.state as string
|
const state = route?.query?.state as string
|
||||||
|
|
||||||
const loginDataLoginForm = { ...loginData.loginForm }
|
const loginDataLoginForm = { ...loginData.loginForm }
|
||||||
const res = await LoginApi.login({
|
const res = await LoginApi.login({
|
||||||
// 账号密码登录
|
// 账号密码登录
|
||||||
|
|
@ -316,19 +298,15 @@ const handleLogin = async (params) => {
|
||||||
loading.value.close()
|
loading.value.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getCookie()
|
getCookie()
|
||||||
tryLogin()
|
tryLogin()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$prefix-cls: #{$namespace}-login;
|
$prefix-cls: #{$namespace}-login;
|
||||||
|
|
||||||
.#{$prefix-cls} {
|
.#{$prefix-cls} {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
&__left {
|
&__left {
|
||||||
&::before {
|
&::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -344,4 +322,4 @@ $prefix-cls: #{$namespace}-login;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -120,9 +120,7 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||||
|
|
||||||
import { useIcon } from '@/hooks/web/useIcon'
|
import { useIcon } from '@/hooks/web/useIcon'
|
||||||
|
|
||||||
import { sendSmsCode, smsResetPassword } from '@/api/login'
|
import { sendSmsCode, smsResetPassword } from '@/api/login'
|
||||||
import LoginFormTitle from './LoginFormTitle.vue'
|
import LoginFormTitle from './LoginFormTitle.vue'
|
||||||
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
||||||
|
|
@ -131,7 +129,6 @@ import * as authUtil from '@/utils/auth'
|
||||||
import * as LoginApi from '@/api/login'
|
import * as LoginApi from '@/api/login'
|
||||||
defineOptions({ name: 'ForgetPasswordForm' })
|
defineOptions({ name: 'ForgetPasswordForm' })
|
||||||
const verify = ref()
|
const verify = ref()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const { currentRoute } = useRouter()
|
const { currentRoute } = useRouter()
|
||||||
|
|
@ -144,7 +141,6 @@ const { validForm } = useFormValid(formSmsResetPassword)
|
||||||
const { handleBackLogin, getLoginState, setLoginState } = useLoginState()
|
const { handleBackLogin, getLoginState, setLoginState } = useLoginState()
|
||||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.RESET_PASSWORD)
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.RESET_PASSWORD)
|
||||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
|
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
|
||||||
|
|
||||||
const validatePass2 = (_rule, value, callback) => {
|
const validatePass2 = (_rule, value, callback) => {
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
callback(new Error('请再次输入密码'))
|
callback(new Error('请再次输入密码'))
|
||||||
|
|
@ -154,7 +150,6 @@ const validatePass2 = (_rule, value, callback) => {
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
tenantName: [{ required: true, min: 2, max: 20, trigger: 'blur', message: '长度为4到16位' }],
|
tenantName: [{ required: true, min: 2, max: 20, trigger: 'blur', message: '长度为4到16位' }],
|
||||||
mobile: [{ required: true, min: 11, max: 11, trigger: 'blur', message: '手机号长度为11位' }],
|
mobile: [{ required: true, min: 11, max: 11, trigger: 'blur', message: '手机号长度为11位' }],
|
||||||
|
|
@ -171,7 +166,6 @@ const rules = {
|
||||||
check_password: [{ required: true, validator: validatePass2, trigger: 'blur' }],
|
check_password: [{ required: true, validator: validatePass2, trigger: 'blur' }],
|
||||||
code: [required]
|
code: [required]
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetPasswordData = reactive({
|
const resetPasswordData = reactive({
|
||||||
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
||||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
||||||
|
|
@ -182,7 +176,6 @@ const resetPasswordData = reactive({
|
||||||
mobile: '',
|
mobile: '',
|
||||||
code: ''
|
code: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const smsVO = reactive({
|
const smsVO = reactive({
|
||||||
tenantName: '',
|
tenantName: '',
|
||||||
mobile: '',
|
mobile: '',
|
||||||
|
|
@ -191,7 +184,6 @@ const smsVO = reactive({
|
||||||
})
|
})
|
||||||
const mobileCodeTimer = ref(0)
|
const mobileCodeTimer = ref(0)
|
||||||
const redirect = ref<string>('')
|
const redirect = ref<string>('')
|
||||||
|
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
const getCode = async () => {
|
const getCode = async () => {
|
||||||
// 情况一,未开启:则直接发送验证码
|
// 情况一,未开启:则直接发送验证码
|
||||||
|
|
@ -203,7 +195,6 @@ const getCode = async () => {
|
||||||
verify.value.show()
|
verify.value.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSmsCode = async (params) => {
|
const getSmsCode = async (params) => {
|
||||||
if (resetPasswordData.tenantEnable === 'true') {
|
if (resetPasswordData.tenantEnable === 'true') {
|
||||||
await getTenantId()
|
await getTenantId()
|
||||||
|
|
@ -231,7 +222,6 @@ watch(
|
||||||
immediate: true
|
immediate: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const getTenantId = async () => {
|
const getTenantId = async () => {
|
||||||
if (resetPasswordData.tenantEnable === 'true') {
|
if (resetPasswordData.tenantEnable === 'true') {
|
||||||
const res = await LoginApi.getTenantIdByName(resetPasswordData.tenantName)
|
const res = await LoginApi.getTenantIdByName(resetPasswordData.tenantName)
|
||||||
|
|
@ -242,7 +232,6 @@ const getTenantId = async () => {
|
||||||
authUtil.setTenantId(res)
|
authUtil.setTenantId(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置密码
|
// 重置密码
|
||||||
const resetPassword = async () => {
|
const resetPassword = async () => {
|
||||||
const data = await validForm()
|
const data = await validForm()
|
||||||
|
|
@ -264,15 +253,13 @@ const resetPassword = async () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep(.anticon) {
|
:deep(.anticon) {
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--el-color-primary) !important;
|
color: var(--el-color-primary) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.smsbtn {
|
.smsbtn {
|
||||||
margin-top: 33px;
|
margin-top: 33px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -15,17 +15,6 @@
|
||||||
<LoginFormTitle class="w-full" />
|
<LoginFormTitle class="w-full" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" class="px-10px">
|
|
||||||
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
|
||||||
<el-input
|
|
||||||
v-model="loginData.loginForm.tenantName"
|
|
||||||
:placeholder="t('login.tenantNamePlaceholder')"
|
|
||||||
:prefix-icon="iconHouse"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="24" class="px-10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="username">
|
<el-form-item prop="username">
|
||||||
<el-input
|
<el-input
|
||||||
|
|
@ -86,7 +75,7 @@
|
||||||
mode="pop"
|
mode="pop"
|
||||||
@success="handleLogin"
|
@success="handleLogin"
|
||||||
/>
|
/>
|
||||||
<el-col :span="24" class="px-10px">
|
<el-col :span="24" class="px-10px hidden">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
|
|
@ -113,8 +102,8 @@
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
|
<el-divider content-position="center" class="hidden">{{ t('login.otherLogin') }}</el-divider>
|
||||||
<el-col :span="24" class="px-10px">
|
<el-col :span="24" class="px-10px hidden">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<div class="w-full flex justify-between">
|
<div class="w-full flex justify-between">
|
||||||
<Icon
|
<Icon
|
||||||
|
|
@ -129,8 +118,8 @@
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-divider content-position="center">萌新必读</el-divider>
|
<el-divider content-position="center" class="hidden">萌新必读</el-divider>
|
||||||
<el-col :span="24" class="px-10px">
|
<el-col :span="24" class="px-10px hidden">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<div class="w-full flex justify-between">
|
<div class="w-full flex justify-between">
|
||||||
<el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link>
|
<el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link>
|
||||||
|
|
@ -151,19 +140,14 @@
|
||||||
import { ElLoading } from 'element-plus'
|
import { ElLoading } from 'element-plus'
|
||||||
import LoginFormTitle from './LoginFormTitle.vue'
|
import LoginFormTitle from './LoginFormTitle.vue'
|
||||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||||
|
|
||||||
import { useIcon } from '@/hooks/web/useIcon'
|
import { useIcon } from '@/hooks/web/useIcon'
|
||||||
|
|
||||||
import * as authUtil from '@/utils/auth'
|
import * as authUtil from '@/utils/auth'
|
||||||
import { usePermissionStore } from '@/store/modules/permission'
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
import * as LoginApi from '@/api/login'
|
import * as LoginApi from '@/api/login'
|
||||||
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
||||||
|
|
||||||
defineOptions({ name: 'LoginForm' })
|
defineOptions({ name: 'LoginForm' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const iconHouse = useIcon({ icon: 'ep:house' })
|
|
||||||
const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
||||||
const iconLock = useIcon({ icon: 'ep:lock' })
|
const iconLock = useIcon({ icon: 'ep:lock' })
|
||||||
const formLogin = ref()
|
const formLogin = ref()
|
||||||
|
|
@ -175,11 +159,8 @@ const redirect = ref<string>('')
|
||||||
const loginLoading = ref(false)
|
const loginLoading = ref(false)
|
||||||
const verify = ref()
|
const verify = ref()
|
||||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
|
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
|
||||||
|
|
||||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
||||||
|
|
||||||
const LoginRules = {
|
const LoginRules = {
|
||||||
tenantName: [required],
|
|
||||||
username: [required],
|
username: [required],
|
||||||
password: [required]
|
password: [required]
|
||||||
}
|
}
|
||||||
|
|
@ -188,21 +169,18 @@ const loginData = reactive({
|
||||||
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
||||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
||||||
loginForm: {
|
loginForm: {
|
||||||
tenantName: import.meta.env.VITE_APP_DEFAULT_LOGIN_TENANT || '',
|
|
||||||
username: import.meta.env.VITE_APP_DEFAULT_LOGIN_USERNAME || '',
|
username: import.meta.env.VITE_APP_DEFAULT_LOGIN_USERNAME || '',
|
||||||
password: import.meta.env.VITE_APP_DEFAULT_LOGIN_PASSWORD || '',
|
password: import.meta.env.VITE_APP_DEFAULT_LOGIN_PASSWORD || '',
|
||||||
captchaVerification: '',
|
captchaVerification: '',
|
||||||
rememberMe: true // 默认记录我。如果不需要,可手动修改
|
rememberMe: true // 默认记录我。如果不需要,可手动修改
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const socialList = [
|
const socialList = [
|
||||||
{ icon: 'ant-design:wechat-filled', type: 30 },
|
{ icon: 'ant-design:wechat-filled', type: 30 },
|
||||||
{ icon: 'ant-design:dingtalk-circle-filled', type: 20 },
|
{ icon: 'ant-design:dingtalk-circle-filled', type: 20 },
|
||||||
{ icon: 'ant-design:github-filled', type: 0 },
|
{ icon: 'ant-design:github-filled', type: 0 },
|
||||||
{ icon: 'ant-design:alipay-circle-filled', type: 0 }
|
{ icon: 'ant-design:alipay-circle-filled', type: 0 }
|
||||||
]
|
]
|
||||||
|
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
const getCode = async () => {
|
const getCode = async () => {
|
||||||
// 情况一,未开启:则直接登录
|
// 情况一,未开启:则直接登录
|
||||||
|
|
@ -214,13 +192,6 @@ const getCode = async () => {
|
||||||
verify.value.show()
|
verify.value.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 获取租户 ID
|
|
||||||
const getTenantId = async () => {
|
|
||||||
if (loginData.tenantEnable === 'true') {
|
|
||||||
const res = await LoginApi.getTenantIdByName(loginData.loginForm.tenantName)
|
|
||||||
authUtil.setTenantId(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 记住我
|
// 记住我
|
||||||
const getLoginFormCache = () => {
|
const getLoginFormCache = () => {
|
||||||
const loginForm = authUtil.getLoginForm()
|
const loginForm = authUtil.getLoginForm()
|
||||||
|
|
@ -229,19 +200,7 @@ const getLoginFormCache = () => {
|
||||||
...loginData.loginForm,
|
...loginData.loginForm,
|
||||||
username: loginForm.username ? loginForm.username : loginData.loginForm.username,
|
username: loginForm.username ? loginForm.username : loginData.loginForm.username,
|
||||||
password: loginForm.password ? loginForm.password : loginData.loginForm.password,
|
password: loginForm.password ? loginForm.password : loginData.loginForm.password,
|
||||||
rememberMe: loginForm.rememberMe,
|
rememberMe: loginForm.rememberMe
|
||||||
tenantName: loginForm.tenantName ? loginForm.tenantName : loginData.loginForm.tenantName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 根据域名,获得租户信息
|
|
||||||
const getTenantByWebsite = async () => {
|
|
||||||
if (loginData.tenantEnable === 'true') {
|
|
||||||
const website = location.host
|
|
||||||
const res = await LoginApi.getTenantByWebsite(website)
|
|
||||||
if (res) {
|
|
||||||
loginData.loginForm.tenantName = res.name
|
|
||||||
authUtil.setTenantId(res.id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -250,7 +209,6 @@ const loading = ref() // ElLoading.service 返回的实例
|
||||||
const handleLogin = async (params: any) => {
|
const handleLogin = async (params: any) => {
|
||||||
loginLoading.value = true
|
loginLoading.value = true
|
||||||
try {
|
try {
|
||||||
await getTenantId()
|
|
||||||
const data = await validForm()
|
const data = await validForm()
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return
|
return
|
||||||
|
|
@ -272,6 +230,10 @@ const handleLogin = async (params: any) => {
|
||||||
authUtil.removeLoginForm()
|
authUtil.removeLoginForm()
|
||||||
}
|
}
|
||||||
authUtil.setToken(res)
|
authUtil.setToken(res)
|
||||||
|
// 保存租户ID
|
||||||
|
if (res.tenantId) {
|
||||||
|
authUtil.setTenantId(res.tenantId)
|
||||||
|
}
|
||||||
if (!redirect.value) {
|
if (!redirect.value) {
|
||||||
redirect.value = '/'
|
redirect.value = '/'
|
||||||
}
|
}
|
||||||
|
|
@ -286,30 +248,12 @@ const handleLogin = async (params: any) => {
|
||||||
loading.value.close()
|
loading.value.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 社交登录
|
// 社交登录
|
||||||
const doSocialLogin = async (type: number) => {
|
const doSocialLogin = async (type: number) => {
|
||||||
if (type === 0) {
|
if (type === 0) {
|
||||||
message.error('此方式未配置')
|
message.error('此方式未配置')
|
||||||
} else {
|
} else {
|
||||||
loginLoading.value = true
|
loginLoading.value = true
|
||||||
if (loginData.tenantEnable === 'true') {
|
|
||||||
// 尝试先通过 tenantName 获取租户
|
|
||||||
await getTenantId()
|
|
||||||
// 如果获取不到,则需要弹出提示,进行处理
|
|
||||||
if (!authUtil.getTenantId()) {
|
|
||||||
try {
|
|
||||||
const data = await message.prompt('请输入租户名称', t('common.reminder'))
|
|
||||||
if (data?.action !== 'confirm') throw 'cancel'
|
|
||||||
const res = await LoginApi.getTenantIdByName(data.value)
|
|
||||||
authUtil.setTenantId(res)
|
|
||||||
} catch (error) {
|
|
||||||
if (error === 'cancel') return
|
|
||||||
} finally {
|
|
||||||
loginLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 计算 redirectUri
|
// 计算 redirectUri
|
||||||
// 注意: type、redirect 需要先 encode 一次,否则钉钉回调会丢失。
|
// 注意: type、redirect 需要先 encode 一次,否则钉钉回调会丢失。
|
||||||
// 配合 social-login.vue#getUrlValue() 使用
|
// 配合 social-login.vue#getUrlValue() 使用
|
||||||
|
|
@ -317,7 +261,6 @@ const doSocialLogin = async (type: number) => {
|
||||||
location.origin +
|
location.origin +
|
||||||
'/social-login?' +
|
'/social-login?' +
|
||||||
encodeURIComponent(`type=${type}&redirect=${redirect.value || '/'}`)
|
encodeURIComponent(`type=${type}&redirect=${redirect.value || '/'}`)
|
||||||
|
|
||||||
// 进行跳转
|
// 进行跳转
|
||||||
window.location.href = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))
|
window.location.href = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))
|
||||||
}
|
}
|
||||||
|
|
@ -333,22 +276,18 @@ watch(
|
||||||
)
|
)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getLoginFormCache()
|
getLoginFormCache()
|
||||||
getTenantByWebsite()
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep(.anticon) {
|
:deep(.anticon) {
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--el-color-primary) !important;
|
color: var(--el-color-primary) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-code {
|
.login-code {
|
||||||
float: right;
|
float: right;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 38px;
|
height: 38px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
@ -357,4 +296,4 @@ onMounted(() => {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -5,13 +5,9 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { LoginStateEnum, useLoginState } from './useLogin'
|
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||||
|
|
||||||
defineOptions({ name: 'LoginFormTitle' })
|
defineOptions({ name: 'LoginFormTitle' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const { getLoginState } = useLoginState()
|
const { getLoginState } = useLoginState()
|
||||||
|
|
||||||
const getFormTitle = computed(() => {
|
const getFormTitle = computed(() => {
|
||||||
const titleObj = {
|
const titleObj = {
|
||||||
[LoginStateEnum.RESET_PASSWORD]: t('sys.login.forgetFormTitle'),
|
[LoginStateEnum.RESET_PASSWORD]: t('sys.login.forgetFormTitle'),
|
||||||
|
|
@ -23,4 +19,4 @@ const getFormTitle = computed(() => {
|
||||||
}
|
}
|
||||||
return titleObj[unref(getLoginState)]
|
return titleObj[unref(getLoginState)]
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -16,17 +16,6 @@
|
||||||
<LoginFormTitle class="w-full" />
|
<LoginFormTitle class="w-full" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" class="px-10px">
|
|
||||||
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
|
||||||
<el-input
|
|
||||||
v-model="loginData.loginForm.tenantName"
|
|
||||||
:placeholder="t('login.tenantNamePlaceholder')"
|
|
||||||
:prefix-icon="iconHouse"
|
|
||||||
type="primary"
|
|
||||||
link
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<!-- 手机号 -->
|
<!-- 手机号 -->
|
||||||
<el-col :span="24" class="px-10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="mobileNumber">
|
<el-form-item prop="mobileNumber">
|
||||||
|
|
@ -94,33 +83,26 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||||
|
|
||||||
import { useIcon } from '@/hooks/web/useIcon'
|
import { useIcon } from '@/hooks/web/useIcon'
|
||||||
|
|
||||||
import { setTenantId, setToken } from '@/utils/auth'
|
import { setTenantId, setToken } from '@/utils/auth'
|
||||||
import { usePermissionStore } from '@/store/modules/permission'
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
import { getTenantIdByName, sendSmsCode, smsLogin } from '@/api/login'
|
import { sendSmsCode, smsLogin } from '@/api/login'
|
||||||
import LoginFormTitle from './LoginFormTitle.vue'
|
import LoginFormTitle from './LoginFormTitle.vue'
|
||||||
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
||||||
import { ElLoading } from 'element-plus'
|
import { ElLoading } from 'element-plus'
|
||||||
|
|
||||||
defineOptions({ name: 'MobileForm' })
|
defineOptions({ name: 'MobileForm' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const permissionStore = usePermissionStore()
|
const permissionStore = usePermissionStore()
|
||||||
const { currentRoute, push } = useRouter()
|
const { currentRoute, push } = useRouter()
|
||||||
const formSmsLogin = ref()
|
const formSmsLogin = ref()
|
||||||
const loginLoading = ref(false)
|
const loginLoading = ref(false)
|
||||||
const iconHouse = useIcon({ icon: 'ep:house' })
|
|
||||||
const iconCellphone = useIcon({ icon: 'ep:cellphone' })
|
const iconCellphone = useIcon({ icon: 'ep:cellphone' })
|
||||||
const iconCircleCheck = useIcon({ icon: 'ep:circle-check' })
|
const iconCircleCheck = useIcon({ icon: 'ep:circle-check' })
|
||||||
const { validForm } = useFormValid(formSmsLogin)
|
const { validForm } = useFormValid(formSmsLogin)
|
||||||
const { handleBackLogin, getLoginState } = useLoginState()
|
const { handleBackLogin, getLoginState } = useLoginState()
|
||||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.MOBILE)
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.MOBILE)
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
tenantName: [required],
|
|
||||||
mobileNumber: [required],
|
mobileNumber: [required],
|
||||||
code: [required]
|
code: [required]
|
||||||
}
|
}
|
||||||
|
|
@ -133,7 +115,6 @@ const loginData = reactive({
|
||||||
},
|
},
|
||||||
loginForm: {
|
loginForm: {
|
||||||
uuid: '',
|
uuid: '',
|
||||||
tenantName: '研筑科技',
|
|
||||||
mobileNumber: '',
|
mobileNumber: '',
|
||||||
code: ''
|
code: ''
|
||||||
}
|
}
|
||||||
|
|
@ -151,7 +132,6 @@ const smsVO = reactive({
|
||||||
const mobileCodeTimer = ref(0)
|
const mobileCodeTimer = ref(0)
|
||||||
const redirect = ref<string>('')
|
const redirect = ref<string>('')
|
||||||
const getSmsCode = async () => {
|
const getSmsCode = async () => {
|
||||||
await getTenantId()
|
|
||||||
smsVO.smsCode.mobile = loginData.loginForm.mobileNumber
|
smsVO.smsCode.mobile = loginData.loginForm.mobileNumber
|
||||||
await sendSmsCode(smsVO.smsCode).then(async () => {
|
await sendSmsCode(smsVO.smsCode).then(async () => {
|
||||||
message.success(t('login.SmsSendMsg'))
|
message.success(t('login.SmsSendMsg'))
|
||||||
|
|
@ -174,16 +154,8 @@ watch(
|
||||||
immediate: true
|
immediate: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
// 获取租户 ID
|
|
||||||
const getTenantId = async () => {
|
|
||||||
if (loginData.tenantEnable === 'true') {
|
|
||||||
const res = await getTenantIdByName(loginData.loginForm.tenantName)
|
|
||||||
setTenantId(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 登录
|
// 登录
|
||||||
const signIn = async () => {
|
const signIn = async () => {
|
||||||
await getTenantId()
|
|
||||||
const data = await validForm()
|
const data = await validForm()
|
||||||
if (!data) return
|
if (!data) return
|
||||||
ElLoading.service({
|
ElLoading.service({
|
||||||
|
|
@ -197,6 +169,10 @@ const signIn = async () => {
|
||||||
await smsLogin(smsVO.loginSms)
|
await smsLogin(smsVO.loginSms)
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
setToken(res)
|
setToken(res)
|
||||||
|
// 保存租户ID
|
||||||
|
if (res.tenantId) {
|
||||||
|
setTenantId(res.tenantId)
|
||||||
|
}
|
||||||
if (!redirect.value) {
|
if (!redirect.value) {
|
||||||
redirect.value = '/'
|
redirect.value = '/'
|
||||||
}
|
}
|
||||||
|
|
@ -212,15 +188,13 @@ const signIn = async () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep(.anticon) {
|
:deep(.anticon) {
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--el-color-primary) !important;
|
color: var(--el-color-primary) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.smsbtn {
|
.smsbtn {
|
||||||
margin-top: 33px;
|
margin-top: 33px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -18,13 +18,10 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import logoImg from '@/assets/imgs/logo.png'
|
import logoImg from '@/assets/imgs/logo.png'
|
||||||
|
|
||||||
import LoginFormTitle from './LoginFormTitle.vue'
|
import LoginFormTitle from './LoginFormTitle.vue'
|
||||||
import { LoginStateEnum, useLoginState } from './useLogin'
|
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||||
|
|
||||||
defineOptions({ name: 'QrCodeForm' })
|
defineOptions({ name: 'QrCodeForm' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { handleBackLogin, getLoginState } = useLoginState()
|
const { handleBackLogin, getLoginState } = useLoginState()
|
||||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.QR_CODE)
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.QR_CODE)
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -105,9 +105,7 @@ import * as authUtil from '@/utils/auth'
|
||||||
import { usePermissionStore } from '@/store/modules/permission'
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
import * as LoginApi from '@/api/login'
|
import * as LoginApi from '@/api/login'
|
||||||
import { LoginStateEnum, useLoginState, useFormValid } from './useLogin'
|
import { LoginStateEnum, useLoginState, useFormValid } from './useLogin'
|
||||||
|
|
||||||
defineOptions({ name: 'RegisterForm' })
|
defineOptions({ name: 'RegisterForm' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const iconHouse = useIcon({ icon: 'ep:house' })
|
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||||
const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
||||||
|
|
@ -121,9 +119,7 @@ const redirect = ref<string>('')
|
||||||
const loginLoading = ref(false)
|
const loginLoading = ref(false)
|
||||||
const verify = ref()
|
const verify = ref()
|
||||||
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
|
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字 pictureWord 文字验证码
|
||||||
|
|
||||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)
|
||||||
|
|
||||||
const equalToPassword = (_rule, value, callback) => {
|
const equalToPassword = (_rule, value, callback) => {
|
||||||
if (registerData.registerForm.password !== value) {
|
if (registerData.registerForm.password !== value) {
|
||||||
callback(new Error('两次输入的密码不一致'))
|
callback(new Error('两次输入的密码不一致'))
|
||||||
|
|
@ -131,7 +127,6 @@ const equalToPassword = (_rule, value, callback) => {
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const registerRules = {
|
const registerRules = {
|
||||||
tenantName: [
|
tenantName: [
|
||||||
{ required: true, trigger: 'blur', message: '请输入您所属的租户' },
|
{ required: true, trigger: 'blur', message: '请输入您所属的租户' },
|
||||||
|
|
@ -155,7 +150,6 @@ const registerRules = {
|
||||||
{ required: true, validator: equalToPassword, trigger: 'blur' }
|
{ required: true, validator: equalToPassword, trigger: 'blur' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const registerData = reactive({
|
const registerData = reactive({
|
||||||
isShowPassword: false,
|
isShowPassword: false,
|
||||||
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
||||||
|
|
@ -170,7 +164,6 @@ const registerData = reactive({
|
||||||
captchaVerification: ''
|
captchaVerification: ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const loading = ref() // ElLoading.service 返回的实例
|
const loading = ref() // ElLoading.service 返回的实例
|
||||||
// 提交注册
|
// 提交注册
|
||||||
const handleRegister = async (params: any) => {
|
const handleRegister = async (params: any) => {
|
||||||
|
|
@ -180,16 +173,13 @@ const handleRegister = async (params: any) => {
|
||||||
await getTenantId()
|
await getTenantId()
|
||||||
registerData.registerForm.tenantId = authUtil.getTenantId()
|
registerData.registerForm.tenantId = authUtil.getTenantId()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registerData.captchaEnable) {
|
if (registerData.captchaEnable) {
|
||||||
registerData.registerForm.captchaVerification = params.captchaVerification
|
registerData.registerForm.captchaVerification = params.captchaVerification
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await validForm()
|
const data = await validForm()
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await LoginApi.register(registerData.registerForm)
|
const res = await LoginApi.register(registerData.registerForm)
|
||||||
if (!res) {
|
if (!res) {
|
||||||
return
|
return
|
||||||
|
|
@ -199,9 +189,7 @@ const handleRegister = async (params: any) => {
|
||||||
text: '正在加载系统中...',
|
text: '正在加载系统中...',
|
||||||
background: 'rgba(0, 0, 0, 0.7)'
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
})
|
})
|
||||||
|
|
||||||
authUtil.removeLoginForm()
|
authUtil.removeLoginForm()
|
||||||
|
|
||||||
authUtil.setToken(res)
|
authUtil.setToken(res)
|
||||||
if (!redirect.value) {
|
if (!redirect.value) {
|
||||||
redirect.value = '/'
|
redirect.value = '/'
|
||||||
|
|
@ -217,7 +205,6 @@ const handleRegister = async (params: any) => {
|
||||||
loading.value.close()
|
loading.value.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
const getCode = async () => {
|
const getCode = async () => {
|
||||||
// 情况一,未开启:则直接注册
|
// 情况一,未开启:则直接注册
|
||||||
|
|
@ -229,7 +216,6 @@ const getCode = async () => {
|
||||||
verify.value.show()
|
verify.value.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取租户 ID
|
// 获取租户 ID
|
||||||
const getTenantId = async () => {
|
const getTenantId = async () => {
|
||||||
if (registerData.tenantEnable === 'true') {
|
if (registerData.tenantEnable === 'true') {
|
||||||
|
|
@ -237,7 +223,6 @@ const getTenantId = async () => {
|
||||||
authUtil.setTenantId(res)
|
authUtil.setTenantId(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据域名,获得租户信息
|
// 根据域名,获得租户信息
|
||||||
const getTenantByWebsite = async () => {
|
const getTenantByWebsite = async () => {
|
||||||
if (registerData.tenantEnable === 'true') {
|
if (registerData.tenantEnable === 'true') {
|
||||||
|
|
@ -249,7 +234,6 @@ const getTenantByWebsite = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => currentRoute.value,
|
() => currentRoute.value,
|
||||||
(route: RouteLocationNormalizedLoaded) => {
|
(route: RouteLocationNormalizedLoaded) => {
|
||||||
|
|
@ -264,19 +248,16 @@ onMounted(() => {
|
||||||
getTenantByWebsite()
|
getTenantByWebsite()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep(.anticon) {
|
:deep(.anticon) {
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--el-color-primary) !important;
|
color: var(--el-color-primary) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-code {
|
.login-code {
|
||||||
float: right;
|
float: right;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 38px;
|
height: 38px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
@ -285,4 +266,4 @@ onMounted(() => {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -43,13 +43,10 @@ import LoginFormTitle from './LoginFormTitle.vue'
|
||||||
import * as OAuth2Api from '@/api/login/oauth2'
|
import * as OAuth2Api from '@/api/login/oauth2'
|
||||||
import { LoginStateEnum, useLoginState } from './useLogin'
|
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||||
|
|
||||||
defineOptions({ name: 'SSOLogin' })
|
defineOptions({ name: 'SSOLogin' })
|
||||||
|
|
||||||
const route = useRoute() // 路由
|
const route = useRoute() // 路由
|
||||||
const { currentRoute } = useRouter() // 路由
|
const { currentRoute } = useRouter() // 路由
|
||||||
const { getLoginState, setLoginState } = useLoginState()
|
const { getLoginState, setLoginState } = useLoginState()
|
||||||
|
|
||||||
const client = ref({
|
const client = ref({
|
||||||
// 客户端信息
|
// 客户端信息
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -78,7 +75,6 @@ const formData = reactive<formType>({
|
||||||
scopes: [] // 已选中的 scope 数组
|
scopes: [] // 已选中的 scope 数组
|
||||||
})
|
})
|
||||||
const formLoading = ref(false) // 表单是否提交中
|
const formLoading = ref(false) // 表单是否提交中
|
||||||
|
|
||||||
/** 初始化授权信息 */
|
/** 初始化授权信息 */
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
// 防止在没有登录的情况下循环弹窗
|
// 防止在没有登录的情况下循环弹窗
|
||||||
|
|
@ -93,7 +89,6 @@ const init = async () => {
|
||||||
if (route.query.scope) {
|
if (route.query.scope) {
|
||||||
queryParams.scopes = (route.query.scope as string).split(' ')
|
queryParams.scopes = (route.query.scope as string).split(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。
|
// 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。
|
||||||
if (queryParams.scopes.length > 0) {
|
if (queryParams.scopes.length > 0) {
|
||||||
const data = await doAuthorize(true, queryParams.scopes, [])
|
const data = await doAuthorize(true, queryParams.scopes, [])
|
||||||
|
|
@ -102,7 +97,6 @@ const init = async () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取授权页的基本信息
|
// 获取授权页的基本信息
|
||||||
const data = await OAuth2Api.getAuthorize(queryParams.clientId)
|
const data = await OAuth2Api.getAuthorize(queryParams.clientId)
|
||||||
client.value = data.client
|
client.value = data.client
|
||||||
|
|
@ -130,7 +124,6 @@ const init = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理授权的提交 */
|
/** 处理授权的提交 */
|
||||||
const handleAuthorize = async (approved) => {
|
const handleAuthorize = async (approved) => {
|
||||||
// 计算 checkedScopes + uncheckedScopes
|
// 计算 checkedScopes + uncheckedScopes
|
||||||
|
|
@ -157,7 +150,6 @@ const handleAuthorize = async (approved) => {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 调用授权 API 接口 */
|
/** 调用授权 API 接口 */
|
||||||
const doAuthorize = (autoApprove, checkedScopes, uncheckedScopes) => {
|
const doAuthorize = (autoApprove, checkedScopes, uncheckedScopes) => {
|
||||||
return OAuth2Api.authorize(
|
return OAuth2Api.authorize(
|
||||||
|
|
@ -170,7 +162,6 @@ const doAuthorize = (autoApprove, checkedScopes, uncheckedScopes) => {
|
||||||
uncheckedScopes
|
uncheckedScopes
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 格式化 scope 文本 */
|
/** 格式化 scope 文本 */
|
||||||
const formatScope = (scope) => {
|
const formatScope = (scope) => {
|
||||||
// 格式化 scope 授权范围,方便用户理解。
|
// 格式化 scope 授权范围,方便用户理解。
|
||||||
|
|
@ -184,7 +175,6 @@ const formatScope = (scope) => {
|
||||||
return scope
|
return scope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 监听当前路由为 SSOLogin 时,进行数据的初始化 */
|
/** 监听当前路由为 SSOLogin 时,进行数据的初始化 */
|
||||||
watch(
|
watch(
|
||||||
() => currentRoute.value,
|
() => currentRoute.value,
|
||||||
|
|
@ -196,4 +186,4 @@ watch(
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -28,12 +28,10 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { BasicInfo, ProfileUser, ResetPwd, UserSocial } from './components'
|
import { BasicInfo, ProfileUser, ResetPwd, UserSocial } from './components'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
defineOptions({ name: 'Profile' })
|
defineOptions({ name: 'Profile' })
|
||||||
const activeName = ref('basicInfo')
|
const activeName = ref('basicInfo')
|
||||||
const profileUserRef = ref()
|
const profileUserRef = ref()
|
||||||
|
|
||||||
// 处理基本信息更新成功
|
// 处理基本信息更新成功
|
||||||
const handleBasicInfoSuccess = async () => {
|
const handleBasicInfoSuccess = async () => {
|
||||||
await profileUserRef.value?.refresh()
|
await profileUserRef.value?.refresh()
|
||||||
|
|
@ -44,24 +42,20 @@ const handleBasicInfoSuccess = async () => {
|
||||||
max-height: 960px;
|
max-height: 960px;
|
||||||
padding: 15px 20px 20px;
|
padding: 15px 20px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-card .el-card__header, .el-card .el-card__body) {
|
:deep(.el-card .el-card__header, .el-card .el-card__body) {
|
||||||
padding: 15px !important;
|
padding: 15px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-tabs > .el-tabs__content {
|
.profile-tabs > .el-tabs__content {
|
||||||
padding: 32px;
|
padding: 32px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #6b778c;
|
color: #6b778c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tabs--left .el-tabs__content {
|
.el-tabs--left .el-tabs__content {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -22,18 +22,14 @@ import {
|
||||||
UserProfileUpdateReqVO
|
UserProfileUpdateReqVO
|
||||||
} from '@/api/system/user/profile'
|
} from '@/api/system/user/profile'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
||||||
defineOptions({ name: 'BasicInfo' })
|
defineOptions({ name: 'BasicInfo' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
// 定义事件
|
// 定义事件
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'success'): void
|
(e: 'success'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// 表单校验
|
// 表单校验
|
||||||
const rules = reactive<FormRules>({
|
const rules = reactive<FormRules>({
|
||||||
nickname: [{ required: true, message: t('profile.rules.nickname'), trigger: 'blur' }],
|
nickname: [{ required: true, message: t('profile.rules.nickname'), trigger: 'blur' }],
|
||||||
|
|
@ -78,7 +74,6 @@ const schema = reactive<FormSchema[]>([
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
const formRef = ref<FormExpose>() // 表单 Ref
|
const formRef = ref<FormExpose>() // 表单 Ref
|
||||||
|
|
||||||
// 监听 userStore 中头像的变化,同步更新表单数据
|
// 监听 userStore 中头像的变化,同步更新表单数据
|
||||||
watch(
|
watch(
|
||||||
() => userStore.getUser.avatar,
|
() => userStore.getUser.avatar,
|
||||||
|
|
@ -92,7 +87,6 @@ watch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
const elForm = unref(formRef)?.getElFormRef()
|
const elForm = unref(formRef)?.getElFormRef()
|
||||||
if (!elForm) return
|
if (!elForm) return
|
||||||
|
|
@ -108,14 +102,12 @@ const submit = () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
const res = await getUserProfile()
|
const res = await getUserProfile()
|
||||||
unref(formRef)?.setValues(res)
|
unref(formRef)?.setValues(res)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await init()
|
await init()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -50,20 +50,15 @@
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
import UserAvatar from './UserAvatar.vue'
|
import UserAvatar from './UserAvatar.vue'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
||||||
import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
|
import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
|
||||||
|
|
||||||
defineOptions({ name: 'ProfileUser' })
|
defineOptions({ name: 'ProfileUser' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const userInfo = ref({} as ProfileVO)
|
const userInfo = ref({} as ProfileVO)
|
||||||
|
|
||||||
const getUserInfo = async () => {
|
const getUserInfo = async () => {
|
||||||
const users = await getUserProfile()
|
const users = await getUserProfile()
|
||||||
userInfo.value = users
|
userInfo.value = users
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听 userStore 中头像的变化,同步更新本地 userInfo
|
// 监听 userStore 中头像的变化,同步更新本地 userInfo
|
||||||
watch(
|
watch(
|
||||||
() => userStore.getUser.avatar,
|
() => userStore.getUser.avatar,
|
||||||
|
|
@ -73,24 +68,20 @@ watch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// 暴露刷新方法
|
// 暴露刷新方法
|
||||||
defineExpose({
|
defineExpose({
|
||||||
refresh: getUserInfo
|
refresh: getUserInfo
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getUserInfo()
|
await getUserInfo()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.text-center {
|
.text-center {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-group-striped > .list-group-item {
|
.list-group-striped > .list-group-item {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
|
|
@ -98,12 +89,10 @@ onMounted(async () => {
|
||||||
border-left: 0;
|
border-left: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-group {
|
.list-group {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-group-item {
|
.list-group-item {
|
||||||
padding: 11px 0;
|
padding: 11px 0;
|
||||||
margin-bottom: -1px;
|
margin-bottom: -1px;
|
||||||
|
|
@ -111,8 +100,7 @@ onMounted(async () => {
|
||||||
border-top: 1px solid #e7eaec;
|
border-top: 1px solid #e7eaec;
|
||||||
border-bottom: 1px solid #e7eaec;
|
border-bottom: 1px solid #e7eaec;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pull-right {
|
.pull-right {
|
||||||
float: right !important;
|
float: right !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -17,12 +17,9 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
|
||||||
import { InputPassword } from '@/components/InputPassword'
|
import { InputPassword } from '@/components/InputPassword'
|
||||||
import { updateUserPassword } from '@/api/system/user/profile'
|
import { updateUserPassword } from '@/api/system/user/profile'
|
||||||
|
|
||||||
defineOptions({ name: 'ResetPwd' })
|
defineOptions({ name: 'ResetPwd' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const formRef = ref<FormInstance>()
|
const formRef = ref<FormInstance>()
|
||||||
|
|
@ -31,7 +28,6 @@ const password = reactive({
|
||||||
newPassword: '',
|
newPassword: '',
|
||||||
confirmPassword: ''
|
confirmPassword: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// 表单校验
|
// 表单校验
|
||||||
const equalToPassword = (_rule, value, callback) => {
|
const equalToPassword = (_rule, value, callback) => {
|
||||||
if (password.newPassword !== value) {
|
if (password.newPassword !== value) {
|
||||||
|
|
@ -40,7 +36,6 @@ const equalToPassword = (_rule, value, callback) => {
|
||||||
callback()
|
callback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rules = reactive<FormRules>({
|
const rules = reactive<FormRules>({
|
||||||
oldPassword: [
|
oldPassword: [
|
||||||
{ required: true, message: t('profile.password.oldPwdMsg'), trigger: 'blur' },
|
{ required: true, message: t('profile.password.oldPwdMsg'), trigger: 'blur' },
|
||||||
|
|
@ -55,7 +50,6 @@ const rules = reactive<FormRules>({
|
||||||
{ required: true, validator: equalToPassword, trigger: 'blur' }
|
{ required: true, validator: equalToPassword, trigger: 'blur' }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const submit = (formEl: FormInstance | undefined) => {
|
const submit = (formEl: FormInstance | undefined) => {
|
||||||
if (!formEl) return
|
if (!formEl) return
|
||||||
formEl.validate(async (valid) => {
|
formEl.validate(async (valid) => {
|
||||||
|
|
@ -65,9 +59,8 @@ const submit = (formEl: FormInstance | undefined) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const reset = (formEl: FormInstance | undefined) => {
|
const reset = (formEl: FormInstance | undefined) => {
|
||||||
if (!formEl) return
|
if (!formEl) return
|
||||||
formEl.resetFields()
|
formEl.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -17,15 +17,11 @@ import { CropperAvatar } from '@/components/Cropper'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
||||||
import { UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
|
import { UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'
|
||||||
|
|
||||||
defineOptions({ name: 'UserAvatar' })
|
defineOptions({ name: 'UserAvatar' })
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
img: propTypes.string.def('')
|
img: propTypes.string.def('')
|
||||||
})
|
})
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const cropperRef = ref()
|
const cropperRef = ref()
|
||||||
const handelUpload = async ({ data }) => {
|
const handelUpload = async ({ data }) => {
|
||||||
const { httpRequest } = useUpload()
|
const { httpRequest } = useUpload()
|
||||||
|
|
@ -36,13 +32,11 @@ const handelUpload = async ({ data }) => {
|
||||||
} as UploadRequestOptions)) as unknown as { data: string }
|
} as UploadRequestOptions)) as unknown as { data: string }
|
||||||
).data
|
).data
|
||||||
await updateUserProfile({ avatar })
|
await updateUserProfile({ avatar })
|
||||||
|
|
||||||
// 关闭弹窗,并更新 userStore
|
// 关闭弹窗,并更新 userStore
|
||||||
cropperRef.value.close()
|
cropperRef.value.close()
|
||||||
await userStore.setUserAvatarAction(avatar)
|
await userStore.setUserAvatarAction(avatar)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.change-avatar {
|
.change-avatar {
|
||||||
img {
|
img {
|
||||||
|
|
@ -51,4 +45,4 @@ const handelUpload = async ({ data }) => {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -25,14 +25,12 @@
|
||||||
import { SystemUserSocialTypeEnum } from '@/utils/constants'
|
import { SystemUserSocialTypeEnum } from '@/utils/constants'
|
||||||
import { getBindSocialUserList } from '@/api/system/social/user'
|
import { getBindSocialUserList } from '@/api/system/social/user'
|
||||||
import { socialAuthRedirect, socialBind, socialUnbind } from '@/api/system/user/socialUser'
|
import { socialAuthRedirect, socialBind, socialUnbind } from '@/api/system/user/socialUser'
|
||||||
|
|
||||||
defineOptions({ name: 'UserSocial' })
|
defineOptions({ name: 'UserSocial' })
|
||||||
defineProps<{
|
defineProps<{
|
||||||
activeName: string
|
activeName: string
|
||||||
}>()
|
}>()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const socialUsers = ref<any[]>([])
|
const socialUsers = ref<any[]>([])
|
||||||
|
|
||||||
const initSocial = async () => {
|
const initSocial = async () => {
|
||||||
socialUsers.value = [] // 重置避免无限增长
|
socialUsers.value = [] // 重置避免无限增长
|
||||||
// 获取已绑定的社交用户列表
|
// 获取已绑定的社交用户列表
|
||||||
|
|
@ -68,13 +66,11 @@ const bindSocial = () => {
|
||||||
emit('update:activeName', 'userSocial')
|
emit('update:activeName', 'userSocial')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 双层 encode 需要在回调后进行 decode
|
// 双层 encode 需要在回调后进行 decode
|
||||||
function getUrlValue(key: string): string {
|
function getUrlValue(key: string): string {
|
||||||
const url = new URL(decodeURIComponent(location.href))
|
const url = new URL(decodeURIComponent(location.href))
|
||||||
return url.searchParams.get(key) ?? ''
|
return url.searchParams.get(key) ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const bind = (row) => {
|
const bind = (row) => {
|
||||||
// 双层 encode 解决钉钉回调 type 参数丢失的问题
|
// 双层 encode 解决钉钉回调 type 参数丢失的问题
|
||||||
const redirectUri = location.origin + '/user/profile?' + encodeURIComponent(`type=${row.type}`)
|
const redirectUri = location.origin + '/user/profile?' + encodeURIComponent(`type=${row.type}`)
|
||||||
|
|
@ -90,11 +86,9 @@ const unbind = async (row) => {
|
||||||
}
|
}
|
||||||
message.success('解绑成功')
|
message.success('解绑成功')
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await initSocial()
|
await initSocial()
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route,
|
() => route,
|
||||||
() => {
|
() => {
|
||||||
|
|
@ -104,4 +98,4 @@ watch(
|
||||||
immediate: true
|
immediate: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -3,16 +3,12 @@
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineOptions({ name: 'Redirect' })
|
defineOptions({ name: 'Redirect' })
|
||||||
|
|
||||||
const { currentRoute, replace } = useRouter()
|
const { currentRoute, replace } = useRouter()
|
||||||
const { params, query } = unref(currentRoute)
|
const { params, query } = unref(currentRoute)
|
||||||
const { path, _redirect_type = 'path' } = params
|
const { path, _redirect_type = 'path' } = params
|
||||||
|
|
||||||
Reflect.deleteProperty(params, '_redirect_type')
|
Reflect.deleteProperty(params, '_redirect_type')
|
||||||
Reflect.deleteProperty(params, 'path')
|
Reflect.deleteProperty(params, 'path')
|
||||||
|
|
||||||
const _path = Array.isArray(path) ? path.join('/') : path
|
const _path = Array.isArray(path) ? path.join('/') : path
|
||||||
|
|
||||||
if (_redirect_type === 'name') {
|
if (_redirect_type === 'name') {
|
||||||
replace({
|
replace({
|
||||||
name: _path,
|
name: _path,
|
||||||
|
|
@ -25,4 +21,4 @@ if (_redirect_type === 'name') {
|
||||||
query
|
query
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
<Icon icon="ep:plus" class="mr-5px" />
|
<Icon icon="ep:plus" class="mr-5px" />
|
||||||
新建对话
|
新建对话
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<!-- 左顶部:搜索对话 -->
|
<!-- 左顶部:搜索对话 -->
|
||||||
<el-input
|
<el-input
|
||||||
v-model="searchName"
|
v-model="searchName"
|
||||||
|
|
@ -23,7 +22,6 @@
|
||||||
<Icon icon="ep:search" />
|
<Icon icon="ep:search" />
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
|
||||||
<!-- 左中间:对话列表 -->
|
<!-- 左中间:对话列表 -->
|
||||||
<div class="overflow-auto h-full">
|
<div class="overflow-auto h-full">
|
||||||
<!-- 情况一:加载中 -->
|
<!-- 情况一:加载中 -->
|
||||||
|
|
@ -98,7 +96,6 @@
|
||||||
<div class="h-160px w-100%"></div>
|
<div class="h-160px w-100%"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 左底部:工具栏 -->
|
<!-- 左底部:工具栏 -->
|
||||||
<div
|
<div
|
||||||
class="absolute bottom-0 left-0 right-0 px-5 leading-8.75 flex justify-between items-center"
|
class="absolute bottom-0 left-0 right-0 px-5 leading-8.75 flex justify-between items-center"
|
||||||
|
|
@ -125,22 +122,18 @@
|
||||||
<el-text class="ml-1.25" size="small">清空未置顶对话</el-text>
|
<el-text class="ml-1.25" size="small">清空未置顶对话</el-text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 角色仓库抽屉 -->
|
<!-- 角色仓库抽屉 -->
|
||||||
<el-drawer v-model="roleRepositoryOpen" title="角色仓库" size="754px">
|
<el-drawer v-model="roleRepositoryOpen" title="角色仓库" size="754px">
|
||||||
<RoleRepository />
|
<RoleRepository />
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
||||||
import RoleRepository from '../role/RoleRepository.vue'
|
import RoleRepository from '../role/RoleRepository.vue'
|
||||||
import { Bottom, Top } from '@element-plus/icons-vue'
|
import { Bottom, Top } from '@element-plus/icons-vue'
|
||||||
import roleAvatarDefaultImg from '@/assets/ai/gpt.svg'
|
import roleAvatarDefaultImg from '@/assets/ai/gpt.svg'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
// 定义属性
|
// 定义属性
|
||||||
const searchName = ref<string>('') // 对话搜索
|
const searchName = ref<string>('') // 对话搜索
|
||||||
const activeConversationId = ref<number | null>(null) // 选中的对话,默认为 null
|
const activeConversationId = ref<number | null>(null) // 选中的对话,默认为 null
|
||||||
|
|
@ -149,7 +142,6 @@ const conversationList = ref([] as ChatConversationVO[]) // 对话列表
|
||||||
const conversationMap = ref<any>({}) // 对话分组 (置顶、今天、三天前、一星期前、一个月前)
|
const conversationMap = ref<any>({}) // 对话分组 (置顶、今天、三天前、一星期前、一个月前)
|
||||||
const loading = ref<boolean>(false) // 加载中
|
const loading = ref<boolean>(false) // 加载中
|
||||||
const loadingTime = ref<any>() // 加载中定时器
|
const loadingTime = ref<any>() // 加载中定时器
|
||||||
|
|
||||||
// 定义组件 props
|
// 定义组件 props
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
activeId: {
|
activeId: {
|
||||||
|
|
@ -157,7 +149,6 @@ const props = defineProps({
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 定义钩子
|
// 定义钩子
|
||||||
const emits = defineEmits([
|
const emits = defineEmits([
|
||||||
'onConversationCreate',
|
'onConversationCreate',
|
||||||
|
|
@ -165,7 +156,6 @@ const emits = defineEmits([
|
||||||
'onConversationClear',
|
'onConversationClear',
|
||||||
'onConversationDelete'
|
'onConversationDelete'
|
||||||
])
|
])
|
||||||
|
|
||||||
/** 搜索对话 */
|
/** 搜索对话 */
|
||||||
const searchConversation = async (e) => {
|
const searchConversation = async (e) => {
|
||||||
// 恢复数据
|
// 恢复数据
|
||||||
|
|
@ -179,7 +169,6 @@ const searchConversation = async (e) => {
|
||||||
conversationMap.value = await getConversationGroupByCreateTime(filterValues)
|
conversationMap.value = await getConversationGroupByCreateTime(filterValues)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 点击对话 */
|
/** 点击对话 */
|
||||||
const handleConversationClick = async (id: number) => {
|
const handleConversationClick = async (id: number) => {
|
||||||
// 过滤出选中的对话
|
// 过滤出选中的对话
|
||||||
|
|
@ -194,7 +183,6 @@ const handleConversationClick = async (id: number) => {
|
||||||
activeConversationId.value = id
|
activeConversationId.value = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取对话列表 */
|
/** 获取对话列表 */
|
||||||
const getChatConversationList = async () => {
|
const getChatConversationList = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -202,7 +190,6 @@ const getChatConversationList = async () => {
|
||||||
loadingTime.value = setTimeout(() => {
|
loadingTime.value = setTimeout(() => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
}, 50)
|
}, 50)
|
||||||
|
|
||||||
// 1.1 获取 对话数据
|
// 1.1 获取 对话数据
|
||||||
conversationList.value = await ChatConversationApi.getChatConversationMyList()
|
conversationList.value = await ChatConversationApi.getChatConversationMyList()
|
||||||
// 1.2 排序
|
// 1.2 排序
|
||||||
|
|
@ -215,7 +202,6 @@ const getChatConversationList = async () => {
|
||||||
conversationMap.value = {}
|
conversationMap.value = {}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 对话根据时间分组(置顶、今天、一天前、三天前、七天前、30 天前)
|
// 2. 对话根据时间分组(置顶、今天、一天前、三天前、七天前、30 天前)
|
||||||
conversationMap.value = await getConversationGroupByCreateTime(conversationList.value)
|
conversationMap.value = await getConversationGroupByCreateTime(conversationList.value)
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -227,7 +213,6 @@ const getChatConversationList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 按照 creteTime 创建时间,进行分组 */
|
/** 按照 creteTime 创建时间,进行分组 */
|
||||||
const getConversationGroupByCreateTime = async (list: ChatConversationVO[]) => {
|
const getConversationGroupByCreateTime = async (list: ChatConversationVO[]) => {
|
||||||
// 排序、指定、时间分组(今天、一天前、三天前、七天前、30天前)
|
// 排序、指定、时间分组(今天、一天前、三天前、七天前、30天前)
|
||||||
|
|
@ -270,7 +255,6 @@ const getConversationGroupByCreateTime = async (list: ChatConversationVO[]) => {
|
||||||
}
|
}
|
||||||
return groupMap
|
return groupMap
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 新建对话 */
|
/** 新建对话 */
|
||||||
const createConversation = async () => {
|
const createConversation = async () => {
|
||||||
// 1. 新建对话
|
// 1. 新建对话
|
||||||
|
|
@ -284,7 +268,6 @@ const createConversation = async () => {
|
||||||
// 4. 回调
|
// 4. 回调
|
||||||
emits('onConversationCreate')
|
emits('onConversationCreate')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改对话的标题 */
|
/** 修改对话的标题 */
|
||||||
const updateConversationTitle = async (conversation: ChatConversationVO) => {
|
const updateConversationTitle = async (conversation: ChatConversationVO) => {
|
||||||
// 1. 二次确认
|
// 1. 二次确认
|
||||||
|
|
@ -312,7 +295,6 @@ const updateConversationTitle = async (conversation: ChatConversationVO) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除聊天对话 */
|
/** 删除聊天对话 */
|
||||||
const deleteChatConversation = async (conversation: ChatConversationVO) => {
|
const deleteChatConversation = async (conversation: ChatConversationVO) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -327,7 +309,6 @@ const deleteChatConversation = async (conversation: ChatConversationVO) => {
|
||||||
emits('onConversationDelete', conversation)
|
emits('onConversationDelete', conversation)
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 清空对话 */
|
/** 清空对话 */
|
||||||
const handleClearConversation = async () => {
|
const handleClearConversation = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -345,7 +326,6 @@ const handleClearConversation = async () => {
|
||||||
emits('onConversationClear')
|
emits('onConversationClear')
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 对话置顶 */
|
/** 对话置顶 */
|
||||||
const handleTop = async (conversation: ChatConversationVO) => {
|
const handleTop = async (conversation: ChatConversationVO) => {
|
||||||
// 更新对话置顶
|
// 更新对话置顶
|
||||||
|
|
@ -354,24 +334,19 @@ const handleTop = async (conversation: ChatConversationVO) => {
|
||||||
// 刷新对话
|
// 刷新对话
|
||||||
await getChatConversationList()
|
await getChatConversationList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ 角色仓库 ============
|
// ============ 角色仓库 ============
|
||||||
|
|
||||||
/** 角色仓库抽屉 */
|
/** 角色仓库抽屉 */
|
||||||
const roleRepositoryOpen = ref<boolean>(false) // 角色仓库是否打开
|
const roleRepositoryOpen = ref<boolean>(false) // 角色仓库是否打开
|
||||||
const handleRoleRepository = async () => {
|
const handleRoleRepository = async () => {
|
||||||
roleRepositoryOpen.value = !roleRepositoryOpen.value
|
roleRepositoryOpen.value = !roleRepositoryOpen.value
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 监听选中的对话 */
|
/** 监听选中的对话 */
|
||||||
const { activeId } = toRefs(props)
|
const { activeId } = toRefs(props)
|
||||||
watch(activeId, async (newValue, oldValue) => {
|
watch(activeId, async (newValue, oldValue) => {
|
||||||
activeConversationId.value = newValue as string
|
activeConversationId.value = newValue as string
|
||||||
})
|
})
|
||||||
|
|
||||||
// 定义 public 方法
|
// 定义 public 方法
|
||||||
defineExpose({ createConversation })
|
defineExpose({ createConversation })
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 获取 对话列表
|
// 获取 对话列表
|
||||||
|
|
@ -388,4 +363,4 @@ onMounted(async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -64,12 +64,9 @@
|
||||||
import { ModelApi, ModelVO } from '@/api/ai/model/model'
|
import { ModelApi, ModelVO } from '@/api/ai/model/model'
|
||||||
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
||||||
import { AiModelTypeEnum } from '@/views/ai/utils/constants'
|
import { AiModelTypeEnum } from '@/views/ai/utils/constants'
|
||||||
|
|
||||||
/** AI 聊天对话的更新表单 */
|
/** AI 聊天对话的更新表单 */
|
||||||
defineOptions({ name: 'ChatConversationUpdateForm' })
|
defineOptions({ name: 'ChatConversationUpdateForm' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
|
|
@ -89,7 +86,6 @@ const formRules = reactive({
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
const models = ref([] as ModelVO[]) // 聊天模型列表
|
const models = ref([] as ModelVO[]) // 聊天模型列表
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (id: number) => {
|
const open = async (id: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
|
@ -113,7 +109,6 @@ const open = async (id: number) => {
|
||||||
models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT)
|
models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT)
|
||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
/** 提交表单 */
|
/** 提交表单 */
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
|
|
@ -132,7 +127,6 @@ const submitForm = async () => {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
|
|
@ -145,4 +139,4 @@ const resetForm = () => {
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -23,7 +23,6 @@
|
||||||
{{ fileList.length }}
|
{{ fileList.length }}
|
||||||
</span>
|
</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<!-- 隐藏的文件输入框 -->
|
<!-- 隐藏的文件输入框 -->
|
||||||
<input
|
<input
|
||||||
ref="fileInputRef"
|
ref="fileInputRef"
|
||||||
|
|
@ -33,7 +32,6 @@
|
||||||
:accept="acceptTypes"
|
:accept="acceptTypes"
|
||||||
@change="handleFileSelect"
|
@change="handleFileSelect"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Hover 显示的文件列表 -->
|
<!-- Hover 显示的文件列表 -->
|
||||||
<div
|
<div
|
||||||
v-if="fileList.length > 0 && showTooltip"
|
v-if="fileList.length > 0 && showTooltip"
|
||||||
|
|
@ -82,11 +80,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
||||||
import { formatFileSize, getFileIcon } from '@/utils/file'
|
import { formatFileSize, getFileIcon } from '@/utils/file'
|
||||||
|
|
||||||
export interface FileItem {
|
export interface FileItem {
|
||||||
name: string
|
name: string
|
||||||
size: number
|
size: number
|
||||||
|
|
@ -95,9 +91,7 @@ export interface FileItem {
|
||||||
progress?: number
|
progress?: number
|
||||||
raw?: File
|
raw?: File
|
||||||
}
|
}
|
||||||
|
|
||||||
defineOptions({ name: 'MessageFileUpload' })
|
defineOptions({ name: 'MessageFileUpload' })
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Array as PropType<string[]>,
|
type: Array as PropType<string[]>,
|
||||||
|
|
@ -120,9 +114,7 @@ const props = defineProps({
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'upload-success', 'upload-error'])
|
const emit = defineEmits(['update:modelValue', 'upload-success', 'upload-error'])
|
||||||
|
|
||||||
const fileInputRef = ref<HTMLInputElement>()
|
const fileInputRef = ref<HTMLInputElement>()
|
||||||
const fileList = ref<FileItem[]>([]) // 内部管理文件列表
|
const fileList = ref<FileItem[]>([]) // 内部管理文件列表
|
||||||
const uploadedUrls = ref<string[]>([]) // 已上传的 URL 列表
|
const uploadedUrls = ref<string[]>([]) // 已上传的 URL 列表
|
||||||
|
|
@ -130,7 +122,6 @@ const showTooltip = ref(false) // 控制 tooltip 显示
|
||||||
const hideTimer = ref<NodeJS.Timeout | null>(null) // 隐藏延迟定时器
|
const hideTimer = ref<NodeJS.Timeout | null>(null) // 隐藏延迟定时器
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const { httpRequest } = useUpload()
|
const { httpRequest } = useUpload()
|
||||||
|
|
||||||
/** 监听 v-model 变化 */
|
/** 监听 v-model 变化 */
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
|
|
@ -143,12 +134,10 @@ watch(
|
||||||
},
|
},
|
||||||
{ immediate: true, deep: true }
|
{ immediate: true, deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 触发文件选择 */
|
/** 触发文件选择 */
|
||||||
const triggerFileInput = () => {
|
const triggerFileInput = () => {
|
||||||
fileInputRef.value?.click()
|
fileInputRef.value?.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 显示 tooltip */
|
/** 显示 tooltip */
|
||||||
const showTooltipHandler = () => {
|
const showTooltipHandler = () => {
|
||||||
if (hideTimer.value) {
|
if (hideTimer.value) {
|
||||||
|
|
@ -157,7 +146,6 @@ const showTooltipHandler = () => {
|
||||||
}
|
}
|
||||||
showTooltip.value = true
|
showTooltip.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 隐藏 tooltip */
|
/** 隐藏 tooltip */
|
||||||
const hideTooltipHandler = () => {
|
const hideTooltipHandler = () => {
|
||||||
hideTimer.value = setTimeout(() => {
|
hideTimer.value = setTimeout(() => {
|
||||||
|
|
@ -165,7 +153,6 @@ const hideTooltipHandler = () => {
|
||||||
hideTimer.value = null
|
hideTimer.value = null
|
||||||
}, 300) // 300ms 延迟隐藏
|
}, 300) // 300ms 延迟隐藏
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理文件选择 */
|
/** 处理文件选择 */
|
||||||
const handleFileSelect = (event: Event) => {
|
const handleFileSelect = (event: Event) => {
|
||||||
const target = event.target as HTMLInputElement
|
const target = event.target as HTMLInputElement
|
||||||
|
|
@ -196,11 +183,9 @@ const handleFileSelect = (event: Event) => {
|
||||||
// 立即开始上传
|
// 立即开始上传
|
||||||
uploadFile(fileItem)
|
uploadFile(fileItem)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 清空 input 值,允许重复选择相同文件
|
// 清空 input 值,允许重复选择相同文件
|
||||||
target.value = ''
|
target.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 上传文件 */
|
/** 上传文件 */
|
||||||
const uploadFile = async (fileItem: FileItem) => {
|
const uploadFile = async (fileItem: FileItem) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -210,7 +195,6 @@ const uploadFile = async (fileItem: FileItem) => {
|
||||||
fileItem.progress = (fileItem.progress || 0) + Math.random() * 10
|
fileItem.progress = (fileItem.progress || 0) + Math.random() * 10
|
||||||
}
|
}
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
||||||
// 调用上传接口
|
// 调用上传接口
|
||||||
// const formData = new FormData()
|
// const formData = new FormData()
|
||||||
// formData.append('file', fileItem.raw!)
|
// formData.append('file', fileItem.raw!)
|
||||||
|
|
@ -223,16 +207,13 @@ const uploadFile = async (fileItem: FileItem) => {
|
||||||
fileItem.url = (response as any).data
|
fileItem.url = (response as any).data
|
||||||
// 添加到 URL 列表
|
// 添加到 URL 列表
|
||||||
uploadedUrls.value.push(fileItem.url!)
|
uploadedUrls.value.push(fileItem.url!)
|
||||||
|
|
||||||
clearInterval(progressInterval)
|
clearInterval(progressInterval)
|
||||||
|
|
||||||
emit('upload-success', fileItem)
|
emit('upload-success', fileItem)
|
||||||
updateModelValue()
|
updateModelValue()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
fileItem.uploading = false
|
fileItem.uploading = false
|
||||||
message.error(`文件 ${fileItem.name} 上传失败`)
|
message.error(`文件 ${fileItem.name} 上传失败`)
|
||||||
emit('upload-error', error)
|
emit('upload-error', error)
|
||||||
|
|
||||||
// 移除上传失败的文件
|
// 移除上传失败的文件
|
||||||
const index = fileList.value.indexOf(fileItem)
|
const index = fileList.value.indexOf(fileItem)
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
|
|
@ -240,7 +221,6 @@ const uploadFile = async (fileItem: FileItem) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除文件 */
|
/** 删除文件 */
|
||||||
const removeFile = (index: number) => {
|
const removeFile = (index: number) => {
|
||||||
// 从 URL 列表中移除
|
// 从 URL 列表中移除
|
||||||
|
|
@ -252,15 +232,12 @@ const removeFile = (index: number) => {
|
||||||
uploadedUrls.value.splice(urlIndex, 1)
|
uploadedUrls.value.splice(urlIndex, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateModelValue()
|
updateModelValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 更新 v-model */
|
/** 更新 v-model */
|
||||||
const updateModelValue = () => {
|
const updateModelValue = () => {
|
||||||
emit('update:modelValue', [...uploadedUrls.value])
|
emit('update:modelValue', [...uploadedUrls.value])
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露方法
|
// 暴露方法
|
||||||
defineExpose({
|
defineExpose({
|
||||||
triggerFileInput,
|
triggerFileInput,
|
||||||
|
|
@ -270,7 +247,6 @@ defineExpose({
|
||||||
updateModelValue()
|
updateModelValue()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 组件销毁时清理定时器
|
// 组件销毁时清理定时器
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (hideTimer.value) {
|
if (hideTimer.value) {
|
||||||
|
|
@ -278,7 +254,6 @@ onUnmounted(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 上传按钮样式 */
|
/* 上传按钮样式 */
|
||||||
.upload-btn {
|
.upload-btn {
|
||||||
|
|
@ -288,12 +263,10 @@ onUnmounted(() => {
|
||||||
--el-button-hover-border-color: transparent;
|
--el-button-hover-border-color: transparent;
|
||||||
color: var(--el-text-color-regular);
|
color: var(--el-text-color-regular);
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-btn.has-files {
|
.upload-btn.has-files {
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
--el-button-hover-bg-color: var(--el-color-primary-light-9);
|
--el-button-hover-bg-color: var(--el-color-primary-light-9);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-tooltip {
|
.file-tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: calc(100% + 8px);
|
bottom: calc(100% + 8px);
|
||||||
|
|
@ -309,7 +282,6 @@ onUnmounted(() => {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
animation: fadeInDown 0.2s ease;
|
animation: fadeInDown 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip-arrow {
|
.tooltip-arrow {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -5px;
|
bottom: -5px;
|
||||||
|
|
@ -321,7 +293,6 @@ onUnmounted(() => {
|
||||||
border-right: 5px solid transparent;
|
border-right: 5px solid transparent;
|
||||||
border-top: 5px solid var(--el-border-color-light);
|
border-top: 5px solid var(--el-border-color-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tooltip 箭头伪元素 */
|
/* Tooltip 箭头伪元素 */
|
||||||
.tooltip-arrow::after {
|
.tooltip-arrow::after {
|
||||||
content: '';
|
content: '';
|
||||||
|
|
@ -334,7 +305,6 @@ onUnmounted(() => {
|
||||||
border-right: 4px solid transparent;
|
border-right: 4px solid transparent;
|
||||||
border-top: 4px solid white;
|
border-top: 4px solid white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeInDown {
|
@keyframes fadeInDown {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
@ -345,7 +315,6 @@ onUnmounted(() => {
|
||||||
transform: translateX(-50%) translateY(0);
|
transform: translateX(-50%) translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeInDown {
|
@keyframes fadeInDown {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
@ -356,21 +325,17 @@ onUnmounted(() => {
|
||||||
transform: translateX(-50%) translateY(0);
|
transform: translateX(-50%) translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动条样式 */
|
/* 滚动条样式 */
|
||||||
.file-list::-webkit-scrollbar {
|
.file-list::-webkit-scrollbar {
|
||||||
width: 4px;
|
width: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list::-webkit-scrollbar-track {
|
.file-list::-webkit-scrollbar-track {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list::-webkit-scrollbar-thumb {
|
.file-list::-webkit-scrollbar-thumb {
|
||||||
background: var(--el-border-color-light);
|
background: var(--el-border-color-light);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list::-webkit-scrollbar-thumb:hover {
|
.file-list::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--el-border-color);
|
background: var(--el-border-color);
|
||||||
}
|
}
|
||||||
|
|
@ -378,17 +343,14 @@ onUnmounted(() => {
|
||||||
.file-list::-webkit-scrollbar {
|
.file-list::-webkit-scrollbar {
|
||||||
width: 4px;
|
width: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list::-webkit-scrollbar-track {
|
.file-list::-webkit-scrollbar-track {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list::-webkit-scrollbar-thumb {
|
.file-list::-webkit-scrollbar-thumb {
|
||||||
background: var(--el-border-color-light);
|
background: var(--el-border-color-light);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-list::-webkit-scrollbar-thumb:hover {
|
.file-list::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--el-border-color);
|
background: var(--el-border-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -27,16 +27,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getFileIcon, getFileNameFromUrl, isImage } from '@/utils/file'
|
import { getFileIcon, getFileNameFromUrl, isImage } from '@/utils/file'
|
||||||
|
|
||||||
defineOptions({ name: 'MessageFiles' })
|
defineOptions({ name: 'MessageFiles' })
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
attachmentUrls?: string[]
|
attachmentUrls?: string[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
/** 获取文件类型样式类 */
|
/** 获取文件类型样式类 */
|
||||||
const getFileTypeClass = (filename: string): string => {
|
const getFileTypeClass = (filename: string): string => {
|
||||||
const ext = filename.split('.').pop()?.toLowerCase() || ''
|
const ext = filename.split('.').pop()?.toLowerCase() || ''
|
||||||
|
|
@ -58,9 +54,8 @@ const getFileTypeClass = (filename: string): string => {
|
||||||
return 'bg-gradient-to-br from-gray-5 to-gray-7'
|
return 'bg-gradient-to-br from-gray-5 to-gray-7'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 点击文件 */
|
/** 点击文件 */
|
||||||
const handleFileClick = (url: string) => {
|
const handleFileClick = (url: string) => {
|
||||||
window.open(url, '_blank')
|
window.open(url, '_blank')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -19,7 +19,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 知识引用详情弹窗 -->
|
<!-- 知识引用详情弹窗 -->
|
||||||
<el-popover
|
<el-popover
|
||||||
v-model:visible="dialogVisible"
|
v-model:visible="dialogVisible"
|
||||||
|
|
@ -53,7 +52,6 @@
|
||||||
</template>
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
segments: {
|
segments: {
|
||||||
|
|
@ -63,7 +61,6 @@ const props = defineProps<{
|
||||||
content: string
|
content: string
|
||||||
}[]
|
}[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const document = ref<{
|
const document = ref<{
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
|
|
@ -74,11 +71,9 @@ const document = ref<{
|
||||||
} | null>(null) // 知识库文档列表
|
} | null>(null) // 知识库文档列表
|
||||||
const dialogVisible = ref(false) // 知识引用详情弹窗
|
const dialogVisible = ref(false) // 知识引用详情弹窗
|
||||||
const documentRef = ref<HTMLElement>() // 知识引用详情弹窗 Ref
|
const documentRef = ref<HTMLElement>() // 知识引用详情弹窗 Ref
|
||||||
|
|
||||||
/** 按照 document 聚合 segments */
|
/** 按照 document 聚合 segments */
|
||||||
const documentList = computed(() => {
|
const documentList = computed(() => {
|
||||||
if (!props.segments) return []
|
if (!props.segments) return []
|
||||||
|
|
||||||
const docMap = new Map()
|
const docMap = new Map()
|
||||||
props.segments.forEach((segment) => {
|
props.segments.forEach((segment) => {
|
||||||
if (!docMap.has(segment.documentId)) {
|
if (!docMap.has(segment.documentId)) {
|
||||||
|
|
@ -95,10 +90,9 @@ const documentList = computed(() => {
|
||||||
})
|
})
|
||||||
return Array.from(docMap.values())
|
return Array.from(docMap.values())
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 点击 document 处理 */
|
/** 点击 document 处理 */
|
||||||
const handleClick = (doc: any) => {
|
const handleClick = (doc: any) => {
|
||||||
document.value = doc
|
document.value = doc
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -124,18 +124,14 @@ import { ChatConversationVO } from '@/api/ai/chat/conversation'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
import userAvatarDefaultImg from '@/assets/imgs/avatar.gif'
|
import userAvatarDefaultImg from '@/assets/imgs/avatar.gif'
|
||||||
import roleAvatarDefaultImg from '@/assets/ai/gpt.svg'
|
import roleAvatarDefaultImg from '@/assets/ai/gpt.svg'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { copy } = useClipboard({ legacy: true }) // 初始化 copy 到粘贴板
|
const { copy } = useClipboard({ legacy: true }) // 初始化 copy 到粘贴板
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
// 判断“消息列表”滚动的位置(用于判断是否需要滚动到消息最下方)
|
// 判断“消息列表”滚动的位置(用于判断是否需要滚动到消息最下方)
|
||||||
const messageContainer: any = ref(null)
|
const messageContainer: any = ref(null)
|
||||||
const isScrolling = ref(false) //用于判断用户是否在滚动
|
const isScrolling = ref(false) //用于判断用户是否在滚动
|
||||||
|
|
||||||
const userAvatar = computed(() => userStore.user.avatar || userAvatarDefaultImg)
|
const userAvatar = computed(() => userStore.user.avatar || userAvatarDefaultImg)
|
||||||
const roleAvatar = computed(() => props.conversation.roleAvatar ?? roleAvatarDefaultImg)
|
const roleAvatar = computed(() => props.conversation.roleAvatar ?? roleAvatarDefaultImg)
|
||||||
|
|
||||||
// 定义 props
|
// 定义 props
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
conversation: {
|
conversation: {
|
||||||
|
|
@ -147,13 +143,9 @@ const props = defineProps({
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const { list } = toRefs(props) // 消息列表
|
const { list } = toRefs(props) // 消息列表
|
||||||
|
|
||||||
const emits = defineEmits(['onDeleteSuccess', 'onRefresh', 'onEdit']) // 定义 emits
|
const emits = defineEmits(['onDeleteSuccess', 'onRefresh', 'onEdit']) // 定义 emits
|
||||||
|
|
||||||
// ============ 处理对话滚动 ==============
|
// ============ 处理对话滚动 ==============
|
||||||
|
|
||||||
/** 滚动到底部 */
|
/** 滚动到底部 */
|
||||||
const scrollToBottom = async (isIgnore?: boolean) => {
|
const scrollToBottom = async (isIgnore?: boolean) => {
|
||||||
// 注意要使用 nextTick 以免获取不到 dom
|
// 注意要使用 nextTick 以免获取不到 dom
|
||||||
|
|
@ -163,7 +155,6 @@ const scrollToBottom = async (isIgnore?: boolean) => {
|
||||||
messageContainer.value.scrollHeight - messageContainer.value.offsetHeight
|
messageContainer.value.scrollHeight - messageContainer.value.offsetHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleScroll() {
|
function handleScroll() {
|
||||||
const scrollContainer = messageContainer.value
|
const scrollContainer = messageContainer.value
|
||||||
const scrollTop = scrollContainer.scrollTop
|
const scrollTop = scrollContainer.scrollTop
|
||||||
|
|
@ -177,29 +168,23 @@ function handleScroll() {
|
||||||
isScrolling.value = false
|
isScrolling.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 回到底部 */
|
/** 回到底部 */
|
||||||
const handleGoBottom = async () => {
|
const handleGoBottom = async () => {
|
||||||
const scrollContainer = messageContainer.value
|
const scrollContainer = messageContainer.value
|
||||||
scrollContainer.scrollTop = scrollContainer.scrollHeight
|
scrollContainer.scrollTop = scrollContainer.scrollHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 回到顶部 */
|
/** 回到顶部 */
|
||||||
const handlerGoTop = async () => {
|
const handlerGoTop = async () => {
|
||||||
const scrollContainer = messageContainer.value
|
const scrollContainer = messageContainer.value
|
||||||
scrollContainer.scrollTop = 0
|
scrollContainer.scrollTop = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ scrollToBottom, handlerGoTop }) // 提供方法给 parent 调用
|
defineExpose({ scrollToBottom, handlerGoTop }) // 提供方法给 parent 调用
|
||||||
|
|
||||||
// ============ 处理消息操作 ==============
|
// ============ 处理消息操作 ==============
|
||||||
|
|
||||||
/** 复制 */
|
/** 复制 */
|
||||||
const copyContent = async (content: string) => {
|
const copyContent = async (content: string) => {
|
||||||
await copy(content)
|
await copy(content)
|
||||||
message.success('复制成功!')
|
message.success('复制成功!')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除 */
|
/** 删除 */
|
||||||
const onDelete = async (id) => {
|
const onDelete = async (id) => {
|
||||||
// 删除 message
|
// 删除 message
|
||||||
|
|
@ -208,19 +193,16 @@ const onDelete = async (id) => {
|
||||||
// 回调
|
// 回调
|
||||||
emits('onDeleteSuccess')
|
emits('onDeleteSuccess')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 刷新 */
|
/** 刷新 */
|
||||||
const onRefresh = async (message: ChatMessageVO) => {
|
const onRefresh = async (message: ChatMessageVO) => {
|
||||||
emits('onRefresh', message)
|
emits('onRefresh', message)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑 */
|
/** 编辑 */
|
||||||
const onEdit = async (message: ChatMessageVO) => {
|
const onEdit = async (message: ChatMessageVO) => {
|
||||||
emits('onEdit', message)
|
emits('onEdit', message)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
messageContainer.value.addEventListener('scroll', handleScroll)
|
messageContainer.value.addEventListener('scroll', handleScroll)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -26,11 +26,9 @@ const promptList = [
|
||||||
prompt: '写一首好听的诗歌?'
|
prompt: '写一首好听的诗歌?'
|
||||||
}
|
}
|
||||||
] // prompt 列表
|
] // prompt 列表
|
||||||
|
|
||||||
const emits = defineEmits(['onPrompt'])
|
const emits = defineEmits(['onPrompt'])
|
||||||
|
|
||||||
/** 选中 prompt 点击 */
|
/** 选中 prompt 点击 */
|
||||||
const handlerPromptClick = async ({ prompt }) => {
|
const handlerPromptClick = async ({ prompt }) => {
|
||||||
emits('onPrompt', prompt)
|
emits('onPrompt', prompt)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -3,4 +3,4 @@
|
||||||
<div class="p-30px">
|
<div class="p-30px">
|
||||||
<el-skeleton animated />
|
<el-skeleton animated />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -11,9 +11,8 @@
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const emits = defineEmits(['onNewConversation'])
|
const emits = defineEmits(['onNewConversation'])
|
||||||
|
|
||||||
/** 新建 conversation 聊天对话 */
|
/** 新建 conversation 聊天对话 */
|
||||||
const handlerNewChat = () => {
|
const handlerNewChat = () => {
|
||||||
emits('onNewConversation')
|
emits('onNewConversation')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -19,7 +19,6 @@
|
||||||
<ArrowDown />
|
<ArrowDown />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 推理内容区域 -->
|
<!-- 推理内容区域 -->
|
||||||
<div
|
<div
|
||||||
v-show="isExpanded"
|
v-show="isExpanded"
|
||||||
|
|
@ -33,25 +32,20 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { ArrowDown, ChatDotSquare } from '@element-plus/icons-vue'
|
import { ArrowDown, ChatDotSquare } from '@element-plus/icons-vue'
|
||||||
import MarkdownView from '@/components/MarkdownView/index.vue'
|
import MarkdownView from '@/components/MarkdownView/index.vue'
|
||||||
|
|
||||||
// 定义 props
|
// 定义 props
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
reasoningContent?: string
|
reasoningContent?: string
|
||||||
content?: string
|
content?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const isExpanded = ref(true) // 默认展开
|
const isExpanded = ref(true) // 默认展开
|
||||||
|
|
||||||
/** 计算属性:判断是否应该显示组件(有思考内容时,则展示) */
|
/** 计算属性:判断是否应该显示组件(有思考内容时,则展示) */
|
||||||
const shouldShowComponent = computed(() => {
|
const shouldShowComponent = computed(() => {
|
||||||
return !(!props.reasoningContent || props.reasoningContent.trim() === '')
|
return !(!props.reasoningContent || props.reasoningContent.trim() === '')
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 计算属性:标题文本 */
|
/** 计算属性:标题文本 */
|
||||||
const titleText = computed(() => {
|
const titleText = computed(() => {
|
||||||
const hasReasoningContent = props.reasoningContent && props.reasoningContent.trim() !== ''
|
const hasReasoningContent = props.reasoningContent && props.reasoningContent.trim() !== ''
|
||||||
|
|
@ -61,29 +55,24 @@ const titleText = computed(() => {
|
||||||
}
|
}
|
||||||
return '已深度思考'
|
return '已深度思考'
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 切换展开/收缩状态 */
|
/** 切换展开/收缩状态 */
|
||||||
const toggleExpanded = () => {
|
const toggleExpanded = () => {
|
||||||
isExpanded.value = !isExpanded.value
|
isExpanded.value = !isExpanded.value
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 自定义滚动条样式 */
|
/* 自定义滚动条样式 */
|
||||||
.max-h-300px::-webkit-scrollbar {
|
.max-h-300px::-webkit-scrollbar {
|
||||||
width: 4px;
|
width: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.max-h-300px::-webkit-scrollbar-track {
|
.max-h-300px::-webkit-scrollbar-track {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.max-h-300px::-webkit-scrollbar-thumb {
|
.max-h-300px::-webkit-scrollbar-thumb {
|
||||||
background: rgba(156, 163, 175, 0.4);
|
background: rgba(156, 163, 175, 0.4);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.max-h-300px::-webkit-scrollbar-thumb:hover {
|
.max-h-300px::-webkit-scrollbar-thumb:hover {
|
||||||
background: rgba(156, 163, 175, 0.6);
|
background: rgba(156, 163, 175, 0.6);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -19,7 +19,6 @@
|
||||||
class="text-12px transition-transform duration-200"
|
class="text-12px transition-transform duration-200"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 可展开的搜索结果列表 -->
|
<!-- 可展开的搜索结果列表 -->
|
||||||
<div v-show="isExpanded" class="flex flex-col gap-8px transition-all duration-200 ease-in-out">
|
<div v-show="isExpanded" class="flex flex-col gap-8px transition-all duration-200 ease-in-out">
|
||||||
<div
|
<div
|
||||||
|
|
@ -40,24 +39,20 @@
|
||||||
/>
|
/>
|
||||||
<Icon v-else icon="ep:link" class="w-full h-full text-[#666]" />
|
<Icon v-else icon="ep:link" class="w-full h-full text-[#666]" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<!-- 标题和来源 -->
|
<!-- 标题和来源 -->
|
||||||
<div class="flex items-center gap-4px mb-4px">
|
<div class="flex items-center gap-4px mb-4px">
|
||||||
<span class="text-12px text-[#999] truncate">{{ result.name }}</span>
|
<span class="text-12px text-[#999] truncate">{{ result.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 主标题 -->
|
<!-- 主标题 -->
|
||||||
<div class="text-14px text-[#1a73e8] font-medium mb-4px line-clamp-2 leading-[1.4]">
|
<div class="text-14px text-[#1a73e8] font-medium mb-4px line-clamp-2 leading-[1.4]">
|
||||||
{{ result.title }}
|
{{ result.title }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 描述 -->
|
<!-- 描述 -->
|
||||||
<div class="text-13px text-[#666] line-clamp-2 leading-[1.4] mb-4px">
|
<div class="text-13px text-[#666] line-clamp-2 leading-[1.4] mb-4px">
|
||||||
{{ result.snippet }}
|
{{ result.snippet }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- URL -->
|
<!-- URL -->
|
||||||
<div class="text-12px text-[#006621] truncate">
|
<div class="text-12px text-[#006621] truncate">
|
||||||
{{ result.url }}
|
{{ result.url }}
|
||||||
|
|
@ -67,7 +62,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 联网搜索详情弹窗 -->
|
<!-- 联网搜索详情弹窗 -->
|
||||||
<el-popover
|
<el-popover
|
||||||
v-model:visible="dialogVisible"
|
v-model:visible="dialogVisible"
|
||||||
|
|
@ -102,7 +96,6 @@
|
||||||
<div class="text-12px text-[#006621] break-all">{{ selectedResult.url }}</div>
|
<div class="text-12px text-[#006621] break-all">{{ selectedResult.url }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<div class="max-h-[60vh] overflow-y-auto">
|
<div class="max-h-[60vh] overflow-y-auto">
|
||||||
<!-- 简短描述 -->
|
<!-- 简短描述 -->
|
||||||
|
|
@ -112,7 +105,6 @@
|
||||||
{{ selectedResult.snippet }}
|
{{ selectedResult.snippet }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 内容摘要 -->
|
<!-- 内容摘要 -->
|
||||||
<div v-if="selectedResult.summary">
|
<div v-if="selectedResult.summary">
|
||||||
<div class="text-14px font-medium text-[#333] mb-6px">内容摘要</div>
|
<div class="text-14px font-medium text-[#333] mb-6px">内容摘要</div>
|
||||||
|
|
@ -123,7 +115,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<div class="flex justify-end gap-8px mt-12px pt-12px border-t border-[#eee]">
|
<div class="flex justify-end gap-8px mt-12px pt-12px border-t border-[#eee]">
|
||||||
<el-button size="small" @click="dialogVisible = false">关闭</el-button>
|
<el-button size="small" @click="dialogVisible = false">关闭</el-button>
|
||||||
|
|
@ -135,7 +126,6 @@
|
||||||
</template>
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
defineProps<{
|
||||||
webSearchPages: {
|
webSearchPages: {
|
||||||
|
|
@ -147,7 +137,6 @@ defineProps<{
|
||||||
summary: string // 内容的文本摘要
|
summary: string // 内容的文本摘要
|
||||||
}[]
|
}[]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const isExpanded = ref(false) // 是否展开搜索结果
|
const isExpanded = ref(false) // 是否展开搜索结果
|
||||||
const selectedResult = ref<{
|
const selectedResult = ref<{
|
||||||
name: string
|
name: string
|
||||||
|
|
@ -159,32 +148,27 @@ const selectedResult = ref<{
|
||||||
} | null>(null) // 选中的搜索结果
|
} | null>(null) // 选中的搜索结果
|
||||||
const dialogVisible = ref(false) // 详情弹窗
|
const dialogVisible = ref(false) // 详情弹窗
|
||||||
const resultRef = ref<HTMLElement>() // 详情弹窗 Ref
|
const resultRef = ref<HTMLElement>() // 详情弹窗 Ref
|
||||||
|
|
||||||
/** 切换展开/收起状态 */
|
/** 切换展开/收起状态 */
|
||||||
const toggleExpanded = () => {
|
const toggleExpanded = () => {
|
||||||
isExpanded.value = !isExpanded.value
|
isExpanded.value = !isExpanded.value
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 点击搜索结果处理 */
|
/** 点击搜索结果处理 */
|
||||||
const handleClick = (result: any) => {
|
const handleClick = (result: any) => {
|
||||||
selectedResult.value = result
|
selectedResult.value = result
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理图片加载错误 */
|
/** 处理图片加载错误 */
|
||||||
const handleImageError = (event: Event) => {
|
const handleImageError = (event: Event) => {
|
||||||
const img = event.target as HTMLImageElement
|
const img = event.target as HTMLImageElement
|
||||||
img.style.display = 'none'
|
img.style.display = 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 打开URL */
|
/** 打开URL */
|
||||||
const openUrl = (url: string) => {
|
const openUrl = (url: string) => {
|
||||||
window.open(url, '_blank')
|
window.open(url, '_blank')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.web-search-popover {
|
.web-search-popover {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
|
|
||||||
// 定义属性
|
// 定义属性
|
||||||
defineProps({
|
defineProps({
|
||||||
categoryList: {
|
categoryList: {
|
||||||
|
|
@ -28,12 +27,10 @@ defineProps({
|
||||||
default: '全部'
|
default: '全部'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 定义回调
|
// 定义回调
|
||||||
const emits = defineEmits(['onCategoryClick'])
|
const emits = defineEmits(['onCategoryClick'])
|
||||||
|
|
||||||
/** 处理分类点击事件 */
|
/** 处理分类点击事件 */
|
||||||
const handleCategoryClick = async (category: string) => {
|
const handleCategoryClick = async (category: string) => {
|
||||||
emits('onCategoryClick', category)
|
emits('onCategoryClick', category)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -50,14 +50,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ChatRoleVO } from '@/api/ai/model/chatRole'
|
import { ChatRoleVO } from '@/api/ai/model/chatRole'
|
||||||
import { PropType, ref } from 'vue'
|
import { PropType, ref } from 'vue'
|
||||||
import { More } from '@element-plus/icons-vue'
|
import { More } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
const tabsRef = ref<any>() // tabs ref
|
const tabsRef = ref<any>() // tabs ref
|
||||||
|
|
||||||
// 定义属性
|
// 定义属性
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
loading: {
|
loading: {
|
||||||
|
|
@ -74,10 +71,8 @@ const props = defineProps({
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 定义钩子
|
// 定义钩子
|
||||||
const emits = defineEmits(['onDelete', 'onEdit', 'onUse', 'onPage'])
|
const emits = defineEmits(['onDelete', 'onEdit', 'onUse', 'onPage'])
|
||||||
|
|
||||||
/** 操作:编辑、删除 */
|
/** 操作:编辑、删除 */
|
||||||
const handleMoreClick = async (data) => {
|
const handleMoreClick = async (data) => {
|
||||||
const type = data[0]
|
const type = data[0]
|
||||||
|
|
@ -88,12 +83,10 @@ const handleMoreClick = async (data) => {
|
||||||
emits('onEdit', role)
|
emits('onEdit', role)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 选中 */
|
/** 选中 */
|
||||||
const handleUseClick = (role: any) => {
|
const handleUseClick = (role: any) => {
|
||||||
emits('onUse', role)
|
emits('onUse', role)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 滚动 */
|
/** 滚动 */
|
||||||
const handleTabsScroll = async () => {
|
const handleTabsScroll = async () => {
|
||||||
if (tabsRef.value) {
|
if (tabsRef.value) {
|
||||||
|
|
@ -103,4 +96,4 @@ const handleTabsScroll = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -60,7 +60,6 @@
|
||||||
</el-main>
|
</el-main>
|
||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import RoleList from './RoleList.vue'
|
import RoleList from './RoleList.vue'
|
||||||
|
|
@ -71,11 +70,9 @@ import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversat
|
||||||
import { Search } from '@element-plus/icons-vue'
|
import { Search } from '@element-plus/icons-vue'
|
||||||
import { TabsPaneContext } from 'element-plus'
|
import { TabsPaneContext } from 'element-plus'
|
||||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||||
|
|
||||||
const router = useRouter() // 路由对象
|
const router = useRouter() // 路由对象
|
||||||
const { currentRoute } = useRouter() // 路由
|
const { currentRoute } = useRouter() // 路由
|
||||||
const { delView } = useTagsViewStore() // 视图操作
|
const { delView } = useTagsViewStore() // 视图操作
|
||||||
|
|
||||||
// 属性定义
|
// 属性定义
|
||||||
const loading = ref<boolean>(false) // 加载中
|
const loading = ref<boolean>(false) // 加载中
|
||||||
const activeTab = ref<string>('my-role') // 选中的角色 Tab
|
const activeTab = ref<string>('my-role') // 选中的角色 Tab
|
||||||
|
|
@ -92,7 +89,6 @@ const publicRoleParams = reactive({
|
||||||
const publicRoleList = ref<ChatRoleVO[]>([]) // public 分页大小
|
const publicRoleList = ref<ChatRoleVO[]>([]) // public 分页大小
|
||||||
const activeCategory = ref<string>('全部') // 选择中的分类
|
const activeCategory = ref<string>('全部') // 选择中的分类
|
||||||
const categoryList = ref<string[]>([]) // 角色分类类别
|
const categoryList = ref<string[]>([]) // 角色分类类别
|
||||||
|
|
||||||
/** tabs 点击 */
|
/** tabs 点击 */
|
||||||
const handleTabsClick = async (tab: TabsPaneContext) => {
|
const handleTabsClick = async (tab: TabsPaneContext) => {
|
||||||
// 设置切换状态
|
// 设置切换状态
|
||||||
|
|
@ -100,7 +96,6 @@ const handleTabsClick = async (tab: TabsPaneContext) => {
|
||||||
// 切换的时候重新加载数据
|
// 切换的时候重新加载数据
|
||||||
await getActiveTabsRole()
|
await getActiveTabsRole()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取 my role 我的角色 */
|
/** 获取 my role 我的角色 */
|
||||||
const getMyRole = async (append?: boolean) => {
|
const getMyRole = async (append?: boolean) => {
|
||||||
const params: ChatRolePageReqVO = {
|
const params: ChatRolePageReqVO = {
|
||||||
|
|
@ -115,7 +110,6 @@ const getMyRole = async (append?: boolean) => {
|
||||||
myRoleList.value = list
|
myRoleList.value = list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取 public role 公共角色 */
|
/** 获取 public role 公共角色 */
|
||||||
const getPublicRole = async (append?: boolean) => {
|
const getPublicRole = async (append?: boolean) => {
|
||||||
const params: ChatRolePageReqVO = {
|
const params: ChatRolePageReqVO = {
|
||||||
|
|
@ -131,7 +125,6 @@ const getPublicRole = async (append?: boolean) => {
|
||||||
publicRoleList.value = list
|
publicRoleList.value = list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取选中的 tabs 角色 */
|
/** 获取选中的 tabs 角色 */
|
||||||
const getActiveTabsRole = async () => {
|
const getActiveTabsRole = async () => {
|
||||||
if (activeTab.value === 'my-role') {
|
if (activeTab.value === 'my-role') {
|
||||||
|
|
@ -142,12 +135,10 @@ const getActiveTabsRole = async () => {
|
||||||
await getPublicRole()
|
await getPublicRole()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取角色分类列表 */
|
/** 获取角色分类列表 */
|
||||||
const getRoleCategoryList = async () => {
|
const getRoleCategoryList = async () => {
|
||||||
categoryList.value = ['全部', ...(await ChatRoleApi.getCategoryList())]
|
categoryList.value = ['全部', ...(await ChatRoleApi.getCategoryList())]
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理分类点击 */
|
/** 处理分类点击 */
|
||||||
const handlerCategoryClick = async (category: string) => {
|
const handlerCategoryClick = async (category: string) => {
|
||||||
// 切换选择的分类
|
// 切换选择的分类
|
||||||
|
|
@ -155,7 +146,6 @@ const handlerCategoryClick = async (category: string) => {
|
||||||
// 筛选
|
// 筛选
|
||||||
await getActiveTabsRole()
|
await getActiveTabsRole()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const handlerAddRole = async () => {
|
const handlerAddRole = async () => {
|
||||||
|
|
@ -165,20 +155,17 @@ const handlerAddRole = async () => {
|
||||||
const handlerCardEdit = async (role) => {
|
const handlerCardEdit = async (role) => {
|
||||||
formRef.value.open('my-update', role.id, '编辑角色')
|
formRef.value.open('my-update', role.id, '编辑角色')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加角色成功 */
|
/** 添加角色成功 */
|
||||||
const handlerAddRoleSuccess = async (e) => {
|
const handlerAddRoleSuccess = async (e) => {
|
||||||
// 刷新数据
|
// 刷新数据
|
||||||
await getActiveTabsRole()
|
await getActiveTabsRole()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除角色 */
|
/** 删除角色 */
|
||||||
const handlerCardDelete = async (role) => {
|
const handlerCardDelete = async (role) => {
|
||||||
await ChatRoleApi.deleteMy(role.id)
|
await ChatRoleApi.deleteMy(role.id)
|
||||||
// 刷新数据
|
// 刷新数据
|
||||||
await getActiveTabsRole()
|
await getActiveTabsRole()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 角色分页:获取下一页 */
|
/** 角色分页:获取下一页 */
|
||||||
const handlerCardPage = async (type) => {
|
const handlerCardPage = async (type) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -194,7 +181,6 @@ const handlerCardPage = async (type) => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 选择 card 角色:新建聊天对话 */
|
/** 选择 card 角色:新建聊天对话 */
|
||||||
const handlerCardUse = async (role) => {
|
const handlerCardUse = async (role) => {
|
||||||
// 1. 创建对话
|
// 1. 创建对话
|
||||||
|
|
@ -202,7 +188,6 @@ const handlerCardUse = async (role) => {
|
||||||
roleId: role.id
|
roleId: role.id
|
||||||
} as unknown as ChatConversationVO
|
} as unknown as ChatConversationVO
|
||||||
const conversationId = await ChatConversationApi.createChatConversationMy(data)
|
const conversationId = await ChatConversationApi.createChatConversationMy(data)
|
||||||
|
|
||||||
// 2. 跳转页面
|
// 2. 跳转页面
|
||||||
delView(unref(currentRoute))
|
delView(unref(currentRoute))
|
||||||
await router.replace({
|
await router.replace({
|
||||||
|
|
@ -212,7 +197,6 @@ const handlerCardUse = async (role) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 获取分类
|
// 获取分类
|
||||||
|
|
@ -226,21 +210,17 @@ onMounted(async () => {
|
||||||
.el-tabs__nav-scroll {
|
.el-tabs__nav-scroll {
|
||||||
margin: 2px 8px !important;
|
margin: 2px 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tabs__header {
|
.el-tabs__header {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tabs__nav-wrap {
|
.el-tabs__nav-wrap {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tabs__content {
|
.el-tabs__content {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-tab-pane {
|
.el-tab-pane {
|
||||||
padding: 8px 0 0 0 !important;
|
padding: 8px 0 0 0 !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -37,7 +37,6 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-header>
|
</el-header>
|
||||||
|
|
||||||
<!-- main:消息列表 -->
|
<!-- main:消息列表 -->
|
||||||
<el-main class="m-0 p-0 relative h-full w-full">
|
<el-main class="m-0 p-0 relative h-full w-full">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -67,7 +66,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-main>
|
</el-main>
|
||||||
|
|
||||||
<!-- 底部 -->
|
<!-- 底部 -->
|
||||||
<el-footer class="flex flex-col !h-auto !p-0">
|
<el-footer class="flex flex-col !h-auto !p-0">
|
||||||
<!-- TODO @芋艿:这块要想办法迁移下! -->
|
<!-- TODO @芋艿:这块要想办法迁移下! -->
|
||||||
|
|
@ -114,7 +112,6 @@
|
||||||
</form>
|
</form>
|
||||||
</el-footer>
|
</el-footer>
|
||||||
</el-container>
|
</el-container>
|
||||||
|
|
||||||
<!-- 更新对话 Form -->
|
<!-- 更新对话 Form -->
|
||||||
<ConversationUpdateForm
|
<ConversationUpdateForm
|
||||||
ref="conversationUpdateFormRef"
|
ref="conversationUpdateFormRef"
|
||||||
|
|
@ -122,7 +119,6 @@
|
||||||
/>
|
/>
|
||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message'
|
import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message'
|
||||||
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
||||||
|
|
@ -133,19 +129,15 @@ import MessageListEmpty from './components/message/MessageListEmpty.vue'
|
||||||
import MessageLoading from './components/message/MessageLoading.vue'
|
import MessageLoading from './components/message/MessageLoading.vue'
|
||||||
import MessageNewConversation from './components/message/MessageNewConversation.vue'
|
import MessageNewConversation from './components/message/MessageNewConversation.vue'
|
||||||
import MessageFileUpload from './components/message/MessageFileUpload.vue'
|
import MessageFileUpload from './components/message/MessageFileUpload.vue'
|
||||||
|
|
||||||
/** AI 聊天对话 列表 */
|
/** AI 聊天对话 列表 */
|
||||||
defineOptions({ name: 'AiChat' })
|
defineOptions({ name: 'AiChat' })
|
||||||
|
|
||||||
const route = useRoute() // 路由
|
const route = useRoute() // 路由
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
// 聊天对话
|
// 聊天对话
|
||||||
const conversationListRef = ref()
|
const conversationListRef = ref()
|
||||||
const activeConversationId = ref<number | null>(null) // 选中的对话编号
|
const activeConversationId = ref<number | null>(null) // 选中的对话编号
|
||||||
const activeConversation = ref<ChatConversationVO | null>(null) // 选中的 Conversation
|
const activeConversation = ref<ChatConversationVO | null>(null) // 选中的 Conversation
|
||||||
const conversationInProgress = ref(false) // 对话是否正在进行中。目前只有【发送】消息时,会更新为 true,避免切换对话、删除对话等操作
|
const conversationInProgress = ref(false) // 对话是否正在进行中。目前只有【发送】消息时,会更新为 true,避免切换对话、删除对话等操作
|
||||||
|
|
||||||
// 消息列表
|
// 消息列表
|
||||||
const messageRef = ref()
|
const messageRef = ref()
|
||||||
const activeMessageList = ref<ChatMessageVO[]>([]) // 选中对话的消息列表
|
const activeMessageList = ref<ChatMessageVO[]>([]) // 选中对话的消息列表
|
||||||
|
|
@ -154,7 +146,6 @@ const activeMessageListLoadingTimer = ref<any>() // activeMessageListLoading Tim
|
||||||
// 消息滚动
|
// 消息滚动
|
||||||
const textSpeed = ref<number>(50) // Typing speed in milliseconds
|
const textSpeed = ref<number>(50) // Typing speed in milliseconds
|
||||||
const textRoleRunning = ref<boolean>(false) // Typing speed in milliseconds
|
const textRoleRunning = ref<boolean>(false) // Typing speed in milliseconds
|
||||||
|
|
||||||
// 发送消息输入框
|
// 发送消息输入框
|
||||||
const isComposing = ref(false) // 判断用户是否在输入
|
const isComposing = ref(false) // 判断用户是否在输入
|
||||||
const conversationInAbortController = ref<any>() // 对话进行中 abort 控制器(控制 stream 对话)
|
const conversationInAbortController = ref<any>() // 对话进行中 abort 控制器(控制 stream 对话)
|
||||||
|
|
@ -166,9 +157,7 @@ const uploadFiles = ref<string[]>([]) // 上传的文件 URL 列表
|
||||||
// 接收 Stream 消息
|
// 接收 Stream 消息
|
||||||
const receiveMessageFullText = ref('')
|
const receiveMessageFullText = ref('')
|
||||||
const receiveMessageDisplayedText = ref('')
|
const receiveMessageDisplayedText = ref('')
|
||||||
|
|
||||||
// =========== 【聊天对话】相关 ===========
|
// =========== 【聊天对话】相关 ===========
|
||||||
|
|
||||||
/** 获取对话信息 */
|
/** 获取对话信息 */
|
||||||
const getConversation = async (id: number | null) => {
|
const getConversation = async (id: number | null) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
|
|
@ -181,7 +170,6 @@ const getConversation = async (id: number | null) => {
|
||||||
activeConversation.value = conversation
|
activeConversation.value = conversation
|
||||||
activeConversationId.value = conversation.id
|
activeConversationId.value = conversation.id
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 点击某个对话
|
* 点击某个对话
|
||||||
*
|
*
|
||||||
|
|
@ -194,7 +182,6 @@ const handleConversationClick = async (conversation: ChatConversationVO) => {
|
||||||
message.alert('对话中,不允许切换!')
|
message.alert('对话中,不允许切换!')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新选中的对话 id
|
// 更新选中的对话 id
|
||||||
activeConversationId.value = conversation.id
|
activeConversationId.value = conversation.id
|
||||||
activeConversation.value = conversation
|
activeConversation.value = conversation
|
||||||
|
|
@ -208,7 +195,6 @@ const handleConversationClick = async (conversation: ChatConversationVO) => {
|
||||||
uploadFiles.value = []
|
uploadFiles.value = []
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除某个对话*/
|
/** 删除某个对话*/
|
||||||
const handlerConversationDelete = async (delConversation: ChatConversationVO) => {
|
const handlerConversationDelete = async (delConversation: ChatConversationVO) => {
|
||||||
// 删除的对话如果是当前选中的,那么就重置
|
// 删除的对话如果是当前选中的,那么就重置
|
||||||
|
|
@ -227,7 +213,6 @@ const handleConversationClear = async () => {
|
||||||
activeConversation.value = null
|
activeConversation.value = null
|
||||||
activeMessageList.value = []
|
activeMessageList.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改聊天对话 */
|
/** 修改聊天对话 */
|
||||||
const conversationUpdateFormRef = ref()
|
const conversationUpdateFormRef = ref()
|
||||||
const openChatConversationUpdateForm = async () => {
|
const openChatConversationUpdateForm = async () => {
|
||||||
|
|
@ -237,7 +222,6 @@ const handleConversationUpdateSuccess = async () => {
|
||||||
// 对话更新成功,刷新最新信息
|
// 对话更新成功,刷新最新信息
|
||||||
await getConversation(activeConversationId.value)
|
await getConversation(activeConversationId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理聊天对话的创建成功 */
|
/** 处理聊天对话的创建成功 */
|
||||||
const handleConversationCreate = async () => {
|
const handleConversationCreate = async () => {
|
||||||
// 创建对话
|
// 创建对话
|
||||||
|
|
@ -250,9 +234,7 @@ const handleConversationCreateSuccess = async () => {
|
||||||
// 清空文件列表
|
// 清空文件列表
|
||||||
uploadFiles.value = []
|
uploadFiles.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========== 【消息列表】相关 ===========
|
// =========== 【消息列表】相关 ===========
|
||||||
|
|
||||||
/** 获取消息 message 列表 */
|
/** 获取消息 message 列表 */
|
||||||
const getMessageList = async () => {
|
const getMessageList = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -263,12 +245,10 @@ const getMessageList = async () => {
|
||||||
activeMessageListLoadingTimer.value = setTimeout(() => {
|
activeMessageListLoadingTimer.value = setTimeout(() => {
|
||||||
activeMessageListLoading.value = true
|
activeMessageListLoading.value = true
|
||||||
}, 60)
|
}, 60)
|
||||||
|
|
||||||
// 获取消息列表
|
// 获取消息列表
|
||||||
activeMessageList.value = await ChatMessageApi.getChatMessageListByConversationId(
|
activeMessageList.value = await ChatMessageApi.getChatMessageListByConversationId(
|
||||||
activeConversationId.value
|
activeConversationId.value
|
||||||
)
|
)
|
||||||
|
|
||||||
// 滚动到最下面
|
// 滚动到最下面
|
||||||
await nextTick()
|
await nextTick()
|
||||||
await scrollToBottom()
|
await scrollToBottom()
|
||||||
|
|
@ -281,7 +261,6 @@ const getMessageList = async () => {
|
||||||
activeMessageListLoading.value = false
|
activeMessageListLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息列表
|
* 消息列表
|
||||||
*
|
*
|
||||||
|
|
@ -312,7 +291,6 @@ const messageList = computed(() => {
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 处理删除 message 消息 */
|
/** 处理删除 message 消息 */
|
||||||
const handleMessageDelete = () => {
|
const handleMessageDelete = () => {
|
||||||
if (conversationInProgress.value) {
|
if (conversationInProgress.value) {
|
||||||
|
|
@ -322,7 +300,6 @@ const handleMessageDelete = () => {
|
||||||
// 刷新 message 列表
|
// 刷新 message 列表
|
||||||
getMessageList()
|
getMessageList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理 message 清空 */
|
/** 处理 message 清空 */
|
||||||
const handlerMessageClear = async () => {
|
const handlerMessageClear = async () => {
|
||||||
if (!activeConversationId.value) {
|
if (!activeConversationId.value) {
|
||||||
|
|
@ -337,14 +314,11 @@ const handlerMessageClear = async () => {
|
||||||
activeMessageList.value = []
|
activeMessageList.value = []
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 回到 message 列表的顶部 */
|
/** 回到 message 列表的顶部 */
|
||||||
const handleGoTopMessage = () => {
|
const handleGoTopMessage = () => {
|
||||||
messageRef.value.handlerGoTop()
|
messageRef.value.handlerGoTop()
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========== 【发送消息】相关 ===========
|
// =========== 【发送消息】相关 ===========
|
||||||
|
|
||||||
/** 处理来自 keydown 的发送消息 */
|
/** 处理来自 keydown 的发送消息 */
|
||||||
const handleSendByKeydown = async (event) => {
|
const handleSendByKeydown = async (event) => {
|
||||||
// 判断用户是否在输入
|
// 判断用户是否在输入
|
||||||
|
|
@ -368,12 +342,10 @@ const handleSendByKeydown = async (event) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理来自【发送】按钮的发送消息 */
|
/** 处理来自【发送】按钮的发送消息 */
|
||||||
const handleSendByButton = () => {
|
const handleSendByButton = () => {
|
||||||
doSendMessage(prompt.value?.trim() as string)
|
doSendMessage(prompt.value?.trim() as string)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理 prompt 输入变化 */
|
/** 处理 prompt 输入变化 */
|
||||||
const handlePromptInput = (event) => {
|
const handlePromptInput = (event) => {
|
||||||
// 非输入法 输入设置为 true
|
// 非输入法 输入设置为 true
|
||||||
|
|
@ -403,7 +375,6 @@ const onCompositionend = () => {
|
||||||
isComposing.value = false
|
isComposing.value = false
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 真正执行【发送】消息操作 */
|
/** 真正执行【发送】消息操作 */
|
||||||
const doSendMessage = async (content: string) => {
|
const doSendMessage = async (content: string) => {
|
||||||
// 校验
|
// 校验
|
||||||
|
|
@ -415,14 +386,11 @@ const doSendMessage = async (content: string) => {
|
||||||
message.error('还没创建对话,不能发送!')
|
message.error('还没创建对话,不能发送!')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 准备附件 URL 数组
|
// 准备附件 URL 数组
|
||||||
const attachmentUrls = [...uploadFiles.value]
|
const attachmentUrls = [...uploadFiles.value]
|
||||||
|
|
||||||
// 清空输入框和文件列表
|
// 清空输入框和文件列表
|
||||||
prompt.value = ''
|
prompt.value = ''
|
||||||
uploadFiles.value = []
|
uploadFiles.value = []
|
||||||
|
|
||||||
// 执行发送
|
// 执行发送
|
||||||
await doSendMessageStream({
|
await doSendMessageStream({
|
||||||
conversationId: activeConversationId.value,
|
conversationId: activeConversationId.value,
|
||||||
|
|
@ -430,7 +398,6 @@ const doSendMessage = async (content: string) => {
|
||||||
attachmentUrls: attachmentUrls
|
attachmentUrls: attachmentUrls
|
||||||
} as ChatMessageVO)
|
} as ChatMessageVO)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 真正执行【发送】消息操作 */
|
/** 真正执行【发送】消息操作 */
|
||||||
const doSendMessageStream = async (userMessage: ChatMessageVO) => {
|
const doSendMessageStream = async (userMessage: ChatMessageVO) => {
|
||||||
// 创建 AbortController 实例,以便中止请求
|
// 创建 AbortController 实例,以便中止请求
|
||||||
|
|
@ -439,7 +406,6 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
|
||||||
conversationInProgress.value = true
|
conversationInProgress.value = true
|
||||||
// 设置为空
|
// 设置为空
|
||||||
receiveMessageFullText.value = ''
|
receiveMessageFullText.value = ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1.1 先添加两个假数据,等 stream 返回再替换
|
// 1.1 先添加两个假数据,等 stream 返回再替换
|
||||||
activeMessageList.value.push({
|
activeMessageList.value.push({
|
||||||
|
|
@ -463,7 +429,6 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
|
||||||
await scrollToBottom() // 底部
|
await scrollToBottom() // 底部
|
||||||
// 1.3 开始滚动
|
// 1.3 开始滚动
|
||||||
textRoll()
|
textRoll()
|
||||||
|
|
||||||
// 2. 发送 event stream
|
// 2. 发送 event stream
|
||||||
let isFirstChunk = true // 是否是第一个 chunk 消息段
|
let isFirstChunk = true // 是否是第一个 chunk 消息段
|
||||||
await ChatMessageApi.sendChatMessageStream(
|
await ChatMessageApi.sendChatMessageStream(
|
||||||
|
|
@ -482,12 +447,10 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果内容为空,就不处理。
|
// 如果内容为空,就不处理。
|
||||||
if (data.receive.content === '' && !data.receive.reasoningContent) {
|
if (data.receive.content === '' && !data.receive.reasoningContent) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 首次返回需要添加一个 message 到页面,后面的都是更新
|
// 首次返回需要添加一个 message 到页面,后面的都是更新
|
||||||
if (isFirstChunk) {
|
if (isFirstChunk) {
|
||||||
isFirstChunk = false
|
isFirstChunk = false
|
||||||
|
|
@ -499,14 +462,12 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
|
||||||
data.send.attachmentUrls = userMessage.attachmentUrls
|
data.send.attachmentUrls = userMessage.attachmentUrls
|
||||||
activeMessageList.value.push(data.receive)
|
activeMessageList.value.push(data.receive)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理 reasoningContent
|
// 处理 reasoningContent
|
||||||
if (data.receive.reasoningContent) {
|
if (data.receive.reasoningContent) {
|
||||||
const lastMessage = activeMessageList.value[activeMessageList.value.length - 1]
|
const lastMessage = activeMessageList.value[activeMessageList.value.length - 1]
|
||||||
lastMessage.reasoningContent =
|
lastMessage.reasoningContent =
|
||||||
lastMessage.reasoningContent + data.receive.reasoningContent
|
lastMessage.reasoningContent + data.receive.reasoningContent
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理正常内容
|
// 处理正常内容
|
||||||
if (data.receive.content !== '') {
|
if (data.receive.content !== '') {
|
||||||
receiveMessageFullText.value = receiveMessageFullText.value + data.receive.content
|
receiveMessageFullText.value = receiveMessageFullText.value + data.receive.content
|
||||||
|
|
@ -528,7 +489,6 @@ const doSendMessageStream = async (userMessage: ChatMessageVO) => {
|
||||||
)
|
)
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 停止 stream 流式调用 */
|
/** 停止 stream 流式调用 */
|
||||||
const stopStream = async () => {
|
const stopStream = async () => {
|
||||||
// tip:如果 stream 进行中的 message,就需要调用 controller 结束
|
// tip:如果 stream 进行中的 message,就需要调用 controller 结束
|
||||||
|
|
@ -538,19 +498,15 @@ const stopStream = async () => {
|
||||||
// 设置为 false
|
// 设置为 false
|
||||||
conversationInProgress.value = false
|
conversationInProgress.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑 message:设置为 prompt,可以再次编辑 */
|
/** 编辑 message:设置为 prompt,可以再次编辑 */
|
||||||
const handleMessageEdit = (message: ChatMessageVO) => {
|
const handleMessageEdit = (message: ChatMessageVO) => {
|
||||||
prompt.value = message.content
|
prompt.value = message.content
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 刷新 message:基于指定消息,再次发起对话 */
|
/** 刷新 message:基于指定消息,再次发起对话 */
|
||||||
const handleMessageRefresh = (message: ChatMessageVO) => {
|
const handleMessageRefresh = (message: ChatMessageVO) => {
|
||||||
doSendMessage(message.content)
|
doSendMessage(message.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============== 【消息滚动】相关 =============
|
// ============== 【消息滚动】相关 =============
|
||||||
|
|
||||||
/** 滚动到 message 底部 */
|
/** 滚动到 message 底部 */
|
||||||
const scrollToBottom = async (isIgnore?: boolean) => {
|
const scrollToBottom = async (isIgnore?: boolean) => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
@ -558,7 +514,6 @@ const scrollToBottom = async (isIgnore?: boolean) => {
|
||||||
messageRef.value.scrollToBottom(isIgnore)
|
messageRef.value.scrollToBottom(isIgnore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 自提滚动效果 */
|
/** 自提滚动效果 */
|
||||||
const textRoll = async () => {
|
const textRoll = async () => {
|
||||||
let index = 0
|
let index = 0
|
||||||
|
|
@ -587,11 +542,9 @@ const textRoll = async () => {
|
||||||
if (!conversationInProgress.value) {
|
if (!conversationInProgress.value) {
|
||||||
textSpeed.value = 10
|
textSpeed.value = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index < receiveMessageFullText.value.length) {
|
if (index < receiveMessageFullText.value.length) {
|
||||||
receiveMessageDisplayedText.value += receiveMessageFullText.value[index]
|
receiveMessageDisplayedText.value += receiveMessageFullText.value[index]
|
||||||
index++
|
index++
|
||||||
|
|
||||||
// 更新 message
|
// 更新 message
|
||||||
const lastMessage = activeMessageList.value[activeMessageList.value.length - 1]
|
const lastMessage = activeMessageList.value[activeMessageList.value.length - 1]
|
||||||
lastMessage.content = receiveMessageDisplayedText.value
|
lastMessage.content = receiveMessageDisplayedText.value
|
||||||
|
|
@ -613,7 +566,6 @@ const textRoll = async () => {
|
||||||
let timer = setTimeout(task, textSpeed.value)
|
let timer = setTimeout(task, textSpeed.value)
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 如果有 conversationId 参数,则默认选中
|
// 如果有 conversationId 参数,则默认选中
|
||||||
|
|
@ -622,9 +574,8 @@ onMounted(async () => {
|
||||||
activeConversationId.value = id
|
activeConversationId.value = id
|
||||||
await getConversation(id)
|
await getConversation(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取列表数据
|
// 获取列表数据
|
||||||
activeMessageListLoading.value = true
|
activeMessageListLoading.value = true
|
||||||
await getMessageList()
|
await getMessageList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -49,7 +49,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -95,15 +94,12 @@
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<ChatConversationVO[]>([]) // 列表的数据
|
const list = ref<ChatConversationVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -116,7 +112,6 @@ const queryParams = reactive({
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -128,19 +123,16 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -153,11 +145,10 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
getList()
|
getList()
|
||||||
// 获得用户列表
|
// 获得用户列表
|
||||||
userList.value = await UserApi.getSimpleUserList()
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -49,7 +49,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -105,16 +104,13 @@
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message'
|
import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import { DICT_TYPE } from '@/utils/dict'
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<ChatMessageVO[]>([]) // 列表的数据
|
const list = ref<ChatMessageVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -128,7 +124,6 @@ const queryParams = reactive({
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -140,19 +135,16 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -165,11 +157,10 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
getList()
|
getList()
|
||||||
// 获得用户列表
|
// 获得用户列表
|
||||||
userList.value = await UserApi.getSimpleUserList()
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<doc-alert title="AI 对话聊天" url="https://doc.iocoder.cn/ai/chat/" />
|
|
||||||
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-tabs>
|
<el-tabs>
|
||||||
<el-tab-pane label="对话列表">
|
<el-tab-pane label="对话列表">
|
||||||
|
|
@ -12,11 +10,9 @@
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ChatConversationList from './ChatConversationList.vue'
|
import ChatConversationList from './ChatConversationList.vue'
|
||||||
import ChatMessageList from './ChatMessageList.vue'
|
import ChatMessageList from './ChatMessageList.vue'
|
||||||
|
|
||||||
/** AI 聊天对话 列表 */
|
/** AI 聊天对话 列表 */
|
||||||
defineOptions({ name: 'AiChatManager' })
|
defineOptions({ name: 'AiChatManager' })
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -74,39 +74,31 @@ import { ImageVO, ImageMidjourneyButtonsVO } from '@/api/ai/image'
|
||||||
import { PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
import { ElLoading, LoadingOptionsResolved } from 'element-plus'
|
import { ElLoading, LoadingOptionsResolved } from 'element-plus'
|
||||||
import { AiImageStatusEnum } from '@/views/ai/utils/constants'
|
import { AiImageStatusEnum } from '@/views/ai/utils/constants'
|
||||||
|
|
||||||
const message = useMessage() // 消息
|
const message = useMessage() // 消息
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
detail: {
|
detail: {
|
||||||
type: Object as PropType<ImageVO>,
|
type: Object as PropType<ImageVO>,
|
||||||
require: true
|
require: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const cardImageRef = ref<any>() // 卡片 image ref
|
const cardImageRef = ref<any>() // 卡片 image ref
|
||||||
const cardImageLoadingInstance = ref<any>() // 卡片 image ref
|
const cardImageLoadingInstance = ref<any>() // 卡片 image ref
|
||||||
|
|
||||||
/** 处理点击事件 */
|
/** 处理点击事件 */
|
||||||
const handleButtonClick = async (type, detail: ImageVO) => {
|
const handleButtonClick = async (type, detail: ImageVO) => {
|
||||||
emits('onBtnClick', type, detail)
|
emits('onBtnClick', type, detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理 Midjourney 按钮点击事件 */
|
/** 处理 Midjourney 按钮点击事件 */
|
||||||
const handleMidjourneyBtnClick = async (button: ImageMidjourneyButtonsVO) => {
|
const handleMidjourneyBtnClick = async (button: ImageMidjourneyButtonsVO) => {
|
||||||
// 确认窗体
|
// 确认窗体
|
||||||
await message.confirm(`确认操作 "${button.label} ${button.emoji}" ?`)
|
await message.confirm(`确认操作 "${button.label} ${button.emoji}" ?`)
|
||||||
emits('onMjBtnClick', button, props.detail)
|
emits('onMjBtnClick', button, props.detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
const emits = defineEmits(['onBtnClick', 'onMjBtnClick']) // emits
|
const emits = defineEmits(['onBtnClick', 'onMjBtnClick']) // emits
|
||||||
|
|
||||||
/** 监听详情 */
|
/** 监听详情 */
|
||||||
const { detail } = toRefs(props)
|
const { detail } = toRefs(props)
|
||||||
watch(detail, async (newVal, oldVal) => {
|
watch(detail, async (newVal, oldVal) => {
|
||||||
await handleLoading(newVal.status as string)
|
await handleLoading(newVal.status as string)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 处理加载状态 */
|
/** 处理加载状态 */
|
||||||
const handleLoading = async (status: number) => {
|
const handleLoading = async (status: number) => {
|
||||||
// 情况一:如果是生成中,则设置加载中的 loading
|
// 情况一:如果是生成中,则设置加载中的 loading
|
||||||
|
|
@ -123,9 +115,8 @@ const handleLoading = async (status: number) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await handleLoading(props.detail.status as string)
|
await handleLoading(props.detail.status as string)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
fit="contain"
|
fit="contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 基础信息 -->
|
<!-- 基础信息 -->
|
||||||
<el-descriptions title="基础信息" :column="1" :label-width="100" border>
|
<el-descriptions title="基础信息" :column="1" :label-width="100" border>
|
||||||
<el-descriptions-item label="提交时间">
|
<el-descriptions-item label="提交时间">
|
||||||
|
|
@ -34,7 +33,6 @@
|
||||||
<div class="break-all text-xs">{{ detail.picUrl }}</div>
|
<div class="break-all text-xs">{{ detail.picUrl }}</div>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
<!-- StableDiffusion 专属区域 -->
|
<!-- StableDiffusion 专属区域 -->
|
||||||
<el-descriptions
|
<el-descriptions
|
||||||
v-if="detail.platform === AiPlatformEnum.STABLE_DIFFUSION && hasStableDiffusionOptions"
|
v-if="detail.platform === AiPlatformEnum.STABLE_DIFFUSION && hasStableDiffusionOptions"
|
||||||
|
|
@ -75,7 +73,6 @@
|
||||||
{{ detail?.options?.seed }}
|
{{ detail?.options?.seed }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
<!-- Dall3 专属区域 -->
|
<!-- Dall3 专属区域 -->
|
||||||
<el-descriptions
|
<el-descriptions
|
||||||
v-if="detail.platform === AiPlatformEnum.OPENAI && detail?.options?.style"
|
v-if="detail.platform === AiPlatformEnum.OPENAI && detail?.options?.style"
|
||||||
|
|
@ -89,7 +86,6 @@
|
||||||
{{ Dall3StyleList.find((item: ImageModelVO) => item.key === detail?.options?.style)?.name }}
|
{{ Dall3StyleList.find((item: ImageModelVO) => item.key === detail?.options?.style)?.name }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
<!-- Midjourney 专属区域 -->
|
<!-- Midjourney 专属区域 -->
|
||||||
<el-descriptions
|
<el-descriptions
|
||||||
v-if="detail.platform === AiPlatformEnum.MIDJOURNEY && hasMidjourneyOptions"
|
v-if="detail.platform === AiPlatformEnum.MIDJOURNEY && hasMidjourneyOptions"
|
||||||
|
|
@ -112,7 +108,6 @@
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ImageApi, ImageVO } from '@/api/ai/image'
|
import { ImageApi, ImageVO } from '@/api/ai/image'
|
||||||
import {
|
import {
|
||||||
|
|
@ -124,10 +119,8 @@ import {
|
||||||
StableDiffusionStylePresets
|
StableDiffusionStylePresets
|
||||||
} from '@/views/ai/utils/constants'
|
} from '@/views/ai/utils/constants'
|
||||||
import { formatTime } from '@/utils'
|
import { formatTime } from '@/utils'
|
||||||
|
|
||||||
const showDrawer = ref<boolean>(false) // 是否显示
|
const showDrawer = ref<boolean>(false) // 是否显示
|
||||||
const detail = ref<ImageVO>({} as ImageVO) // 图片详细信息
|
const detail = ref<ImageVO>({} as ImageVO) // 图片详细信息
|
||||||
|
|
||||||
// 计算属性:判断是否有 StableDiffusion 选项
|
// 计算属性:判断是否有 StableDiffusion 选项
|
||||||
const hasStableDiffusionOptions = computed(() => {
|
const hasStableDiffusionOptions = computed(() => {
|
||||||
const options = detail.value?.options
|
const options = detail.value?.options
|
||||||
|
|
@ -140,13 +133,11 @@ const hasStableDiffusionOptions = computed(() => {
|
||||||
options?.seed
|
options?.seed
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 计算属性:判断是否有 Midjourney 选项
|
// 计算属性:判断是否有 Midjourney 选项
|
||||||
const hasMidjourneyOptions = computed(() => {
|
const hasMidjourneyOptions = computed(() => {
|
||||||
const options = detail.value?.options
|
const options = detail.value?.options
|
||||||
return options?.version || options?.referImageUrl
|
return options?.version || options?.referImageUrl
|
||||||
})
|
})
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: {
|
show: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
@ -158,23 +149,19 @@ const props = defineProps({
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 关闭抽屉 */
|
/** 关闭抽屉 */
|
||||||
const handleDrawerClose = async () => {
|
const handleDrawerClose = async () => {
|
||||||
emits('handleDrawerClose')
|
emits('handleDrawerClose')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 监听 drawer 是否打开 */
|
/** 监听 drawer 是否打开 */
|
||||||
const { show } = toRefs(props)
|
const { show } = toRefs(props)
|
||||||
watch(show, async (newValue, _oldValue) => {
|
watch(show, async (newValue, _oldValue) => {
|
||||||
showDrawer.value = newValue as boolean
|
showDrawer.value = newValue as boolean
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 获取图片详情 */
|
/** 获取图片详情 */
|
||||||
const getImageDetail = async (id: number) => {
|
const getImageDetail = async (id: number) => {
|
||||||
detail.value = await ImageApi.getImageMy(id)
|
detail.value = await ImageApi.getImageMy(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 监听 id 变化,加载最新图片详情 */
|
/** 监听 id 变化,加载最新图片详情 */
|
||||||
const { id } = toRefs(props)
|
const { id } = toRefs(props)
|
||||||
watch(id, async (newVal, _oldVal) => {
|
watch(id, async (newVal, _oldVal) => {
|
||||||
|
|
@ -182,6 +169,5 @@ watch(id, async (newVal, _oldVal) => {
|
||||||
await getImageDetail(newVal)
|
await getImageDetail(newVal)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emits = defineEmits(['handleDrawerClose'])
|
const emits = defineEmits(['handleDrawerClose'])
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -33,7 +33,6 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 图片详情 -->
|
<!-- 图片详情 -->
|
||||||
<ImageDetail
|
<ImageDetail
|
||||||
:show="isShowImageDetail"
|
:show="isShowImageDetail"
|
||||||
|
|
@ -53,10 +52,8 @@ import ImageCard from './ImageCard.vue'
|
||||||
import { ElLoading, LoadingOptionsResolved } from 'element-plus'
|
import { ElLoading, LoadingOptionsResolved } from 'element-plus'
|
||||||
import { AiImageStatusEnum } from '@/views/ai/utils/constants'
|
import { AiImageStatusEnum } from '@/views/ai/utils/constants'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const router = useRouter() // 路由
|
const router = useRouter() // 路由
|
||||||
|
|
||||||
// 图片分页相关的参数
|
// 图片分页相关的参数
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
|
|
@ -72,24 +69,20 @@ const inProgressTimer = ref<any>() // 生成中的 image 定时器,轮询生
|
||||||
// 图片详情相关的参数
|
// 图片详情相关的参数
|
||||||
const isShowImageDetail = ref<boolean>(false) // 图片详情是否展示
|
const isShowImageDetail = ref<boolean>(false) // 图片详情是否展示
|
||||||
const showImageDetailId = ref<number>(0) // 图片详情的图片编号
|
const showImageDetailId = ref<number>(0) // 图片详情的图片编号
|
||||||
|
|
||||||
/** 处理查看绘图作品 */
|
/** 处理查看绘图作品 */
|
||||||
const handleViewPublic = () => {
|
const handleViewPublic = () => {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'AiImageSquare'
|
name: 'AiImageSquare'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查看图片的详情 */
|
/** 查看图片的详情 */
|
||||||
const handleDetailOpen = async () => {
|
const handleDetailOpen = async () => {
|
||||||
isShowImageDetail.value = true
|
isShowImageDetail.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 关闭图片的详情 */
|
/** 关闭图片的详情 */
|
||||||
const handleDetailClose = async () => {
|
const handleDetailClose = async () => {
|
||||||
isShowImageDetail.value = false
|
isShowImageDetail.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获得 image 图片列表 */
|
/** 获得 image 图片列表 */
|
||||||
const getImageList = async () => {
|
const getImageList = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -101,7 +94,6 @@ const getImageList = async () => {
|
||||||
const { list, total } = await ImageApi.getImagePageMy(queryParams)
|
const { list, total } = await ImageApi.getImagePageMy(queryParams)
|
||||||
imageList.value = list
|
imageList.value = list
|
||||||
pageTotal.value = total
|
pageTotal.value = total
|
||||||
|
|
||||||
// 2. 计算需要轮询的图片
|
// 2. 计算需要轮询的图片
|
||||||
const newWatImages = {}
|
const newWatImages = {}
|
||||||
imageList.value.forEach((item) => {
|
imageList.value.forEach((item) => {
|
||||||
|
|
@ -118,7 +110,6 @@ const getImageList = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 轮询生成中的 image 列表 */
|
/** 轮询生成中的 image 列表 */
|
||||||
const refreshWatchImages = async () => {
|
const refreshWatchImages = async () => {
|
||||||
const imageIds = Object.keys(inProgressImageMap.value).map(Number)
|
const imageIds = Object.keys(inProgressImageMap.value).map(Number)
|
||||||
|
|
@ -140,7 +131,6 @@ const refreshWatchImages = async () => {
|
||||||
})
|
})
|
||||||
inProgressImageMap.value = newWatchImages
|
inProgressImageMap.value = newWatchImages
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 图片的点击事件 */
|
/** 图片的点击事件 */
|
||||||
const handleImageButtonClick = async (type: string, imageDetail: ImageVO) => {
|
const handleImageButtonClick = async (type: string, imageDetail: ImageVO) => {
|
||||||
// 详情
|
// 详情
|
||||||
|
|
@ -168,7 +158,6 @@ const handleImageButtonClick = async (type: string, imageDetail: ImageVO) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理 Midjourney 按钮点击事件 */
|
/** 处理 Midjourney 按钮点击事件 */
|
||||||
const handleImageMidjourneyButtonClick = async (
|
const handleImageMidjourneyButtonClick = async (
|
||||||
button: ImageMidjourneyButtonsVO,
|
button: ImageMidjourneyButtonsVO,
|
||||||
|
|
@ -184,11 +173,8 @@ const handleImageMidjourneyButtonClick = async (
|
||||||
// 3. 刷新列表
|
// 3. 刷新列表
|
||||||
await getImageList()
|
await getImageList()
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ getImageList }) // 暴露组件方法
|
defineExpose({ getImageList }) // 暴露组件方法
|
||||||
|
|
||||||
const emits = defineEmits(['onRegeneration'])
|
const emits = defineEmits(['onRegeneration'])
|
||||||
|
|
||||||
/** 组件挂在的时候 */
|
/** 组件挂在的时候 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 获取 image 列表
|
// 获取 image 列表
|
||||||
|
|
@ -198,11 +184,10 @@ onMounted(async () => {
|
||||||
await refreshWatchImages()
|
await refreshWatchImages()
|
||||||
}, 1000 * 3)
|
}, 1000 * 3)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 组件取消挂在的时候 */
|
/** 组件取消挂在的时候 */
|
||||||
onUnmounted(async () => {
|
onUnmounted(async () => {
|
||||||
if (inProgressTimer.value) {
|
if (inProgressTimer.value) {
|
||||||
clearInterval(inProgressTimer.value)
|
clearInterval(inProgressTimer.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -93,9 +93,7 @@
|
||||||
import { ImageApi, ImageDrawReqVO, ImageVO } from '@/api/ai/image'
|
import { ImageApi, ImageDrawReqVO, ImageVO } from '@/api/ai/image'
|
||||||
import { AiPlatformEnum, ImageHotWords, OtherPlatformEnum } from '@/views/ai/utils/constants'
|
import { AiPlatformEnum, ImageHotWords, OtherPlatformEnum } from '@/views/ai/utils/constants'
|
||||||
import { ModelVO } from '@/api/ai/model/model'
|
import { ModelVO } from '@/api/ai/model/model'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
// 接收父组件传入的模型列表
|
// 接收父组件传入的模型列表
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
models: {
|
models: {
|
||||||
|
|
@ -104,7 +102,6 @@ const props = defineProps({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const emits = defineEmits(['onDrawStart', 'onDrawComplete']) // 定义 emits
|
const emits = defineEmits(['onDrawStart', 'onDrawComplete']) // 定义 emits
|
||||||
|
|
||||||
// 定义属性
|
// 定义属性
|
||||||
const drawIn = ref<boolean>(false) // 生成中
|
const drawIn = ref<boolean>(false) // 生成中
|
||||||
const selectHotWord = ref<string>('') // 选中的热词
|
const selectHotWord = ref<string>('') // 选中的热词
|
||||||
|
|
@ -115,7 +112,6 @@ const height = ref<number>(512) // 图片高度
|
||||||
const otherPlatform = ref<string>(AiPlatformEnum.TONG_YI) // 平台
|
const otherPlatform = ref<string>(AiPlatformEnum.TONG_YI) // 平台
|
||||||
const platformModels = ref<ModelVO[]>([]) // 模型列表
|
const platformModels = ref<ModelVO[]>([]) // 模型列表
|
||||||
const modelId = ref<number>() // 选中的模型
|
const modelId = ref<number>() // 选中的模型
|
||||||
|
|
||||||
/** 选择热词 */
|
/** 选择热词 */
|
||||||
const handleHotWordClick = async (hotWord: string) => {
|
const handleHotWordClick = async (hotWord: string) => {
|
||||||
// 情况一:取消选中
|
// 情况一:取消选中
|
||||||
|
|
@ -123,12 +119,10 @@ const handleHotWordClick = async (hotWord: string) => {
|
||||||
selectHotWord.value = ''
|
selectHotWord.value = ''
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 情况二:选中
|
// 情况二:选中
|
||||||
selectHotWord.value = hotWord // 选中
|
selectHotWord.value = hotWord // 选中
|
||||||
prompt.value = hotWord // 替换提示词
|
prompt.value = hotWord // 替换提示词
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 图片生成 */
|
/** 图片生成 */
|
||||||
const handleGenerateImage = async () => {
|
const handleGenerateImage = async () => {
|
||||||
// 二次确认
|
// 二次确认
|
||||||
|
|
@ -155,19 +149,16 @@ const handleGenerateImage = async () => {
|
||||||
drawIn.value = false
|
drawIn.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 填充值 */
|
/** 填充值 */
|
||||||
const settingValues = async (detail: ImageVO) => {
|
const settingValues = async (detail: ImageVO) => {
|
||||||
prompt.value = detail.prompt
|
prompt.value = detail.prompt
|
||||||
width.value = detail.width
|
width.value = detail.width
|
||||||
height.value = detail.height
|
height.value = detail.height
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 平台切换 */
|
/** 平台切换 */
|
||||||
const handlerPlatformChange = async (platform: string) => {
|
const handlerPlatformChange = async (platform: string) => {
|
||||||
// 根据选择的平台筛选模型
|
// 根据选择的平台筛选模型
|
||||||
platformModels.value = props.models.filter((item: ModelVO) => item.platform === platform)
|
platformModels.value = props.models.filter((item: ModelVO) => item.platform === platform)
|
||||||
|
|
||||||
// 切换平台,默认选择一个模型
|
// 切换平台,默认选择一个模型
|
||||||
if (platformModels.value.length > 0) {
|
if (platformModels.value.length > 0) {
|
||||||
modelId.value = platformModels.value[0].id // 使用 model 属性作为值
|
modelId.value = platformModels.value[0].id // 使用 model 属性作为值
|
||||||
|
|
@ -175,7 +166,6 @@ const handlerPlatformChange = async (platform: string) => {
|
||||||
modelId.value = undefined
|
modelId.value = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 监听 models 变化 */
|
/** 监听 models 变化 */
|
||||||
watch(
|
watch(
|
||||||
() => props.models,
|
() => props.models,
|
||||||
|
|
@ -186,4 +176,4 @@ watch(
|
||||||
)
|
)
|
||||||
/** 暴露组件方法 */
|
/** 暴露组件方法 */
|
||||||
defineExpose({ settingValues })
|
defineExpose({ settingValues })
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -106,9 +106,7 @@ import {
|
||||||
ImageSizeVO
|
ImageSizeVO
|
||||||
} from '@/views/ai/utils/constants'
|
} from '@/views/ai/utils/constants'
|
||||||
import { ModelVO } from '@/api/ai/model/model'
|
import { ModelVO } from '@/api/ai/model/model'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
// 接收父组件传入的模型列表
|
// 接收父组件传入的模型列表
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
models: {
|
models: {
|
||||||
|
|
@ -117,7 +115,6 @@ const props = defineProps({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const emits = defineEmits(['onDrawStart', 'onDrawComplete']) // 定义 emits
|
const emits = defineEmits(['onDrawStart', 'onDrawComplete']) // 定义 emits
|
||||||
|
|
||||||
// 定义属性
|
// 定义属性
|
||||||
const prompt = ref<string>('') // 提示词
|
const prompt = ref<string>('') // 提示词
|
||||||
const drawIn = ref<boolean>(false) // 生成中
|
const drawIn = ref<boolean>(false) // 生成中
|
||||||
|
|
@ -125,7 +122,6 @@ const selectHotWord = ref<string>('') // 选中的热词
|
||||||
const selectModel = ref<string>('dall-e-3') // 模型
|
const selectModel = ref<string>('dall-e-3') // 模型
|
||||||
const selectSize = ref<string>('1024x1024') // 选中 size
|
const selectSize = ref<string>('1024x1024') // 选中 size
|
||||||
const style = ref<string>('vivid') // style 样式
|
const style = ref<string>('vivid') // style 样式
|
||||||
|
|
||||||
/** 选择热词 */
|
/** 选择热词 */
|
||||||
const handleHotWordClick = async (hotWord: string) => {
|
const handleHotWordClick = async (hotWord: string) => {
|
||||||
// 情况一:取消选中
|
// 情况一:取消选中
|
||||||
|
|
@ -133,12 +129,10 @@ const handleHotWordClick = async (hotWord: string) => {
|
||||||
selectHotWord.value = ''
|
selectHotWord.value = ''
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 情况二:选中
|
// 情况二:选中
|
||||||
selectHotWord.value = hotWord
|
selectHotWord.value = hotWord
|
||||||
prompt.value = hotWord
|
prompt.value = hotWord
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 选择 model 模型 */
|
/** 选择 model 模型 */
|
||||||
const handleModelClick = async (model: ImageModelVO) => {
|
const handleModelClick = async (model: ImageModelVO) => {
|
||||||
selectModel.value = model.key
|
selectModel.value = model.key
|
||||||
|
|
@ -151,7 +145,6 @@ const handleModelClick = async (model: ImageModelVO) => {
|
||||||
// DALL-E-2 模型特定的处理
|
// DALL-E-2 模型特定的处理
|
||||||
style.value = 'natural' // 如果有其他DALL-E-2适合的默认风格
|
style.value = 'natural' // 如果有其他DALL-E-2适合的默认风格
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新其他相关参数
|
// 更新其他相关参数
|
||||||
// 例如可以默认选择最适合当前模型的尺寸
|
// 例如可以默认选择最适合当前模型的尺寸
|
||||||
const recommendedSize = Dall3SizeList.find(
|
const recommendedSize = Dall3SizeList.find(
|
||||||
|
|
@ -159,22 +152,18 @@ const handleModelClick = async (model: ImageModelVO) => {
|
||||||
(model.key === 'dall-e-3' && size.key === '1024x1024') ||
|
(model.key === 'dall-e-3' && size.key === '1024x1024') ||
|
||||||
(model.key === 'dall-e-2' && size.key === '512x512')
|
(model.key === 'dall-e-2' && size.key === '512x512')
|
||||||
)
|
)
|
||||||
|
|
||||||
if (recommendedSize) {
|
if (recommendedSize) {
|
||||||
selectSize.value = recommendedSize.key
|
selectSize.value = recommendedSize.key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 选择 style 样式 */
|
/** 选择 style 样式 */
|
||||||
const handleStyleClick = async (imageStyle: ImageModelVO) => {
|
const handleStyleClick = async (imageStyle: ImageModelVO) => {
|
||||||
style.value = imageStyle.key
|
style.value = imageStyle.key
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 选择 size 大小 */
|
/** 选择 size 大小 */
|
||||||
const handleSizeClick = async (imageSize: ImageSizeVO) => {
|
const handleSizeClick = async (imageSize: ImageSizeVO) => {
|
||||||
selectSize.value = imageSize.key
|
selectSize.value = imageSize.key
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 图片生产 */
|
/** 图片生产 */
|
||||||
const handleGenerateImage = async () => {
|
const handleGenerateImage = async () => {
|
||||||
// 从 models 中查找匹配的模型
|
// 从 models 中查找匹配的模型
|
||||||
|
|
@ -185,7 +174,6 @@ const handleGenerateImage = async () => {
|
||||||
message.error('该模型不可用,请选择其它模型')
|
message.error('该模型不可用,请选择其它模型')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 二次确认
|
// 二次确认
|
||||||
await message.confirm(`确认生成内容?`)
|
await message.confirm(`确认生成内容?`)
|
||||||
try {
|
try {
|
||||||
|
|
@ -214,7 +202,6 @@ const handleGenerateImage = async () => {
|
||||||
drawIn.value = false
|
drawIn.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 填充值 */
|
/** 填充值 */
|
||||||
const settingValues = async (detail: ImageVO) => {
|
const settingValues = async (detail: ImageVO) => {
|
||||||
prompt.value = detail.prompt
|
prompt.value = detail.prompt
|
||||||
|
|
@ -225,8 +212,6 @@ const settingValues = async (detail: ImageVO) => {
|
||||||
) as ImageSizeVO
|
) as ImageSizeVO
|
||||||
await handleSizeClick(imageSize)
|
await handleSizeClick(imageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 暴露组件方法 */
|
/** 暴露组件方法 */
|
||||||
defineExpose({ settingValues })
|
defineExpose({ settingValues })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -119,9 +119,7 @@ import {
|
||||||
NijiVersionList
|
NijiVersionList
|
||||||
} from '@/views/ai/utils/constants'
|
} from '@/views/ai/utils/constants'
|
||||||
import { ModelVO } from '@/api/ai/model/model'
|
import { ModelVO } from '@/api/ai/model/model'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
// 接收父组件传入的模型列表
|
// 接收父组件传入的模型列表
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
models: {
|
models: {
|
||||||
|
|
@ -130,7 +128,6 @@ const props = defineProps({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const emits = defineEmits(['onDrawStart', 'onDrawComplete']) // 定义 emits
|
const emits = defineEmits(['onDrawStart', 'onDrawComplete']) // 定义 emits
|
||||||
|
|
||||||
// 定义属性
|
// 定义属性
|
||||||
const drawIn = ref<boolean>(false) // 生成中
|
const drawIn = ref<boolean>(false) // 生成中
|
||||||
const selectHotWord = ref<string>('') // 选中的热词
|
const selectHotWord = ref<string>('') // 选中的热词
|
||||||
|
|
@ -141,7 +138,6 @@ const selectModel = ref<string>('midjourney') // 选中的模型
|
||||||
const selectSize = ref<string>('1:1') // 选中 size
|
const selectSize = ref<string>('1:1') // 选中 size
|
||||||
const selectVersion = ref<any>('6.0') // 选中的 version
|
const selectVersion = ref<any>('6.0') // 选中的 version
|
||||||
const versionList = ref<any>(MidjourneyVersions) // version 列表
|
const versionList = ref<any>(MidjourneyVersions) // version 列表
|
||||||
|
|
||||||
/** 选择热词 */
|
/** 选择热词 */
|
||||||
const handleHotWordClick = async (hotWord: string) => {
|
const handleHotWordClick = async (hotWord: string) => {
|
||||||
// 情况一:取消选中
|
// 情况一:取消选中
|
||||||
|
|
@ -149,17 +145,14 @@ const handleHotWordClick = async (hotWord: string) => {
|
||||||
selectHotWord.value = ''
|
selectHotWord.value = ''
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 情况二:选中
|
// 情况二:选中
|
||||||
selectHotWord.value = hotWord // 选中
|
selectHotWord.value = hotWord // 选中
|
||||||
prompt.value = hotWord // 设置提示次
|
prompt.value = hotWord // 设置提示次
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 点击 size 尺寸 */
|
/** 点击 size 尺寸 */
|
||||||
const handleSizeClick = async (imageSize: ImageSizeVO) => {
|
const handleSizeClick = async (imageSize: ImageSizeVO) => {
|
||||||
selectSize.value = imageSize.key
|
selectSize.value = imageSize.key
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 点击 model 模型 */
|
/** 点击 model 模型 */
|
||||||
const handleModelClick = async (model: ImageModelVO) => {
|
const handleModelClick = async (model: ImageModelVO) => {
|
||||||
selectModel.value = model.key
|
selectModel.value = model.key
|
||||||
|
|
@ -170,7 +163,6 @@ const handleModelClick = async (model: ImageModelVO) => {
|
||||||
}
|
}
|
||||||
selectVersion.value = versionList.value[0].value
|
selectVersion.value = versionList.value[0].value
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 图片生成 */
|
/** 图片生成 */
|
||||||
const handleGenerateImage = async () => {
|
const handleGenerateImage = async () => {
|
||||||
// 从 models 中查找匹配的模型
|
// 从 models 中查找匹配的模型
|
||||||
|
|
@ -181,7 +173,6 @@ const handleGenerateImage = async () => {
|
||||||
message.error('该模型不可用,请选择其它模型')
|
message.error('该模型不可用,请选择其它模型')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 二次确认
|
// 二次确认
|
||||||
await message.confirm(`确认生成内容?`)
|
await message.confirm(`确认生成内容?`)
|
||||||
try {
|
try {
|
||||||
|
|
@ -209,7 +200,6 @@ const handleGenerateImage = async () => {
|
||||||
drawIn.value = false
|
drawIn.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 填充值 */
|
/** 填充值 */
|
||||||
const settingValues = async (detail: ImageVO) => {
|
const settingValues = async (detail: ImageVO) => {
|
||||||
// 提示词
|
// 提示词
|
||||||
|
|
@ -229,8 +219,6 @@ const settingValues = async (detail: ImageVO) => {
|
||||||
// image
|
// image
|
||||||
referImageUrl.value = detail.options.referImageUrl
|
referImageUrl.value = detail.options.referImageUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 暴露组件方法 */
|
/** 暴露组件方法 */
|
||||||
defineExpose({ settingValues })
|
defineExpose({ settingValues })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -151,9 +151,7 @@ import {
|
||||||
StableDiffusionStylePresets
|
StableDiffusionStylePresets
|
||||||
} from '@/views/ai/utils/constants'
|
} from '@/views/ai/utils/constants'
|
||||||
import { ModelVO } from '@/api/ai/model/model'
|
import { ModelVO } from '@/api/ai/model/model'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
// 接收父组件传入的模型列表
|
// 接收父组件传入的模型列表
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
models: {
|
models: {
|
||||||
|
|
@ -162,7 +160,6 @@ const props = defineProps({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const emits = defineEmits(['onDrawStart', 'onDrawComplete']) // 定义 emits
|
const emits = defineEmits(['onDrawStart', 'onDrawComplete']) // 定义 emits
|
||||||
|
|
||||||
// 定义属性
|
// 定义属性
|
||||||
const drawIn = ref<boolean>(false) // 生成中
|
const drawIn = ref<boolean>(false) // 生成中
|
||||||
const selectHotWord = ref<string>('') // 选中的热词
|
const selectHotWord = ref<string>('') // 选中的热词
|
||||||
|
|
@ -176,7 +173,6 @@ const seed = ref<number>(42) // 控制生成图像的随机性
|
||||||
const scale = ref<number>(7.5) // 引导系数
|
const scale = ref<number>(7.5) // 引导系数
|
||||||
const clipGuidancePreset = ref<string>('NONE') // 文本提示相匹配的图像(clip_guidance_preset) 简称 CLIP
|
const clipGuidancePreset = ref<string>('NONE') // 文本提示相匹配的图像(clip_guidance_preset) 简称 CLIP
|
||||||
const stylePreset = ref<string>('3d-model') // 风格
|
const stylePreset = ref<string>('3d-model') // 风格
|
||||||
|
|
||||||
/** 选择热词 */
|
/** 选择热词 */
|
||||||
const handleHotWordClick = async (hotWord: string) => {
|
const handleHotWordClick = async (hotWord: string) => {
|
||||||
// 情况一:取消选中
|
// 情况一:取消选中
|
||||||
|
|
@ -184,12 +180,10 @@ const handleHotWordClick = async (hotWord: string) => {
|
||||||
selectHotWord.value = ''
|
selectHotWord.value = ''
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 情况二:选中
|
// 情况二:选中
|
||||||
selectHotWord.value = hotWord // 选中
|
selectHotWord.value = hotWord // 选中
|
||||||
prompt.value = hotWord // 替换提示词
|
prompt.value = hotWord // 替换提示词
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 图片生成 */
|
/** 图片生成 */
|
||||||
const handleGenerateImage = async () => {
|
const handleGenerateImage = async () => {
|
||||||
// 从 models 中查找匹配的模型
|
// 从 models 中查找匹配的模型
|
||||||
|
|
@ -201,14 +195,12 @@ const handleGenerateImage = async () => {
|
||||||
message.error('该模型不可用,请选择其它模型')
|
message.error('该模型不可用,请选择其它模型')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 二次确认
|
// 二次确认
|
||||||
if (hasChinese(prompt.value)) {
|
if (hasChinese(prompt.value)) {
|
||||||
message.alert('暂不支持中文!')
|
message.alert('暂不支持中文!')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await message.confirm(`确认生成内容?`)
|
await message.confirm(`确认生成内容?`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 加载中
|
// 加载中
|
||||||
drawIn.value = true
|
drawIn.value = true
|
||||||
|
|
@ -237,7 +229,6 @@ const handleGenerateImage = async () => {
|
||||||
drawIn.value = false
|
drawIn.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 填充值 */
|
/** 填充值 */
|
||||||
const settingValues = async (detail: ImageVO) => {
|
const settingValues = async (detail: ImageVO) => {
|
||||||
prompt.value = detail.prompt
|
prompt.value = detail.prompt
|
||||||
|
|
@ -250,8 +241,6 @@ const settingValues = async (detail: ImageVO) => {
|
||||||
clipGuidancePreset.value = detail.options?.clipGuidancePreset
|
clipGuidancePreset.value = detail.options?.clipGuidancePreset
|
||||||
stylePreset.value = detail.options?.stylePreset
|
stylePreset.value = detail.options?.stylePreset
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 暴露组件方法 */
|
/** 暴露组件方法 */
|
||||||
defineExpose({ settingValues })
|
defineExpose({ settingValues })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -41,7 +41,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ImageList from './components/ImageList.vue'
|
import ImageList from './components/ImageList.vue'
|
||||||
import { AiPlatformEnum } from '@/views/ai/utils/constants'
|
import { AiPlatformEnum } from '@/views/ai/utils/constants'
|
||||||
|
|
@ -52,13 +51,11 @@ import StableDiffusion from './components/stableDiffusion/index.vue'
|
||||||
import Common from './components/common/index.vue'
|
import Common from './components/common/index.vue'
|
||||||
import { ModelApi, ModelVO } from '@/api/ai/model/model'
|
import { ModelApi, ModelVO } from '@/api/ai/model/model'
|
||||||
import { AiModelTypeEnum } from '@/views/ai/utils/constants'
|
import { AiModelTypeEnum } from '@/views/ai/utils/constants'
|
||||||
|
|
||||||
const imageListRef = ref<any>() // image 列表 ref
|
const imageListRef = ref<any>() // image 列表 ref
|
||||||
const dall3Ref = ref<any>() // dall3(openai) ref
|
const dall3Ref = ref<any>() // dall3(openai) ref
|
||||||
const midjourneyRef = ref<any>() // midjourney ref
|
const midjourneyRef = ref<any>() // midjourney ref
|
||||||
const stableDiffusionRef = ref<any>() // stable diffusion ref
|
const stableDiffusionRef = ref<any>() // stable diffusion ref
|
||||||
const commonRef = ref<any>() // stable diffusion ref
|
const commonRef = ref<any>() // stable diffusion ref
|
||||||
|
|
||||||
// 定义属性
|
// 定义属性
|
||||||
const selectPlatform = ref('common') // 选中的平台
|
const selectPlatform = ref('common') // 选中的平台
|
||||||
const platformOptions = [
|
const platformOptions = [
|
||||||
|
|
@ -79,17 +76,13 @@ const platformOptions = [
|
||||||
value: AiPlatformEnum.STABLE_DIFFUSION
|
value: AiPlatformEnum.STABLE_DIFFUSION
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const models = ref<ModelVO[]>([]) // 模型列表
|
const models = ref<ModelVO[]>([]) // 模型列表
|
||||||
|
|
||||||
/** 绘画 start */
|
/** 绘画 start */
|
||||||
const handleDrawStart = async (_platform: string) => {}
|
const handleDrawStart = async (_platform: string) => {}
|
||||||
|
|
||||||
/** 绘画 complete */
|
/** 绘画 complete */
|
||||||
const handleDrawComplete = async (_platform: string) => {
|
const handleDrawComplete = async (_platform: string) => {
|
||||||
await imageListRef.value.getImageList()
|
await imageListRef.value.getImageList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重新生成:将画图详情填充到对应平台 */
|
/** 重新生成:将画图详情填充到对应平台 */
|
||||||
const handleRegeneration = async (image: ImageVO) => {
|
const handleRegeneration = async (image: ImageVO) => {
|
||||||
// 切换平台
|
// 切换平台
|
||||||
|
|
@ -105,10 +98,9 @@ const handleRegeneration = async (image: ImageVO) => {
|
||||||
}
|
}
|
||||||
// TODO @fan:貌似 other 重新设置不行?
|
// TODO @fan:貌似 other 重新设置不行?
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 组件挂载的时候 */
|
/** 组件挂载的时候 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 获取模型列表
|
// 获取模型列表
|
||||||
models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.IMAGE)
|
models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.IMAGE)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<doc-alert title="AI 绘图创作" url="https://doc.iocoder.cn/ai/image/" />
|
|
||||||
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form
|
||||||
|
|
@ -82,7 +80,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -161,20 +158,16 @@
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getIntDictOptions, DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import { ImageApi, ImageVO } from '@/api/ai/image'
|
import { ImageApi, ImageVO } from '@/api/ai/image'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import { AiImageStatusEnum } from '@/views/ai/utils/constants'
|
import { AiImageStatusEnum } from '@/views/ai/utils/constants'
|
||||||
|
|
||||||
/** AI 绘画 列表 */
|
/** AI 绘画 列表 */
|
||||||
defineOptions({ name: 'AiImageManager' })
|
defineOptions({ name: 'AiImageManager' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<ImageVO[]>([]) // 列表的数据
|
const list = ref<ImageVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -189,7 +182,6 @@ const queryParams = reactive({
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -201,19 +193,16 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -226,7 +215,6 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改是否发布 */
|
/** 修改是否发布 */
|
||||||
const handleUpdatePublicStatusChange = async (row: ImageVO) => {
|
const handleUpdatePublicStatusChange = async (row: ImageVO) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -243,11 +231,10 @@ const handleUpdatePublicStatusChange = async (row: ImageVO) => {
|
||||||
row.publicStatus = !row.publicStatus
|
row.publicStatus = !row.publicStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
getList()
|
getList()
|
||||||
// 获得用户列表
|
// 获得用户列表
|
||||||
userList.value = await UserApi.getSimpleUserList()
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -29,7 +29,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ImageApi, ImageVO } from '@/api/ai/image'
|
import { ImageApi, ImageVO } from '@/api/ai/image'
|
||||||
import { Search } from '@element-plus/icons-vue'
|
import { Search } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
// TODO @fan:加个 loading 加载中的状态
|
// TODO @fan:加个 loading 加载中的状态
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<ImageVO[]>([]) // 列表的数据
|
const list = ref<ImageVO[]>([]) // 列表的数据
|
||||||
|
|
@ -40,7 +39,6 @@ const queryParams = reactive({
|
||||||
publicStatus: true,
|
publicStatus: true,
|
||||||
prompt: undefined
|
prompt: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -52,16 +50,13 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getList()
|
await getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
<Icon icon="ep:document" class="mr-8px text-[#409eff]" />
|
<Icon icon="ep:document" class="mr-8px text-[#409eff]" />
|
||||||
<span class="text-[13px] text-[#303133] break-all">{{ file.name }}</span>
|
<span class="text-[13px] text-[#303133] break-all">{{ file.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 处理进度 -->
|
<!-- 处理进度 -->
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<el-progress
|
<el-progress
|
||||||
|
|
@ -21,14 +20,12 @@
|
||||||
:status="isProcessComplete(file) ? 'success' : ''"
|
:status="isProcessComplete(file) ? 'success' : ''"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 分段数量 -->
|
<!-- 分段数量 -->
|
||||||
<div class="ml-10px text-[13px] text-[#606266]">
|
<div class="ml-10px text-[13px] text-[#606266]">
|
||||||
分段数量:{{ file.count ? file.count : '-' }}
|
分段数量:{{ file.count ? file.count : '-' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 底部完成按钮 -->
|
<!-- 底部完成按钮 -->
|
||||||
<div class="flex justify-end mt-20px">
|
<div class="flex justify-end mt-20px">
|
||||||
<el-button
|
<el-button
|
||||||
|
|
@ -41,38 +38,31 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { KnowledgeSegmentApi } from '@/api/ai/knowledge/segment'
|
import { KnowledgeSegmentApi } from '@/api/ai/knowledge/segment'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const parent = inject('parent') as any
|
const parent = inject('parent') as any
|
||||||
const pollingTimer = ref<number | null>(null) // 轮询定时器 ID,用于跟踪和清除轮询进程
|
const pollingTimer = ref<number | null>(null) // 轮询定时器 ID,用于跟踪和清除轮询进程
|
||||||
|
|
||||||
/** 判断文件处理是否完成 */
|
/** 判断文件处理是否完成 */
|
||||||
const isProcessComplete = (file) => {
|
const isProcessComplete = (file) => {
|
||||||
return file.progress === 100
|
return file.progress === 100
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 判断所有文件是否都处理完成 */
|
/** 判断所有文件是否都处理完成 */
|
||||||
const allProcessComplete = computed(() => {
|
const allProcessComplete = computed(() => {
|
||||||
return props.modelValue.list.every((file) => isProcessComplete(file))
|
return props.modelValue.list.every((file) => isProcessComplete(file))
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 完成按钮点击事件处理 */
|
/** 完成按钮点击事件处理 */
|
||||||
const handleComplete = () => {
|
const handleComplete = () => {
|
||||||
if (parent?.exposed?.handleBack) {
|
if (parent?.exposed?.handleBack) {
|
||||||
parent.exposed.handleBack()
|
parent.exposed.handleBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取文件处理进度 */
|
/** 获取文件处理进度 */
|
||||||
const getProcessList = async () => {
|
const getProcessList = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -82,7 +72,6 @@ const getProcessList = async () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const result = await KnowledgeSegmentApi.getKnowledgeSegmentProcessList(documentIds)
|
const result = await KnowledgeSegmentApi.getKnowledgeSegmentProcessList(documentIds)
|
||||||
|
|
||||||
// 2.1更新进度
|
// 2.1更新进度
|
||||||
const updatedList = props.modelValue.list.map((file) => {
|
const updatedList = props.modelValue.list.map((file) => {
|
||||||
const processInfo = result.find((item) => item.documentId === file.id)
|
const processInfo = result.find((item) => item.documentId === file.id)
|
||||||
|
|
@ -100,13 +89,11 @@ const getProcessList = async () => {
|
||||||
}
|
}
|
||||||
return file
|
return file
|
||||||
})
|
})
|
||||||
|
|
||||||
// 2.2 更新数据
|
// 2.2 更新数据
|
||||||
emit('update:modelValue', {
|
emit('update:modelValue', {
|
||||||
...props.modelValue,
|
...props.modelValue,
|
||||||
list: updatedList
|
list: updatedList
|
||||||
})
|
})
|
||||||
|
|
||||||
// 3. 如果未完成,继续轮询
|
// 3. 如果未完成,继续轮询
|
||||||
if (!updatedList.every((file) => isProcessComplete(file))) {
|
if (!updatedList.every((file) => isProcessComplete(file))) {
|
||||||
pollingTimer.value = window.setTimeout(getProcessList, 3000)
|
pollingTimer.value = window.setTimeout(getProcessList, 3000)
|
||||||
|
|
@ -117,7 +104,6 @@ const getProcessList = async () => {
|
||||||
pollingTimer.value = window.setTimeout(getProcessList, 5000)
|
pollingTimer.value = window.setTimeout(getProcessList, 5000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 组件挂载时开始轮询 */
|
/** 组件挂载时开始轮询 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 1. 初始化进度为 0
|
// 1. 初始化进度为 0
|
||||||
|
|
@ -125,16 +111,13 @@ onMounted(() => {
|
||||||
...file,
|
...file,
|
||||||
progress: 0
|
progress: 0
|
||||||
}))
|
}))
|
||||||
|
|
||||||
emit('update:modelValue', {
|
emit('update:modelValue', {
|
||||||
...props.modelValue,
|
...props.modelValue,
|
||||||
list: initialList
|
list: initialList
|
||||||
})
|
})
|
||||||
|
|
||||||
// 2. 开始轮询获取进度
|
// 2. 开始轮询获取进度
|
||||||
getProcessList()
|
getProcessList()
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 组件卸载前清除轮询 */
|
/** 组件卸载前清除轮询 */
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
// 1. 清除定时器
|
// 1. 清除定时器
|
||||||
|
|
@ -143,4 +126,4 @@ onBeforeUnmount(() => {
|
||||||
pollingTimer.value = null
|
pollingTimer.value = null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -18,7 +18,6 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="segment-settings mb-20px">
|
<div class="segment-settings mb-20px">
|
||||||
<el-form label-width="120px">
|
<el-form label-width="120px">
|
||||||
<el-form-item label="最大 Token 数">
|
<el-form-item label="最大 Token 数">
|
||||||
|
|
@ -27,11 +26,9 @@
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 下部文件预览部分 -->
|
<!-- 下部文件预览部分 -->
|
||||||
<div class="mb-10px">
|
<div class="mb-10px">
|
||||||
<div class="text-16px font-bold mb-10px">分段预览</div>
|
<div class="text-16px font-bold mb-10px">分段预览</div>
|
||||||
|
|
||||||
<!-- 文件选择器 -->
|
<!-- 文件选择器 -->
|
||||||
<div class="file-selector mb-10px">
|
<div class="file-selector mb-10px">
|
||||||
<el-dropdown v-if="modelData.list && modelData.list.length > 0" trigger="click">
|
<el-dropdown v-if="modelData.list && modelData.list.length > 0" trigger="click">
|
||||||
|
|
@ -60,7 +57,6 @@
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
<div v-else class="text-gray-400">暂无上传文件</div>
|
<div v-else class="text-gray-400">暂无上传文件</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 文件内容预览 -->
|
<!-- 文件内容预览 -->
|
||||||
<div class="file-preview bg-gray-50 p-15px rounded-md max-h-600px overflow-y-auto">
|
<div class="file-preview bg-gray-50 p-15px rounded-md max-h-600px overflow-y-auto">
|
||||||
<div v-if="splitLoading" class="flex justify-center items-center py-20px">
|
<div v-if="splitLoading" class="flex justify-center items-center py-20px">
|
||||||
|
|
@ -81,7 +77,6 @@
|
||||||
<el-empty v-else description="暂无预览内容" />
|
<el-empty v-else description="暂无预览内容" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 添加底部按钮 -->
|
<!-- 添加底部按钮 -->
|
||||||
<div class="mt-20px flex justify-between">
|
<div class="mt-20px flex justify-between">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -95,47 +90,39 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, getCurrentInstance, inject, onMounted, PropType, ref } from 'vue'
|
import { computed, getCurrentInstance, inject, onMounted, PropType, ref } from 'vue'
|
||||||
import { Icon } from '@/components/Icon'
|
import { Icon } from '@/components/Icon'
|
||||||
import { KnowledgeSegmentApi } from '@/api/ai/knowledge/segment'
|
import { KnowledgeSegmentApi } from '@/api/ai/knowledge/segment'
|
||||||
import { useMessage } from '@/hooks/web/useMessage'
|
import { useMessage } from '@/hooks/web/useMessage'
|
||||||
import { KnowledgeDocumentApi } from '@/api/ai/knowledge/document'
|
import { KnowledgeDocumentApi } from '@/api/ai/knowledge/document'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Object as PropType<any>,
|
type: Object as PropType<any>,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const message = useMessage() // 消息提示
|
const message = useMessage() // 消息提示
|
||||||
const parent = inject('parent', null) // 获取父组件实例
|
const parent = inject('parent', null) // 获取父组件实例
|
||||||
|
|
||||||
const modelData = computed({
|
const modelData = computed({
|
||||||
get: () => props.modelValue,
|
get: () => props.modelValue,
|
||||||
set: (val) => emit('update:modelValue', val)
|
set: (val) => emit('update:modelValue', val)
|
||||||
}) // 表单数据
|
}) // 表单数据
|
||||||
|
|
||||||
const splitLoading = ref(false) // 分段加载状态
|
const splitLoading = ref(false) // 分段加载状态
|
||||||
const currentFile = ref<any>(null) // 当前选中的文件
|
const currentFile = ref<any>(null) // 当前选中的文件
|
||||||
const submitLoading = ref(false) // 提交按钮加载状态
|
const submitLoading = ref(false) // 提交按钮加载状态
|
||||||
|
|
||||||
/** 选择文件 */
|
/** 选择文件 */
|
||||||
const selectFile = async (index: number) => {
|
const selectFile = async (index: number) => {
|
||||||
currentFile.value = modelData.value.list[index]
|
currentFile.value = modelData.value.list[index]
|
||||||
await splitContent(currentFile.value)
|
await splitContent(currentFile.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取文件分段内容 */
|
/** 获取文件分段内容 */
|
||||||
const splitContent = async (file: any) => {
|
const splitContent = async (file: any) => {
|
||||||
if (!file || !file.url) {
|
if (!file || !file.url) {
|
||||||
message.warning('文件 URL 不存在')
|
message.warning('文件 URL 不存在')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
splitLoading.value = true
|
splitLoading.value = true
|
||||||
try {
|
try {
|
||||||
// 调用后端分段接口,获取文档的分段内容、字符数和 Token 数
|
// 调用后端分段接口,获取文档的分段内容、字符数和 Token 数
|
||||||
|
|
@ -149,7 +136,6 @@ const splitContent = async (file: any) => {
|
||||||
splitLoading.value = false
|
splitLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理预览分段 */
|
/** 处理预览分段 */
|
||||||
const handleAutoSegment = async () => {
|
const handleAutoSegment = async () => {
|
||||||
// 如果没有选中文件,默认选中第一个
|
// 如果没有选中文件,默认选中第一个
|
||||||
|
|
@ -161,11 +147,9 @@ const handleAutoSegment = async () => {
|
||||||
message.warning('请先选择文件')
|
message.warning('请先选择文件')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取分段内容
|
// 获取分段内容
|
||||||
await splitContent(currentFile.value)
|
await splitContent(currentFile.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 上一步按钮处理 */
|
/** 上一步按钮处理 */
|
||||||
const handlePrevStep = () => {
|
const handlePrevStep = () => {
|
||||||
const parentEl = parent || getCurrentInstance()?.parent
|
const parentEl = parent || getCurrentInstance()?.parent
|
||||||
|
|
@ -173,7 +157,6 @@ const handlePrevStep = () => {
|
||||||
parentEl.exposed.goToPrevStep()
|
parentEl.exposed.goToPrevStep()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 保存操作 */
|
/** 保存操作 */
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
// 保存前验证
|
// 保存前验证
|
||||||
|
|
@ -181,7 +164,6 @@ const handleSave = async () => {
|
||||||
message.warning('请先预览分段内容')
|
message.warning('请先预览分段内容')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置按钮加载状态
|
// 设置按钮加载状态
|
||||||
submitLoading.value = true
|
submitLoading.value = true
|
||||||
try {
|
try {
|
||||||
|
|
@ -205,7 +187,6 @@ const handleSave = async () => {
|
||||||
document.id = data[index]
|
document.id = data[index]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 进入下一步
|
// 进入下一步
|
||||||
const parentEl = parent || getCurrentInstance()?.parent
|
const parentEl = parent || getCurrentInstance()?.parent
|
||||||
if (parentEl && typeof parentEl.exposed?.goToNextStep === 'function') {
|
if (parentEl && typeof parentEl.exposed?.goToNextStep === 'function') {
|
||||||
|
|
@ -218,7 +199,6 @@ const handleSave = async () => {
|
||||||
submitLoading.value = false
|
submitLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 确保 segmentMaxTokens 存在
|
// 确保 segmentMaxTokens 存在
|
||||||
|
|
@ -229,10 +209,9 @@ onMounted(async () => {
|
||||||
if (!currentFile.value && modelData.value.list && modelData.value.list.length > 0) {
|
if (!currentFile.value && modelData.value.list && modelData.value.list.length > 0) {
|
||||||
currentFile.value = modelData.value.list[0]
|
currentFile.value = modelData.value.list[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有选中的文件,获取分段内容
|
// 如果有选中的文件,获取分段内容
|
||||||
if (currentFile.value) {
|
if (currentFile.value) {
|
||||||
await splitContent(currentFile.value)
|
await splitContent(currentFile.value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -34,7 +34,6 @@
|
||||||
</div>
|
</div>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="modelData.list && modelData.list.length > 0"
|
v-if="modelData.list && modelData.list.length > 0"
|
||||||
class="mt-15px grid grid-cols-1 gap-2"
|
class="mt-15px grid grid-cols-1 gap-2"
|
||||||
|
|
@ -55,7 +54,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<!-- 添加下一步按钮 -->
|
<!-- 添加下一步按钮 -->
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<div class="flex justify-end w-full">
|
<div class="flex justify-end w-full">
|
||||||
|
|
@ -66,23 +64,19 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { PropType, ref, computed, inject, getCurrentInstance, onMounted } from 'vue'
|
import { PropType, ref, computed, inject, getCurrentInstance, onMounted } from 'vue'
|
||||||
import { useMessage } from '@/hooks/web/useMessage'
|
import { useMessage } from '@/hooks/web/useMessage'
|
||||||
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
||||||
import { generateAcceptedFileTypes } from '@/utils'
|
import { generateAcceptedFileTypes } from '@/utils'
|
||||||
import { Icon } from '@/components/Icon'
|
import { Icon } from '@/components/Icon'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Object as PropType<any>,
|
type: Object as PropType<any>,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const formRef = ref() // 表单引用
|
const formRef = ref() // 表单引用
|
||||||
const uploadRef = ref() // 上传组件引用
|
const uploadRef = ref() // 上传组件引用
|
||||||
const parent = inject('parent', null) // 获取父组件实例
|
const parent = inject('parent', null) // 获取父组件实例
|
||||||
|
|
@ -90,7 +84,6 @@ const { uploadUrl, httpRequest } = useUpload() // 使用上传组件的钩子
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const fileList = ref([]) // 文件列表
|
const fileList = ref([]) // 文件列表
|
||||||
const uploadingCount = ref(0) // 上传中的文件数量
|
const uploadingCount = ref(0) // 上传中的文件数量
|
||||||
|
|
||||||
// 支持的文件类型和大小限制
|
// 支持的文件类型和大小限制
|
||||||
const supportedFileTypes = [
|
const supportedFileTypes = [
|
||||||
'TXT',
|
'TXT',
|
||||||
|
|
@ -114,10 +107,8 @@ const supportedFileTypes = [
|
||||||
]
|
]
|
||||||
const allowedExtensions = supportedFileTypes.map((ext) => ext.toLowerCase()) // 小写的扩展名列表
|
const allowedExtensions = supportedFileTypes.map((ext) => ext.toLowerCase()) // 小写的扩展名列表
|
||||||
const maxFileSize = 15 // 最大文件大小(MB)
|
const maxFileSize = 15 // 最大文件大小(MB)
|
||||||
|
|
||||||
// 构建 accept 属性值,用于限制文件选择对话框中可见的文件类型
|
// 构建 accept 属性值,用于限制文件选择对话框中可见的文件类型
|
||||||
const acceptedFileTypes = computed(() => generateAcceptedFileTypes(supportedFileTypes))
|
const acceptedFileTypes = computed(() => generateAcceptedFileTypes(supportedFileTypes))
|
||||||
|
|
||||||
/** 表单数据 */
|
/** 表单数据 */
|
||||||
const modelData = computed({
|
const modelData = computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
|
|
@ -125,7 +116,6 @@ const modelData = computed({
|
||||||
},
|
},
|
||||||
set: (val) => emit('update:modelValue', val)
|
set: (val) => emit('update:modelValue', val)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 确保 list 属性存在 */
|
/** 确保 list 属性存在 */
|
||||||
const ensureListExists = () => {
|
const ensureListExists = () => {
|
||||||
if (!props.modelValue.list) {
|
if (!props.modelValue.list) {
|
||||||
|
|
@ -135,12 +125,10 @@ const ensureListExists = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 是否所有文件都已上传完成 */
|
/** 是否所有文件都已上传完成 */
|
||||||
const isAllUploaded = computed(() => {
|
const isAllUploaded = computed(() => {
|
||||||
return modelData.value.list && modelData.value.list.length > 0 && uploadingCount.value === 0
|
return modelData.value.list && modelData.value.list.length > 0 && uploadingCount.value === 0
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传前检查文件类型和大小
|
* 上传前检查文件类型和大小
|
||||||
*
|
*
|
||||||
|
|
@ -160,12 +148,10 @@ const beforeUpload = (file) => {
|
||||||
message.error(`文件大小不能超过 ${maxFileSize} MB!`)
|
message.error(`文件大小不能超过 ${maxFileSize} MB!`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 增加上传中的文件计数
|
// 2. 增加上传中的文件计数
|
||||||
uploadingCount.value++
|
uploadingCount.value++
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件上传成功处理
|
* 文件上传成功处理
|
||||||
*
|
*
|
||||||
|
|
@ -189,11 +175,9 @@ const handleUploadSuccess = (response, file) => {
|
||||||
} else {
|
} else {
|
||||||
message.error(`文件 ${file.name} 上传失败`)
|
message.error(`文件 ${file.name} 上传失败`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 减少上传中的文件计数
|
// 减少上传中的文件计数
|
||||||
uploadingCount.value = Math.max(0, uploadingCount.value - 1)
|
uploadingCount.value = Math.max(0, uploadingCount.value - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件上传失败处理
|
* 文件上传失败处理
|
||||||
*
|
*
|
||||||
|
|
@ -205,7 +189,6 @@ const handleUploadError = (error, file) => {
|
||||||
// 减少上传中的文件计数
|
// 减少上传中的文件计数
|
||||||
uploadingCount.value = Math.max(0, uploadingCount.value - 1)
|
uploadingCount.value = Math.max(0, uploadingCount.value - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件变更处理
|
* 文件变更处理
|
||||||
*
|
*
|
||||||
|
|
@ -216,7 +199,6 @@ const handleFileChange = (file) => {
|
||||||
uploadingCount.value = Math.max(0, uploadingCount.value - 1)
|
uploadingCount.value = Math.max(0, uploadingCount.value - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件移除处理
|
* 文件移除处理
|
||||||
*
|
*
|
||||||
|
|
@ -227,7 +209,6 @@ const handleFileRemove = (file) => {
|
||||||
uploadingCount.value = Math.max(0, uploadingCount.value - 1)
|
uploadingCount.value = Math.max(0, uploadingCount.value - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从列表中移除文件
|
* 从列表中移除文件
|
||||||
*
|
*
|
||||||
|
|
@ -243,7 +224,6 @@ const removeFile = (index: number) => {
|
||||||
list: newList
|
list: newList
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 下一步按钮处理 */
|
/** 下一步按钮处理 */
|
||||||
const handleNextStep = () => {
|
const handleNextStep = () => {
|
||||||
// 1.1 检查是否有文件上传
|
// 1.1 检查是否有文件上传
|
||||||
|
|
@ -256,18 +236,15 @@ const handleNextStep = () => {
|
||||||
message.warning('请等待所有文件上传完成')
|
message.warning('请等待所有文件上传完成')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 获取父组件的goToNextStep方法
|
// 2. 获取父组件的goToNextStep方法
|
||||||
const parentEl = parent || getCurrentInstance()?.parent
|
const parentEl = parent || getCurrentInstance()?.parent
|
||||||
if (parentEl && typeof parentEl.exposed?.goToNextStep === 'function') {
|
if (parentEl && typeof parentEl.exposed?.goToNextStep === 'function') {
|
||||||
parentEl.exposed.goToNextStep()
|
parentEl.exposed.goToNextStep()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
ensureListExists()
|
ensureListExists()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
{{ formData.id ? '编辑知识库文档' : '创建知识库文档' }}
|
{{ formData.id ? '编辑知识库文档' : '创建知识库文档' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 步骤条 -->
|
<!-- 步骤条 -->
|
||||||
<div class="flex-1 flex items-center justify-center h-full">
|
<div class="flex-1 flex items-center justify-center h-full">
|
||||||
<div class="w-400px flex items-center justify-between h-full">
|
<div class="w-400px flex items-center justify-between h-full">
|
||||||
|
|
@ -40,23 +39,19 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧按钮 - 已移除 -->
|
<!-- 右侧按钮 - 已移除 -->
|
||||||
<div class="w-200px flex items-center justify-end gap-2"> </div>
|
<div class="w-200px flex items-center justify-end gap-2"> </div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 主体内容 -->
|
<!-- 主体内容 -->
|
||||||
<div class="mt-50px">
|
<div class="mt-50px">
|
||||||
<!-- 第一步:上传文档 -->
|
<!-- 第一步:上传文档 -->
|
||||||
<div v-if="currentStep === 0" class="mx-auto w-560px">
|
<div v-if="currentStep === 0" class="mx-auto w-560px">
|
||||||
<UploadStep v-model="formData" ref="uploadDocumentRef" />
|
<UploadStep v-model="formData" ref="uploadDocumentRef" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第二步:文档分段 -->
|
<!-- 第二步:文档分段 -->
|
||||||
<div v-if="currentStep === 1" class="mx-auto w-560px">
|
<div v-if="currentStep === 1" class="mx-auto w-560px">
|
||||||
<SplitStep v-model="formData" ref="documentSegmentRef" />
|
<SplitStep v-model="formData" ref="documentSegmentRef" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第三步:处理并完成 -->
|
<!-- 第三步:处理并完成 -->
|
||||||
<div v-if="currentStep === 2" class="mx-auto w-560px">
|
<div v-if="currentStep === 2" class="mx-auto w-560px">
|
||||||
<ProcessStep v-model="formData" ref="processCompleteRef" />
|
<ProcessStep v-model="formData" ref="processCompleteRef" />
|
||||||
|
|
@ -65,7 +60,6 @@
|
||||||
</div>
|
</div>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||||
|
|
@ -73,11 +67,9 @@ import UploadStep from './UploadStep.vue'
|
||||||
import SplitStep from './SplitStep.vue'
|
import SplitStep from './SplitStep.vue'
|
||||||
import ProcessStep from './ProcessStep.vue'
|
import ProcessStep from './ProcessStep.vue'
|
||||||
import { KnowledgeDocumentApi } from '@/api/ai/knowledge/document'
|
import { KnowledgeDocumentApi } from '@/api/ai/knowledge/document'
|
||||||
|
|
||||||
const { delView } = useTagsViewStore() // 视图操作
|
const { delView } = useTagsViewStore() // 视图操作
|
||||||
const route = useRoute() // 路由
|
const route = useRoute() // 路由
|
||||||
const router = useRouter() // 路由
|
const router = useRouter() // 路由
|
||||||
|
|
||||||
// 组件引用
|
// 组件引用
|
||||||
const uploadDocumentRef = ref()
|
const uploadDocumentRef = ref()
|
||||||
const documentSegmentRef = ref()
|
const documentSegmentRef = ref()
|
||||||
|
|
@ -101,16 +93,13 @@ const formData = ref({
|
||||||
process?: number // 处理进度
|
process?: number // 处理进度
|
||||||
}> // 用于存储上传的文件列表
|
}> // 用于存储上传的文件列表
|
||||||
}) // 表单数据
|
}) // 表单数据
|
||||||
|
|
||||||
provide('parent', getCurrentInstance()) // 提供 parent 给子组件使用
|
provide('parent', getCurrentInstance()) // 提供 parent 给子组件使用
|
||||||
|
|
||||||
/** 初始化数据 */
|
/** 初始化数据 */
|
||||||
const initData = async () => {
|
const initData = async () => {
|
||||||
// 【新增场景】从路由参数中获取知识库 ID
|
// 【新增场景】从路由参数中获取知识库 ID
|
||||||
if (route.query.knowledgeId) {
|
if (route.query.knowledgeId) {
|
||||||
formData.value.knowledgeId = route.query.knowledgeId as any
|
formData.value.knowledgeId = route.query.knowledgeId as any
|
||||||
}
|
}
|
||||||
|
|
||||||
// 【修改场景】从路由参数中获取文档 ID
|
// 【修改场景】从路由参数中获取文档 ID
|
||||||
const documentId = route.query.id
|
const documentId = route.query.id
|
||||||
if (documentId) {
|
if (documentId) {
|
||||||
|
|
@ -130,21 +119,18 @@ const initData = async () => {
|
||||||
goToNextStep()
|
goToNextStep()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 切换到下一步 */
|
/** 切换到下一步 */
|
||||||
const goToNextStep = () => {
|
const goToNextStep = () => {
|
||||||
if (currentStep.value < steps.length - 1) {
|
if (currentStep.value < steps.length - 1) {
|
||||||
currentStep.value++
|
currentStep.value++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 切换到上一步 */
|
/** 切换到上一步 */
|
||||||
const goToPrevStep = () => {
|
const goToPrevStep = () => {
|
||||||
if (currentStep.value > 0) {
|
if (currentStep.value > 0) {
|
||||||
currentStep.value--
|
currentStep.value--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 返回列表页 */
|
/** 返回列表页 */
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
// 先删除当前页签
|
// 先删除当前页签
|
||||||
|
|
@ -152,12 +138,10 @@ const handleBack = () => {
|
||||||
// 跳转到列表页
|
// 跳转到列表页
|
||||||
router.push({ name: 'AiKnowledgeDocument', query: { knowledgeId: formData.value.knowledgeId } })
|
router.push({ name: 'AiKnowledgeDocument', query: { knowledgeId: formData.value.knowledgeId } })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await initData()
|
await initData()
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 添加组件卸载前的清理代码 */
|
/** 添加组件卸载前的清理代码 */
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
// 清理所有的引用
|
// 清理所有的引用
|
||||||
|
|
@ -165,7 +149,6 @@ onBeforeUnmount(() => {
|
||||||
documentSegmentRef.value = null
|
documentSegmentRef.value = null
|
||||||
processCompleteRef.value = null
|
processCompleteRef.value = null
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 暴露方法给子组件使用 */
|
/** 暴露方法给子组件使用 */
|
||||||
defineExpose({
|
defineExpose({
|
||||||
goToNextStep,
|
goToNextStep,
|
||||||
|
|
@ -173,21 +156,17 @@ defineExpose({
|
||||||
handleBack
|
handleBack
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.border-bottom {
|
.border-bottom {
|
||||||
border-bottom: 1px solid #dcdfe6;
|
border-bottom: 1px solid #dcdfe6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-primary {
|
.text-primary {
|
||||||
color: #3473ff;
|
color: #3473ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-primary {
|
.bg-primary {
|
||||||
background-color: #3473ff;
|
background-color: #3473ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-primary {
|
.border-primary {
|
||||||
border-color: #3473ff;
|
border-color: #3473ff;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -41,7 +41,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -106,11 +105,9 @@
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<!-- <KnowledgeDocumentForm ref="formRef" @success="getList" /> -->
|
<!-- <KnowledgeDocumentForm ref="formRef" @success="getList" /> -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
|
@ -119,15 +116,12 @@ import { useRoute, useRouter } from 'vue-router'
|
||||||
import { checkPermi } from '@/utils/permission'
|
import { checkPermi } from '@/utils/permission'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
// import KnowledgeDocumentForm from './KnowledgeDocumentForm.vue'
|
// import KnowledgeDocumentForm from './KnowledgeDocumentForm.vue'
|
||||||
|
|
||||||
/** AI 知识库文档 列表 */
|
/** AI 知识库文档 列表 */
|
||||||
defineOptions({ name: 'KnowledgeDocument' })
|
defineOptions({ name: 'KnowledgeDocument' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const route = useRoute() // 路由
|
const route = useRoute() // 路由
|
||||||
const router = useRouter() // 路由
|
const router = useRouter() // 路由
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<KnowledgeDocumentVO[]>([]) // 列表的数据
|
const list = ref<KnowledgeDocumentVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -139,7 +133,6 @@ const queryParams = reactive({
|
||||||
knowledgeId: undefined
|
knowledgeId: undefined
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -151,19 +144,16 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 跳转到创建文档页面 */
|
/** 跳转到创建文档页面 */
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
router.push({
|
router.push({
|
||||||
|
|
@ -171,7 +161,6 @@ const handleCreate = () => {
|
||||||
query: { knowledgeId: queryParams.knowledgeId }
|
query: { knowledgeId: queryParams.knowledgeId }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 跳转到更新文档页面 */
|
/** 跳转到更新文档页面 */
|
||||||
const handleUpdate = (id: number) => {
|
const handleUpdate = (id: number) => {
|
||||||
router.push({
|
router.push({
|
||||||
|
|
@ -179,7 +168,6 @@ const handleUpdate = (id: number) => {
|
||||||
query: { id, knowledgeId: queryParams.knowledgeId }
|
query: { id, knowledgeId: queryParams.knowledgeId }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -192,7 +180,6 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改状态操作 */
|
/** 修改状态操作 */
|
||||||
const handleStatusChange = async (row: KnowledgeDocumentVO) => {
|
const handleStatusChange = async (row: KnowledgeDocumentVO) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -210,7 +197,6 @@ const handleStatusChange = async (row: KnowledgeDocumentVO) => {
|
||||||
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
|
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 跳转到知识库分段页面 */
|
/** 跳转到知识库分段页面 */
|
||||||
const handleSegment = (id: number) => {
|
const handleSegment = (id: number) => {
|
||||||
router.push({
|
router.push({
|
||||||
|
|
@ -218,7 +204,6 @@ const handleSegment = (id: number) => {
|
||||||
query: { documentId: id }
|
query: { documentId: id }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 如果知识库 ID 不存在,显示错误提示并关闭页面
|
// 如果知识库 ID 不存在,显示错误提示并关闭页面
|
||||||
|
|
@ -228,9 +213,8 @@ onMounted(() => {
|
||||||
router.push({ name: 'AiKnowledge' })
|
router.push({ name: 'AiKnowledge' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从路由参数中获取知识库 ID
|
// 从路由参数中获取知识库 ID
|
||||||
queryParams.knowledgeId = route.query.knowledgeId as any
|
queryParams.knowledgeId = route.query.knowledgeId as any
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -72,13 +72,10 @@ import { KnowledgeApi, KnowledgeVO } from '@/api/ai/knowledge/knowledge'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
import { ModelApi, ModelVO } from '@/api/ai/model/model'
|
import { ModelApi, ModelVO } from '@/api/ai/model/model'
|
||||||
import { AiModelTypeEnum } from '../../utils/constants'
|
import { AiModelTypeEnum } from '../../utils/constants'
|
||||||
|
|
||||||
/** AI 知识库表单 */
|
/** AI 知识库表单 */
|
||||||
defineOptions({ name: 'KnowledgeForm' })
|
defineOptions({ name: 'KnowledgeForm' })
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
|
@ -101,7 +98,6 @@ const formRules = reactive({
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
const modelList = ref<ModelVO[]>([]) // 向量模型选项
|
const modelList = ref<ModelVO[]>([]) // 向量模型选项
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (type: string, id?: number) => {
|
const open = async (type: string, id?: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
|
@ -121,7 +117,6 @@ const open = async (type: string, id?: number) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
/** 提交表单 */
|
/** 提交表单 */
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
|
|
@ -145,7 +140,6 @@ const submitForm = async () => {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
|
|
@ -159,4 +153,4 @@ const resetForm = () => {
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<doc-alert title="AI 知识库" url="https://doc.iocoder.cn/ai/knowledge/" />
|
|
||||||
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form
|
||||||
|
|
@ -59,7 +57,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -124,24 +121,19 @@
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<KnowledgeForm ref="formRef" @success="getList" />
|
<KnowledgeForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import { KnowledgeApi, KnowledgeVO } from '@/api/ai/knowledge/knowledge'
|
import { KnowledgeApi, KnowledgeVO } from '@/api/ai/knowledge/knowledge'
|
||||||
import KnowledgeForm from './KnowledgeForm.vue'
|
import KnowledgeForm from './KnowledgeForm.vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
/** AI 知识库列表 */
|
/** AI 知识库列表 */
|
||||||
defineOptions({ name: 'Knowledge' })
|
defineOptions({ name: 'Knowledge' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<KnowledgeVO[]>([]) // 列表的数据
|
const list = ref<KnowledgeVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -153,7 +145,6 @@ const queryParams = reactive({
|
||||||
createTime: []
|
createTime: []
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -165,25 +156,21 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, id?: number) => {
|
||||||
formRef.value.open(type, id)
|
formRef.value.open(type, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -196,7 +183,6 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 文档按钮操作 */
|
/** 文档按钮操作 */
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const handleDocument = (id: number) => {
|
const handleDocument = (id: number) => {
|
||||||
|
|
@ -205,7 +191,6 @@ const handleDocument = (id: number) => {
|
||||||
query: { knowledgeId: id }
|
query: { knowledgeId: id }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 跳转到文档召回测试页面 */
|
/** 跳转到文档召回测试页面 */
|
||||||
const handleRetrieval = (id: number) => {
|
const handleRetrieval = (id: number) => {
|
||||||
router.push({
|
router.push({
|
||||||
|
|
@ -213,9 +198,8 @@ const handleRetrieval = (id: number) => {
|
||||||
query: { id }
|
query: { id }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -37,7 +37,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 右侧召回结果区域 -->
|
<!-- 右侧召回结果区域 -->
|
||||||
<ContentWrap class="flex-1 min-w-300px">
|
<ContentWrap class="flex-1 min-w-300px">
|
||||||
<el-empty v-if="loading" description="正在检索中..." />
|
<el-empty v-if="loading" description="正在检索中..." />
|
||||||
|
|
@ -84,18 +83,15 @@
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useMessage } from '@/hooks/web/useMessage'
|
import { useMessage } from '@/hooks/web/useMessage'
|
||||||
import { KnowledgeSegmentApi } from '@/api/ai/knowledge/segment'
|
import { KnowledgeSegmentApi } from '@/api/ai/knowledge/segment'
|
||||||
import { KnowledgeApi } from '@/api/ai/knowledge/knowledge'
|
import { KnowledgeApi } from '@/api/ai/knowledge/knowledge'
|
||||||
/** 文档召回测试 */
|
/** 文档召回测试 */
|
||||||
defineOptions({ name: 'KnowledgeDocumentRetrieval' })
|
defineOptions({ name: 'KnowledgeDocumentRetrieval' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const route = useRoute() // 路由
|
const route = useRoute() // 路由
|
||||||
const router = useRouter() // 路由
|
const router = useRouter() // 路由
|
||||||
|
|
||||||
const loading = ref(false) // 加载状态
|
const loading = ref(false) // 加载状态
|
||||||
const segments = ref<any[]>([]) // 召回结果
|
const segments = ref<any[]>([]) // 召回结果
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
|
|
@ -104,17 +100,14 @@ const queryParams = reactive({
|
||||||
topK: 10,
|
topK: 10,
|
||||||
similarityThreshold: 0.5
|
similarityThreshold: 0.5
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 调用文档召回测试接口 */
|
/** 调用文档召回测试接口 */
|
||||||
const getRetrievalResult = async () => {
|
const getRetrievalResult = async () => {
|
||||||
if (!queryParams.content) {
|
if (!queryParams.content) {
|
||||||
message.warning('请输入查询文本')
|
message.warning('请输入查询文本')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
segments.value = []
|
segments.value = []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await KnowledgeSegmentApi.searchKnowledgeSegment({
|
const data = await KnowledgeSegmentApi.searchKnowledgeSegment({
|
||||||
knowledgeId: queryParams.id,
|
knowledgeId: queryParams.id,
|
||||||
|
|
@ -129,12 +122,10 @@ const getRetrievalResult = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 展开/收起段落内容 */
|
/** 展开/收起段落内容 */
|
||||||
const toggleExpand = (segment: any) => {
|
const toggleExpand = (segment: any) => {
|
||||||
segment.expanded = !segment.expanded
|
segment.expanded = !segment.expanded
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取知识库信息 */
|
/** 获取知识库信息 */
|
||||||
const getKnowledgeInfo = async (id: number) => {
|
const getKnowledgeInfo = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -146,7 +137,6 @@ const getKnowledgeInfo = async (id: number) => {
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 如果知识库 ID 不存在,显示错误提示并关闭页面
|
// 如果知识库 ID 不存在,显示错误提示并关闭页面
|
||||||
|
|
@ -156,8 +146,7 @@ onMounted(() => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
queryParams.id = route.query.id as any
|
queryParams.id = route.query.id as any
|
||||||
|
|
||||||
// 获取知识库信息并设置默认值
|
// 获取知识库信息并设置默认值
|
||||||
getKnowledgeInfo(queryParams.id as any)
|
getKnowledgeInfo(queryParams.id as any)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -24,13 +24,10 @@
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { KnowledgeSegmentApi, KnowledgeSegmentVO } from '@/api/ai/knowledge/segment'
|
import { KnowledgeSegmentApi, KnowledgeSegmentVO } from '@/api/ai/knowledge/segment'
|
||||||
|
|
||||||
/** AI 知识库分段表单 */
|
/** AI 知识库分段表单 */
|
||||||
defineOptions({ name: 'KnowledgeSegmentForm' })
|
defineOptions({ name: 'KnowledgeSegmentForm' })
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
|
@ -44,7 +41,6 @@ const formRules = reactive({
|
||||||
content: [{ required: true, message: '切片内容不能为空', trigger: 'blur' }]
|
content: [{ required: true, message: '切片内容不能为空', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (type: string, id?: number, documentId?: any) => {
|
const open = async (type: string, id?: number, documentId?: any) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
|
@ -52,7 +48,6 @@ const open = async (type: string, id?: number, documentId?: any) => {
|
||||||
formType.value = type
|
formType.value = type
|
||||||
resetForm()
|
resetForm()
|
||||||
formData.value.documentId = documentId as any
|
formData.value.documentId = documentId as any
|
||||||
|
|
||||||
// 修改时,设置数据
|
// 修改时,设置数据
|
||||||
if (id) {
|
if (id) {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
|
|
@ -64,7 +59,6 @@ const open = async (type: string, id?: number, documentId?: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
/** 提交表单 */
|
/** 提交表单 */
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
|
|
@ -88,7 +82,6 @@ const submitForm = async () => {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
|
|
@ -98,4 +91,4 @@ const resetForm = () => {
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -46,7 +46,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -131,11 +130,9 @@
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<KnowledgeSegmentForm ref="formRef" @success="getList" />
|
<KnowledgeSegmentForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
|
@ -143,15 +140,12 @@ import { KnowledgeSegmentApi, KnowledgeSegmentVO } from '@/api/ai/knowledge/segm
|
||||||
import KnowledgeSegmentForm from './KnowledgeSegmentForm.vue'
|
import KnowledgeSegmentForm from './KnowledgeSegmentForm.vue'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
import { checkPermi } from '@/utils/permission'
|
import { checkPermi } from '@/utils/permission'
|
||||||
|
|
||||||
/** AI 知识库分段 列表 */
|
/** AI 知识库分段 列表 */
|
||||||
defineOptions({ name: 'KnowledgeSegment' })
|
defineOptions({ name: 'KnowledgeSegment' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const router = useRouter() // 路由
|
const router = useRouter() // 路由
|
||||||
const route = useRoute() // 路由
|
const route = useRoute() // 路由
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<KnowledgeSegmentVO[]>([]) // 列表的数据
|
const list = ref<KnowledgeSegmentVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -163,7 +157,6 @@ const queryParams = reactive({
|
||||||
status: undefined
|
status: undefined
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -175,25 +168,21 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, id?: number) => {
|
||||||
formRef.value.open(type, id, queryParams.documentId)
|
formRef.value.open(type, id, queryParams.documentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -206,7 +195,6 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改状态操作 */
|
/** 修改状态操作 */
|
||||||
const handleStatusChange = async (row: KnowledgeSegmentVO) => {
|
const handleStatusChange = async (row: KnowledgeSegmentVO) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -224,7 +212,6 @@ const handleStatusChange = async (row: KnowledgeSegmentVO) => {
|
||||||
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
|
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 如果文档 ID 不存在,显示错误提示并关闭页面
|
// 如果文档 ID 不存在,显示错误提示并关闭页面
|
||||||
|
|
@ -234,9 +221,8 @@ onMounted(() => {
|
||||||
router.push({ name: 'AiKnowledgeDocument' })
|
router.push({ name: 'AiKnowledgeDocument' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从路由参数中获取文档 ID
|
// 从路由参数中获取文档 ID
|
||||||
queryParams.documentId = route.query.documentId as any
|
queryParams.documentId = route.query.documentId as any
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -48,10 +48,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { MindMapContentExample } from '@/views/ai/utils/constants'
|
import { MindMapContentExample } from '@/views/ai/utils/constants'
|
||||||
|
|
||||||
const emits = defineEmits(['submit', 'directGenerate'])
|
const emits = defineEmits(['submit', 'directGenerate'])
|
||||||
defineProps<{
|
defineProps<{
|
||||||
isGenerating: boolean
|
isGenerating: boolean
|
||||||
|
|
@ -60,9 +58,7 @@ defineProps<{
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
prompt: ''
|
prompt: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const generatedContent = ref(MindMapContentExample) // 已有的内容
|
const generatedContent = ref(MindMapContentExample) // 已有的内容
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
setGeneratedContent(newContent: string) {
|
setGeneratedContent(newContent: string) {
|
||||||
// 设置已有的内容,在生成结束的时候将结果赋值给该值
|
// 设置已有的内容,在生成结束的时候将结果赋值给该值
|
||||||
|
|
@ -70,9 +66,8 @@ defineExpose({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.title {
|
.title {
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -12,13 +12,11 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
</h3>
|
</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div ref="contentRef" class="hide-scroll-bar h-full box-border">
|
<div ref="contentRef" class="hide-scroll-bar h-full box-border">
|
||||||
<!--展示 markdown 的容器,最终生成的是 html 字符串,直接用 v-html 嵌入-->
|
<!--展示 markdown 的容器,最终生成的是 html 字符串,直接用 v-html 嵌入-->
|
||||||
<div v-if="isGenerating" ref="mdContainerRef" class="wh-full overflow-y-auto">
|
<div v-if="isGenerating" ref="mdContainerRef" class="wh-full overflow-y-auto">
|
||||||
<div class="flex flex-col items-center justify-center" v-html="html"></div>
|
<div class="flex flex-col items-center justify-center" v-html="html"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ref="mindMapRef" class="wh-full">
|
<div ref="mindMapRef" class="wh-full">
|
||||||
<svg ref="svgRef" :style="{ height: `${contentAreaHeight}px` }" class="w-full" />
|
<svg ref="svgRef" :style="{ height: `${contentAreaHeight}px` }" class="w-full" />
|
||||||
<div ref="toolBarRef" class="absolute bottom-[10px] right-5"></div>
|
<div ref="toolBarRef" class="absolute bottom-[10px] right-5"></div>
|
||||||
|
|
@ -26,17 +24,14 @@
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Markmap } from 'markmap-view'
|
import { Markmap } from 'markmap-view'
|
||||||
import { Transformer } from 'markmap-lib'
|
import { Transformer } from 'markmap-lib'
|
||||||
import { Toolbar } from 'markmap-toolbar'
|
import { Toolbar } from 'markmap-toolbar'
|
||||||
import markdownit from 'markdown-it'
|
import markdownit from 'markdown-it'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
|
|
||||||
const md = markdownit()
|
const md = markdownit()
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
generatedContent: string // 生成结果
|
generatedContent: string // 生成结果
|
||||||
isEnd: boolean // 是否结束
|
isEnd: boolean // 是否结束
|
||||||
|
|
@ -52,7 +47,6 @@ const html = ref('') // 生成过程中的文本
|
||||||
const contentAreaHeight = ref(0) // 生成区域的高度,出去 header 部分
|
const contentAreaHeight = ref(0) // 生成区域的高度,出去 header 部分
|
||||||
let markMap: Markmap | null = null
|
let markMap: Markmap | null = null
|
||||||
const transformer = new Transformer()
|
const transformer = new Transformer()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
contentAreaHeight.value = contentRef.value?.clientHeight || 0 // 获取区域高度
|
contentAreaHeight.value = contentRef.value?.clientHeight || 0 // 获取区域高度
|
||||||
/** 初始化思维导图 **/
|
/** 初始化思维导图 **/
|
||||||
|
|
@ -65,7 +59,6 @@ onMounted(() => {
|
||||||
message.error('思维导图初始化失败')
|
message.error('思维导图初始化失败')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(props, ({ generatedContent, isGenerating, isEnd, isStart }) => {
|
watch(props, ({ generatedContent, isGenerating, isEnd, isStart }) => {
|
||||||
// 开始生成的时候清空一下 markdown 的内容
|
// 开始生成的时候清空一下 markdown 的内容
|
||||||
if (isStart) {
|
if (isStart) {
|
||||||
|
|
@ -80,7 +73,6 @@ watch(props, ({ generatedContent, isGenerating, isEnd, isStart }) => {
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 更新思维导图的展示 */
|
/** 更新思维导图的展示 */
|
||||||
const update = () => {
|
const update = () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -91,7 +83,6 @@ const update = () => {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理内容 */
|
/** 处理内容 */
|
||||||
const processContent = (text: string) => {
|
const processContent = (text: string) => {
|
||||||
const arr: string[] = []
|
const arr: string[] = []
|
||||||
|
|
@ -105,7 +96,6 @@ const processContent = (text: string) => {
|
||||||
}
|
}
|
||||||
return arr.join('\n')
|
return arr.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 下载图片:download SVG to png file */
|
/** 下载图片:download SVG to png file */
|
||||||
const downloadImage = () => {
|
const downloadImage = () => {
|
||||||
const svgElement = mindMapRef.value
|
const svgElement = mindMapRef.value
|
||||||
|
|
@ -120,7 +110,6 @@ const downloadImage = () => {
|
||||||
drawWithImageSize: false
|
drawWithImageSize: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
scrollBottom() {
|
scrollBottom() {
|
||||||
mdContainerRef.value?.scrollTo(0, mdContainerRef.value?.scrollHeight)
|
mdContainerRef.value?.scrollTo(0, mdContainerRef.value?.scrollHeight)
|
||||||
|
|
@ -131,17 +120,14 @@ defineExpose({
|
||||||
.hide-scroll-bar {
|
.hide-scroll-bar {
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-card {
|
.my-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
:deep(.el-card__body) {
|
:deep(.el-card__body) {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
@ -150,18 +136,15 @@ defineExpose({
|
||||||
@extend .hide-scroll-bar;
|
@extend .hide-scroll-bar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// markmap的tool样式覆盖
|
// markmap的tool样式覆盖
|
||||||
:deep(.markmap) {
|
:deep(.markmap) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.mm-toolbar-brand) {
|
:deep(.mm-toolbar-brand) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.mm-toolbar) {
|
:deep(.mm-toolbar) {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -17,13 +17,11 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Left from './components/Left.vue'
|
import Left from './components/Left.vue'
|
||||||
import Right from './components/Right.vue'
|
import Right from './components/Right.vue'
|
||||||
import { AiMindMapApi, AiMindMapGenerateReqVO } from '@/api/ai/mindmap'
|
import { AiMindMapApi, AiMindMapGenerateReqVO } from '@/api/ai/mindmap'
|
||||||
import { MindMapContentExample } from '@/views/ai/utils/constants'
|
import { MindMapContentExample } from '@/views/ai/utils/constants'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'AiMindMap'
|
name: 'AiMindMap'
|
||||||
})
|
})
|
||||||
|
|
@ -32,26 +30,21 @@ const isGenerating = ref(false) // 是否正在生成思维导图
|
||||||
const isStart = ref(false) // 开始生成,用来清空思维导图
|
const isStart = ref(false) // 开始生成,用来清空思维导图
|
||||||
const isEnd = ref(true) // 用来判断结束的时候渲染思维导图
|
const isEnd = ref(true) // 用来判断结束的时候渲染思维导图
|
||||||
const message = useMessage() // 消息提示
|
const message = useMessage() // 消息提示
|
||||||
|
|
||||||
const generatedContent = ref('') // 生成思维导图结果
|
const generatedContent = ref('') // 生成思维导图结果
|
||||||
|
|
||||||
const leftRef = ref<InstanceType<typeof Left>>() // 左边组件
|
const leftRef = ref<InstanceType<typeof Left>>() // 左边组件
|
||||||
const rightRef = ref<InstanceType<typeof Right>>() // 右边组件
|
const rightRef = ref<InstanceType<typeof Right>>() // 右边组件
|
||||||
|
|
||||||
/** 使用已有内容直接生成 **/
|
/** 使用已有内容直接生成 **/
|
||||||
const directGenerate = (existPrompt: string) => {
|
const directGenerate = (existPrompt: string) => {
|
||||||
isEnd.value = false // 先设置为 false 再设置为 true,让子组建的 watch 能够监听到
|
isEnd.value = false // 先设置为 false 再设置为 true,让子组建的 watch 能够监听到
|
||||||
generatedContent.value = existPrompt
|
generatedContent.value = existPrompt
|
||||||
isEnd.value = true
|
isEnd.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 停止 stream 生成 */
|
/** 停止 stream 生成 */
|
||||||
const stopStream = () => {
|
const stopStream = () => {
|
||||||
isGenerating.value = false
|
isGenerating.value = false
|
||||||
isStart.value = false
|
isStart.value = false
|
||||||
ctrl.value?.abort()
|
ctrl.value?.abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 提交生成 */
|
/** 提交生成 */
|
||||||
const submit = (data: AiMindMapGenerateReqVO) => {
|
const submit = (data: AiMindMapGenerateReqVO) => {
|
||||||
isGenerating.value = true
|
isGenerating.value = true
|
||||||
|
|
@ -86,9 +79,8 @@ const submit = (data: AiMindMapGenerateReqVO) => {
|
||||||
ctrl: ctrl.value
|
ctrl: ctrl.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
generatedContent.value = MindMapContentExample
|
generatedContent.value = MindMapContentExample
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<doc-alert title="AI 思维导图" url="https://doc.iocoder.cn/ai/mindmap/" />
|
|
||||||
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form
|
||||||
|
|
@ -51,7 +49,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -94,7 +91,6 @@
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 思维导图的预览 -->
|
<!-- 思维导图的预览 -->
|
||||||
<el-drawer v-model="previewVisible" :with-header="false" size="800px">
|
<el-drawer v-model="previewVisible" :with-header="false" size="800px">
|
||||||
<Right
|
<Right
|
||||||
|
|
@ -106,19 +102,15 @@
|
||||||
/>
|
/>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import { AiMindMapApi, MindMapVO } from '@/api/ai/mindmap'
|
import { AiMindMapApi, MindMapVO } from '@/api/ai/mindmap'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import Right from '@/views/ai/mindmap/index/components/Right.vue'
|
import Right from '@/views/ai/mindmap/index/components/Right.vue'
|
||||||
|
|
||||||
/** AI 思维导图 列表 */
|
/** AI 思维导图 列表 */
|
||||||
defineOptions({ name: 'AiMindMapManager' })
|
defineOptions({ name: 'AiMindMapManager' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<MindMapVO[]>([]) // 列表的数据
|
const list = ref<MindMapVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -131,7 +123,6 @@ const queryParams = reactive({
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -143,19 +134,16 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -168,7 +156,6 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 预览操作按钮 */
|
/** 预览操作按钮 */
|
||||||
const previewVisible = ref(false) // drawer 的显示隐藏
|
const previewVisible = ref(false) // drawer 的显示隐藏
|
||||||
const previewVisible2 = ref(false) // right 的显示隐藏
|
const previewVisible2 = ref(false) // right 的显示隐藏
|
||||||
|
|
@ -181,11 +168,10 @@ const openPreview = async (row: MindMapVO) => {
|
||||||
previewVisible2.value = true
|
previewVisible2.value = true
|
||||||
previewContent.value = row.generatedContent
|
previewContent.value = row.generatedContent
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
getList()
|
getList()
|
||||||
// 获得用户列表
|
// 获得用户列表
|
||||||
userList.value = await UserApi.getSimpleUserList()
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -48,13 +48,10 @@
|
||||||
import { getIntDictOptions, DICT_TYPE, getStrDictOptions } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE, getStrDictOptions } from '@/utils/dict'
|
||||||
import { ApiKeyApi, ApiKeyVO } from '@/api/ai/model/apiKey'
|
import { ApiKeyApi, ApiKeyVO } from '@/api/ai/model/apiKey'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
|
||||||
/** AI API 密钥 表单 */
|
/** AI API 密钥 表单 */
|
||||||
defineOptions({ name: 'ApiKeyForm' })
|
defineOptions({ name: 'ApiKeyForm' })
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
|
@ -74,7 +71,6 @@ const formRules = reactive({
|
||||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (type: string, id?: number) => {
|
const open = async (type: string, id?: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
|
@ -92,7 +88,6 @@ const open = async (type: string, id?: number) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
/** 提交表单 */
|
/** 提交表单 */
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
|
|
@ -116,7 +111,6 @@ const submitForm = async () => {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
|
|
@ -129,4 +123,4 @@ const resetForm = () => {
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<doc-alert title="AI 手册" url="https://doc.iocoder.cn/ai/build/" />
|
|
||||||
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form
|
||||||
|
|
@ -58,7 +56,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -104,22 +101,17 @@
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<ApiKeyForm ref="formRef" @success="getList" />
|
<ApiKeyForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getIntDictOptions, DICT_TYPE, getStrDictOptions } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE, getStrDictOptions } from '@/utils/dict'
|
||||||
import { ApiKeyApi, ApiKeyVO } from '@/api/ai/model/apiKey'
|
import { ApiKeyApi, ApiKeyVO } from '@/api/ai/model/apiKey'
|
||||||
import ApiKeyForm from './ApiKeyForm.vue'
|
import ApiKeyForm from './ApiKeyForm.vue'
|
||||||
|
|
||||||
/** AI API 密钥 列表 */
|
/** AI API 密钥 列表 */
|
||||||
defineOptions({ name: 'AiApiKey' })
|
defineOptions({ name: 'AiApiKey' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<ApiKeyVO[]>([]) // 列表的数据
|
const list = ref<ApiKeyVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -131,7 +123,6 @@ const queryParams = reactive({
|
||||||
status: undefined
|
status: undefined
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -143,25 +134,21 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, id?: number) => {
|
||||||
formRef.value.open(type, id)
|
formRef.value.open(type, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -174,9 +161,8 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -98,13 +98,10 @@ import { FormRules } from 'element-plus'
|
||||||
import { AiModelTypeEnum } from '@/views/ai/utils/constants'
|
import { AiModelTypeEnum } from '@/views/ai/utils/constants'
|
||||||
import { KnowledgeApi, KnowledgeVO } from '@/api/ai/knowledge/knowledge'
|
import { KnowledgeApi, KnowledgeVO } from '@/api/ai/knowledge/knowledge'
|
||||||
import { ToolApi, ToolVO } from '@/api/ai/model/tool'
|
import { ToolApi, ToolVO } from '@/api/ai/model/tool'
|
||||||
|
|
||||||
/** AI 聊天角色 表单 */
|
/** AI 聊天角色 表单 */
|
||||||
defineOptions({ name: 'ChatRoleForm' })
|
defineOptions({ name: 'ChatRoleForm' })
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
|
@ -128,12 +125,10 @@ const formRef = ref() // 表单 Ref
|
||||||
const models = ref([] as ModelVO[]) // 聊天模型列表
|
const models = ref([] as ModelVO[]) // 聊天模型列表
|
||||||
const knowledgeList = ref([] as KnowledgeVO[]) // 知识库列表
|
const knowledgeList = ref([] as KnowledgeVO[]) // 知识库列表
|
||||||
const toolList = ref([] as ToolVO[]) // 工具列表
|
const toolList = ref([] as ToolVO[]) // 工具列表
|
||||||
|
|
||||||
/** 是否【我】自己创建,私有角色 */
|
/** 是否【我】自己创建,私有角色 */
|
||||||
const isUser = computed(() => {
|
const isUser = computed(() => {
|
||||||
return formType.value === 'my-create' || formType.value === 'my-update'
|
return formType.value === 'my-create' || formType.value === 'my-update'
|
||||||
})
|
})
|
||||||
|
|
||||||
const formRules = reactive<FormRules>({
|
const formRules = reactive<FormRules>({
|
||||||
name: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],
|
name: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],
|
||||||
avatar: [{ required: true, message: '角色头像不能为空', trigger: 'blur' }],
|
avatar: [{ required: true, message: '角色头像不能为空', trigger: 'blur' }],
|
||||||
|
|
@ -143,7 +138,6 @@ const formRules = reactive<FormRules>({
|
||||||
systemMessage: [{ required: true, message: '角色设定不能为空', trigger: 'blur' }],
|
systemMessage: [{ required: true, message: '角色设定不能为空', trigger: 'blur' }],
|
||||||
publicStatus: [{ required: true, message: '是否公开不能为空', trigger: 'blur' }]
|
publicStatus: [{ required: true, message: '是否公开不能为空', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
// TODO @fan:title 是不是收敛到 type 判断生成 title,会更合理
|
// TODO @fan:title 是不是收敛到 type 判断生成 title,会更合理
|
||||||
const open = async (type: string, id?: number, title?: string) => {
|
const open = async (type: string, id?: number, title?: string) => {
|
||||||
|
|
@ -168,7 +162,6 @@ const open = async (type: string, id?: number, title?: string) => {
|
||||||
toolList.value = await ToolApi.getToolSimpleList()
|
toolList.value = await ToolApi.getToolSimpleList()
|
||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
/** 提交表单 */
|
/** 提交表单 */
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
|
|
@ -200,7 +193,6 @@ const submitForm = async () => {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
|
|
@ -220,4 +212,4 @@ const resetForm = () => {
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<doc-alert title="AI 对话聊天" url="https://doc.iocoder.cn/ai/chat/" />
|
|
||||||
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form
|
||||||
|
|
@ -57,7 +55,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -123,22 +120,17 @@
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<ChatRoleForm ref="formRef" @success="getList" />
|
<ChatRoleForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { ChatRoleApi, ChatRoleVO } from '@/api/ai/model/chatRole'
|
import { ChatRoleApi, ChatRoleVO } from '@/api/ai/model/chatRole'
|
||||||
import ChatRoleForm from './ChatRoleForm.vue'
|
import ChatRoleForm from './ChatRoleForm.vue'
|
||||||
|
|
||||||
/** AI 聊天角色 列表 */
|
/** AI 聊天角色 列表 */
|
||||||
defineOptions({ name: 'AiChatRole' })
|
defineOptions({ name: 'AiChatRole' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<ChatRoleVO[]>([]) // 列表的数据
|
const list = ref<ChatRoleVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -150,7 +142,6 @@ const queryParams = reactive({
|
||||||
publicStatus: true
|
publicStatus: true
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -162,25 +153,21 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, id?: number) => {
|
||||||
formRef.value.open(type, id)
|
formRef.value.open(type, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -193,9 +180,8 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -115,13 +115,10 @@ import { ApiKeyApi, ApiKeyVO } from '@/api/ai/model/apiKey'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
|
||||||
import { AiModelTypeEnum } from '@/views/ai/utils/constants'
|
import { AiModelTypeEnum } from '@/views/ai/utils/constants'
|
||||||
|
|
||||||
/** API 模型的表单 */
|
/** API 模型的表单 */
|
||||||
defineOptions({ name: 'ModelForm' })
|
defineOptions({ name: 'ModelForm' })
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
|
@ -153,7 +150,6 @@ const formRules = reactive({
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
const apiKeyList = ref([] as ApiKeyVO[]) // API 密钥列表
|
const apiKeyList = ref([] as ApiKeyVO[]) // API 密钥列表
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (type: string, id?: number) => {
|
const open = async (type: string, id?: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
|
@ -173,7 +169,6 @@ const open = async (type: string, id?: number) => {
|
||||||
apiKeyList.value = await ApiKeyApi.getApiKeySimpleList()
|
apiKeyList.value = await ApiKeyApi.getApiKeySimpleList()
|
||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
/** 提交表单 */
|
/** 提交表单 */
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
|
|
@ -202,7 +197,6 @@ const submitForm = async () => {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
|
|
@ -220,4 +214,4 @@ const resetForm = () => {
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<doc-alert title="AI 手册" url="https://doc.iocoder.cn/ai/build/" />
|
|
||||||
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form
|
||||||
|
|
@ -51,7 +49,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -110,23 +107,18 @@
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<ModelForm ref="formRef" @success="getList" />
|
<ModelForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ModelApi, ModelVO } from '@/api/ai/model/model'
|
import { ModelApi, ModelVO } from '@/api/ai/model/model'
|
||||||
import ModelForm from './ModelForm.vue'
|
import ModelForm from './ModelForm.vue'
|
||||||
import { DICT_TYPE } from '@/utils/dict'
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
import { ApiKeyApi, ApiKeyVO } from '@/api/ai/model/apiKey'
|
import { ApiKeyApi, ApiKeyVO } from '@/api/ai/model/apiKey'
|
||||||
|
|
||||||
/** API 模型列表 */
|
/** API 模型列表 */
|
||||||
defineOptions({ name: 'AiModel' })
|
defineOptions({ name: 'AiModel' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<ModelVO[]>([]) // 列表的数据
|
const list = ref<ModelVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -139,7 +131,6 @@ const queryParams = reactive({
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const apiKeyList = ref([] as ApiKeyVO[]) // API 密钥列表
|
const apiKeyList = ref([] as ApiKeyVO[]) // API 密钥列表
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -151,25 +142,21 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, id?: number) => {
|
||||||
formRef.value.open(type, id)
|
formRef.value.open(type, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -182,11 +169,10 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getList()
|
await getList()
|
||||||
// 获得下拉数据
|
// 获得下拉数据
|
||||||
apiKeyList.value = await ApiKeyApi.getApiKeySimpleList()
|
apiKeyList.value = await ApiKeyApi.getApiKeySimpleList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -35,13 +35,10 @@
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { ToolApi, ToolVO } from '@/api/ai/model/tool'
|
import { ToolApi, ToolVO } from '@/api/ai/model/tool'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
|
||||||
/** AI 工具表单 */
|
/** AI 工具表单 */
|
||||||
defineOptions({ name: 'ToolForm' })
|
defineOptions({ name: 'ToolForm' })
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
|
@ -56,7 +53,6 @@ const formRules = reactive({
|
||||||
name: [{ required: true, message: '工具名称不能为空', trigger: 'blur' }]
|
name: [{ required: true, message: '工具名称不能为空', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (type: string, id?: number) => {
|
const open = async (type: string, id?: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
|
@ -74,7 +70,6 @@ const open = async (type: string, id?: number) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
/** 提交表单 */
|
/** 提交表单 */
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
|
|
@ -98,7 +93,6 @@ const submitForm = async () => {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
|
|
@ -109,4 +103,4 @@ const resetForm = () => {
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<doc-alert title="AI 工具调用(function calling)" url="https://doc.iocoder.cn/ai/tool/" />
|
|
||||||
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form
|
||||||
|
|
@ -49,7 +47,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -97,23 +94,18 @@
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<ToolForm ref="formRef" @success="getList" />
|
<ToolForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import { ToolApi, ToolVO } from '@/api/ai/model/tool'
|
import { ToolApi, ToolVO } from '@/api/ai/model/tool'
|
||||||
import ToolForm from './ToolForm.vue'
|
import ToolForm from './ToolForm.vue'
|
||||||
|
|
||||||
/** AI 工具 列表 */
|
/** AI 工具 列表 */
|
||||||
defineOptions({ name: 'AiTool' })
|
defineOptions({ name: 'AiTool' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<ToolVO[]>([]) // 列表的数据
|
const list = ref<ToolVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -127,7 +119,6 @@ const queryParams = reactive({
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const exportLoading = ref(false) // 导出的加载中
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -139,25 +130,21 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, id?: number) => {
|
||||||
formRef.value.open(type, id)
|
formRef.value.open(type, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -170,9 +157,8 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -6,15 +6,11 @@
|
||||||
<List ref="listRef" class="flex-auto"/>
|
<List ref="listRef" class="flex-auto"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Mode from './mode/index.vue'
|
import Mode from './mode/index.vue'
|
||||||
import List from './list/index.vue'
|
import List from './list/index.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'Index' })
|
defineOptions({ name: 'Index' })
|
||||||
|
|
||||||
const listRef = ref<Nullable<{generateMusic: (...args) => void}>>(null)
|
const listRef = ref<Nullable<{generateMusic: (...args) => void}>>(null)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*@Description: 拿到左侧配置信息调用右侧音乐生成的方法
|
*@Description: 拿到左侧配置信息调用右侧音乐生成的方法
|
||||||
*@MethodAuthor: xiaohong
|
*@MethodAuthor: xiaohong
|
||||||
|
|
@ -23,4 +19,4 @@ const listRef = ref<Nullable<{generateMusic: (...args) => void}>>(null)
|
||||||
function generateMusic (args: {formData: Recordable}) {
|
function generateMusic (args: {formData: Recordable}) {
|
||||||
unref(listRef)?.generateMusic(args.formData)
|
unref(listRef)?.generateMusic(args.formData)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
<div class="text-[12px] text-gray-400">{{currentSong.singer}}</div>
|
<div class="text-[12px] text-gray-400">{{currentSong.singer}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 音频controls -->
|
<!-- 音频controls -->
|
||||||
<div class="flex gap-[12px] items-center">
|
<div class="flex gap-[12px] items-center">
|
||||||
<Icon icon="majesticons:back-circle" :size="20" class="text-gray-300 cursor-pointer"/>
|
<Icon icon="majesticons:back-circle" :size="20" class="text-gray-300 cursor-pointer"/>
|
||||||
|
|
@ -24,7 +23,6 @@
|
||||||
<source :src="audioUrl"/>
|
<source :src="audioUrl"/>
|
||||||
</audio>
|
</audio>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 音量控制器 -->
|
<!-- 音量控制器 -->
|
||||||
<div class="flex gap-[16px] items-center">
|
<div class="flex gap-[16px] items-center">
|
||||||
<Icon :icon="audioProps.muted ? 'tabler:volume-off' : 'tabler:volume'" :size="20" class="cursor-pointer" @click="toggleStatus('muted')"/>
|
<Icon :icon="audioProps.muted ? 'tabler:volume-off' : 'tabler:volume'" :size="20" class="cursor-pointer" @click="toggleStatus('muted')"/>
|
||||||
|
|
@ -32,15 +30,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { formatPast } from '@/utils/formatTime'
|
import { formatPast } from '@/utils/formatTime'
|
||||||
import audioUrl from '@/assets/audio/response.mp3'
|
import audioUrl from '@/assets/audio/response.mp3'
|
||||||
|
|
||||||
defineOptions({ name: 'Index' })
|
defineOptions({ name: 'Index' })
|
||||||
|
|
||||||
const currentSong = inject('currentSong', {})
|
const currentSong = inject('currentSong', {})
|
||||||
|
|
||||||
const audioRef = ref<Nullable<HTMLElement>>(null)
|
const audioRef = ref<Nullable<HTMLElement>>(null)
|
||||||
// 音频相关属性https://www.runoob.com/tags/ref-av-dom.html
|
// 音频相关属性https://www.runoob.com/tags/ref-av-dom.html
|
||||||
const audioProps = reactive({
|
const audioProps = reactive({
|
||||||
|
|
@ -51,7 +45,6 @@ const audioProps = reactive({
|
||||||
muted: false,
|
muted: false,
|
||||||
volume: 50,
|
volume: 50,
|
||||||
})
|
})
|
||||||
|
|
||||||
function toggleStatus (type: string) {
|
function toggleStatus (type: string) {
|
||||||
audioProps[type] = !audioProps[type]
|
audioProps[type] = !audioProps[type]
|
||||||
if (type === 'paused' && audioRef.value) {
|
if (type === 'paused' && audioRef.value) {
|
||||||
|
|
@ -62,9 +55,8 @@ function toggleStatus (type: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新播放位置
|
// 更新播放位置
|
||||||
function audioTimeUpdate (args) {
|
function audioTimeUpdate (args) {
|
||||||
audioProps.currentTime = formatPast(new Date(args.timeStamp), 'mm:ss')
|
audioProps.currentTime = formatPast(new Date(args.timeStamp), 'mm:ss')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-empty v-else description="暂无音乐"/>
|
<el-empty v-else description="暂无音乐"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<!-- 试听广场 -->
|
<!-- 试听广场 -->
|
||||||
<el-tab-pane v-loading="loading" label="试听广场" name="square">
|
<el-tab-pane v-loading="loading" label="试听广场" name="square">
|
||||||
<el-row v-if="squareSongList.length" v-loading="loading" :gutter="12">
|
<el-row v-if="squareSongList.length" v-loading="loading" :gutter="12">
|
||||||
|
|
@ -28,26 +27,19 @@
|
||||||
<audioBar class="flex-none"/>
|
<audioBar class="flex-none"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import songCard from './songCard/index.vue'
|
import songCard from './songCard/index.vue'
|
||||||
import songInfo from './songInfo/index.vue'
|
import songInfo from './songInfo/index.vue'
|
||||||
import audioBar from './audioBar/index.vue'
|
import audioBar from './audioBar/index.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'Index' })
|
defineOptions({ name: 'Index' })
|
||||||
|
|
||||||
|
|
||||||
const currentType = ref('mine')
|
const currentType = ref('mine')
|
||||||
// loading 状态
|
// loading 状态
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
// 当前音乐
|
// 当前音乐
|
||||||
const currentSong = ref({})
|
const currentSong = ref({})
|
||||||
|
|
||||||
const mySongList = ref<Recordable[]>([])
|
const mySongList = ref<Recordable[]>([])
|
||||||
const squareSongList = ref<Recordable[]>([])
|
const squareSongList = ref<Recordable[]>([])
|
||||||
|
|
||||||
provide('currentSong', currentSong)
|
provide('currentSong', currentSong)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*@Description: 调接口生成音乐列表
|
*@Description: 调接口生成音乐列表
|
||||||
*@MethodAuthor: xiaohong
|
*@MethodAuthor: xiaohong
|
||||||
|
|
@ -80,7 +72,6 @@ function generateMusic (formData: Recordable) {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}, 3000)
|
}, 3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*@Description: 设置当前播放的音乐
|
*@Description: 设置当前播放的音乐
|
||||||
*@MethodAuthor: xiaohong
|
*@MethodAuthor: xiaohong
|
||||||
|
|
@ -89,13 +80,10 @@ function generateMusic (formData: Recordable) {
|
||||||
function setCurrentSong (music: Recordable) {
|
function setCurrentSong (music: Recordable) {
|
||||||
currentSong.value = music
|
currentSong.value = music
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
generateMusic
|
generateMusic
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep(.el-tabs) {
|
:deep(.el-tabs) {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -105,4 +93,4 @@ defineExpose({
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -14,23 +14,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
defineOptions({ name: 'Index' })
|
defineOptions({ name: 'Index' })
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
songInfo: {
|
songInfo: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emits = defineEmits(['play'])
|
const emits = defineEmits(['play'])
|
||||||
|
|
||||||
const currentSong = inject('currentSong', {})
|
const currentSong = inject('currentSong', {})
|
||||||
|
|
||||||
function playSong () {
|
function playSong () {
|
||||||
emits('play')
|
emits('play')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -12,11 +12,7 @@
|
||||||
<div class="text-[var(--el-text-color-secondary)] text-12px" v-html="currentSong.lyric"></div>
|
<div class="text-[var(--el-text-color-secondary)] text-12px" v-html="currentSong.lyric"></div>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
defineOptions({ name: 'Index' })
|
defineOptions({ name: 'Index' })
|
||||||
|
|
||||||
const currentSong = inject('currentSong', {})
|
const currentSong = inject('currentSong', {})
|
||||||
|
</script>
|
||||||
</script>
|
|
||||||
|
|
@ -11,13 +11,11 @@
|
||||||
placeholder="一首关于糟糕分手的欢快歌曲"
|
placeholder="一首关于糟糕分手的欢快歌曲"
|
||||||
/>
|
/>
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
<Title title="纯音乐" desc="创建一首没有歌词的歌曲">
|
<Title title="纯音乐" desc="创建一首没有歌词的歌曲">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<el-switch v-model="formData.pure" size="small"/>
|
<el-switch v-model="formData.pure" size="small"/>
|
||||||
</template>
|
</template>
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
<Title title="版本" desc="描述您想要的音乐风格和主题,使用流派和氛围而不是特定的艺术家和歌曲">
|
<Title title="版本" desc="描述您想要的音乐风格和主题,使用流派和氛围而不是特定的艺术家和歌曲">
|
||||||
<el-select v-model="formData.version" placeholder="请选择">
|
<el-select v-model="formData.version" placeholder="请选择">
|
||||||
<el-option
|
<el-option
|
||||||
|
|
@ -36,20 +34,15 @@
|
||||||
</Title>
|
</Title>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Title from '../title/index.vue'
|
import Title from '../title/index.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'Desc' })
|
defineOptions({ name: 'Desc' })
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
desc: '',
|
desc: '',
|
||||||
pure: false,
|
pure: false,
|
||||||
version: '3'
|
version: '3'
|
||||||
})
|
})
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
formData
|
formData
|
||||||
})
|
})
|
||||||
|
</script>
|
||||||
</script>
|
|
||||||
|
|
@ -4,26 +4,18 @@
|
||||||
<el-radio-button value="desc"> 描述模式 </el-radio-button>
|
<el-radio-button value="desc"> 描述模式 </el-radio-button>
|
||||||
<el-radio-button value="lyric"> 歌词模式 </el-radio-button>
|
<el-radio-button value="lyric"> 歌词模式 </el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
|
|
||||||
<!-- 描述模式/歌词模式 切换 -->
|
<!-- 描述模式/歌词模式 切换 -->
|
||||||
<component :is="generateMode === 'desc' ? desc : lyric" ref="modeRef" />
|
<component :is="generateMode === 'desc' ? desc : lyric" ref="modeRef" />
|
||||||
|
|
||||||
<el-button type="primary" round class="w-full" @click="generateMusic"> 创作音乐 </el-button>
|
<el-button type="primary" round class="w-full" @click="generateMusic"> 创作音乐 </el-button>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import desc from './desc.vue'
|
import desc from './desc.vue'
|
||||||
import lyric from './lyric.vue'
|
import lyric from './lyric.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'Index' })
|
defineOptions({ name: 'Index' })
|
||||||
|
|
||||||
const emits = defineEmits(['generate-music'])
|
const emits = defineEmits(['generate-music'])
|
||||||
|
|
||||||
const generateMode = ref('lyric')
|
const generateMode = ref('lyric')
|
||||||
|
|
||||||
const modeRef = ref<Nullable<{ formData: Recordable }>>(null)
|
const modeRef = ref<Nullable<{ formData: Recordable }>>(null)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*@Description: 根据信息生成音乐
|
*@Description: 根据信息生成音乐
|
||||||
*@MethodAuthor: xiaohong
|
*@MethodAuthor: xiaohong
|
||||||
|
|
@ -32,4 +24,4 @@ const modeRef = ref<Nullable<{ formData: Recordable }>>(null)
|
||||||
function generateMusic() {
|
function generateMusic() {
|
||||||
emits('generate-music', { formData: unref(modeRef)?.formData })
|
emits('generate-music', { formData: unref(modeRef)?.formData })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -11,12 +11,10 @@
|
||||||
placeholder="请输入您自己的歌词"
|
placeholder="请输入您自己的歌词"
|
||||||
/>
|
/>
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
<Title title="音乐风格">
|
<Title title="音乐风格">
|
||||||
<el-space class="flex-wrap">
|
<el-space class="flex-wrap">
|
||||||
<el-tag v-for="tag in tags" :key="tag" round class="mb-8px">{{tag}}</el-tag>
|
<el-tag v-for="tag in tags" :key="tag" round class="mb-8px">{{tag}}</el-tag>
|
||||||
</el-space>
|
</el-space>
|
||||||
|
|
||||||
<el-button
|
<el-button
|
||||||
:type="showCustom ? 'primary': 'default'"
|
:type="showCustom ? 'primary': 'default'"
|
||||||
round
|
round
|
||||||
|
|
@ -26,7 +24,6 @@
|
||||||
>自定义风格
|
>自定义风格
|
||||||
</el-button>
|
</el-button>
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
<Title v-show="showCustom" desc="描述您想要的音乐风格,Suno无法识别艺术家的名字,但可以理解流派和氛围" class="-mt-12px">
|
<Title v-show="showCustom" desc="描述您想要的音乐风格,Suno无法识别艺术家的名字,但可以理解流派和氛围" class="-mt-12px">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="formData.style"
|
v-model="formData.style"
|
||||||
|
|
@ -38,11 +35,9 @@
|
||||||
placeholder="输入音乐风格(英文)"
|
placeholder="输入音乐风格(英文)"
|
||||||
/>
|
/>
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
<Title title="音乐/歌曲名称">
|
<Title title="音乐/歌曲名称">
|
||||||
<el-input v-model="formData.name" placeholder="请输入音乐/歌曲名称"/>
|
<el-input v-model="formData.name" placeholder="请输入音乐/歌曲名称"/>
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
<Title title="版本">
|
<Title title="版本">
|
||||||
<el-select v-model="formData.version" placeholder="请选择">
|
<el-select v-model="formData.version" placeholder="请选择">
|
||||||
<el-option
|
<el-option
|
||||||
|
|
@ -61,23 +56,18 @@
|
||||||
</Title>
|
</Title>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Title from '../title/index.vue'
|
import Title from '../title/index.vue'
|
||||||
defineOptions({ name: 'Lyric' })
|
defineOptions({ name: 'Lyric' })
|
||||||
|
|
||||||
const tags = ['rock', 'punk', 'jazz', 'soul', 'country', 'kidsmusic', 'pop']
|
const tags = ['rock', 'punk', 'jazz', 'soul', 'country', 'kidsmusic', 'pop']
|
||||||
|
|
||||||
const showCustom = ref(false)
|
const showCustom = ref(false)
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
lyric: '',
|
lyric: '',
|
||||||
style: '',
|
style: '',
|
||||||
name: '',
|
name: '',
|
||||||
version: ''
|
version: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
formData
|
formData
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -10,10 +10,8 @@
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
defineOptions({ name: 'Index' })
|
defineOptions({ name: 'Index' })
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
title: {
|
title: {
|
||||||
type: String
|
type: String
|
||||||
|
|
@ -22,4 +20,4 @@ defineProps({
|
||||||
type: String
|
type: String
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<doc-alert title="AI 音乐创作" url="https://doc.iocoder.cn/ai/music/" />
|
|
||||||
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form
|
||||||
|
|
@ -96,7 +94,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -201,20 +198,16 @@
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getIntDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import { MusicApi, MusicVO } from '@/api/ai/music'
|
import { MusicApi, MusicVO } from '@/api/ai/music'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import { AiMusicStatusEnum } from '@/views/ai/utils/constants'
|
import { AiMusicStatusEnum } from '@/views/ai/utils/constants'
|
||||||
|
|
||||||
/** AI 音乐 列表 */
|
/** AI 音乐 列表 */
|
||||||
defineOptions({ name: 'AiMusicManager' })
|
defineOptions({ name: 'AiMusicManager' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<MusicVO[]>([]) // 列表的数据
|
const list = ref<MusicVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -230,7 +223,6 @@ const queryParams = reactive({
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -242,19 +234,16 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -267,7 +256,6 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改是否发布 */
|
/** 修改是否发布 */
|
||||||
const handleUpdatePublicStatusChange = async (row: MusicVO) => {
|
const handleUpdatePublicStatusChange = async (row: MusicVO) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -284,11 +272,10 @@ const handleUpdatePublicStatusChange = async (row: MusicVO) => {
|
||||||
row.publicStatus = !row.publicStatus
|
row.publicStatus = !row.publicStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
getList()
|
getList()
|
||||||
// 获得用户列表
|
// 获得用户列表
|
||||||
userList.value = await UserApi.getSimpleUserList()
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -34,16 +34,13 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { FormRules } from 'element-plus'
|
import { FormRules } from 'element-plus'
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
|
||||||
const modelData = defineModel<any>()
|
const modelData = defineModel<any>()
|
||||||
|
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
const formRules = reactive<FormRules>({
|
const formRules = reactive<FormRules>({
|
||||||
code: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
code: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
||||||
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
||||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 表单校验 */
|
/** 表单校验 */
|
||||||
const validate = async () => {
|
const validate = async () => {
|
||||||
await formRef.value?.validate()
|
await formRef.value?.validate()
|
||||||
|
|
@ -51,4 +48,4 @@ const validate = async () => {
|
||||||
defineExpose({
|
defineExpose({
|
||||||
validate
|
validate
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -13,7 +13,6 @@
|
||||||
测试
|
测试
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 测试窗口 -->
|
<!-- 测试窗口 -->
|
||||||
<el-drawer v-model="showTestDrawer" title="工作流测试" :modal="false">
|
<el-drawer v-model="showTestDrawer" title="工作流测试" :modal="false">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
|
@ -59,17 +58,14 @@
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Tinyflow from '@/components/Tinyflow/Tinyflow.vue'
|
import Tinyflow from '@/components/Tinyflow/Tinyflow.vue'
|
||||||
import * as WorkflowApi from '@/api/ai/workflow'
|
import * as WorkflowApi from '@/api/ai/workflow'
|
||||||
// TODO @lesan:要不使用 ICon 哪个组件哈
|
// TODO @lesan:要不使用 ICon 哪个组件哈
|
||||||
import { Delete } from '@element-plus/icons-vue'
|
import { Delete } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
provider: any
|
provider: any
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tinyflowRef = ref()
|
const tinyflowRef = ref()
|
||||||
const workflowData = inject('workflowData') as Ref
|
const workflowData = inject('workflowData') as Ref
|
||||||
const showTestDrawer = ref(false)
|
const showTestDrawer = ref(false)
|
||||||
|
|
@ -78,12 +74,10 @@ const paramsOfStartNode = ref({})
|
||||||
const testResult = ref(null)
|
const testResult = ref(null)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const error = ref(null)
|
const error = ref(null)
|
||||||
|
|
||||||
/** 展示工作流测试抽屉 */
|
/** 展示工作流测试抽屉 */
|
||||||
const testWorkflowModel = () => {
|
const testWorkflowModel = () => {
|
||||||
showTestDrawer.value = !showTestDrawer.value
|
showTestDrawer.value = !showTestDrawer.value
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 运行流程 */
|
/** 运行流程 */
|
||||||
const goRun = async () => {
|
const goRun = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -93,37 +87,31 @@ const goRun = async () => {
|
||||||
testResult.value = null
|
testResult.value = null
|
||||||
/// 查找start节点
|
/// 查找start节点
|
||||||
const startNode = getStartNode()
|
const startNode = getStartNode()
|
||||||
|
|
||||||
// 获取参数定义
|
// 获取参数定义
|
||||||
const parameters = startNode.data?.parameters || []
|
const parameters = startNode.data?.parameters || []
|
||||||
const paramDefinitions = {}
|
const paramDefinitions = {}
|
||||||
parameters.forEach((param) => {
|
parameters.forEach((param) => {
|
||||||
paramDefinitions[param.name] = param.dataType
|
paramDefinitions[param.name] = param.dataType
|
||||||
})
|
})
|
||||||
|
|
||||||
// 参数类型转换
|
// 参数类型转换
|
||||||
const convertedParams = {}
|
const convertedParams = {}
|
||||||
for (const { key, value } of params4Test.value) {
|
for (const { key, value } of params4Test.value) {
|
||||||
const paramKey = key.trim()
|
const paramKey = key.trim()
|
||||||
if (!paramKey) continue
|
if (!paramKey) continue
|
||||||
|
|
||||||
let dataType = paramDefinitions[paramKey]
|
let dataType = paramDefinitions[paramKey]
|
||||||
if (!dataType) {
|
if (!dataType) {
|
||||||
dataType = 'String'
|
dataType = 'String'
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
convertedParams[paramKey] = convertParamValue(value, dataType)
|
convertedParams[paramKey] = convertParamValue(value, dataType)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`参数 ${paramKey} 转换失败: ${e.message}`)
|
throw new Error(`参数 ${paramKey} 转换失败: ${e.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
graph: JSON.stringify(val),
|
graph: JSON.stringify(val),
|
||||||
params: convertedParams
|
params: convertedParams
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await WorkflowApi.testWorkflow(data)
|
const response = await WorkflowApi.testWorkflow(data)
|
||||||
testResult.value = response
|
testResult.value = response
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -132,31 +120,24 @@ const goRun = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 监听测试抽屉的开启,获取开始节点参数列表 */
|
/** 监听测试抽屉的开启,获取开始节点参数列表 */
|
||||||
watch(showTestDrawer, (value) => {
|
watch(showTestDrawer, (value) => {
|
||||||
if (!value) return
|
if (!value) return
|
||||||
|
|
||||||
/// 查找start节点
|
/// 查找start节点
|
||||||
const startNode = getStartNode()
|
const startNode = getStartNode()
|
||||||
|
|
||||||
// 获取参数定义
|
// 获取参数定义
|
||||||
const parameters = startNode.data?.parameters || []
|
const parameters = startNode.data?.parameters || []
|
||||||
const paramDefinitions = {}
|
const paramDefinitions = {}
|
||||||
|
|
||||||
// 加入参数选项方便用户添加非必须参数
|
// 加入参数选项方便用户添加非必须参数
|
||||||
parameters.forEach((param) => {
|
parameters.forEach((param) => {
|
||||||
paramDefinitions[param.name] = param
|
paramDefinitions[param.name] = param
|
||||||
})
|
})
|
||||||
|
|
||||||
function mergeIfRequiredButNotSet(target) {
|
function mergeIfRequiredButNotSet(target) {
|
||||||
let needPushList = []
|
let needPushList = []
|
||||||
for (let key in paramDefinitions) {
|
for (let key in paramDefinitions) {
|
||||||
let param = paramDefinitions[key]
|
let param = paramDefinitions[key]
|
||||||
|
|
||||||
if (param.required) {
|
if (param.required) {
|
||||||
let item = target.find((item) => item.key === key)
|
let item = target.find((item) => item.key === key)
|
||||||
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
needPushList.push({ key: param.name, value: param.defaultValue || '' })
|
needPushList.push({ key: param.name, value: param.defaultValue || '' })
|
||||||
}
|
}
|
||||||
|
|
@ -166,10 +147,8 @@ watch(showTestDrawer, (value) => {
|
||||||
}
|
}
|
||||||
// 自动装载需必填的参数
|
// 自动装载需必填的参数
|
||||||
mergeIfRequiredButNotSet(params4Test.value)
|
mergeIfRequiredButNotSet(params4Test.value)
|
||||||
|
|
||||||
paramsOfStartNode.value = paramDefinitions
|
paramsOfStartNode.value = paramDefinitions
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 获取开始节点 */
|
/** 获取开始节点 */
|
||||||
const getStartNode = () => {
|
const getStartNode = () => {
|
||||||
const val = tinyflowRef.value.getData()
|
const val = tinyflowRef.value.getData()
|
||||||
|
|
@ -179,21 +158,17 @@ const getStartNode = () => {
|
||||||
}
|
}
|
||||||
return startNode
|
return startNode
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加参数项 */
|
/** 添加参数项 */
|
||||||
const addParam = () => {
|
const addParam = () => {
|
||||||
params4Test.value.push({ key: '', value: '' })
|
params4Test.value.push({ key: '', value: '' })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除参数项 */
|
/** 删除参数项 */
|
||||||
const removeParam = (index) => {
|
const removeParam = (index) => {
|
||||||
params4Test.value.splice(index, 1)
|
params4Test.value.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 类型转换函数 */
|
/** 类型转换函数 */
|
||||||
const convertParamValue = (value, dataType) => {
|
const convertParamValue = (value, dataType) => {
|
||||||
if (value === '') return null // 空值处理
|
if (value === '') return null // 空值处理
|
||||||
|
|
||||||
switch (dataType) {
|
switch (dataType) {
|
||||||
case 'String':
|
case 'String':
|
||||||
return String(value)
|
return String(value)
|
||||||
|
|
@ -216,7 +191,6 @@ const convertParamValue = (value, dataType) => {
|
||||||
throw new Error(`不支持的类型: ${dataType}`)
|
throw new Error(`不支持的类型: ${dataType}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表单校验 */
|
/** 表单校验 */
|
||||||
const validate = async () => {
|
const validate = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -234,7 +208,6 @@ defineExpose({
|
||||||
validate
|
validate
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
.result-content {
|
.result-content {
|
||||||
background: white;
|
background: white;
|
||||||
|
|
@ -247,4 +220,4 @@ defineExpose({
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
{{ formData.name || '创建流程' }}
|
{{ formData.name || '创建流程' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 步骤条 -->
|
<!-- 步骤条 -->
|
||||||
<div class="flex-1 flex items-center justify-center h-full">
|
<div class="flex-1 flex items-center justify-center h-full">
|
||||||
<div class="w-400px flex items-center justify-between h-full">
|
<div class="w-400px flex items-center justify-between h-full">
|
||||||
|
|
@ -41,20 +40,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧按钮 -->
|
<!-- 右侧按钮 -->
|
||||||
<div class="w-200px flex items-center justify-end gap-2">
|
<div class="w-200px flex items-center justify-end gap-2">
|
||||||
<el-button type="primary" @click="handleSave"> 保 存 </el-button>
|
<el-button type="primary" @click="handleSave"> 保 存 </el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 主体内容 -->
|
<!-- 主体内容 -->
|
||||||
<div class="mt-50px">
|
<div class="mt-50px">
|
||||||
<!-- 第一步:基本信息 -->
|
<!-- 第一步:基本信息 -->
|
||||||
<div v-if="currentStep === 0" class="mx-auto w-560px">
|
<div v-if="currentStep === 0" class="mx-auto w-560px">
|
||||||
<BasicInfo v-model="formData" ref="basicInfoRef" />
|
<BasicInfo v-model="formData" ref="basicInfoRef" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第二步:工作流设计 -->
|
<!-- 第二步:工作流设计 -->
|
||||||
<WorkflowDesign
|
<WorkflowDesign
|
||||||
v-if="currentStep === 1"
|
v-if="currentStep === 1"
|
||||||
|
|
@ -66,7 +62,6 @@
|
||||||
</div>
|
</div>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
|
@ -75,28 +70,23 @@ import BasicInfo from './BasicInfo.vue'
|
||||||
import WorkflowDesign from './WorkflowDesign.vue'
|
import WorkflowDesign from './WorkflowDesign.vue'
|
||||||
import { ModelApi } from '@/api/ai/model/model'
|
import { ModelApi } from '@/api/ai/model/model'
|
||||||
import { AiModelTypeEnum } from '@/views/ai/utils/constants'
|
import { AiModelTypeEnum } from '@/views/ai/utils/constants'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { delView } = useTagsViewStore()
|
const { delView } = useTagsViewStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const basicInfoRef = ref()
|
const basicInfoRef = ref()
|
||||||
const workflowDesignRef = ref()
|
const workflowDesignRef = ref()
|
||||||
|
|
||||||
const validateBasic = async () => {
|
const validateBasic = async () => {
|
||||||
await basicInfoRef.value?.validate()
|
await basicInfoRef.value?.validate()
|
||||||
}
|
}
|
||||||
const validateWorkflow = async () => {
|
const validateWorkflow = async () => {
|
||||||
await workflowDesignRef.value?.validate()
|
await workflowDesignRef.value?.validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentStep = ref(-1)
|
const currentStep = ref(-1)
|
||||||
const steps = [
|
const steps = [
|
||||||
{ title: '基本信息', validator: validateBasic },
|
{ title: '基本信息', validator: validateBasic },
|
||||||
{ title: '工作流设计', validator: validateWorkflow }
|
{ title: '工作流设计', validator: validateWorkflow }
|
||||||
]
|
]
|
||||||
|
|
||||||
const formData: any = ref({
|
const formData: any = ref({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -108,7 +98,6 @@ const formData: any = ref({
|
||||||
const llmProvider = ref<any>([])
|
const llmProvider = ref<any>([])
|
||||||
const workflowData = ref<any>({})
|
const workflowData = ref<any>({})
|
||||||
provide('workflowData', workflowData)
|
provide('workflowData', workflowData)
|
||||||
|
|
||||||
/** 初始化数据 */
|
/** 初始化数据 */
|
||||||
const actionType = route.params.type as string
|
const actionType = route.params.type as string
|
||||||
const initData = async () => {
|
const initData = async () => {
|
||||||
|
|
@ -118,7 +107,6 @@ const initData = async () => {
|
||||||
formData.value = await WorkflowApi.getWorkflow(workflowId)
|
formData.value = await WorkflowApi.getWorkflow(workflowId)
|
||||||
workflowData.value = JSON.parse(formData.value.graph)
|
workflowData.value = JSON.parse(formData.value.graph)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载模型列表
|
// 加载模型列表
|
||||||
const models = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT)
|
const models = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT)
|
||||||
llmProvider.value = {
|
llmProvider.value = {
|
||||||
|
|
@ -132,11 +120,9 @@ const initData = async () => {
|
||||||
}
|
}
|
||||||
// TODO @lesan:知识库(可以看下 knowledge)
|
// TODO @lesan:知识库(可以看下 knowledge)
|
||||||
// TODO @lesan:搜索引擎(这个之前有个 pr 搞了,,,可能来接下)
|
// TODO @lesan:搜索引擎(这个之前有个 pr 搞了,,,可能来接下)
|
||||||
|
|
||||||
// 设置当前步骤
|
// 设置当前步骤
|
||||||
currentStep.value = 0
|
currentStep.value = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 校验所有步骤数据是否完整 */
|
/** 校验所有步骤数据是否完整 */
|
||||||
const validateAllSteps = async () => {
|
const validateAllSteps = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -147,7 +133,6 @@ const validateAllSteps = async () => {
|
||||||
currentStep.value = 0
|
currentStep.value = 0
|
||||||
throw new Error('请完善基本信息')
|
throw new Error('请完善基本信息')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 工作流设计校验
|
// 工作流设计校验
|
||||||
try {
|
try {
|
||||||
await validateWorkflow()
|
await validateWorkflow()
|
||||||
|
|
@ -160,13 +145,11 @@ const validateAllSteps = async () => {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 保存操作 */
|
/** 保存操作 */
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
// 保存前校验所有步骤的数据
|
// 保存前校验所有步骤的数据
|
||||||
await validateAllSteps()
|
await validateAllSteps()
|
||||||
|
|
||||||
// 更新表单数据
|
// 更新表单数据
|
||||||
const data = {
|
const data = {
|
||||||
...formData.value,
|
...formData.value,
|
||||||
|
|
@ -177,7 +160,6 @@ const handleSave = async () => {
|
||||||
} else {
|
} else {
|
||||||
await WorkflowApi.createWorkflow(data)
|
await WorkflowApi.createWorkflow(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存成功,提示并跳转到列表页
|
// 保存成功,提示并跳转到列表页
|
||||||
message.success('保存成功')
|
message.success('保存成功')
|
||||||
delView(unref(router.currentRoute))
|
delView(unref(router.currentRoute))
|
||||||
|
|
@ -187,7 +169,6 @@ const handleSave = async () => {
|
||||||
message.warning(error.message || '请完善所有步骤的必填信息')
|
message.warning(error.message || '请完善所有步骤的必填信息')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 步骤切换处理 */
|
/** 步骤切换处理 */
|
||||||
const handleStepClick = async (index: number) => {
|
const handleStepClick = async (index: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -197,7 +178,6 @@ const handleStepClick = async (index: number) => {
|
||||||
if (index !== 1) {
|
if (index !== 1) {
|
||||||
await validateWorkflow()
|
await validateWorkflow()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换步骤
|
// 切换步骤
|
||||||
currentStep.value = index
|
currentStep.value = index
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -205,7 +185,6 @@ const handleStepClick = async (index: number) => {
|
||||||
message.warning('请先完善当前步骤必填信息')
|
message.warning('请先完善当前步骤必填信息')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 返回列表页 */
|
/** 返回列表页 */
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
// 先删除当前页签
|
// 先删除当前页签
|
||||||
|
|
@ -213,28 +192,23 @@ const handleBack = () => {
|
||||||
// 跳转到列表页
|
// 跳转到列表页
|
||||||
router.push({ name: 'AiWorkflow' })
|
router.push({ name: 'AiWorkflow' })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await initData()
|
await initData()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- TODO @lesan:可以用 cursor 搞成 unocss 哈 -->
|
<!-- TODO @lesan:可以用 cursor 搞成 unocss 哈 -->
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.border-bottom {
|
.border-bottom {
|
||||||
border-bottom: 1px solid #dcdfe6;
|
border-bottom: 1px solid #dcdfe6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-primary {
|
.text-primary {
|
||||||
color: #3473ff;
|
color: #3473ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-primary {
|
.bg-primary {
|
||||||
background-color: #3473ff;
|
background-color: #3473ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-primary {
|
.border-primary {
|
||||||
border-color: #3473ff;
|
border-color: #3473ff;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -61,7 +61,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -109,21 +108,16 @@
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 添加或修改工作流对话框 -->
|
<!-- 添加或修改工作流对话框 -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import * as WorkflowApi from '@/api/ai/workflow'
|
import * as WorkflowApi from '@/api/ai/workflow'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
|
||||||
defineOptions({ name: 'AiWorkflow' })
|
defineOptions({ name: 'AiWorkflow' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const { push } = useRouter() // 路由
|
const { push } = useRouter() // 路由
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref([]) // 列表的数据
|
const list = ref([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -136,7 +130,6 @@ const queryParams = reactive({
|
||||||
createTime: []
|
createTime: []
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -148,19 +141,16 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -173,7 +163,6 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const openForm = async (type: string, id?: number) => {
|
const openForm = async (type: string, id?: number) => {
|
||||||
if (type === 'create') {
|
if (type === 'create') {
|
||||||
|
|
@ -185,9 +174,8 @@ const openForm = async (type: string, id?: number) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -23,7 +23,6 @@
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
</DefineLabel>
|
</DefineLabel>
|
||||||
|
|
||||||
<div class="flex flex-col" v-bind="$attrs">
|
<div class="flex flex-col" v-bind="$attrs">
|
||||||
<!-- tab -->
|
<!-- tab -->
|
||||||
<div class="w-full pt-2 bg-[#f5f7f9] flex justify-center">
|
<div class="w-full pt-2 bg-[#f5f7f9] flex justify-center">
|
||||||
|
|
@ -59,7 +58,6 @@
|
||||||
type="textarea"
|
type="textarea"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ReuseLabel :hint-click="() => example('reply')" hint="示例" label="原文" />
|
<ReuseLabel :hint-click="() => example('reply')" hint="示例" label="原文" />
|
||||||
<el-input
|
<el-input
|
||||||
|
|
@ -70,7 +68,6 @@
|
||||||
showWordLimit
|
showWordLimit
|
||||||
type="textarea"
|
type="textarea"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ReuseLabel label="回复内容" />
|
<ReuseLabel label="回复内容" />
|
||||||
<el-input
|
<el-input
|
||||||
v-model="formData.prompt"
|
v-model="formData.prompt"
|
||||||
|
|
@ -81,7 +78,6 @@
|
||||||
type="textarea"
|
type="textarea"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<ReuseLabel label="长度" />
|
<ReuseLabel label="长度" />
|
||||||
<Tag v-model="formData.length" :tags="getIntDictOptions(DICT_TYPE.AI_WRITE_LENGTH)" />
|
<Tag v-model="formData.length" :tags="getIntDictOptions(DICT_TYPE.AI_WRITE_LENGTH)" />
|
||||||
<ReuseLabel label="格式" />
|
<ReuseLabel label="格式" />
|
||||||
|
|
@ -90,7 +86,6 @@
|
||||||
<Tag v-model="formData.tone" :tags="getIntDictOptions(DICT_TYPE.AI_WRITE_TONE)" />
|
<Tag v-model="formData.tone" :tags="getIntDictOptions(DICT_TYPE.AI_WRITE_TONE)" />
|
||||||
<ReuseLabel label="语言" />
|
<ReuseLabel label="语言" />
|
||||||
<Tag v-model="formData.language" :tags="getIntDictOptions(DICT_TYPE.AI_WRITE_LANGUAGE)" />
|
<Tag v-model="formData.language" :tags="getIntDictOptions(DICT_TYPE.AI_WRITE_LANGUAGE)" />
|
||||||
|
|
||||||
<div class="flex items-center justify-center mt-3">
|
<div class="flex items-center justify-center mt-3">
|
||||||
<el-button :disabled="isWriting" @click="reset">重置</el-button>
|
<el-button :disabled="isWriting" @click="reset">重置</el-button>
|
||||||
<el-button :loading="isWriting" color="#846af7" @click="submit">生成</el-button>
|
<el-button :loading="isWriting" color="#846af7" @click="submit">生成</el-button>
|
||||||
|
|
@ -99,7 +94,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { createReusableTemplate } from '@vueuse/core'
|
import { createReusableTemplate } from '@vueuse/core'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
@ -108,21 +102,16 @@ import { WriteVO } from '@/api/ai/write'
|
||||||
import { omit } from 'lodash-es'
|
import { omit } from 'lodash-es'
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { AiWriteTypeEnum, WriteExample } from '@/views/ai/utils/constants'
|
import { AiWriteTypeEnum, WriteExample } from '@/views/ai/utils/constants'
|
||||||
|
|
||||||
type TabType = WriteVO['type']
|
type TabType = WriteVO['type']
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
isWriting: boolean
|
isWriting: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
(e: 'submit', params: Partial<WriteVO>)
|
(e: 'submit', params: Partial<WriteVO>)
|
||||||
(e: 'example', param: 'write' | 'reply')
|
(e: 'example', param: 'write' | 'reply')
|
||||||
(e: 'reset')
|
(e: 'reset')
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
/** 点击示例的时候,将定义好的文章作为示例展示出来 **/
|
/** 点击示例的时候,将定义好的文章作为示例展示出来 **/
|
||||||
const example = (type: 'write' | 'reply') => {
|
const example = (type: 'write' | 'reply') => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
|
|
@ -131,13 +120,11 @@ const example = (type: 'write' | 'reply') => {
|
||||||
}
|
}
|
||||||
emits('example', type)
|
emits('example', type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置,将表单值作为初选值 **/
|
/** 重置,将表单值作为初选值 **/
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
formData.value = { ...initData }
|
formData.value = { ...initData }
|
||||||
emits('reset')
|
emits('reset')
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedTab = ref<TabType>(AiWriteTypeEnum.WRITING)
|
const selectedTab = ref<TabType>(AiWriteTypeEnum.WRITING)
|
||||||
const tabs: {
|
const tabs: {
|
||||||
text: string
|
text: string
|
||||||
|
|
@ -151,7 +138,6 @@ const [DefineTab, ReuseTab] = createReusableTemplate<{
|
||||||
text: string
|
text: string
|
||||||
itemClick: () => void
|
itemClick: () => void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可以在 template 里边定义可复用的组件,DefineLabel,ReuseLabel 是采用的解构赋值,都是 Vue 组件
|
* 可以在 template 里边定义可复用的组件,DefineLabel,ReuseLabel 是采用的解构赋值,都是 Vue 组件
|
||||||
*
|
*
|
||||||
|
|
@ -167,7 +153,6 @@ const [DefineLabel, ReuseLabel] = createReusableTemplate<{
|
||||||
hint?: string
|
hint?: string
|
||||||
hintClick?: () => void
|
hintClick?: () => void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const initData: WriteVO = {
|
const initData: WriteVO = {
|
||||||
type: 1,
|
type: 1,
|
||||||
prompt: '',
|
prompt: '',
|
||||||
|
|
@ -178,10 +163,8 @@ const initData: WriteVO = {
|
||||||
format: 1
|
format: 1
|
||||||
}
|
}
|
||||||
const formData = ref<WriteVO>({ ...initData })
|
const formData = ref<WriteVO>({ ...initData })
|
||||||
|
|
||||||
/** 用来记录切换之前所填写的数据,切换的时候给赋值回来 **/
|
/** 用来记录切换之前所填写的数据,切换的时候给赋值回来 **/
|
||||||
const recordFormData = {} as Record<AiWriteTypeEnum, WriteVO>
|
const recordFormData = {} as Record<AiWriteTypeEnum, WriteVO>
|
||||||
|
|
||||||
/** 切换tab **/
|
/** 切换tab **/
|
||||||
const switchTab = (value: TabType) => {
|
const switchTab = (value: TabType) => {
|
||||||
if (value !== selectedTab.value) {
|
if (value !== selectedTab.value) {
|
||||||
|
|
@ -192,7 +175,6 @@ const switchTab = (value: TabType) => {
|
||||||
formData.value = { ...initData, ...recordFormData[value] }
|
formData.value = { ...initData, ...recordFormData[value] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 提交写作 */
|
/** 提交写作 */
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (selectedTab.value === 2 && !formData.value.originalContent) {
|
if (selectedTab.value === 2 && !formData.value.originalContent) {
|
||||||
|
|
@ -210,4 +192,4 @@ const submit = () => {
|
||||||
type: selectedTab.value
|
type: selectedTab.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
</h3>
|
</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div ref="contentRef" class="hide-scroll-bar h-full box-border overflow-y-auto">
|
<div ref="contentRef" class="hide-scroll-bar h-full box-border overflow-y-auto">
|
||||||
<div class="w-full min-h-full relative flex-grow bg-white box-border p-3 sm:p-7">
|
<div class="w-full min-h-full relative flex-grow bg-white box-border p-3 sm:p-7">
|
||||||
<!-- 终止生成内容的按钮 -->
|
<!-- 终止生成内容的按钮 -->
|
||||||
|
|
@ -40,13 +39,10 @@
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useClipboard } from '@vueuse/core'
|
import { useClipboard } from '@vueuse/core'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { copied, copy } = useClipboard({ legacy: true }) // 粘贴板
|
const { copied, copy } = useClipboard({ legacy: true }) // 粘贴板
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
content: {
|
content: {
|
||||||
// 生成的结果
|
// 生成的结果
|
||||||
|
|
@ -59,9 +55,7 @@ const props = defineProps({
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emits = defineEmits(['update:content', 'stopStream'])
|
const emits = defineEmits(['update:content', 'stopStream'])
|
||||||
|
|
||||||
/** 通过计算属性,双向绑定,更改生成的内容,考虑到用户想要更改生成文章的情况 */
|
/** 通过计算属性,双向绑定,更改生成的内容,考虑到用户想要更改生成文章的情况 */
|
||||||
const compContent = computed({
|
const compContent = computed({
|
||||||
get() {
|
get() {
|
||||||
|
|
@ -71,7 +65,6 @@ const compContent = computed({
|
||||||
emits('update:content', val)
|
emits('update:content', val)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 滚动 */
|
/** 滚动 */
|
||||||
const contentRef = ref<HTMLDivElement>()
|
const contentRef = ref<HTMLDivElement>()
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
|
@ -79,13 +72,11 @@ defineExpose({
|
||||||
contentRef.value?.scrollTo(0, contentRef.value?.scrollHeight)
|
contentRef.value?.scrollTo(0, contentRef.value?.scrollHeight)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 点击复制的时候复制内容 */
|
/** 点击复制的时候复制内容 */
|
||||||
const showCopy = computed(() => props.content && !props.isWriting) // 是否展示复制按钮,在生成内容完成的时候展示
|
const showCopy = computed(() => props.content && !props.isWriting) // 是否展示复制按钮,在生成内容完成的时候展示
|
||||||
const copyContent = () => {
|
const copyContent = () => {
|
||||||
copy(props.content)
|
copy(props.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 复制成功的时候 copied.value 为 true */
|
/** 复制成功的时候 copied.value 为 true */
|
||||||
watch(copied, (val) => {
|
watch(copied, (val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
|
|
@ -93,22 +84,18 @@ watch(copied, (val) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.hide-scroll-bar {
|
.hide-scroll-bar {
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-card {
|
.my-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
:deep(.el-card__body) {
|
:deep(.el-card__body) {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
@ -117,4 +104,4 @@ watch(copied, (val) => {
|
||||||
@extend .hide-scroll-bar;
|
@extend .hide-scroll-bar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -12,7 +12,6 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
@ -24,8 +23,7 @@ const props = withDefaults(
|
||||||
tags: () => []
|
tags: () => []
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
(e: 'update:modelValue', value: string): void
|
(e: 'update:modelValue', value: string): void
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -16,25 +16,20 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Left from './components/Left.vue'
|
import Left from './components/Left.vue'
|
||||||
import Right from './components/Right.vue'
|
import Right from './components/Right.vue'
|
||||||
import { WriteApi, WriteVO } from '@/api/ai/write'
|
import { WriteApi, WriteVO } from '@/api/ai/write'
|
||||||
import { WriteExample } from '@/views/ai/utils/constants'
|
import { WriteExample } from '@/views/ai/utils/constants'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const writeResult = ref('') // 写作结果
|
const writeResult = ref('') // 写作结果
|
||||||
const isWriting = ref(false) // 是否正在写作中
|
const isWriting = ref(false) // 是否正在写作中
|
||||||
const abortController = ref<AbortController>() // // 写作进行中 abort 控制器(控制 stream 写作)
|
const abortController = ref<AbortController>() // // 写作进行中 abort 控制器(控制 stream 写作)
|
||||||
|
|
||||||
/** 停止 stream 生成 */
|
/** 停止 stream 生成 */
|
||||||
const stopStream = () => {
|
const stopStream = () => {
|
||||||
abortController.value?.abort()
|
abortController.value?.abort()
|
||||||
isWriting.value = false
|
isWriting.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 执行写作 */
|
/** 执行写作 */
|
||||||
const rightRef = ref<InstanceType<typeof Right>>()
|
const rightRef = ref<InstanceType<typeof Right>>()
|
||||||
const submit = (data: WriteVO) => {
|
const submit = (data: WriteVO) => {
|
||||||
|
|
@ -65,14 +60,12 @@ const submit = (data: WriteVO) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 点击示例触发 */
|
/** 点击示例触发 */
|
||||||
const handleExampleClick = (type: keyof typeof WriteExample) => {
|
const handleExampleClick = (type: keyof typeof WriteExample) => {
|
||||||
writeResult.value = WriteExample[type].data
|
writeResult.value = WriteExample[type].data
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 点击重置的时候清空写作的结果**/
|
/** 点击重置的时候清空写作的结果**/
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
writeResult.value = ''
|
writeResult.value = ''
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<doc-alert title="AI 写作助手" url="https://doc.iocoder.cn/ai/write/" />
|
|
||||||
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form
|
||||||
|
|
@ -72,7 +70,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -152,21 +149,17 @@
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { WriteApi, AiWritePageReqVO, AiWriteRespVo } from '@/api/ai/write'
|
import { WriteApi, AiWritePageReqVO, AiWriteRespVo } from '@/api/ai/write'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
|
|
||||||
/** AI 写作列表 */
|
/** AI 写作列表 */
|
||||||
defineOptions({ name: 'AiWriteManager' })
|
defineOptions({ name: 'AiWriteManager' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const router = useRouter() // 路由
|
const router = useRouter() // 路由
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<AiWriteRespVo[]>([]) // 列表的数据
|
const list = ref<AiWriteRespVo[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -180,7 +173,6 @@ const queryParams = reactive<AiWritePageReqVO>({
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -192,19 +184,16 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -217,11 +206,10 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
getList()
|
getList()
|
||||||
// 获得用户列表
|
// 获得用户列表
|
||||||
userList.value = await UserApi.getSimpleUserList()
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -46,13 +46,10 @@
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
|
||||||
/** BPM 流程分类 表单 */
|
/** BPM 流程分类 表单 */
|
||||||
defineOptions({ name: 'CategoryForm' })
|
defineOptions({ name: 'CategoryForm' })
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
|
@ -72,7 +69,6 @@ const formRules = reactive({
|
||||||
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }]
|
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (type: string, id?: number) => {
|
const open = async (type: string, id?: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
|
@ -90,7 +86,6 @@ const open = async (type: string, id?: number) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
/** 提交表单 */
|
/** 提交表单 */
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
|
|
@ -114,7 +109,6 @@ const submitForm = async () => {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置表单 */
|
/** 重置表单 */
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
|
|
@ -127,4 +121,4 @@ const resetForm = () => {
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
|
||||||
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form
|
||||||
|
|
@ -68,7 +66,6 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
|
@ -118,23 +115,18 @@
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<CategoryForm ref="formRef" @success="getList" />
|
<CategoryForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
|
||||||
import CategoryForm from './CategoryForm.vue'
|
import CategoryForm from './CategoryForm.vue'
|
||||||
|
|
||||||
/** BPM 流程分类 列表 */
|
/** BPM 流程分类 列表 */
|
||||||
defineOptions({ name: 'BpmCategory' })
|
defineOptions({ name: 'BpmCategory' })
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<CategoryVO[]>([]) // 列表的数据
|
const list = ref<CategoryVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|
@ -148,7 +140,6 @@ const queryParams = reactive({
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const exportLoading = ref(false) // 导出的加载中
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
@ -160,25 +151,21 @@ const getList = async () => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, id?: number) => {
|
||||||
formRef.value.open(type, id)
|
formRef.value.open(type, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -191,9 +178,8 @@ const handleDelete = async (id: number) => {
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue