Jelajahi Sumber

feature(知识库管理,智能体管理) 静态页面完成
1、登录注册验证支持国际化
2、知识库管理页面
3、智能体管理页面

hanyang 1 tahun lalu
induk
melakukan
f35a4d0afc
33 mengubah file dengan 633 tambahan dan 31 penghapusan
  1. 16 1
      public/i18n/lang-en.json
  2. 16 1
      public/i18n/lang-zh.json
  3. TEMPAT SAMPAH
      src/assets/image/login/logo.png
  4. TEMPAT SAMPAH
      src/assets/image/management/intelligent-frame1.png
  5. TEMPAT SAMPAH
      src/assets/image/management/intelligent-frame2.png
  6. TEMPAT SAMPAH
      src/assets/image/management/intelligent-frame3.png
  7. TEMPAT SAMPAH
      src/assets/image/management/intelligent-frame4.png
  8. TEMPAT SAMPAH
      src/assets/image/management/intelligent-frame5.png
  9. TEMPAT SAMPAH
      src/assets/image/management/intelligent-frame6.png
  10. TEMPAT SAMPAH
      src/assets/image/management/intelligent-frame7.png
  11. TEMPAT SAMPAH
      src/assets/image/management/intelligent-frame8.png
  12. TEMPAT SAMPAH
      src/assets/image/management/knowledge-frame1.png
  13. TEMPAT SAMPAH
      src/assets/image/management/knowledge-frame2.png
  14. TEMPAT SAMPAH
      src/assets/image/management/knowledge-frame3.png
  15. TEMPAT SAMPAH
      src/assets/image/management/knowledge-frame4.png
  16. 0 6
      src/components/HeadManage.vue
  17. 2 0
      src/index.js
  18. 40 0
      src/modules/Model/MessageBody.vue
  19. 66 0
      src/modules/Model/ModelList.vue
  20. 21 0
      src/modules/Model/TopSetting.vue
  21. 139 0
      src/modules/intelligent/MessageBody.vue
  22. 28 0
      src/modules/intelligent/TopSetting.vue
  23. 103 0
      src/modules/knowledge/MessageBody.vue
  24. 28 0
      src/modules/knowledge/TopSetting.vue
  25. 5 3
      src/modules/login/FormItem.vue
  26. 16 0
      src/modules/login/auth.js
  27. 0 14
      src/utils/auth.js
  28. 9 1
      src/views/Intelligent.vue
  29. 9 2
      src/views/Knowledge.vue
  30. 6 2
      src/views/Login.vue
  31. 9 1
      src/views/Model.vue
  32. 75 0
      src/views/manage-common/CardItem.vue
  33. 45 0
      src/views/manage-common/HeadManage.vue

+ 16 - 1
public/i18n/lang-en.json

@@ -32,7 +32,22 @@
     "emailTips": "Please enter your email",
     "loginBtn": "Login",
     "registerBtn": "Register",
