ソースを参照

修复client权限问题,采用服务端授权方式

tycoding 1 年間 前
コミット
0fe32519aa
22 ファイル変更194 行追加97 行削除
  1. 0 1
      docs/langchat.sql
  2. 6 6
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcConversationController.java
  3. 4 4
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcDocsController.java
  4. 4 4
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcDocsSliceController.java
  5. 4 4
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcKnowledgeController.java
  6. 3 7
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcMessageController.java
  7. 4 4
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcModelController.java
  8. 2 1
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcOssController.java
  9. 4 4
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcPromptController.java
  10. 3 7
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcUserController.java
  11. 2 0
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/entity/AigcUser.java
  12. 33 0
      langchat-auth/src/main/java/cn/tycoding/langchat/auth/aspect/AigcPermAspect.java
  13. 1 0
      langchat-auth/src/main/java/cn/tycoding/langchat/auth/endpoint/AigcAuthEndpoint.java
  14. 19 0
      langchat-common/src/main/java/cn/tycoding/langchat/common/annotation/AigcPerm.java
  15. 2 0
      langchat-common/src/main/java/cn/tycoding/langchat/common/properties/AuthProps.java
  16. 2 1
      langchat-server/src/main/java/cn/tycoding/langchat/aigc/endpoint/AigcChatEndpoint.java
  17. 3 0
      langchat-server/src/main/resources/application-dev.yml
  18. 1 4
      langchat-ui-client/src/utils/request/index.ts
  19. 18 15
      langchat-ui-client/src/views/login/PhoneLogin.vue
  20. 22 18
      langchat-ui-client/src/views/login/PhoneRegister.vue
  21. 22 17
      langchat-ui-client/src/views/modules/chat/index.vue
  22. 35 0
      langchat-ui/src/views/aigc/user/columns.ts

ファイルの差分が大きいため隠しています
+ 0 - 1
docs/langchat.sql


+ 6 - 6
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcConversationController.java

@@ -1,9 +1,9 @@
 package cn.tycoding.langchat.aigc.controller;
 
-import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.tycoding.langchat.aigc.entity.AigcConversation;
 import cn.tycoding.langchat.aigc.entity.AigcMessage;
 import cn.tycoding.langchat.aigc.service.AigcMessageService;
+import cn.tycoding.langchat.common.annotation.AigcPerm;
 import cn.tycoding.langchat.common.utils.MybatisUtil;
 import cn.tycoding.langchat.common.utils.QueryPage;
 import cn.tycoding.langchat.common.utils.R;
@@ -43,13 +43,13 @@ public class AigcConversationController {
     }
 
     @PostMapping
-    @SaCheckPermission("aigc:conversation:add")
+    @AigcPerm
     public R addConversation(@RequestBody AigcConversation conversation) {
         return R.ok(aigcMessageService.addConversation(conversation));
     }
 
     @PutMapping
