修改Saas登录方式

main
lj7788 2026-02-04 18:16:22 +08:00
parent 132d024848
commit dc2017266d
741 changed files with 795 additions and 8834 deletions

View File

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

View File

@ -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);
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -35,3 +35,6 @@
border-left-color: var(--el-color-primary); border-left-color: var(--el-color-primary);
} }
} }
.hidden{
display: none;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.vueredirectUriencodedecode // tricky: LoginForm.vueredirectUriencodedecode
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) ?? ''
} }
// : socialLogintoken // : socialLogintoken
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>

View File

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

View File

@ -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
// : typeredirect encode // : typeredirect 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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) // 12 const formLoading = ref(false) // 12
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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,4 +3,4 @@
<div class="p-30px"> <div class="p-30px">
<el-skeleton animated /> <el-skeleton animated />
</div> </div>
</template> </template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) // 12 const formLoading = ref(false) // 12
@ -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>

View File

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

View File

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

View File

@ -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) // 12 const formLoading = ref(false) // 12
@ -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>

View File

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

View File

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

View File

@ -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;
} }
} }
// markmaptool // markmaptool
: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>

View File

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

View File

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

View File

@ -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) // 12 const formLoading = ref(false) // 12
@ -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>

View File

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

View File

@ -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) // 12 const formLoading = ref(false) // 12
@ -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 @fantitle type title // TODO @fantitle 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>

View File

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

View File

@ -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) // 12 const formLoading = ref(false) // 12
@ -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>

View File

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

View File

@ -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) // 12 const formLoading = ref(false) // 12
@ -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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 里边定义可复用的组件DefineLabelReuseLabel 是采用的解构赋值都是 Vue 组件 * 可以在 template 里边定义可复用的组件DefineLabelReuseLabel 是采用的解构赋值都是 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>

View File

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

View File

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

View File

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

View File

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

View File

@ -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) // 12 const formLoading = ref(false) // 12
@ -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>

View File

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