-    "noAccount": "Don't have an account?"
+    "noAccount": "Don't have an account?",
+    "usernameLength": "Username must be between 3 and 20 characters long",
+    "passwordLength": "Password must be between 6 and 20 characters long",
+    "emailFormat": "Incorrect email format"
+  },
+  "management": {
+    "knowledgeTitle": "Welcome back, GuanLi4",
+    "knowledgeDesc": "Which knowledge base are we going to use today?",
+    "modelTitle": "Model Provider",
+    "modelDesc": "Set model parameters and API key here.",
+    "intelligentTitle": "Intelligent Agent Management",
+    "intelligentDesc": "The intelligent agent executes tasks through a visual workflow with clear steps. We provide scenario templates for your use and reference.",
+    "search": "Search",
+    "createKnowledgeBase": "Create Knowledge Base",
+    "setModel": "System Model Settings",
+    "createIntelligent": "Create Intelligent Agent"
   },
   "permission": {
     "role": "Role",

+ 16 - 1
public/i18n/lang-zh.json

@@ -32,7 +32,22 @@
     "emailTips": "请输入邮箱",
     "loginBtn": "登录",
     "registerBtn": "注册",
-    "noAccount": "没有账号?"
+    "noAccount": "没有账号?",
+    "usernameLength": "用户名长度必须在3-20个字符之间",
+    "passwordLength": "密码长度必须在6-20个字符之间",
+    "emailFormat": "邮箱格式不正确"
+  },
+  "management": {
+    "knowledgeTitle": "欢迎回来,GuanLi4",
+    "knowledgeDesc": "今天我们要使用哪个知识库?",
+    "modelTitle": "模型提供商",
+    "modelDesc": "在此设置模型参数和 APIKey。",
+    "intelligentTitle": "智能体管理",
+    "intelligentDesc": "智能体通过可视化的流程编排,明确任务执行步骤我们提供场景模板供您使用和参考。",
+    "search": "搜索",
+    "createKnowledgeBase": "创建知识库",
+    "setModel": "系统模型设置",
+    "createIntelligent": "新建智能体"
   },
   "permission": {
     "role": "角色",

TEMPAT SAMPAH
src/assets/image/login/logo.png


TEMPAT SAMPAH
src/assets/image/management/intelligent-frame1.png


TEMPAT SAMPAH
src/assets/image/management/intelligent-frame2.png


TEMPAT SAMPAH
src/assets/image/management/intelligent-frame3.png


TEMPAT SAMPAH
src/assets/image/management/intelligent-frame4.png


TEMPAT SAMPAH
src/assets/image/management/intelligent-frame5.png


TEMPAT SAMPAH
src/assets/image/management/intelligent-frame6.png


TEMPAT SAMPAH
src/assets/image/management/intelligent-frame7.png


TEMPAT SAMPAH
src/assets/image/management/intelligent-frame8.png


TEMPAT SAMPAH
src/assets/image/management/knowledge-frame1.png


TEMPAT SAMPAH
src/assets/image/management/knowledge-frame2.png


TEMPAT SAMPAH
src/assets/image/management/knowledge-frame3.png


TEMPAT SAMPAH
src/assets/image/management/knowledge-frame4.png


+ 0 - 6
src/components/HeadManage.vue

@@ -1,6 +0,0 @@
-<template>
-  <div>管理页面头部通用组件</div>
-</template>
-
-<script setup></script>
-<style scoped></style>

+ 2 - 0
src/index.js

@@ -38,4 +38,6 @@ setupI18n().then((i18n) => {
   app.use(i18n);
   app.mount('#app');
   initTheme();
+  console.log(i18n);
+  window.$t = i18n.global.t;
 });

+ 40 - 0
src/modules/Model/MessageBody.vue

@@ -0,0 +1,40 @@
+<template>
+  <div class="model-body">
+    <div class="body-item">
+      <div class="item-title">
+        <div class="title">添加了的模型</div>
+        <div class="icon">展开 <icon-down /></div>
+      </div>
+      <ModelList v-for="item in 3"></ModelList>
+    </div>
+    <div class="body-item">
+      <div class="item-title">
+        <div class="title">待添加的模型</div>
+        <div class="icon">展开 <icon-down /></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import ModelList from './ModelList.vue';
+</script>
+<style scoped lang="css">
+.model-body {
+  height: calc(100% - 80px);
+}
+.body-item {
+  margin-bottom: 50px;
+}
+
+.item-title {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  cursor: pointer;
+}
+.title {
+  font-size: 16px;
+  font-weight: bold;
+}
+</style>

+ 66 - 0
src/modules/Model/ModelList.vue

@@ -0,0 +1,66 @@
+<template>
+  <div class="model-list">
+    <div class="list-info">
+      <img src="../../assets/image/management/intelligent-frame1.png" alt="" />
+      <div class="info-desc">
+        <div class="name">OpenAl-APl-Compatible</div>
+        <div class="desc">LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION</div>
+      </div>
+    </div>
+    <div class="list-tools">
+      <a-space>
+        <a-button @click="handlerAddModel">
+          <template #icon>
+            <icon-plus />
+          </template>
+          添加模型
+        </a-button>
+        <a-button @click="showMoreModel">
+          展示更多模型
+          <icon-down />
+        </a-button>
+        <icon-delete class="delete-icon" @click="handlerDeleteModel" />
+      </a-space>
+    </div>
+  </div>
+</template>
+
+<script setup>
+const handleSelect = () => {};
+const handlerAddModel = () => {};
+const handlerDeleteModel = () => {};
+const showMoreModel = () => {};
+</script>
+<style scoped lang="css">
+.model-list {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px 30px;
+  margin-top: 10px;
+  background-color: #fff;
+}
+.list-info {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 20px;
+}
+.info-desc {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+.name {
+  font-size: 16px;
+  font-weight: bold;
+}
+.desc {
+  color: var(--color-neutral-4);
+}
+.delete-icon {
+  font-size: 20px;
+  color: rgb(var(--orange-5));
+  cursor: pointer;
+}
+</style>

+ 21 - 0
src/modules/Model/TopSetting.vue

@@ -0,0 +1,21 @@
+<template>
+  <a-space size="large">
+    <a-button type="primary" size="large" @click="handleCreate">
+      <template #icon>
+        <icon-settings />
+      </template>
+      {{ $t('management.setModel') }}
+    </a-button>
+  </a-space>
+</template>
+
+<script setup>
+// 系统模型设置
+const handleCreate = () => {};
+</script>
+<style scoped lang="scss"></style>
+<style>
+.arco-input-wrapper {
+  background-color: #fff;
+}
+</style>

+ 139 - 0
src/modules/intelligent/MessageBody.vue

@@ -0,0 +1,139 @@
+<template>
+  <div class="body-container">
+    <CardItem v-for="item in mokeData" :key="item.id" :name="item.name" :imgSrc="item.imgSrc" :height="200">
+      <template #description> {{ item.desc }}> </template>
+      <template #settings>
+        <a-switch v-model="item.online">
+          <template #checked> 上线 </template>
+          <template #unchecked> 下线 </template>
+        </a-switch>
+      </template>
+      <template #labels v-if="item.hasLabel">
+        <div class="label-btn">
+          <icon-subscribe />
+          <div>添加标签</div>
+        </div>
+      </template>
+      <template #bottom>
+        <div class="bottom">
+          <div class="user"><icon-user /> 创建用户:{{ item.user }}</div>
+          <div class="tools">
+            <icon-more :style="{ fontSize: '20px' }" />
+          </div>
+        </div>
+      </template>
+    </CardItem>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import CardItem from '../../views/manage-common/CardItem.vue';
+import intelligentFrame1 from '../../assets/image/management/intelligent-frame1.png';
+import intelligentFrame2 from '../../assets/image/management/intelligent-frame2.png';
+import intelligentFrame3 from '../../assets/image/management/intelligent-frame3.png';
+import intelligentFrame4 from '../../assets/image/management/intelligent-frame4.png';
+import intelligentFrame5 from '../../assets/image/management/intelligent-frame5.png';
+import intelligentFrame6 from '../../assets/image/management/intelligent-frame6.png';
+import intelligentFrame7 from '../../assets/image/management/intelligent-frame7.png';
+import intelligentFrame8 from '../../assets/image/management/intelligent-frame8.png';
+
+const mokeData = ref([
+  {
+    id: 1,
+    name: '智能问答',
+    imgSrc: intelligentFrame1,
+    desc: '极目新闻记者了解到,这些患者都有一些共同的习惯:经常把未吃完的食物放在桌上敞着',
+    user: 'admin',
+    online: false,
+    hasLabel: false
+  },
+  {
+    id: 2,
+    name: '智能客服',
+    imgSrc: intelligentFrame2,
+    desc: '极目新闻记者了解到,这些患者都有一些共同的习惯:经常把未吃完的食物放在桌上敞着',
+    user: 'admin',
+    online: false,
+    hasLabel: true
+  },
+  {
+    id: 3,
+    name: '继题组卷',
+    imgSrc: intelligentFrame3,
+    desc: '极目新闻记者了解到,这些患者都有一些共同的习惯:经常把未吃完的食物放在桌上敞着',
+    user: 'admin',
+    online: true,
+    hasLabel: false
+  },
+  {
+    id: 4,
+    name: '小数绘图',
+    imgSrc: intelligentFrame4,
+    desc: '极目新闻记者了解到,这些患者都有一些共同的习惯:经常把未吃完的食物放在桌上敞着',
+    user: 'admin',
+    online: true,
+    hasLabel: true
+  },
+  {
+    id: 5,
+    name: '材据分析',
+    imgSrc: intelligentFrame5,
+    desc: '极目新闻记者了解到,这些患者都有一些共同的习惯:经常把未吃完的食物放在桌上敞着',
+    user: 'admin',
+    online: true,
+    hasLabel: false
+  },
+  {
+    id: 6,
+    name: '报告自动生成 ',
+    imgSrc: intelligentFrame6,
+    desc: '极目新闻记者了解到,这些患者都有一些共同的习惯:经常把未吃完的食物放在桌上敞着',
+    user: 'admin',
+    online: true,
+    hasLabel: false
+  },
+  {
+    id: 7,
+    name: '文档创作',
+    imgSrc: intelligentFrame7,
+    desc: '极目新闻记者了解到,这些患者都有一些共同的习惯:经常把未吃完的食物放在桌上敞着',
+    user: 'admin',
+    online: false,
+    hasLabel: true
+  },
+  {
+    id: 8,
+    name: '设备检测',
+    imgSrc: intelligentFrame8,
+    desc: '极目新闻记者了解到,这些患者都有一些共同的习惯:经常把未吃完的食物放在桌上敞着',
+    user: 'admin',
+    online: true,
+    hasLabel: true
+  }
+]);
+</script>
+<style scoped lang="css">
+.body-container {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  align-content: flex-start;
+  gap: 20px;
+  height: calc(100% - 80px);
+  overflow: auto;
+}
+.label-btn {
+  display: flex;
+  gap: 10px;
+  background-color: var(--color-neutral-3);
+  font-size: 12px;
+  padding: 8px 16px;
+  cursor: pointer;
+}
+.bottom {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+</style>

+ 28 - 0
src/modules/intelligent/TopSetting.vue

@@ -0,0 +1,28 @@
+<template>
+  <a-space size="large">
+    <a-input :placeholder="$t('management.search')" style="width: 300px" allow-clear @input="handleSearch">
+      <template #prefix>
+        <icon-search />
+      </template>
+    </a-input>
+    <a-button type="primary" size="large" @click="handleCreate">
+      <template #icon>
+        <icon-plus />
+      </template>
+      {{ $t('management.createIntelligent') }}
+    </a-button>
+  </a-space>
+</template>
+
+<script setup>
+// 搜索
+const handleSearch = () => {};
+// 创建智能体
+const handleCreate = () => {};
+</script>
+<style scoped lang="scss"></style>
+<style>
+.arco-input-wrapper {
+  background-color: #fff;
+}
+</style>

+ 103 - 0
src/modules/knowledge/MessageBody.vue

@@ -0,0 +1,103 @@
+<template>
+  <div class="body-container">
+    <CardItem v-for="item in mokeData" :key="item.name" :name="item.name" :imgSrc="item.imgSrc">
+      <template #bottom>
+        <div class="bottom">
+          <div>{{ item.count }}文档</div>
+          <div>|</div>
+          <div>{{ item.time }}</div>
+        </div>
+      </template>
+      <template #settings>
+        <a-dropdown trigger="hover" @select="handleSelect">
+          <icon-more :style="{ fontSize: '20px' }" />
+          <template #content>
+            <a-doption :value="{ value: 'edit' }">编辑</a-doption>
+            <a-doption :value="{ value: 'delete' }">删除</a-doption>
+          </template>
+        </a-dropdown>
+      </template>
+    </CardItem>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import CardItem from '../../views/manage-common/CardItem.vue';
+import knowledgeFrame1 from '../../assets/image/management/knowledge-frame1.png';
+import knowledgeFrame2 from '../../assets/image/management/knowledge-frame2.png';
+import knowledgeFrame3 from '../../assets/image/management/knowledge-frame3.png';
+import knowledgeFrame4 from '../../assets/image/management/knowledge-frame4.png';
+
+const mokeData = ref([
+  {
+    name: '知识库1',
+    imgSrc: knowledgeFrame1,
+    count: 292,
+    time: '2024:11:11 12:00:00'
+  },
+  {
+    name: '知识库2',
+    imgSrc: knowledgeFrame2,
+    count: 292,
+    time: '2024:11:11 12:00:00'
+  },
+  {
+    name: '知识库3',
+    imgSrc: knowledgeFrame3,
+    count: 292,
+    time: '2024:11:11 12:00:00'
+  },
+  {
+    name: '知识库4',
+    imgSrc: knowledgeFrame4,
+    count: 292,
+    time: '2024:11:11 12:00:00'
+  },
+  {
+    name: '知识库5',
+    imgSrc: knowledgeFrame1,
+    count: 292,
+    time: '2024:11:11 12:00:00'
+  },
+  {
+    name: '知识库6',
+    imgSrc: knowledgeFrame2,
+    count: 292,
+    time: '2024:11:11 12:00:00'
+  },
+  {
+    name: '知识库7',
+    imgSrc: knowledgeFrame3,
+    count: 292,
+    time: '2024:11:11 12:00:00'
+  },
+  {
+    name: '知识库8',
+    imgSrc: knowledgeFrame4,
+    count: 292,
+    time: '2024:11:11 12:00:00'
+  }
+]);
+
+const handleSelect = ({ value }) => {
+  if (value === 'edit') {
+  } else {
+  }
+};
+</script>
+<style scoped lang="css">
+.body-container {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  align-content: flex-start;
+  gap: 20px;
+  height: calc(100% - 80px);
+  overflow: auto;
+}
+.bottom {
+  display: flex;
+  gap: 10px;
+}
+</style>

+ 28 - 0
src/modules/knowledge/TopSetting.vue

@@ -0,0 +1,28 @@
+<template>
+  <a-space size="large">
+    <a-input :placeholder="$t('management.search')" style="width: 300px" allow-clear @input="handleSearch">
+      <template #prefix>
+        <icon-search />
+      </template>
+    </a-input>
+    <a-button type="primary" size="large" @click="handleCreate">
+      <template #icon>
+        <icon-plus />
+      </template>
+      {{ $t('management.createKnowledgeBase') }}
+    </a-button>
+  </a-space>
+</template>
+
+<script setup>
+// 搜索
+const handleSearch = () => {};
+// 创建知识库
+const handleCreate = () => {};
+</script>
+<style scoped lang="scss"></style>
+<style>
+.arco-input-wrapper {
+  background-color: #fff;
+}
+</style>

+ 5 - 3
src/views/login-register/FormItem.vue → src/modules/login/FormItem.vue

@@ -50,10 +50,12 @@
 import { ref, reactive, getCurrentInstance, nextTick } from 'vue';
 import { useRouter } from 'vue-router';
 import { useCurrentUserStore } from '../../base/store/use-current-user';
-import { loginFormRules } from '../../utils/auth';
+import { getLoginFormRules } from './auth';
 import { register } from '../../base/api/login';
+import { useI18n } from 'vue-i18n';
 
 const router = useRouter();
+const { t } = useI18n();
 
 const { appContext } = getCurrentInstance();
 const userStore = useCurrentUserStore();
@@ -65,7 +67,7 @@ const form = reactive({
   email: '', // 非必填
   password: ''
 });
-const rules = loginFormRules;
+const rules = getLoginFormRules(t);
 /**
  * 表单类型
  * 0:登录
@@ -85,7 +87,7 @@ const handleSubmit = (data) => {
       .toLogin({ username: data.username, password: data.password })
       .then((res) => {
         $message.success(res);
-        router.push('/');
+        router.push({ path: '/knowledge' });
       })
       .catch((err) => {
         $message.error(err);

+ 16 - 0
src/modules/login/auth.js

@@ -0,0 +1,16 @@
+export function getLoginFormRules(t) {
+  return {
+    username: [
+      { required: true, message: t('login.usernameTips'), trigger: 'blur' },
+      { min: 3, max: 20, message: t('login.usernameLength'), trigger: 'blur' }
+    ],
+    email: [
+      { required: true, message: t('login.emailTips'), trigger: 'blur' },
+      { type: 'email', message: t('login.emailFormat'), trigger: 'blur' }
+    ],
+    password: [
+      { required: true, message: t('login.passwordTips'), trigger: 'blur' },
+      { min: 6, max: 20, message: t('login.passwordLength'), trigger: 'blur' }
+    ]
+  };
+}

+ 0 - 14
src/utils/auth.js

@@ -1,14 +0,0 @@
-export const loginFormRules = {
-  username: [
-    { required: true, message: '请输入用户名', trigger: 'blur' },
-    { min: 3, max: 20, message: '用户名长度在 3 到 20 个字符', trigger: 'blur' }
-  ],
-  email: [
-    { required: true, message: '请输入邮箱', trigger: 'blur' },
-    { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
-  ],
-  password: [
-    { required: true, message: '请输入密码', trigger: 'blur' },
-    { min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' }
-  ]
-};

+ 9 - 1
src/views/Intelligent.vue

@@ -1,7 +1,12 @@
 <template>
   <LayoutExperience>
     <ContentContainer>
-      <div class="intelligent">智能体</div>
+      <HeadManage :title="$t('management.intelligentTitle')" :tips="$t('management.intelligentDesc')">
+        <template v-slot:setting>
+          <TopSetting></TopSetting>
+        </template>
+      </HeadManage>
+      <MessageBody></MessageBody>
     </ContentContainer>
   </LayoutExperience>
 </template>
@@ -9,4 +14,7 @@
 <script setup>
 import LayoutExperience from './layout/LayoutExperience.vue';
 import ContentContainer from './layout/ContentContainer.vue';
+import HeadManage from './manage-common/HeadManage.vue';
+import TopSetting from '../modules/intelligent/TopSetting.vue';
+import MessageBody from '../modules/intelligent/MessageBody.vue';
 </script>

+ 9 - 2
src/views/Knowledge.vue

@@ -1,7 +1,12 @@
 <template>
   <LayoutExperience>
     <ContentContainer>
-      <HeadManage></HeadManage>
+      <HeadManage :title="$t('management.knowledgeTitle')" :tips="$t('management.knowledgeDesc')">
+        <template v-slot:setting>
+          <TopSetting></TopSetting>
+        </template>
+      </HeadManage>
+      <MessageBody></MessageBody>
     </ContentContainer>
   </LayoutExperience>
 </template>
@@ -9,5 +14,7 @@
 <script setup>
 import LayoutExperience from './layout/LayoutExperience.vue';
 import ContentContainer from './layout/ContentContainer.vue';
-import HeadManage from '../components/HeadManage.vue';
+import HeadManage from './manage-common/HeadManage.vue';
+import TopSetting from '../modules/knowledge/TopSetting.vue';
+import MessageBody from '../modules/knowledge/MessageBody.vue';
 </script>

+ 6 - 2
src/views/Login.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="login">
     <div class="login-logo">
-      <img src="../assets/image/login/logo.png" alt="logo" />
+      <img class="logo-img" src="../assets/image/login/logo.png" alt="logo" />
       <div class="logo-title">{{ $t('login.logoTitle') }}</div>
     </div>
     <div class="login-section">
@@ -18,7 +18,7 @@
 </template>
 
 <script setup>
-import FormItem from './login-register/FormItem.vue';
+import FormItem from '../modules/login/FormItem.vue';
 </script>
 <style scoped lang="css">
 .login {
@@ -33,7 +33,11 @@ import FormItem from './login-register/FormItem.vue';
   align-items: center;
   padding: 30px;
 }
+.logo-img {
+  width: 50px;
+}
 .logo-title {
+  margin-left: 20px;
   font-size: 24px;
   font-weight: 600;
 }

+ 9 - 1
src/views/Model.vue

@@ -1,7 +1,12 @@
 <template>
   <LayoutExperience>
     <ContentContainer>
-      <div class="model">模型</div>
+      <HeadManage :title="$t('management.modelTitle')" :tips="$t('management.modelDesc')">
+        <template v-slot:setting>
+          <TopSetting></TopSetting>
+        </template>
+      </HeadManage>
+      <MessageBody> </MessageBody>
     </ContentContainer>
   </LayoutExperience>
 </template>
@@ -9,4 +14,7 @@
 <script setup>
 import LayoutExperience from './layout/LayoutExperience.vue';
 import ContentContainer from './layout/ContentContainer.vue';
+import HeadManage from './manage-common/HeadManage.vue';
+import TopSetting from '../modules/model/TopSetting.vue';
+import MessageBody from '../modules/model/MessageBody.vue';
 </script>

+ 75 - 0
src/views/manage-common/CardItem.vue

@@ -0,0 +1,75 @@
+<template>
+  <div class="card-container" :style="{ height: `${height}px` }">
+    <div class="card-name">
+      <img :src="imgSrc" class="logo" />
+      <div class="name">{{ name }}</div>
+    </div>
+    <div class="card-des">
+      <slot name="description"></slot>
+    </div>
+    <div class="card-label">
+      <slot name="labels"></slot>
+    </div>
+    <div class="card-bottom">
+      <slot name="bottom"></slot>
+    </div>
+    <div class="card-settings">
+      <slot name="settings"></slot>
+    </div>
+  </div>
+</template>
+
+<script setup>
+const props = defineProps({
+  name: {
+    type: String
+  },
+  imgSrc: {
+    type: String
+  },
+  height: {
+    type: Number,
+    default: 175
+  }
+});
+</script>
+<style scoped lang="css">
+.card-container {
+  position: relative;
+  width: calc((100vw - 160px) / 7);
+  padding: 10px 20px;
+  background-color: #fff;
+  border-radius: 5px;
+}
+.card-name .name {
+  font-size: 16px;
+  color: var(--color-neutral-8);
+  font-weight: bold;
+  margin-top: 10px;
+}
+.card-des {
+  margin-top: 10px;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.card-label {
+  margin-top: 15px;
+  width: 100%;
+}
+
+.card-bottom {
+  position: absolute;
+  bottom: 10px;
+  left: 20px;
+  width: calc(100% - 40px);
+}
+.card-settings {
+  position: absolute;
+  right: 20px;
+  top: 20px;
+  cursor: pointer;
+}
+</style>

+ 45 - 0
src/views/manage-common/HeadManage.vue

@@ -0,0 +1,45 @@
+<template>
+  <div class="top-common">
+    <div class="common-left">
+      <div class="title">{{ title }}</div>
+      <div class="tips">{{ tips }}</div>
+    </div>
+    <div class="common-right">
+      <slot name="setting"></slot>
+    </div>
+  </div>
+</template>
+
+<script setup>
+const props = defineProps({
+  title: {
+    required: true,
+    type: String
+  },
+  tips: {
+    typeof: String
+  }
+});
+</script>
+<style scoped>
+.top-common {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  height: 80px;
+}
+.common-left {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+.title {
+  font-family: 'pingfang sc';
+  font-size: 18px;
+  font-weight: 600;
+}
+.tips {
+  font-size: 12px;
+  color: var(color-text-3);
+}
+</style>