-    @SaCheckPermission("aigc:conversation:update")
+    @AigcPerm
     public R updateConversation(@RequestBody AigcConversation conversation) {
         if (conversation.getId() == null) {
             return R.fail("conversation id is null");
@@ -59,14 +59,14 @@ public class AigcConversationController {
     }
 
     @DeleteMapping("/{conversationId}")
-    @SaCheckPermission("aigc:conversation:delete")
+    @AigcPerm
     public R delConversation(@PathVariable String conversationId) {
         aigcMessageService.delConversation(conversationId);
         return R.ok();
     }
 
     @DeleteMapping("/message/{conversationId}")
-    @SaCheckPermission("aigc:conversation:delete")
+    @AigcPerm
     public R clearMessage(@PathVariable String conversationId) {
         aigcMessageService.clearMessage(conversationId);
         return R.ok();
@@ -85,7 +85,7 @@ public class AigcConversationController {
      * add message in conversation
      */
     @PostMapping("/message")
-    @SaCheckPermission("aigc:conversation:add")
+    @AigcPerm
     public R addMessage(@RequestBody AigcMessage message) {
         message.setIp(ServletUtil.getIpAddr());
         return R.ok(aigcMessageService.addMessage(message));

+ 4 - 4
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcDocsController.java

@@ -1,8 +1,8 @@
 package cn.tycoding.langchat.aigc.controller;
 
-import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.tycoding.langchat.aigc.entity.AigcDocs;
 import cn.tycoding.langchat.aigc.mapper.AigcDocsMapper;
+import cn.tycoding.langchat.common.annotation.AigcPerm;
 import cn.tycoding.langchat.common.utils.MybatisUtil;
 import cn.tycoding.langchat.common.utils.QueryPage;
 import cn.tycoding.langchat.common.utils.R;
@@ -45,21 +45,21 @@ public class AigcDocsController {
     }
 
     @PostMapping
-    @SaCheckPermission("aigc:docs:add")
+    @AigcPerm
     public R add(@RequestBody AigcDocs data) {
         docsMapper.insert(data);
         return R.ok();
     }
 
     @PutMapping
-    @SaCheckPermission("aigc:docs:update")
+    @AigcPerm
     public R update(@RequestBody AigcDocs data) {
         docsMapper.updateById(data);
         return R.ok();
     }
 
     @DeleteMapping("/{id}")
-    @SaCheckPermission("aigc:docs:delete")
+    @AigcPerm
     public R delete(@PathVariable String id) {
         docsMapper.deleteById(id);
         return R.ok();

+ 4 - 4
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcDocsSliceController.java

@@ -1,8 +1,8 @@
 package cn.tycoding.langchat.aigc.controller;
 
-import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.tycoding.langchat.aigc.entity.AigcDocsSlice;
 import cn.tycoding.langchat.aigc.mapper.AigcDocsSliceMapper;
+import cn.tycoding.langchat.common.annotation.AigcPerm;
 import cn.tycoding.langchat.common.utils.MybatisUtil;
 import cn.tycoding.langchat.common.utils.QueryPage;
 import cn.tycoding.langchat.common.utils.R;
@@ -46,7 +46,7 @@ public class AigcDocsSliceController {
     }
 
     @PostMapping
-    @SaCheckPermission("aigc:docs:slice:add")
+    @AigcPerm
     public R add(@RequestBody AigcDocsSlice data) {
         data.setCreateTime(new Date());
         docsSliceMapper.insert(data);
@@ -54,14 +54,14 @@ public class AigcDocsSliceController {
     }
 
     @PutMapping
-    @SaCheckPermission("aigc:docs:slice:update")
+    @AigcPerm
     public R update(@RequestBody AigcDocsSlice data) {
         docsSliceMapper.updateById(data);
         return R.ok();
     }
 
     @DeleteMapping("/{id}")
-    @SaCheckPermission("aigc:docs:slice:delete")
+    @AigcPerm
     public R delete(@PathVariable String id) {
         docsSliceMapper.deleteById(id);
         return R.ok();

+ 4 - 4
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcKnowledgeController.java

@@ -1,11 +1,11 @@
 package cn.tycoding.langchat.aigc.controller;
 
-import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.hutool.core.util.StrUtil;
 import cn.tycoding.langchat.aigc.entity.AigcDocs;
 import cn.tycoding.langchat.aigc.entity.AigcKnowledge;
 import cn.tycoding.langchat.aigc.mapper.AigcDocsMapper;
 import cn.tycoding.langchat.aigc.service.AigcKnowledgeService;
+import cn.tycoding.langchat.common.annotation.AigcPerm;
 import cn.tycoding.langchat.common.utils.MybatisUtil;
 import cn.tycoding.langchat.common.utils.QueryPage;
 import cn.tycoding.langchat.common.utils.R;
@@ -78,7 +78,7 @@ public class AigcKnowledgeController {
     }
 
     @PostMapping
-    @SaCheckPermission("aigc:knowledge:add")
+    @AigcPerm
     public R add(@RequestBody AigcKnowledge data) {
         data.setCreateTime(String.valueOf(System.currentTimeMillis()));
         kbService.save(data);
@@ -86,14 +86,14 @@ public class AigcKnowledgeController {
     }
 
     @PutMapping
-    @SaCheckPermission("aigc:knowledge:update")
+    @AigcPerm
     public R update(@RequestBody AigcKnowledge data) {
         kbService.updateById(data);
         return R.ok();
     }
 
     @DeleteMapping("/{id}")
-    @SaCheckPermission("aigc:knowledge:delete")
+    @AigcPerm
     public R delete(@PathVariable String id) {
         kbService.removeById(id);
         return R.ok();

+ 3 - 7
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcMessageController.java

@@ -1,9 +1,9 @@
 package cn.tycoding.langchat.aigc.controller;
 
-import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.hutool.core.util.StrUtil;
 import cn.tycoding.langchat.aigc.entity.AigcMessage;
 import cn.tycoding.langchat.aigc.service.AigcMessageService;
+import cn.tycoding.langchat.common.annotation.AigcPerm;
 import cn.tycoding.langchat.common.utils.MybatisUtil;
 import cn.tycoding.langchat.common.utils.QueryPage;
 import cn.tycoding.langchat.common.utils.R;
@@ -11,11 +11,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.AllArgsConstructor;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 /**
  * @author tycoding
@@ -45,7 +41,7 @@ public class AigcMessageController {
     }
 
     @DeleteMapping("/{id}")
-    @SaCheckPermission("aigc:message:delete")
+    @AigcPerm
     public R del(@PathVariable String id) {
         return R.ok(aigcMessageService.removeById(id));
     }

+ 4 - 4
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcModelController.java

@@ -1,10 +1,10 @@
 package cn.tycoding.langchat.aigc.controller;
 
-import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.hutool.core.util.StrUtil;
 import cn.tycoding.langchat.aigc.component.ProviderRefreshEvent;
 import cn.tycoding.langchat.aigc.entity.AigcModel;
 import cn.tycoding.langchat.aigc.service.AigcModelService;
+import cn.tycoding.langchat.common.annotation.AigcPerm;
 import cn.tycoding.langchat.common.component.SpringContextHolder;
 import cn.tycoding.langchat.common.utils.MybatisUtil;
 import cn.tycoding.langchat.common.utils.QueryPage;
@@ -80,7 +80,7 @@ public class AigcModelController {
     }
 
     @PostMapping
-    @SaCheckPermission("aigc:model:add")
+    @AigcPerm
     public R add(@RequestBody AigcModel data) {
         modelService.save(data);
         SpringContextHolder.publishEvent(new ProviderRefreshEvent(data));
@@ -88,7 +88,7 @@ public class AigcModelController {
     }
 
     @PutMapping
-    @SaCheckPermission("aigc:model:update")
+    @AigcPerm
     public R update(@RequestBody AigcModel data) {
         modelService.updateById(data);
         SpringContextHolder.publishEvent(new ProviderRefreshEvent(data));
@@ -96,7 +96,7 @@ public class AigcModelController {
     }
 
     @DeleteMapping("/{id}")
-    @SaCheckPermission("aigc:model:delete")
+    @AigcPerm
     public R delete(@PathVariable String id) {
         modelService.removeById(id);
 

+ 2 - 1
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcOssController.java

@@ -3,6 +3,7 @@ package cn.tycoding.langchat.aigc.controller;
 import cn.tycoding.langchat.aigc.entity.AigcOss;
 import cn.tycoding.langchat.aigc.service.AigcOssService;
 import cn.tycoding.langchat.aigc.utils.AigcAuthUtil;
+import cn.tycoding.langchat.common.annotation.AigcPerm;
 import cn.tycoding.langchat.common.utils.R;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.AllArgsConstructor;
@@ -32,7 +33,7 @@ public class AigcOssController {
     }
 
     @PostMapping("/upload")
-//    @SaCheckPermission("aigc:oss:update")
+    @AigcPerm
     public R upload(MultipartFile file) {
         return R.ok(aigcOssService.upload(file));
     }

+ 4 - 4
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcPromptController.java

@@ -1,9 +1,9 @@
 package cn.tycoding.langchat.aigc.controller;
 
-import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.hutool.core.util.StrUtil;
 import cn.tycoding.langchat.aigc.entity.AigcPrompt;
 import cn.tycoding.langchat.aigc.service.AigcPromptService;
+import cn.tycoding.langchat.common.annotation.AigcPerm;
 import cn.tycoding.langchat.common.utils.MybatisUtil;
 import cn.tycoding.langchat.common.utils.QueryPage;
 import cn.tycoding.langchat.common.utils.R;
@@ -48,20 +48,20 @@ public class AigcPromptController {
     }
 
     @PostMapping
-    @SaCheckPermission("aigc:prompt:add")
+    @AigcPerm
     public R add(@RequestBody AigcPrompt data) {
         data.setCreateTime(new Date());
         return R.ok(aigcPromptService.save(data));
     }
 
     @PutMapping
-    @SaCheckPermission("aigc:prompt:update")
+    @AigcPerm
     public R update(@RequestBody AigcPrompt data) {
         return R.ok(aigcPromptService.updateById(data));
     }
 
     @DeleteMapping("/{id}")
-    @SaCheckPermission("aigc:prompt:delete")
+    @AigcPerm
     public R del(@PathVariable String id) {
         return R.ok(aigcPromptService.removeById(id));
     }

+ 3 - 7
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcUserController.java

@@ -1,6 +1,5 @@
 package cn.tycoding.langchat.aigc.controller;
 
-import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.hutool.core.lang.Dict;
 import cn.tycoding.langchat.aigc.entity.AigcUser;
 import cn.tycoding.langchat.aigc.service.AigcUserService;
@@ -54,24 +53,21 @@ public class AigcUserController {
     }
 
     @PostMapping
-    @ApiLog("新增用户")
-    @SaCheckPermission("aigc:user:add")
+    @ApiLog("新增客户端用户")
     public R<AigcUser> add(@RequestBody AigcUser data) {
         userService.save(data);
         return R.ok();
     }
 
     @PutMapping
-    @ApiLog("修改用户")
-    @SaCheckPermission("aigc:user:update")
+    @ApiLog("修改客户端用户")
     public R update(@RequestBody AigcUser data) {
         userService.updateById(data);
         return R.ok();
     }
 
     @DeleteMapping("/{id}")
-    @ApiLog("删除用户")
-    @SaCheckPermission("aigc:user:delete")
+    @ApiLog("删除客户端用户")
     public R delete(@PathVariable Long id) {
         AigcUser user = userService.getById(id);
         if (user != null) {

+ 2 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/entity/AigcUser.java

@@ -60,6 +60,8 @@ public class AigcUser implements Serializable {
      */
     private Integer chatLimit;
 
+    private Boolean isPerms;
+
     /**
      * 状态 0锁定 1有效
      */

+ 33 - 0
langchat-auth/src/main/java/cn/tycoding/langchat/auth/aspect/AigcPermAspect.java

@@ -0,0 +1,33 @@
+package cn.tycoding.langchat.auth.aspect;
+
+import cn.dev33.satoken.exception.NotPermissionException;
+import cn.tycoding.langchat.aigc.entity.AigcUser;
+import cn.tycoding.langchat.aigc.utils.AigcAuthUtil;
+import cn.tycoding.langchat.common.annotation.AigcPerm;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 自定义AIGC权限切面
+ *
+ * @author tycoding
+ * @since 2024/7/5
+ */
+@Slf4j
+@Aspect
+@Configuration
+public class AigcPermAspect {
+
+    @Around("@annotation(aigcPerm)")
+    public Object around(ProceedingJoinPoint point, AigcPerm aigcPerm) throws Throwable {
+        AigcUser userInfo = AigcAuthUtil.getUserInfo();
+        if (userInfo.getIsPerms() == null || !userInfo.getIsPerms()) {
+            throw new NotPermissionException("当前账号没有操作权限,请联系管理员");
+        }
+        return point.proceed();
+    }
+
+}

+ 1 - 0
langchat-auth/src/main/java/cn/tycoding/langchat/auth/endpoint/AigcAuthEndpoint.java

@@ -111,6 +111,7 @@ public class AigcAuthEndpoint {
                 .setPassword(AuthUtil.encode(props.getSaltKey(), data.getPassword()))
                 .setNickname(data.getEmail())
                 .setEmail(data.getEmail())
+                .setIsPerms(props.getAigcRegUserIsPerms())
                 .setStatus(true)
                 .setCreateTime(new Date());
         userService.save(user);

+ 19 - 0
langchat-common/src/main/java/cn/tycoding/langchat/common/annotation/AigcPerm.java

@@ -0,0 +1,19 @@
+package cn.tycoding.langchat.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 自定义AIGC用户权限
+ *
+ * @author tycoding
+ * @since 2024/4/15
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AigcPerm {
+
+    String value() default "";
+}

+ 2 - 0
langchat-common/src/main/java/cn/tycoding/langchat/common/properties/AuthProps.java

@@ -19,6 +19,8 @@ public class AuthProps {
      */
     private List<String> skipUrl = new ArrayList();
 
+    private Boolean aigcRegUserIsPerms = false;
+
     private EmailProps email = new EmailProps();
 
     /**

+ 2 - 1
langchat-server/src/main/java/cn/tycoding/langchat/aigc/endpoint/AigcChatEndpoint.java

@@ -7,6 +7,7 @@ import cn.tycoding.langchat.aigc.service.AigcOssService;
 import cn.tycoding.langchat.aigc.service.ChatService;
 import cn.tycoding.langchat.aigc.service.EmbeddingService;
 import cn.tycoding.langchat.aigc.utils.AigcAuthUtil;
+import cn.tycoding.langchat.common.annotation.AigcPerm;
 import cn.tycoding.langchat.common.dto.ChatReq;
 import cn.tycoding.langchat.common.dto.ChatRes;
 import cn.tycoding.langchat.common.dto.ImageR;
@@ -34,7 +35,7 @@ public class AigcChatEndpoint {
     private final EmbeddingService embeddingDocs;
 
     @PostMapping
-    @SaCheckPermission("aigc:client:chat")
+    @AigcPerm
     public Object chat(@RequestBody ChatReq req) {
         StreamEmitter emitter = new StreamEmitter();
         req.setEmitter(emitter);

+ 3 - 0
langchat-server/src/main/resources/application-dev.yml

@@ -25,6 +25,9 @@ spring:
 
 langchat:
   auth:
+    # client端新注册的用户是否开启操作权限,如果为false,client端新用户默认只有页面预览权限没有操作权限
+    # 需要在server端给指定用户授权后才能正常使用
+    aigc-reg-user-is-perms: true
     email:
       host: smtp.qq.com
       port: 465

+ 1 - 4
langchat-ui-client/src/utils/request/index.ts

@@ -88,10 +88,7 @@ function axios<T = any>({
           },
         });
       } else {
-        $dialog!.warning({
-          title: 'Tips',
-          content: 'You do not have operation permission, please contact the administrator',
-        });
+        $message!.warning('当前账号没有操作权限,请联系管理员授权');
       }
 
       return;

+ 18 - 15
langchat-ui-client/src/views/login/PhoneLogin.vue

@@ -1,10 +1,12 @@
-<script setup lang="ts">
+<script lang="ts" setup>
   import { SvgIcon } from '@/components/common';
   import { reactive, ref } from 'vue';
   import { t } from '@/locales';
   import { rules } from '@/views/login/data';
+  import { useMessage } from 'naive-ui';
 
   const formRef = ref();
+  const message = useMessage();
   const loading = ref(false);
   const codeLoading = ref(false);
   const form = reactive({
@@ -15,41 +17,42 @@
   const handleSubmit = (e: any) => {};
 
   function onGetCode() {
-    codeLoading.value = true;
+    message.warning('暂时未接入短信登录方式');
+    // codeLoading.value = true;
   }
 </script>
 
 <template>
   <div class="mt-4 login-content-form">
-    <n-form ref="formRef" label-placement="left" size="large" :model="form" :rules="rules">
-      <n-form-item path="username" class="login-animation1">
+    <n-form ref="formRef" :model="form" :rules="rules" label-placement="left" size="large">
+      <n-form-item class="login-animation1" path="username">
         <n-input v-model:value="form.username" :placeholder="t('login.phonePlaceholder')">
           <template #prefix>
-            <n-icon size="18" color="#808695">
+            <n-icon color="#808695" size="18">
               <SvgIcon icon="material-symbols:person-outline" />
             </n-icon>
           </template>
         </n-input>
       </n-form-item>
-      <n-form-item path="code" class="login-animation2">
+      <n-form-item class="login-animation2" path="code">
         <n-input
           v-model:value="form.code"
-          showPasswordOn="click"
           :placeholder="t('login.codePlaceholder')"
+          showPasswordOn="click"
         >
           <template #prefix>
-            <n-icon size="18" color="#808695">
+            <n-icon color="#808695" size="18">
               <SvgIcon icon="ph:key-duotone" />
             </n-icon>
           </template>
           <template #suffix>
-            <n-button @click="onGetCode()" :disabled="codeLoading" text type="success">
+            <n-button :disabled="codeLoading" text type="success" @click="onGetCode()">
               <n-countdown
                 v-if="codeLoading"
                 :active="codeLoading"
-                @finish="codeLoading = false"
                 :duration="59000"
                 :render="({ seconds }) => `${String(seconds) + t('login.codeExp')}`"
+                @finish="codeLoading = false"
               />
               <template v-else>{{ t('login.getCode') }}</template>
             </n-button>
@@ -58,14 +61,14 @@
       </n-form-item>
 
       <n-form-item class="login-animation3">
-        <n-space vertical class="w-full">
+        <n-space class="w-full" vertical>
           <n-button
-            disabled
-            type="primary"
-            @click="handleSubmit"
             :loading="loading"
             block
+            disabled
             secondary
+            type="primary"
+            @click="handleSubmit"
           >
             {{ t('login.title') }}
           </n-button>
@@ -75,4 +78,4 @@
   </div>
 </template>
 
-<style scoped lang="less"></style>
+<style lang="less" scoped></style>

+ 22 - 18
langchat-ui-client/src/views/login/PhoneRegister.vue

@@ -1,10 +1,12 @@
-<script setup lang="ts">
+<script lang="ts" setup>
   import { SvgIcon } from '@/components/common';
   import { reactive, ref } from 'vue';
   import { t } from '@/locales';
   import { rules } from '@/views/login/data';
+  import { useMessage } from 'naive-ui';
 
   const formRef = ref();
+  const message = useMessage();
   const loading = ref(false);
   const form = reactive({
     email: '',
@@ -17,65 +19,67 @@
   };
 
   const codeLoading = ref(false);
-  function onGetCode() {}
+  function onGetCode() {
+    message.warning('暂时未接入短信登录方式');
+  }
 </script>
 
 <template>
   <div class="mt-4 login-content-form">
-    <n-form ref="formRef" label-placement="left" size="large" :model="form" :rules="rules">
-      <n-form-item path="email" class="login-animation1">
+    <n-form ref="formRef" :model="form" :rules="rules" label-placement="left" size="large">
+      <n-form-item class="login-animation1" path="email">
         <n-input v-model:value="form.email" :placeholder="t('login.phonePlaceholder')">
           <template #prefix>
-            <n-icon size="18" color="#808695">
+            <n-icon color="#808695" size="18">
               <SvgIcon icon="material-symbols:person-outline" />
             </n-icon>
           </template>
         </n-input>
       </n-form-item>
-      <n-form-item path="code" class="login-animation2">
+      <n-form-item class="login-animation2" path="code">
         <n-input v-model:value="form.code" :placeholder="t('login.codePlaceholder')">
           <template #prefix>
-            <n-icon size="18" color="#808695">
+            <n-icon color="#808695" size="18">
               <SvgIcon icon="ph:key" />
             </n-icon>
           </template>
           <template #suffix>
-            <n-button @click="onGetCode()" :disabled="codeLoading" text type="success">
+            <n-button :disabled="codeLoading" text type="success" @click="onGetCode()">
               <n-countdown
                 v-if="codeLoading"
                 :active="codeLoading"
-                @finish="codeLoading = false"
                 :duration="59000"
                 :render="({ seconds }) => `${String(seconds) + t('login.codeExp')}`"
+                @finish="codeLoading = false"
               />
               <template v-else>{{ t('login.getCode') }}</template>
             </n-button>
           </template>
         </n-input>
       </n-form-item>
-      <n-form-item path="password" class="login-animation2">
+      <n-form-item class="login-animation2" path="password">
         <n-input
           v-model:value="form.password"
-          type="password"
-          showPasswordOn="click"
           :placeholder="t('login.passPlaceholder')"
+          showPasswordOn="click"
+          type="password"
         >
           <template #prefix>
-            <n-icon size="18" color="#808695">
+            <n-icon color="#808695" size="18">
               <SvgIcon icon="mdi:lock-outline" />
             </n-icon>
           </template>
         </n-input>
       </n-form-item>
       <n-form-item class="login-animation3">
-        <n-space vertical class="w-full">
+        <n-space class="w-full" vertical>
           <n-button
-            type="primary"
-            disabled
-            @click="handleSubmit"
             :loading="loading"
             block
+            disabled
             secondary
+            type="primary"
+            @click="handleSubmit"
           >
             {{ t('login.title') }}
           </n-button>
@@ -85,4 +89,4 @@
   </div>
 </template>
 
-<style scoped lang="less"></style>
+<style lang="less" scoped></style>

+ 22 - 17
langchat-ui-client/src/views/modules/chat/index.vue

@@ -1,5 +1,5 @@
 <script lang="ts" setup>
-  import { computed, onMounted, onUnmounted, onUpdated, Ref, ref } from 'vue';
+  import { computed, onMounted, onUnmounted, onUpdated, ref } from 'vue';
   import { SvgIcon } from '@/components/common';
   import { v4 as uuidv4 } from 'uuid';
   import { chat } from '@/api/chat';
@@ -68,14 +68,19 @@
     loading.value = true;
     prompt.value = '';
 
-    // ai
-    await scrollToBottom();
-    const { conversationId } = await addMessage(data);
-    aiChatId.value = uuidv4();
-    await scrollToBottom();
-    await chatStore.addMessage('', 'assistant', aiChatId.value);
-    await scrollToBottomIfAtBottom();
-    await onChat(message, conversationId);
+    try {
+      // ai
+      await scrollToBottom();
+      const { conversationId } = await addMessage(data);
+      aiChatId.value = uuidv4();
+      await scrollToBottom();
+      await chatStore.addMessage('', 'assistant', aiChatId.value);
+      await scrollToBottomIfAtBottom();
+      await onChat(message, conversationId);
+    } catch (err) {
+      loading.value = false;
+      prompt.value = '';
+    }
   }
 
   async function onChat(message: string, conversationId?: string) {
@@ -236,8 +241,8 @@
               <div
                 v-else
                 ref="scrollRef"
-                class="max-w-screen-2xl m-auto"
                 :class="[isMobile ? 'p-2' : 'p-5 py-8 !px-12']"
+                class="max-w-screen-2xl m-auto"
               >
                 <Message
                   v-for="(item, index) of dataSources"
@@ -263,23 +268,23 @@
 
           <footer :class="footerClass">
             <div
-              class="w-full max-w-screen-2xl m-auto relative"
               :class="isMobile ? 'pb-2' : ' px-20 pb-10 '"
+              class="w-full max-w-screen-2xl m-auto relative"
             >
               <div class="flex items-center justify-between">
                 <n-input
                   ref="inputRef"
                   v-model:value="prompt"
-                  type="textarea"
-                  @keypress="handleEnter"
                   :autosize="{ minRows: 1, maxRows: isMobile ? 1 : 4 }"
-                  class="!rounded-full px-2 py-1"
                   :placeholder="t('chat.placeholder')"
+                  class="!rounded-full px-2 py-1"
                   size="large"
+                  type="textarea"
+                  @keypress="handleEnter"
                 >
                   <template #prefix>
-                    <n-popselect placement="top" :options="menuOptions" trigger="click">
-                      <n-button text class="!mr-2" size="large">
+                    <n-popselect :options="menuOptions" placement="top" trigger="click">
+                      <n-button class="!mr-2" size="large" text>
                         <template #icon>
                           <SvgIcon icon="ion:attach" />
                         </template>
@@ -287,7 +292,7 @@
                     </n-popselect>
                   </template>
                   <template #suffix>
-                    <n-button text :loading="loading" @click="handleSubmit">
+                    <n-button :loading="loading" text @click="handleSubmit">
                       <template #icon>
                         <SvgIcon icon="mdi:sparkles-outline" />
                       </template>

+ 35 - 0
langchat-ui/src/views/aigc/user/columns.ts

@@ -24,6 +24,24 @@ export const columns: BasicColumn[] = [
     key: 'tokenUsed',
     align: 'center',
   },
+  {
+    title: '操作权限',
+    key: 'isPerms',
+    align: 'center',
+    width: 120,
+    render(row) {
+      return h(
+        NTag,
+        {
+          type: row.isPerms ? 'success' : 'error',
+          size: 'small',
+        },
+        {
+          default: () => (row.isPerms ? '可用' : '无权限'),
+        }
+      );
+    },
+  },
   {
     title: '账号状态',
     key: 'status',
@@ -91,6 +109,23 @@ export const formSchemas: FormSchema[] = [
     },
     rules: [{ required: true, message: '请输入用户昵称', trigger: ['blur'] }],
   },
+  {
+    field: 'isPerms',
+    component: 'NRadioGroup',
+    label: '操作权限',
+    componentProps: {
+      options: [
+        {
+          label: '启用',
+          value: true,
+        },
+        {
+          label: '禁用',
+          value: false,
+        },
+      ],
+    },
+  },
   {
     field: 'status',
     component: 'NRadioGroup',

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません