├── .env ├── .env.electron ├── .env.production ├── .eslintrc.cjs ├── .github └── workflows │ ├── Build-and-Deploy-to-GitHub-Pages.yml │ ├── Build-and-Deploy-to-Remote-Server.yml │ ├── Build-and-Deploy-to-S3.yml │ └── Electron-Build-and-Release.yml ├── .gitignore ├── .prettierrc.json ├── Dockerfile ├── LICENSE ├── README.md ├── build ├── getConfigFileName.ts └── icons │ ├── lumen-im-mac.png │ ├── lumen-im-win.ico │ ├── lumenim.icns │ ├── lumenim.ico │ └── lumenim.png ├── components.d.ts ├── electron ├── main.js └── preload.js ├── env.d.ts ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── favicon.svg ├── src ├── App.vue ├── api │ ├── article.js │ ├── auth.js │ ├── chat.js │ ├── chatbot.ts │ ├── common.js │ ├── contact.js │ ├── embedding.js │ ├── emoticon.js │ ├── group.js │ ├── groupnotice.ts │ ├── keyword.ts │ ├── notice.ts │ ├── openAI.js │ ├── order.ts │ ├── organize.js │ ├── qa.ts │ ├── shortCut.js │ ├── statistic.ts │ ├── upload.js │ ├── user.js │ └── whitelist.ts ├── assets │ ├── css │ │ ├── contact.less │ │ ├── define │ │ │ ├── global.less │ │ │ └── theme.less │ │ ├── dropsize.less │ │ ├── editor-mention.less │ │ ├── login.less │ │ ├── settting.less │ │ └── tailwind.css │ ├── echo.png │ ├── fonts │ │ └── AlibabaPuHuiTi │ │ │ ├── AlibabaPuHuiTi_2_45_Light.woff │ │ │ └── AlibabaPuHuiTi_2_45_Light.woff2 │ ├── image │ │ ├── 0A039CDF.png │ │ ├── avatar.png │ │ ├── banners.webp │ │ ├── default-avatar.jpg │ │ ├── empty.svg │ │ ├── favicon.png │ │ ├── gitee-avatar.jpg │ │ ├── github-avatar.jpg │ │ ├── iTab-exSKJMg-_vI.jpeg │ │ ├── iTab-ympj97.png │ │ ├── md.svg │ │ ├── no-data.svg │ │ ├── not-found.svg │ │ ├── notify.png │ │ ├── schoolboy.png │ │ ├── tes.svg │ │ └── welcome.svg │ ├── images │ │ ├── Business.svg │ │ ├── Error.svg │ │ ├── account-logo.png │ │ ├── analysis.svg │ │ ├── exception │ │ │ ├── 403.svg │ │ │ ├── 404.svg │ │ │ ├── 500.svg │ │ │ ├── developing.svg │ │ │ ├── load-error.svg │ │ │ └── nodata.svg │ │ ├── header-theme-dark.svg │ │ ├── login.svg │ │ ├── logo.png │ │ ├── nav-horizontal-mix.svg │ │ ├── nav-horizontal.svg │ │ ├── nav-theme-dark.svg │ │ ├── nav-theme-light.svg │ │ ├── schoolboy.png │ │ └── tool.png │ └── music.mp3 ├── components │ ├── Application │ │ ├── Application.vue │ │ └── index.ts │ ├── Chat │ │ ├── ChatCard.vue │ │ ├── ChatInput.vue │ │ ├── Knowledge.vue │ │ ├── More.vue │ │ ├── chat.vue │ │ ├── index.vue │ │ ├── summary.vue │ │ └── summaryCard.vue │ ├── CountTo │ │ ├── CountTo.vue │ │ └── index.ts │ ├── Form │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicForm.vue │ │ │ ├── helper.ts │ │ │ ├── hooks │ │ │ ├── useForm.ts │ │ │ ├── useFormContext.ts │ │ │ ├── useFormEvents.ts │ │ │ └── useFormValues.ts │ │ │ ├── props.ts │ │ │ └── types │ │ │ ├── form.ts │ │ │ └── index.ts │ ├── Lockscreen │ │ ├── Lockscreen.vue │ │ ├── Recharge.vue │ │ └── index.ts │ ├── Modal │ │ ├── index.ts │ │ └── src │ │ │ ├── basicModal.vue │ │ │ ├── hooks │ │ │ └── useModal.ts │ │ │ ├── props.ts │ │ │ └── type │ │ │ └── index.ts │ ├── Table │ │ ├── index.ts │ │ └── src │ │ │ ├── Table.vue │ │ │ ├── componentMap.ts │ │ │ ├── components │ │ │ ├── TableAction.vue │ │ │ ├── editable │ │ │ │ ├── CellComponent.ts │ │ │ │ ├── EditableCell.vue │ │ │ │ ├── helper.ts │ │ │ │ └── index.ts │ │ │ └── settings │ │ │ │ └── ColumnSetting.vue │ │ │ ├── const.ts │ │ │ ├── hooks │ │ │ ├── useColumns.ts │ │ │ ├── useDataSource.ts │ │ │ ├── useLoading.ts │ │ │ ├── usePagination.ts │ │ │ └── useTableContext.ts │ │ │ ├── props.ts │ │ │ └── types │ │ │ ├── componentType.ts │ │ │ ├── pagination.ts │ │ │ ├── table.ts │ │ │ └── tableAction.ts │ ├── Upload │ │ ├── index.ts │ │ └── src │ │ │ ├── BasicUpload.vue │ │ │ ├── props.ts │ │ │ └── type │ │ │ └── index.ts │ ├── base │ │ ├── Avatar.vue │ │ ├── AvatarCropper.vue │ │ ├── Loading.vue │ │ ├── UploadsModal.vue │ │ └── Xtime.vue │ ├── common │ │ ├── DialogApi.vue │ │ ├── MessageApi.vue │ │ ├── NotificationApi.vue │ │ └── index.ts │ ├── editor │ │ ├── Editor.vue │ │ ├── MeEditorCode.vue │ │ ├── MeEditorEmoticon.vue │ │ ├── MeEditorImage.vue │ │ ├── MeEditorLocation.vue │ │ ├── MeEditorRecorder.vue │ │ ├── MeEditorVote.vue │ │ ├── formats │ │ │ ├── emoji.ts │ │ │ └── quote.ts │ │ └── util.ts │ ├── group │ │ ├── GroupApply.vue │ │ ├── GroupLaunch.vue │ │ ├── GroupNotice.vue │ │ ├── GroupPanel.vue │ │ └── manage │ │ │ ├── ApplyTab.vue │ │ │ ├── ConfigTab.vue │ │ │ ├── DetailTab.vue │ │ │ ├── MemberTab.vue │ │ │ ├── NoticeEditor.vue │ │ │ ├── NoticeTab.vue │ │ │ └── index.vue │ ├── icons │ │ ├── arrowDown.vue │ │ ├── arrowUp.vue │ │ ├── article.vue │ │ ├── book.vue │ │ ├── checkOutlined.vue │ │ ├── close.vue │ │ ├── closeCircle.vue │ │ ├── code.vue │ │ ├── copy.vue │ │ ├── edit.vue │ │ ├── exit.vue │ │ ├── expand.vue │ │ ├── github.vue │ │ ├── history.vue │ │ ├── index.js │ │ ├── map.js │ │ ├── menu.vue │ │ ├── moreOutlined.vue │ │ ├── oct.vue │ │ ├── plus.vue │ │ ├── question.vue │ │ ├── refreshRight.vue │ │ ├── send.vue │ │ ├── set.vue │ │ ├── syntax.vue │ │ ├── tag.vue │ │ ├── tagFilled.vue │ │ ├── transform.vue │ │ ├── update.vue │ │ └── userFilled.vue │ ├── talk │ │ ├── ForwardRecord.vue │ │ ├── HistoryRecord.vue │ │ └── message │ │ │ ├── AudioMessage.vue │ │ │ ├── CodeMessage.vue │ │ │ ├── FileMessage.vue │ │ │ ├── ForwardMessage.vue │ │ │ ├── GroupNoticeMessage.vue │ │ │ ├── ImageMessage.vue │ │ │ ├── LoginMessage.vue │ │ │ ├── MixedMessage.vue │ │ │ ├── RevokeMessage.vue │ │ │ ├── TextMessage.vue │ │ │ ├── UnknownMessage.vue │ │ │ ├── VideoMessage.vue │ │ │ ├── VoteMessage.vue │ │ │ ├── index.js │ │ │ ├── system │ │ │ ├── SysGroupCancelMutedMessage.vue │ │ │ ├── SysGroupCreateMessage.vue │ │ │ ├── SysGroupJoinMessage.vue │ │ │ ├── SysGroupMemberCancelMutedMessage.vue │ │ │ ├── SysGroupMemberKickedMessage.vue │ │ │ ├── SysGroupMemberMutedMessage.vue │ │ │ ├── SysGroupMemberQuitMessage.vue │ │ │ ├── SysGroupMutedMessage.vue │ │ │ ├── SysGroupTransferMessage.vue │ │ │ ├── SysTextMessage.vue │ │ │ └── sys-message.less │ │ │ └── types.d.ts │ └── user │ │ ├── Avatar.vue │ │ ├── ContactModal.vue │ │ ├── EditorConfig.vue │ │ ├── EditorEmail.vue │ │ ├── EditorMobile.vue │ │ ├── EditorPassword.vue │ │ └── UserCardModal.vue ├── connect.ts ├── constant │ ├── default.js │ ├── event-bus.ts │ ├── highlight.js │ ├── message.js │ └── theme.js ├── directive │ ├── copy.js │ ├── drag.js │ ├── dropsize.js │ ├── focus.js │ ├── index.ts │ ├── inner │ │ └── loading.vue │ ├── loading.js │ ├── loading.vue │ └── paste.js ├── directives │ └── clickOutside.ts ├── enums │ ├── breakpointEnum.ts │ ├── cacheEnum.ts │ ├── httpEnum.ts │ ├── pageEnum.ts │ ├── permissionsEnum.ts │ └── roleEnum.ts ├── event │ └── socket │ │ ├── base.js │ │ ├── index.js │ │ ├── keyboard.js │ │ ├── login.js │ │ ├── revoke.js │ │ └── talk.js ├── hooks │ ├── event │ │ ├── useBreakpoint.ts │ │ ├── useEventListener.ts │ │ ├── useWindowSizeFn copy.ts │ │ └── useWindowSizeFn.ts │ ├── setting │ │ └── useDesignSetting.ts │ ├── useAccessPrompt.ts │ ├── useClickEvent.ts │ ├── useConnectStatus.ts │ ├── useEventBus.ts │ ├── useFriendsMenu.ts │ ├── useGroupListMenu.ts │ ├── useNotifyAuth.ts │ ├── useSessionMenu.ts │ ├── useSmsLock.ts │ ├── useTalkRecord.ts │ ├── useThemeMode.ts │ ├── useUnreadMessage.ts │ ├── useUserModal.ts │ ├── useVisibilityChange.ts │ └── web │ │ └── usePermission.ts ├── layout │ ├── MainLayout.vue │ ├── PageTitle.vue │ ├── SubViewLayout.vue │ └── component │ │ ├── AccountCard.vue │ │ ├── Menu.vue │ │ └── Sponsor.vue ├── main.ts ├── plugins │ ├── core.js │ ├── directive.js │ ├── highlight.js │ ├── hljs.js │ ├── im-sdk.js │ ├── index.ts │ ├── md-editor.js │ ├── naive-ui.ts │ ├── sms-lock.js │ └── ws-socket.js ├── router │ ├── index.js │ └── modules │ │ ├── app.ts │ │ ├── auth.js │ │ ├── chatbot.ts │ │ ├── contact.js │ │ ├── qa.ts │ │ ├── setting.js │ │ ├── statistic.ts │ │ └── whitelist.ts ├── settings │ ├── componentSetting.ts │ └── designSetting.ts ├── socket1.js ├── store │ ├── index.ts │ └── modules │ │ ├── designSetting.ts │ │ ├── dialogue.js │ │ ├── editor-draft.js │ │ ├── editor.js │ │ ├── note.js │ │ ├── plugin.js │ │ ├── settings.js │ │ ├── talk.ts │ │ ├── uploads.js │ │ └── user.js ├── styles │ ├── index.less │ ├── tailwind.css │ ├── transition │ │ ├── base.less │ │ ├── fade.less │ │ ├── index.less │ │ ├── scale.less │ │ ├── scroll.less │ │ ├── slide.less │ │ └── zoom.less │ └── var.less ├── types │ ├── chat.ts │ ├── config.d.ts │ ├── global.ts │ ├── index.d.ts │ ├── modules.d.ts │ └── utils.d.ts ├── utils │ ├── auth.js │ ├── common.js │ ├── crypto-use-crypto-js.mjs │ ├── datetime.js │ ├── dom.ts │ ├── domUtils.ts │ ├── emojis.js │ ├── env.ts │ ├── event-bus.ts │ ├── functions.js │ ├── index.ts │ ├── is │ │ └── index.ts │ ├── log.ts │ ├── notification.js │ ├── propTypes.ts │ ├── request.js │ ├── storage.js │ ├── strings.js │ ├── talk.js │ ├── util.ts │ └── validate.js ├── views │ ├── app │ │ ├── CellColumns.ts │ │ ├── basic.vue │ │ ├── basicColumns.ts │ │ ├── columns.ts │ │ ├── detail.vue │ │ ├── editCell.vue │ │ ├── editRow.vue │ │ ├── index.vue │ │ └── layout.vue │ ├── auth │ │ ├── forget.vue │ │ ├── layout.vue │ │ ├── login.vue │ │ └── register.vue │ ├── chatbot │ │ ├── CellColumns.ts │ │ ├── basic.vue │ │ ├── basicColumns.ts │ │ ├── columns.ts │ │ ├── columnsPromt.ts │ │ ├── columnsUser.ts │ │ ├── detail.vue │ │ ├── editCell.vue │ │ ├── editRow.vue │ │ ├── index.vue │ │ ├── layout.vue │ │ ├── prompt.vue │ │ └── white.vue │ ├── contact │ │ ├── apply.vue │ │ ├── friends.vue │ │ ├── groups.vue │ │ ├── inner │ │ │ ├── FriendApply.vue │ │ │ ├── GroupApply.vue │ │ │ ├── GroupCard.vue │ │ │ ├── GroupManage.vue │ │ │ ├── MemberCard.vue │ │ │ └── UserSearchModal.vue │ │ ├── layout.vue │ │ ├── open-group.vue │ │ └── organize.vue │ ├── example │ │ ├── index.vue │ │ └── svg.svg │ ├── groupnotice │ │ ├── columns.ts │ │ └── index.vue │ ├── iframe │ │ └── index.vue │ ├── keyword │ │ ├── columns.ts │ │ └── index.vue │ ├── message │ │ ├── index.vue │ │ └── inner │ │ │ ├── IndexAmicable.vue │ │ │ ├── IndexContent.vue │ │ │ ├── IndexSider.vue │ │ │ ├── Skeleton.vue │ │ │ ├── TalkItem.vue │ │ │ └── panel │ │ │ ├── MultiSelectFooter.vue │ │ │ ├── PanelContent.vue │ │ │ ├── PanelFooter.vue │ │ │ ├── PanelHeader.vue │ │ │ ├── SkipBottom.vue │ │ │ └── menu.ts │ ├── note │ │ ├── index.vue │ │ └── inner │ │ │ ├── AnnexUploadModal.vue │ │ │ ├── NoteEmpty.vue │ │ │ ├── NoteList.vue │ │ │ ├── NoteMenu.vue │ │ │ ├── NoteView.vue │ │ │ ├── RecycleModal.vue │ │ │ └── TagsClipModal.vue │ ├── notice │ │ ├── columns.ts │ │ └── index.vue │ ├── other │ │ └── not-found.vue │ ├── plugin │ │ ├── columns.ts │ │ ├── index.vue │ │ └── inner │ │ │ ├── AnnexUploadModal.vue │ │ │ ├── NoteEmpty.vue │ │ │ ├── NoteList.vue │ │ │ ├── NoteMenu.vue │ │ │ ├── NoteView.vue │ │ │ ├── RecycleModal.vue │ │ │ └── TagsClipModal.vue │ ├── qa │ │ ├── CellColumns.ts │ │ ├── basic.vue │ │ ├── basicColumns.ts │ │ ├── columns.ts │ │ ├── columnsQa.ts │ │ ├── detail.vue │ │ ├── editCell.vue │ │ ├── editRow.vue │ │ ├── index.vue │ │ ├── layout.vue │ │ └── white.vue │ ├── setting │ │ ├── binding.vue │ │ ├── config.vue │ │ ├── detail.vue │ │ ├── layout.vue │ │ ├── notification.vue │ │ ├── personalize.vue │ │ └── security.vue │ ├── statistic │ │ ├── binding.vue │ │ ├── columns.ts │ │ ├── config.vue │ │ ├── detail.vue │ │ ├── layout.vue │ │ ├── notification.vue │ │ ├── order.vue │ │ ├── personalize.vue │ │ ├── security.vue │ │ ├── statistic.vue │ │ └── white.vue │ ├── whitelist │ │ ├── binding.vue │ │ ├── black.vue │ │ ├── columns.ts │ │ ├── config.vue │ │ ├── detail.vue │ │ ├── layout.vue │ │ ├── notification.vue │ │ ├── personalize.vue │ │ ├── security.vue │ │ └── white.vue │ └── workplace │ │ └── workplace.vue └── vue-shim.d.ts ├── tailwind.config.js ├── tsconfig.json ├── vite-env.d.ts ├── vite.config.ts └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | ENV = 'development' 2 | 3 | VITE_BASE=/ 4 | VUE_APP_PREVIEW=false 5 | VITE_BASE_API=https://chat.vlist.cc 6 | # VITE_BASE_API=http://127.0.0.1:9503 7 | VITE_SOCKET_API=wss://broker.emqx.io:8084 8 | VUE_APP_WEBSITE_NAME="ChatFlow" -------------------------------------------------------------------------------- /.env.electron: -------------------------------------------------------------------------------- 1 | ENV = 'production' 2 | 3 | VITE_BASE=./ 4 | VITE_ROUTER_MODE=hash 5 | VITE_BASE_API=https://chat.vlist.cc 6 | VITE_SOCKET_API=wss://broker.emqx.io:8084 7 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'production' 3 | 4 | VITE_BASE=/ 5 | VITE_ROUTER_MODE=history 6 | VITE_BASE_API=https://chat.vlist.cc 7 | VITE_SOCKET_API=wss://broker.emqx.io:8084 -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | env: { 6 | node: true // 只需将该项设置为 true 即可 7 | }, 8 | root: true, 9 | 'extends': [ 10 | 'plugin:vue/vue3-essential', 11 | 'eslint:recommended', 12 | '@vue/eslint-config-typescript', 13 | '@vue/eslint-config-prettier/skip-formatting' 14 | ], 15 | parserOptions: { 16 | ecmaVersion: 'latest' 17 | }, 18 | rules: { 19 | 'vue/multi-word-component-names': 'off', 20 | '@typescript-eslint/no-unused-vars': 'off', 21 | "no-unused-vars":"off" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/Build-and-Deploy-to-GitHub-Pages.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main # 触发构建的分支 7 | workflow_dispatch: 8 | 9 | permissions: # 添加权限配置 10 | contents: read 11 | id-token: write 12 | pages: write 13 | 14 | jobs: 15 | build-and-deploy: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | persist-credentials: false 21 | 22 | - name: Set up Node.js 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: '16.x' 26 | cache: 'npm' 27 | cache-dependency-path: '**/package-lock.json' 28 | 29 | - name: Install Dependencies 30 | run: npm install 31 | 32 | - name: Build 33 | run: npm run build 34 | 35 | # 推送构建内容到 gh-pages 分支 36 | - name: Deploy to GitHub Pages 37 | uses: peaceiris/actions-gh-pages@v3 38 | with: 39 | github_token: ${{ secrets.GIT_TOKEN }} 40 | publish_dir: ./dist 41 | 42 | # 在 gh-pages 分支上触发 GitHub Pages 部署 43 | deploy-to-pages: 44 | runs-on: ubuntu-latest 45 | needs: build-and-deploy # 确保在构建完成后执行 46 | steps: 47 | - name: Checkout gh-pages branch 48 | uses: actions/checkout@v3 49 | with: 50 | ref: 'gh-pages' 51 | 52 | - name: Setup Pages 53 | uses: actions/configure-pages@v3 54 | 55 | - name: Upload artifact 56 | uses: actions/upload-pages-artifact@v2 57 | with: 58 | path: '.' 59 | 60 | - name: Deploy to GitHub Pages 61 | uses: actions/deploy-pages@v2 62 | -------------------------------------------------------------------------------- /.github/workflows/Build-and-Deploy-to-Remote-Server.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy to Remote Server 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build-and-deploy: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Node.js 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: '16.x' 20 | cache: 'npm' 21 | cache-dependency-path: '**/package-lock.json' 22 | 23 | - name: Install Dependencies 24 | run: npm install 25 | 26 | - name: Build 27 | run: npm run build 28 | 29 | # 使用用户名和密码连接 SSH 30 | - name: Deploy dist to Remote Server 31 | env: 32 | SSH_HOST: ${{ secrets.SSH_HOST }} 33 | SSH_USERNAME: ${{ secrets.SSH_USERNAME }} 34 | SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} 35 | run: | 36 | sudo apt-get update 37 | sudo apt-get install -y sshpass 38 | sshpass -p $SSH_PASSWORD scp -o StrictHostKeyChecking=no -r ./dist/* $SSH_USERNAME@$SSH_HOST:/etc/nginx/html/ 39 | -------------------------------------------------------------------------------- /.github/workflows/Build-and-Deploy-to-S3.yml: -------------------------------------------------------------------------------- 1 | # 编译的静态文件部署到S3存储 2 | name: Node.js Build and Deploy to S3 3 | 4 | on: 5 | create: 6 | tags: 7 | - '*' 8 | 9 | jobs: 10 | build-and-deploy: 11 | runs-on: ubuntu-latest 12 | environment: 13 | name: github-pages 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Set up Node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: '16.x' 22 | 23 | - name: Install Dependencies 24 | run: npm install 25 | 26 | - name: Build 27 | run: npm run build 28 | 29 | - name: Deploy to S3 30 | uses: jakejarvis/s3-sync-action@v0.5.1 31 | with: 32 | args: --acl public-read --follow-symlinks --delete --endpoint-url ${{ secrets.CUSTOM_S3_ENDPOINT }} 33 | env: 34 | AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} 35 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 36 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 37 | AWS_REGION: ${{ secrets.AWS_REGION }} # Or your AWS region 38 | SOURCE_DIR: 'dist' 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist_electron 13 | dist-ssr 14 | *.local 15 | makefile 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "semi": false, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "printWidth": 100, 7 | "trailingComma": "none" 8 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 第一阶段:使用 Node.js 构建项目 2 | FROM node:latest as builder 3 | 4 | # 设置工作目录 5 | WORKDIR /app 6 | 7 | # 复制 package.json 和 package-lock.json(如果存在) 8 | COPY package*.json ./ 9 | 10 | # 安装项目依赖 11 | RUN npm install 12 | 13 | # 复制项目文件 14 | COPY . . 15 | 16 | # 构建项目 17 | RUN npm run build 18 | 19 | # 第二阶段:设置 Nginx 20 | FROM nginx:alpine 21 | 22 | # 从 builder 阶段复制构建出的 dist 目录 23 | COPY --from=builder /app/dist /usr/share/nginx/html 24 | 25 | # 创建一个自定义的 Nginx 配置文件 26 | RUN echo 'server {\ 27 | listen 80;\ 28 | server_name localhost;\ 29 | \ 30 | location / {\ 31 | root /usr/share/nginx/html;\ 32 | index index.html index.htm;\ 33 | try_files $uri $uri/ /index.html;\ 34 | }\ 35 | \ 36 | location /api/v1 {\ 37 | proxy_pass http://127.0.0.1:9503;\ 38 | }\ 39 | }' > /etc/nginx/conf.d/default.conf 40 | 41 | # 暴露 80 端口 42 | EXPOSE 80 43 | 44 | # 启动 Nginx 45 | CMD ["nginx", "-g", "daemon off;"] 46 | -------------------------------------------------------------------------------- /build/getConfigFileName.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the configuration file variable name 3 | * @param env 4 | */ 5 | export const getConfigFileName = (env: Record) => { 6 | return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__` 7 | .toUpperCase() 8 | .replace(/\s/g, ''); 9 | }; 10 | -------------------------------------------------------------------------------- /build/icons/lumen-im-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/build/icons/lumen-im-mac.png -------------------------------------------------------------------------------- /build/icons/lumen-im-win.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/build/icons/lumen-im-win.ico -------------------------------------------------------------------------------- /build/icons/lumenim.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/build/icons/lumenim.icns -------------------------------------------------------------------------------- /build/icons/lumenim.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/build/icons/lumenim.ico -------------------------------------------------------------------------------- /build/icons/lumenim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/build/icons/lumenim.png -------------------------------------------------------------------------------- /electron/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require('electron') 2 | 3 | // 暴露方法给渲染进程调用 4 | contextBridge.exposeInMainWorld('electron', { 5 | // 设置消息未读数 6 | setBadge: num => { 7 | ipcRenderer.send('ipc:set-badge', num == 0 ? '' : `${num}`) 8 | }, 9 | // 获取窗口全屏状态 10 | getFullScreenStatus: () => { 11 | return ipcRenderer.sendSync('get-full-screen', '') 12 | }, 13 | // 系统信息 14 | getAppPlatform: () => { 15 | return ipcRenderer.sendSync('app-info', '') 16 | }, 17 | 18 | openLink: link => { 19 | ipcRenderer.send('ipc:open-link', link) 20 | }, 21 | }) 22 | 23 | // 窗口变化事件 24 | ipcRenderer.on('full-screen', function (event, value) { 25 | // isFullScreenStatus = value == 'enter' 26 | 27 | document.dispatchEvent( 28 | new CustomEvent('full-screen-event', { detail: value }) 29 | ) 30 | }) 31 | 32 | // 触发自定义事件 33 | // document.dispatchEvent(new CustomEvent('myTestEvent', {num: i})) 34 | // document.addEventListener('myTestEvent', e => {console.log(e)}) 35 | // 所有Node.js API都可以在预加载过程中使用。 36 | // 它拥有与Chrome扩展一样的沙盒。 37 | window.addEventListener('DOMContentLoaded', () => { 38 | const replaceText = (selector, text) => { 39 | const element = document.getElementById(selector) 40 | if (element) element.innerText = text 41 | } 42 | 43 | for (const dependency of ['chrome', 'node', 'electron']) { 44 | replaceText(`${dependency}-version`, process.versions[dependency]) 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare module '*.vue' { 3 | import { ComponentOptions } from 'vue' 4 | const componentOptions: ComponentOptions 5 | export default componentOptions 6 | } 7 | 8 | declare module 'quill-image-uploader' -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/public/favicon.ico -------------------------------------------------------------------------------- /src/api/auth.js: -------------------------------------------------------------------------------- 1 | // 授权相关接口 2 | import { post } from '@/utils/request' 3 | 4 | // 登录服务接口 5 | export const ServeLogin = (data) => { 6 | return post('/api/v1/auth/login', data) 7 | } 8 | 9 | // 注册服务接口 10 | export const ServeRegister = (data) => { 11 | return post('/api/v1/auth/register', data) 12 | } 13 | 14 | // 退出登录服务接口 15 | export const ServeLogout = (data) => { 16 | return post('/api/v1/auth/logout', data) 17 | } 18 | 19 | // 刷新登录Token服务接口 20 | export const ServeRefreshToken = () => { 21 | return post('/api/v1/auth/refresh-token') 22 | } 23 | 24 | // 找回密码服务 25 | export const ServeForgetPassword = (data) => { 26 | return post('/api/v1/auth/forget', data) 27 | } 28 | -------------------------------------------------------------------------------- /src/api/chatbot.ts: -------------------------------------------------------------------------------- 1 | import { get,post } from '@/utils/request' 2 | 3 | // 获取聊天机器人列表服务接口 4 | export const ServeGetChatbots = (data) => { 5 | return get('/api/v1/chatbot/list', data) 6 | } 7 | 8 | // 创建聊天机器人服务接口 9 | export const ServeCreateChatbots = (data) => { 10 | return post('/api/v1/chatbot/create', data) 11 | } 12 | 13 | // 删除聊天机器人服务接口 14 | export const ServeDeleteChatbots = (data) => { 15 | return post('/api/v1/chatbot/delete', data) 16 | } 17 | 18 | // 获取机器人用户列表服务接口 19 | export const ServeGetChatbotUsers = (data) => { 20 | return get('/api/v1/chatbot/user/list', data) 21 | } 22 | 23 | // 创建机器人用户服务接口 24 | export const ServeCreateChatbotUsers = (data) => { 25 | return post('/api/v1/chatbot/user/create', data) 26 | } 27 | 28 | // 删除机器人用户服务接口 29 | export const ServeDeleteChatbotUsers = (data) => { 30 | return post('/api/v1/chatbot/user/delete', data) 31 | } 32 | -------------------------------------------------------------------------------- /src/api/common.js: -------------------------------------------------------------------------------- 1 | import { post } from '@/utils/request' 2 | 3 | // 发送找回密码验证码 4 | export const ServeSendVerifyCode = (data) => { 5 | return post('/api/v1/common/sms-code', data) 6 | } 7 | 8 | // 发送邮箱验证码服务接口 9 | export const ServeSendEmailCode = (data) => { 10 | return post('/api/v1/common/email-code', data) 11 | } 12 | -------------------------------------------------------------------------------- /src/api/emoticon.js: -------------------------------------------------------------------------------- 1 | import { post, get, upload } from '@/utils/request' 2 | 3 | // 查询用户表情包服务接口 4 | export const ServeFindUserEmoticon = () => { 5 | return get('/api/v1/emoticon/list') 6 | } 7 | 8 | // 查询系统表情包服务接口 9 | export const ServeFindSysEmoticon = () => { 10 | return get('/api/v1/emoticon/system/list') 11 | } 12 | 13 | // 设置用户表情包服务接口 14 | export const ServeSetUserEmoticon = (data) => { 15 | return post('/api/v1/emoticon/system/install', data) 16 | } 17 | 18 | // 移除收藏表情包服务接口 19 | export const ServeDelCollectEmoticon = (data) => { 20 | return post('/api/v1/emoticon/del-collect-emoticon', data) 21 | } 22 | 23 | // 上传表情包服务接口 24 | export const ServeUploadEmoticon = (data) => { 25 | return upload('/api/v1/emoticon/customize/create', data) 26 | } 27 | 28 | export const ServeDeleteEmoticon = (data) => { 29 | return upload('/api/v1/emoticon/customize/delete', data) 30 | } 31 | -------------------------------------------------------------------------------- /src/api/groupnotice.ts: -------------------------------------------------------------------------------- 1 | import { get, post } from '@/utils/request' 2 | 3 | // 获取群发列表服务接口 4 | export const ServeGetGroupnotices = (data) => { 5 | return get('/api/v1/groupnotice/list', data) 6 | } 7 | 8 | // 创建群发任务列表服务接口 9 | export const ServeCreateGroupnotices = (data) => { 10 | return post('/api/v1/groupnotice/create', data) 11 | } 12 | 13 | // 删除群发任务列表服务接口 14 | export const ServeDeleteGroupnotices = (data) => { 15 | return post('/api/v1/groupnotice/delete', data) 16 | } -------------------------------------------------------------------------------- /src/api/keyword.ts: -------------------------------------------------------------------------------- 1 | import { get, post } from '@/utils/request' 2 | 3 | // 获取关键词列表服务接口 4 | export const ServeGetKeywords = (data) => { 5 | return get('/api/v1/keyword/list', data) 6 | } 7 | 8 | // 创建关键词服务接口 9 | export const ServeCreateKeywords = (data) => { 10 | return post('/api/v1/keyword/create', data) 11 | } 12 | 13 | // 删除关键词服务接口 14 | export const ServeDeleteKeywords = (data) => { 15 | return post('/api/v1/keyword/delete', data) 16 | } -------------------------------------------------------------------------------- /src/api/notice.ts: -------------------------------------------------------------------------------- 1 | import { get, post } from '@/utils/request' 2 | 3 | // 获取定时任务列表服务接口 4 | export const ServeGetNotices = (data) => { 5 | return get('/api/v1/notice/list', data) 6 | } 7 | 8 | // 创建定时任务服务接口 9 | export const ServeCreateNotices = (data) => { 10 | return post('/api/v1/notice/create', data) 11 | } 12 | 13 | // 删除定时任务服务接口 14 | export const ServeDeleteNotices = (data) => { 15 | return post('/api/v1/notice/delete', data) 16 | } -------------------------------------------------------------------------------- /src/api/order.ts: -------------------------------------------------------------------------------- 1 | import { get, post } from '@/utils/request' 2 | 3 | // 获取订单列表服务接口 4 | export const ServeGetOrders = (data) => { 5 | return get('/api/v1/order/list', data) 6 | } 7 | 8 | // 创建订单服务接口 9 | export const ServeCreateOrders = (data) => { 10 | return post('/api/v1/order/create', data) 11 | } 12 | 13 | // 删除订单服务接口 14 | export const ServeDeleteOrders = (data) => { 15 | return post('/api/v1/order/delete', data) 16 | } -------------------------------------------------------------------------------- /src/api/organize.js: -------------------------------------------------------------------------------- 1 | import { get } from '@/utils/request' 2 | 3 | export const ServeDepartmentList = () => { 4 | return get('/api/v1/organize/department/all') 5 | } 6 | 7 | export const ServePersonnelList = () => { 8 | return get('/api/v1/organize/personnel/all') 9 | } 10 | 11 | export const ServeCheckQiyeMember = () => { 12 | return get('/api/v1/organize/member/check') 13 | } 14 | -------------------------------------------------------------------------------- /src/api/qa.ts: -------------------------------------------------------------------------------- 1 | import { get, post } from '@/utils/request' 2 | 3 | // 获取问答列表服务接口 4 | export const ServeGetQas = (data) => { 5 | return get('/api/v1/qa/list', data) 6 | } 7 | 8 | // 创建问答服务接口 9 | export const ServeCreateQas = (data) => { 10 | return post('/api/v1/qa/create', data) 11 | } 12 | 13 | // 删除问答服务接口 14 | export const ServeDeleteQas = (data) => { 15 | return post('/api/v1/qa/delete', data) 16 | } 17 | -------------------------------------------------------------------------------- /src/api/statistic.ts: -------------------------------------------------------------------------------- 1 | import { get, post } from '@/utils/request' 2 | 3 | // 获取好友列表服务接口 4 | export const ServeGetStatistics = (data) => { 5 | return get('/api/v1/statistic/list', data) 6 | } 7 | 8 | // 创建好友服务接口 9 | export const ServeCreateStatistics = (data) => { 10 | return post('/api/v1/statistic/create', data) 11 | } 12 | 13 | // 删除好友服务接口 14 | export const ServeDeleteStatistics = (data) => { 15 | return post('/api/v1/statistic/delete', data) 16 | } -------------------------------------------------------------------------------- /src/api/upload.js: -------------------------------------------------------------------------------- 1 | import { post, upload } from '@/utils/request' 2 | 3 | // 上传头像裁剪图片服务接口 4 | export const ServeUploadAvatar = (data) => { 5 | return post('/api/v1/upload/avatar', data) 6 | } 7 | 8 | // 上传头像裁剪图片服务接口 9 | export const ServeUploadImage = (data) => { 10 | console.log(data) 11 | return post('/api/v1/upload/image', data) 12 | } 13 | 14 | // 查询大文件拆分信息服务接口 15 | export const ServeFindFileSplitInfo = (data = {}) => { 16 | return post('/api/v1/upload/multipart/initiate', data) 17 | } 18 | 19 | // 文件拆分上传服务接口 20 | export const ServeFileSubareaUpload = (data = {}, options = {}) => { 21 | return upload('/api/v1/upload/multipart', data, options) 22 | } 23 | -------------------------------------------------------------------------------- /src/api/whitelist.ts: -------------------------------------------------------------------------------- 1 | import { get, post } from '@/utils/request' 2 | 3 | // 获取白名单列表服务接口 4 | export const ServeGetWhitelistWhite = (data) => { 5 | return get('/api/v1/whitelist/list/white', data) 6 | } 7 | 8 | // 创建黑名单列表服务接口 9 | export const ServeCreateWhitelistWhite = (data) => { 10 | return post('/api/v1/whitelist/list/white/create', data) 11 | } 12 | 13 | // 删除黑名单列表服务接口 14 | export const ServeDeleteWhitelistWhite = (data) => { 15 | return post('/api/v1/whitelist/list/white/delete', data) 16 | } 17 | 18 | // 获取黑名单列表服务接口 19 | export const ServeGetWhitelistBlack = (data) => { 20 | return get('/api/v1/whitelist/list/black', data) 21 | } -------------------------------------------------------------------------------- /src/assets/css/contact.less: -------------------------------------------------------------------------------- 1 | .title { 2 | height: 60px; 3 | line-height: 60px; 4 | padding-left: 15px; 5 | color: rgba(0, 0, 0, 0.85); 6 | font-size: 20px; 7 | font-weight: 500; 8 | border-bottom: 1px solid var(--border-color); 9 | } 10 | 11 | .view-box { 12 | padding: 15px; 13 | padding-top: 0; 14 | margin-top: 15px; 15 | 16 | .view-list { 17 | height: 60px; 18 | margin: 5px 0; 19 | padding: 5px; 20 | display: flex; 21 | padding-left: 0; 22 | border: 1px solid transparent; 23 | transition: padding 0.5s ease-in-out; 24 | 25 | &:hover, 26 | &.selectd { 27 | border-radius: 2px; 28 | padding: 5px 10px; 29 | border: 1px solid rgb(80 138 254); 30 | cursor: pointer; 31 | } 32 | 33 | &:first-child { 34 | margin-top: 0; 35 | } 36 | 37 | .image { 38 | width: 60px; 39 | margin-right: 5px; 40 | justify-content: flex-start; 41 | } 42 | 43 | .content { 44 | flex: auto; 45 | 46 | .name { 47 | color: rgba(0, 0, 0, 0.65); 48 | font-size: 15px; 49 | height: 30px; 50 | line-height: 30px; 51 | font-weight: 500; 52 | } 53 | 54 | .desc { 55 | height: 30px; 56 | line-height: 30px; 57 | color: rgba(0, 0, 0, 0.45); 58 | font-size: 14px; 59 | } 60 | } 61 | 62 | .tools { 63 | width: 100px; 64 | display: flex; 65 | align-items: center; 66 | justify-content: flex-end; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/assets/css/dropsize.less: -------------------------------------------------------------------------------- 1 | .dropsize-col-resize { 2 | cursor: col-resize !important; 3 | } 4 | 5 | .dropsize-row-resize { 6 | cursor: row-resize !important; 7 | } 8 | 9 | .dropsize-line { 10 | position: absolute; 11 | cursor: col-resize; 12 | 13 | &:hover, 14 | &.dropsize-resizing { 15 | background-color: #1890ff; 16 | } 17 | 18 | &.dropsize-line-top { 19 | top: 0; 20 | left: 0; 21 | height: 2px; 22 | width: 100%; 23 | cursor: row-resize; 24 | } 25 | 26 | &.dropsize-line-bottom { 27 | bottom: 0; 28 | left: 0; 29 | height: 2px; 30 | width: 100%; 31 | cursor: row-resize; 32 | } 33 | 34 | &.dropsize-line-left { 35 | left: 0; 36 | top: 0; 37 | width: 2px; 38 | height: 100%; 39 | } 40 | 41 | &.dropsize-line-right { 42 | right: 0; 43 | top: 0; 44 | width: 2px; 45 | height: 100%; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/assets/css/settting.less: -------------------------------------------------------------------------------- 1 | .title { 2 | height: 60px; 3 | line-height: 60px; 4 | padding-left: 15px; 5 | color: var(--im-text-color); 6 | font-size: 20px; 7 | font-weight: 500; 8 | border-bottom: 1px solid var(--border-color); 9 | } 10 | 11 | .view-box { 12 | padding: 15px; 13 | padding-top: 0; 14 | 15 | .view-list { 16 | height: 70px; 17 | margin: 5px 0; 18 | border-bottom: 1px solid var(--border-color); 19 | padding: 5px; 20 | display: flex; 21 | padding-left: 0; 22 | 23 | &:first-child { 24 | margin-top: 0; 25 | } 26 | 27 | .image { 28 | width: 80px; 29 | margin-right: 5px; 30 | } 31 | 32 | .content { 33 | flex: auto; 34 | color: var(--im-text-color); 35 | 36 | .name { 37 | font-size: 15px; 38 | height: 40px; 39 | line-height: 40px; 40 | font-weight: 500; 41 | } 42 | 43 | .desc { 44 | height: 30px; 45 | line-height: 30px; 46 | font-size: 14px; 47 | color: #989898; 48 | } 49 | } 50 | 51 | .tools { 52 | width: 120px; 53 | display: flex; 54 | align-items: center; 55 | justify-content: flex-end; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/assets/css/tailwind.css: -------------------------------------------------------------------------------- 1 | /*! @import */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | -------------------------------------------------------------------------------- /src/assets/echo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/echo.png -------------------------------------------------------------------------------- /src/assets/fonts/AlibabaPuHuiTi/AlibabaPuHuiTi_2_45_Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/fonts/AlibabaPuHuiTi/AlibabaPuHuiTi_2_45_Light.woff -------------------------------------------------------------------------------- /src/assets/fonts/AlibabaPuHuiTi/AlibabaPuHuiTi_2_45_Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/fonts/AlibabaPuHuiTi/AlibabaPuHuiTi_2_45_Light.woff2 -------------------------------------------------------------------------------- /src/assets/image/0A039CDF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/image/0A039CDF.png -------------------------------------------------------------------------------- /src/assets/image/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/image/avatar.png -------------------------------------------------------------------------------- /src/assets/image/banners.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/image/banners.webp -------------------------------------------------------------------------------- /src/assets/image/default-avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/image/default-avatar.jpg -------------------------------------------------------------------------------- /src/assets/image/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/image/favicon.png -------------------------------------------------------------------------------- /src/assets/image/gitee-avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/image/gitee-avatar.jpg -------------------------------------------------------------------------------- /src/assets/image/github-avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/image/github-avatar.jpg -------------------------------------------------------------------------------- /src/assets/image/iTab-exSKJMg-_vI.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/image/iTab-exSKJMg-_vI.jpeg -------------------------------------------------------------------------------- /src/assets/image/iTab-ympj97.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/image/iTab-ympj97.png -------------------------------------------------------------------------------- /src/assets/image/md.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/image/no-data.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/image/notify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/image/notify.png -------------------------------------------------------------------------------- /src/assets/image/schoolboy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/image/schoolboy.png -------------------------------------------------------------------------------- /src/assets/images/account-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/images/account-logo.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/images/nav-horizontal-mix.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/images/schoolboy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/images/schoolboy.png -------------------------------------------------------------------------------- /src/assets/images/tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/images/tool.png -------------------------------------------------------------------------------- /src/assets/music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/assets/music.mp3 -------------------------------------------------------------------------------- /src/components/Application/Application.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 27 | -------------------------------------------------------------------------------- /src/components/Application/index.ts: -------------------------------------------------------------------------------- 1 | import AppProvider from './Application.vue'; 2 | 3 | export { AppProvider }; 4 | -------------------------------------------------------------------------------- /src/components/Chat/More.vue: -------------------------------------------------------------------------------- 1 | 2 | 21 | 42 | -------------------------------------------------------------------------------- /src/components/CountTo/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | import countTo from './CountTo.vue'; 3 | 4 | export const CountTo = withInstall(countTo); 5 | -------------------------------------------------------------------------------- /src/components/Form/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicForm } from './src/BasicForm.vue'; 2 | export { useForm } from './src/hooks/useForm'; 3 | export * from './src/types/form'; 4 | export * from './src/types/index'; 5 | -------------------------------------------------------------------------------- /src/components/Form/src/helper.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from './types/index'; 2 | 3 | /** 4 | * @description: 生成placeholder 5 | */ 6 | export function createPlaceholderMessage(component: ComponentType) { 7 | if (component === 'NInput') return '请输入'; 8 | if ( 9 | ['NPicker', 'NSelect', 'NCheckbox', 'NRadio', 'NSwitch', 'NDatePicker', 'NTimePicker'].includes( 10 | component 11 | ) 12 | ) 13 | return '请选择'; 14 | return ''; 15 | } 16 | 17 | const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker']; 18 | 19 | function genType() { 20 | return [...DATE_TYPE, 'RangePicker']; 21 | } 22 | 23 | /** 24 | * 时间字段 25 | */ 26 | export const dateItemType = genType(); 27 | 28 | export function defaultType(component) { 29 | if (component === 'NInput') return ''; 30 | if (component === 'NInputNumber') return null; 31 | return [ 32 | 'NPicker', 33 | 'NSelect', 34 | 'NCheckbox', 35 | 'NRadio', 36 | 'NSwitch', 37 | 'NDatePicker', 38 | 'NTimePicker', 39 | ].includes(component) 40 | ? '' 41 | : undefined; 42 | } 43 | -------------------------------------------------------------------------------- /src/components/Form/src/hooks/useFormContext.ts: -------------------------------------------------------------------------------- 1 | import { provide, inject } from 'vue'; 2 | 3 | const key = Symbol('formElRef'); 4 | 5 | export function createFormContext(instance) { 6 | provide(key, instance); 7 | } 8 | 9 | export function useFormContext() { 10 | return inject(key); 11 | } 12 | -------------------------------------------------------------------------------- /src/components/Form/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type ComponentType = 2 | | 'NInput' 3 | | 'NInputGroup' 4 | | 'NInputPassword' 5 | | 'NInputSearch' 6 | | 'NInputTextArea' 7 | | 'NInputNumber' 8 | | 'NInputCountDown' 9 | | 'NSelect' 10 | | 'NTreeSelect' 11 | | 'NRadioButtonGroup' 12 | | 'NRadioGroup' 13 | | 'NCheckbox' 14 | | 'NCheckboxGroup' 15 | | 'NAutoComplete' 16 | | 'NCascader' 17 | | 'NDatePicker' 18 | | 'NMonthPicker' 19 | | 'NRangePicker' 20 | | 'NWeekPicker' 21 | | 'NTimePicker' 22 | | 'NSwitch' 23 | | 'NStrengthMeter' 24 | | 'NUpload' 25 | | 'NIconPicker' 26 | | 'NRender' 27 | | 'NSlider' 28 | | 'NRate'; 29 | -------------------------------------------------------------------------------- /src/components/Lockscreen/index.ts: -------------------------------------------------------------------------------- 1 | import LockScreen from './Lockscreen.vue'; 2 | 3 | export { LockScreen }; 4 | -------------------------------------------------------------------------------- /src/components/Modal/index.ts: -------------------------------------------------------------------------------- 1 | export { default as basicModal } from './src/basicModal.vue'; 2 | export { useModal } from './src/hooks/useModal'; 3 | export * from './src/type'; 4 | -------------------------------------------------------------------------------- /src/components/Modal/src/hooks/useModal.ts: -------------------------------------------------------------------------------- 1 | import { ref, unref, getCurrentInstance, watch } from 'vue'; 2 | import { isProdMode } from '@/utils/env'; 3 | import { ModalMethods, UseModalReturnType } from '../type'; 4 | import { getDynamicProps } from '@/utils'; 5 | import { tryOnUnmounted } from '@vueuse/core'; 6 | export function useModal(props): UseModalReturnType { 7 | const modalRef = ref>(null); 8 | const currentInstance = getCurrentInstance(); 9 | 10 | const getInstance = () => { 11 | const instance = unref(modalRef.value); 12 | if (!instance) { 13 | console.error('useModal instance is undefined!'); 14 | } 15 | return instance; 16 | }; 17 | 18 | const register = (modalInstance: ModalMethods) => { 19 | isProdMode() && 20 | tryOnUnmounted(() => { 21 | modalRef.value = null; 22 | }); 23 | modalRef.value = modalInstance; 24 | currentInstance?.emit('register', modalInstance); 25 | 26 | watch( 27 | () => props, 28 | () => { 29 | props && modalInstance.setProps(getDynamicProps(props)); 30 | }, 31 | { 32 | immediate: true, 33 | deep: true, 34 | } 35 | ); 36 | }; 37 | 38 | const methods: ModalMethods = { 39 | setProps: (props): void => { 40 | getInstance()?.setProps(props); 41 | }, 42 | openModal: () => { 43 | getInstance()?.openModal(); 44 | }, 45 | closeModal: () => { 46 | getInstance()?.closeModal(); 47 | }, 48 | setSubLoading: (status) => { 49 | getInstance()?.setSubLoading(status); 50 | }, 51 | }; 52 | 53 | return [register, methods]; 54 | } 55 | -------------------------------------------------------------------------------- /src/components/Modal/src/props.ts: -------------------------------------------------------------------------------- 1 | import { NModal } from 'naive-ui'; 2 | 3 | export const basicProps = { 4 | ...NModal.props, 5 | // 确认按钮文字 6 | subBtuText: { 7 | type: String, 8 | default: '确认', 9 | }, 10 | showIcon: { 11 | type: Boolean, 12 | default: false, 13 | }, 14 | width: { 15 | type: Number, 16 | default: 446, 17 | }, 18 | title: { 19 | type: String, 20 | default: '', 21 | }, 22 | maskClosable: { 23 | type: Boolean, 24 | default: false, 25 | }, 26 | preset: { 27 | type: String, 28 | default: 'dialog', 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/Modal/src/type/index.ts: -------------------------------------------------------------------------------- 1 | import type { DialogOptions } from 'naive-ui/lib/dialog'; 2 | /** 3 | * @description: 弹窗对外暴露的方法 4 | */ 5 | export interface ModalMethods { 6 | setProps: (props) => void; 7 | openModal: () => void; 8 | closeModal: () => void; 9 | setSubLoading: (status) => void; 10 | } 11 | 12 | /** 13 | * 支持修改,DialogOptions 參數 14 | */ 15 | export type ModalProps = DialogOptions; 16 | 17 | export type RegisterFn = (ModalInstance: ModalMethods) => void; 18 | 19 | export type UseModalReturnType = [RegisterFn, ModalMethods]; 20 | -------------------------------------------------------------------------------- /src/components/Table/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicTable } from './src/Table.vue'; 2 | export { default as TableAction } from './src/components/TableAction.vue'; 3 | export * from './src/types/table'; 4 | export * from './src/types/tableAction'; 5 | -------------------------------------------------------------------------------- /src/components/Table/src/componentMap.ts: -------------------------------------------------------------------------------- 1 | import type { Component } from 'vue'; 2 | import { 3 | NInput, 4 | NSelect, 5 | NCheckbox, 6 | NInputNumber, 7 | NSwitch, 8 | NDatePicker, 9 | NTimePicker, 10 | } from 'naive-ui'; 11 | import type { ComponentType } from './types/componentType'; 12 | 13 | export enum EventEnum { 14 | NInput = 'on-input', 15 | NInputNumber = 'on-input', 16 | NSelect = 'on-update:value', 17 | NSwitch = 'on-update:value', 18 | NCheckbox = 'on-update:value', 19 | NDatePicker = 'on-update:value', 20 | NTimePicker = 'on-update:value', 21 | } 22 | 23 | const componentMap = new Map(); 24 | 25 | componentMap.set('NInput', NInput); 26 | componentMap.set('NInputNumber', NInputNumber); 27 | componentMap.set('NSelect', NSelect); 28 | componentMap.set('NSwitch', NSwitch); 29 | componentMap.set('NCheckbox', NCheckbox); 30 | componentMap.set('NDatePicker', NDatePicker); 31 | componentMap.set('NTimePicker', NTimePicker); 32 | 33 | export function add(compName: ComponentType, component: Component) { 34 | componentMap.set(compName, component); 35 | } 36 | 37 | export function del(compName: ComponentType) { 38 | componentMap.delete(compName); 39 | } 40 | 41 | export { componentMap }; 42 | -------------------------------------------------------------------------------- /src/components/Table/src/components/editable/CellComponent.ts: -------------------------------------------------------------------------------- 1 | import type { FunctionalComponent, defineComponent } from 'vue'; 2 | import type { ComponentType } from '../../types/componentType'; 3 | import { componentMap } from '@/components/Table/src/componentMap'; 4 | 5 | import { h } from 'vue'; 6 | 7 | import { NPopover } from 'naive-ui'; 8 | 9 | export interface ComponentProps { 10 | component: ComponentType; 11 | rule: boolean; 12 | popoverVisible: boolean; 13 | ruleMessage: string; 14 | } 15 | 16 | export const CellComponent: FunctionalComponent = ( 17 | { component = 'NInput', rule = true, ruleMessage, popoverVisible }: ComponentProps, 18 | { attrs } 19 | ) => { 20 | const Comp = componentMap.get(component) as typeof defineComponent; 21 | 22 | const DefaultComp = h(Comp, attrs); 23 | if (!rule) { 24 | return DefaultComp; 25 | } 26 | return h( 27 | NPopover, 28 | { 'display-directive': 'show', show: !!popoverVisible, manual: 'manual' }, 29 | { 30 | trigger: () => DefaultComp, 31 | default: () => 32 | h( 33 | 'span', 34 | { 35 | style: { 36 | color: 'red', 37 | width: '90px', 38 | display: 'inline-block', 39 | }, 40 | }, 41 | { 42 | default: () => ruleMessage, 43 | } 44 | ), 45 | } 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /src/components/Table/src/components/editable/helper.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from '../../types/componentType'; 2 | 3 | /** 4 | * @description: 生成placeholder 5 | */ 6 | export function createPlaceholderMessage(component: ComponentType) { 7 | if (component === 'NInput') return '请输入'; 8 | if ( 9 | ['NPicker', 'NSelect', 'NCheckbox', 'NRadio', 'NSwitch', 'NDatePicker', 'NTimePicker'].includes( 10 | component 11 | ) 12 | ) 13 | return '请选择'; 14 | return ''; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Table/src/components/editable/index.ts: -------------------------------------------------------------------------------- 1 | import type { BasicColumn } from '@/components/Table/src/types/table'; 2 | import { h, Ref } from 'vue'; 3 | 4 | import EditableCell from './EditableCell.vue'; 5 | 6 | export function renderEditCell(column: BasicColumn) { 7 | return (record, index) => { 8 | const _key = column.key; 9 | const value = record[_key]; 10 | record.onEdit = async (edit: boolean, submit = false) => { 11 | if (!submit) { 12 | record.editable = edit; 13 | } 14 | 15 | if (!edit && submit) { 16 | const res = await record.onSubmitEdit?.(); 17 | if (res) { 18 | record.editable = false; 19 | return true; 20 | } 21 | return false; 22 | } 23 | // cancel 24 | if (!edit && !submit) { 25 | record.onCancelEdit?.(); 26 | } 27 | return true; 28 | }; 29 | return h(EditableCell, { 30 | value, 31 | record, 32 | column, 33 | index, 34 | }); 35 | }; 36 | } 37 | 38 | export type EditRecordRow = Partial< 39 | { 40 | onEdit: (editable: boolean, submit?: boolean) => Promise; 41 | editable: boolean; 42 | onCancel: Fn; 43 | onSubmit: Fn; 44 | submitCbs: Fn[]; 45 | cancelCbs: Fn[]; 46 | validCbs: Fn[]; 47 | editValueRefs: Recordable; 48 | } & T 49 | >; 50 | -------------------------------------------------------------------------------- /src/components/Table/src/const.ts: -------------------------------------------------------------------------------- 1 | import componentSetting from '@/settings/componentSetting'; 2 | 3 | const { table } = componentSetting; 4 | 5 | const { apiSetting, defaultPageSize, pageSizes } = table; 6 | 7 | export const DEFAULTPAGESIZE = defaultPageSize; 8 | 9 | export const APISETTING = apiSetting; 10 | 11 | export const PAGESIZES = pageSizes; 12 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useLoading.ts: -------------------------------------------------------------------------------- 1 | import { ref, ComputedRef, unref, computed, watch } from 'vue'; 2 | import type { BasicTableProps } from '../types/table'; 3 | 4 | export function useLoading(props: ComputedRef) { 5 | const loadingRef = ref(unref(props).loading); 6 | 7 | watch( 8 | () => unref(props).loading, 9 | (loading) => { 10 | loadingRef.value = loading; 11 | } 12 | ); 13 | 14 | const getLoading = computed(() => unref(loadingRef)); 15 | 16 | function setLoading(loading: boolean) { 17 | loadingRef.value = loading; 18 | } 19 | 20 | return { getLoading, setLoading }; 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Table/src/hooks/useTableContext.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | import type { BasicTableProps, TableActionType } from '../types/table'; 3 | import { provide, inject, ComputedRef } from 'vue'; 4 | 5 | const key = Symbol('s-table'); 6 | 7 | type Instance = TableActionType & { 8 | wrapRef: Ref>; 9 | getBindValues: ComputedRef; 10 | }; 11 | 12 | type RetInstance = Omit & { 13 | getBindValues: ComputedRef; 14 | }; 15 | 16 | export function createTableContext(instance: Instance) { 17 | provide(key, instance); 18 | } 19 | 20 | export function useTableContext(): RetInstance { 21 | return inject(key) as RetInstance; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Table/src/props.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue'; 2 | import { propTypes } from '@/utils/propTypes'; 3 | import { BasicColumn } from './types/table'; 4 | import { NDataTable } from 'naive-ui'; 5 | export const basicProps = { 6 | ...NDataTable.props, // 这里继承原 UI 组件的 props 7 | title: { 8 | type: String, 9 | default: null, 10 | }, 11 | titleTooltip: { 12 | type: String, 13 | default: null, 14 | }, 15 | size: { 16 | type: String, 17 | default: 'medium', 18 | }, 19 | dataSource: { 20 | type: [Object], 21 | default: () => [], 22 | }, 23 | columns: { 24 | type: [Array] as PropType, 25 | default: () => [], 26 | required: true, 27 | }, 28 | beforeRequest: { 29 | type: Function as PropType<(...arg: any[]) => void | Promise>, 30 | default: null, 31 | }, 32 | request: { 33 | type: Function as PropType<(...arg: any[]) => Promise>, 34 | default: null, 35 | }, 36 | afterRequest: { 37 | type: Function as PropType<(...arg: any[]) => void | Promise>, 38 | default: null, 39 | }, 40 | rowKey: { 41 | type: [String, Function] as PropType string)>, 42 | default: undefined, 43 | }, 44 | pagination: { 45 | type: [Object, Boolean], 46 | default: () => {}, 47 | }, 48 | //废弃 49 | showPagination: { 50 | type: [String, Boolean], 51 | default: 'auto', 52 | }, 53 | actionColumn: { 54 | type: Object as PropType, 55 | default: null, 56 | }, 57 | canResize: propTypes.bool.def(true), 58 | resizeHeightOffset: propTypes.number.def(0), 59 | }; 60 | -------------------------------------------------------------------------------- /src/components/Table/src/types/componentType.ts: -------------------------------------------------------------------------------- 1 | export type ComponentType = 2 | | 'NInput' 3 | | 'NInputNumber' 4 | | 'NSelect' 5 | | 'NCheckbox' 6 | | 'NSwitch' 7 | | 'NDatePicker' 8 | | 'NTimePicker' 9 | | 'NCascader'; 10 | -------------------------------------------------------------------------------- /src/components/Table/src/types/pagination.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/components/Table/src/types/pagination.ts -------------------------------------------------------------------------------- /src/components/Table/src/types/table.ts: -------------------------------------------------------------------------------- 1 | import type { InternalRowData, TableBaseColumn } from 'naive-ui/lib/data-table/src/interface'; 2 | import { ComponentType } from './componentType'; 3 | export interface BasicColumn extends TableBaseColumn { 4 | //编辑表格 5 | edit?: boolean; 6 | editRow?: boolean; 7 | editable?: boolean; 8 | editComponent?: ComponentType; 9 | editComponentProps?: Recordable; 10 | editRule?: boolean | ((text: string, record: Recordable) => Promise); 11 | editValueMap?: (value: any) => string; 12 | onEditRow?: () => void; 13 | // 权限编码控制是否显示 14 | auth?: string[]; 15 | // 业务控制是否显示 16 | ifShow?: boolean | ((column: BasicColumn) => boolean); 17 | // 控制是否支持拖拽,默认支持 18 | draggable?: boolean; 19 | } 20 | 21 | export interface TableActionType { 22 | reload: (opt) => Promise; 23 | emit?: any; 24 | getColumns: (opt?) => BasicColumn[]; 25 | setColumns: (columns: BasicColumn[] | string[]) => void; 26 | } 27 | 28 | export interface BasicTableProps { 29 | title?: string; 30 | dataSource: Function; 31 | columns: any[]; 32 | pagination: object; 33 | showPagination: boolean; 34 | actionColumn: any[]; 35 | canResize: boolean; 36 | resizeHeightOffset: number; 37 | loading: boolean; 38 | } 39 | -------------------------------------------------------------------------------- /src/components/Table/src/types/tableAction.ts: -------------------------------------------------------------------------------- 1 | import { NButton } from 'naive-ui'; 2 | import type { Component } from 'vue'; 3 | import { PermissionsEnum } from '@/enums/permissionsEnum'; 4 | export interface ActionItem extends Partial> { 5 | onClick?: Fn; 6 | label?: string; 7 | type?: 'success' | 'error' | 'warning' | 'info' | 'primary' | 'default'; 8 | // 设定 color 后会覆盖 type 的样式 9 | color?: string; 10 | icon?: Component; 11 | popConfirm?: PopConfirm; 12 | disabled?: boolean; 13 | divider?: boolean; 14 | // 权限编码控制是否显示 15 | auth?: PermissionsEnum | PermissionsEnum[] | string | string[]; 16 | // 业务控制是否显示 17 | ifShow?: boolean | ((action: ActionItem) => boolean); 18 | } 19 | 20 | export interface PopConfirm { 21 | title: string; 22 | okText?: string; 23 | cancelText?: string; 24 | confirm: Fn; 25 | cancel?: Fn; 26 | icon?: Component; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/Upload/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicUpload } from './src/BasicUpload.vue'; 2 | -------------------------------------------------------------------------------- /src/components/Upload/src/props.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue'; 2 | import { NUpload } from 'naive-ui'; 3 | 4 | export const basicProps = { 5 | ...NUpload.props, 6 | accept: { 7 | type: String, 8 | default: '.jpg,.png,.jpeg,.svg,.gif', 9 | }, 10 | helpText: { 11 | type: String as PropType, 12 | default: '', 13 | }, 14 | maxSize: { 15 | type: Number as PropType, 16 | default: 2, 17 | }, 18 | maxNumber: { 19 | type: Number as PropType, 20 | default: Infinity, 21 | }, 22 | value: { 23 | type: Array as PropType, 24 | default: () => [], 25 | }, 26 | width: { 27 | type: Number as PropType, 28 | default: 104, 29 | }, 30 | height: { 31 | type: Number as PropType, 32 | default: 104, //建议不小于这个尺寸 太小页面可能显示有异常 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/Upload/src/type/index.ts: -------------------------------------------------------------------------------- 1 | export interface BasicProps { 2 | title?: string; 3 | dataSource: Function; 4 | columns: any[]; 5 | pagination: object; 6 | showPagination: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/base/Avatar.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 41 | 42 | -------------------------------------------------------------------------------- /src/components/base/Xtime.vue: -------------------------------------------------------------------------------- 1 | 42 | -------------------------------------------------------------------------------- /src/components/common/DialogApi.vue: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /src/components/common/MessageApi.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /src/components/common/NotificationApi.vue: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /src/components/common/index.ts: -------------------------------------------------------------------------------- 1 | import DialogApi from './DialogApi.vue' 2 | import MessageApi from './MessageApi.vue' 3 | import NotificationApi from './NotificationApi.vue' 4 | 5 | export { DialogApi, MessageApi, NotificationApi } 6 | -------------------------------------------------------------------------------- /src/components/editor/MeEditorLocation.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/components/editor/MeEditorLocation.vue -------------------------------------------------------------------------------- /src/components/editor/formats/emoji.ts: -------------------------------------------------------------------------------- 1 | import Quill from 'quill' 2 | 3 | const ImageBlot = Quill.import('formats/image') 4 | 5 | class EmojiBlot extends ImageBlot { 6 | static blotName = 'emoji' 7 | static tagName = 'img' 8 | static className = 'ed-emoji' 9 | 10 | static create(value: HTMLImageElement) { 11 | const node = super.create() 12 | 13 | node.setAttribute('alt', value.alt) 14 | node.setAttribute('src', value.src) 15 | node.setAttribute('width', value.width) 16 | node.setAttribute('height', value.height) 17 | return node 18 | } 19 | 20 | static formats(node: HTMLImageElement) { 21 | return { 22 | alt: node.getAttribute('alt'), 23 | src: node.getAttribute('src'), 24 | width: node.getAttribute('width'), 25 | height: node.getAttribute('height') 26 | } 27 | } 28 | 29 | static value(node: HTMLImageElement) { 30 | // 主要在有初始值时起作用 31 | return { 32 | alt: node.getAttribute('alt'), 33 | src: node.getAttribute('src'), 34 | width: node.getAttribute('width'), 35 | height: node.getAttribute('height') 36 | } 37 | } 38 | } 39 | 40 | export default EmojiBlot 41 | -------------------------------------------------------------------------------- /src/components/icons/arrowDown.vue: -------------------------------------------------------------------------------- 1 | 15 | 29 | -------------------------------------------------------------------------------- /src/components/icons/arrowUp.vue: -------------------------------------------------------------------------------- 1 | 15 | 33 | -------------------------------------------------------------------------------- /src/components/icons/article.vue: -------------------------------------------------------------------------------- 1 | 15 | 28 | -------------------------------------------------------------------------------- /src/components/icons/book.vue: -------------------------------------------------------------------------------- 1 | 15 | 35 | -------------------------------------------------------------------------------- /src/components/icons/checkOutlined.vue: -------------------------------------------------------------------------------- 1 | 15 | 31 | -------------------------------------------------------------------------------- /src/components/icons/close.vue: -------------------------------------------------------------------------------- 1 | 15 | 26 | -------------------------------------------------------------------------------- /src/components/icons/closeCircle.vue: -------------------------------------------------------------------------------- 1 | 15 | 33 | -------------------------------------------------------------------------------- /src/components/icons/code.vue: -------------------------------------------------------------------------------- 1 | 15 | 27 | -------------------------------------------------------------------------------- /src/components/icons/copy.vue: -------------------------------------------------------------------------------- 1 | 15 | 25 | -------------------------------------------------------------------------------- /src/components/icons/edit.vue: -------------------------------------------------------------------------------- 1 | 15 | 35 | -------------------------------------------------------------------------------- /src/components/icons/exit.vue: -------------------------------------------------------------------------------- 1 | 15 | 35 | -------------------------------------------------------------------------------- /src/components/icons/expand.vue: -------------------------------------------------------------------------------- 1 | 15 | 41 | -------------------------------------------------------------------------------- /src/components/icons/history.vue: -------------------------------------------------------------------------------- 1 | 15 | 41 | -------------------------------------------------------------------------------- /src/components/icons/index.js: -------------------------------------------------------------------------------- 1 | import IArticle from "./article.vue"; 2 | import IBook from "./book.vue"; 3 | import ICode from "./code.vue"; 4 | import IEdit from "./edit.vue"; 5 | import IQuestion from "./question.vue"; 6 | import ISyntax from "./syntax.vue"; 7 | import ITransform from "./transform.vue"; 8 | import IUserFilled from "./userFilled.vue"; 9 | import IMenu from "./menu.vue"; 10 | import ITag from "./tag.vue"; 11 | import IExpand from "./expand.vue"; 12 | import IClose from "./close.vue"; 13 | import IArrowDown from "./arrowDown.vue"; 14 | import IArrowUp from "./arrowUp.vue"; 15 | import ICopy from "./copy.vue"; 16 | import IRefreshRight from "./refreshRight.vue"; 17 | import IUpdate from "./update.vue"; 18 | import IGithub from "./github.vue"; 19 | import IPlus from "./plus.vue"; 20 | import IHistory from "./history.vue"; 21 | import ISend from "./send.vue"; 22 | import ITagFilled from "./tagFilled.vue"; 23 | import ISet from "./set.vue"; 24 | import IExit from "./exit.vue"; 25 | import ICheckOutlined from "./checkOutlined.vue"; 26 | import IMoreOutlined from "./moreOutlined.vue"; 27 | import IOct from "./oct.vue"; 28 | import ICloseCircle from "./closeCircle.vue"; 29 | 30 | export { 31 | IArticle, 32 | IBook, 33 | ICode, 34 | IEdit, 35 | IQuestion, 36 | ISyntax, 37 | ITransform, 38 | IUserFilled, 39 | IMenu, 40 | ITag, 41 | IExpand, 42 | IClose, 43 | IArrowDown, 44 | IArrowUp, 45 | ICopy, 46 | IRefreshRight, 47 | IUpdate, 48 | IGithub, 49 | IPlus, 50 | IHistory, 51 | ISend, 52 | ITagFilled, 53 | ISet, 54 | IExit, 55 | ICheckOutlined, 56 | IMoreOutlined, 57 | IOct, 58 | ICloseCircle, 59 | }; 60 | -------------------------------------------------------------------------------- /src/components/icons/map.js: -------------------------------------------------------------------------------- 1 | import { 2 | IArticle, 3 | IBook, 4 | ICode, 5 | IEdit, 6 | IQuestion, 7 | ISyntax, 8 | ITransform, 9 | IUserFilled, 10 | IMenu, 11 | ITag, 12 | IExpand, 13 | IClose, 14 | IArrowDown, 15 | IArrowUp, 16 | ICopy, 17 | IRefreshRight, 18 | IUpdate, 19 | IGithub, 20 | IPlus, 21 | IHistory, 22 | ISend, 23 | ITagFilled, 24 | ISet, 25 | IExit, 26 | ICheckOutlined, 27 | IOct, 28 | ICloseCircle, 29 | } from "./index"; 30 | 31 | export default { 32 | article: IArticle, 33 | book: IBook, 34 | code: ICode, 35 | edit: IEdit, 36 | question: IQuestion, 37 | syntax: ISyntax, 38 | transform: ITransform, 39 | userfilled: IUserFilled, 40 | menu: IMenu, 41 | tag: ITag, 42 | expand: IExpand, 43 | close: IClose, 44 | arrowdown: IArrowDown, 45 | arrowup: IArrowUp, 46 | copy: ICopy, 47 | refreshRight: IRefreshRight, 48 | update: IUpdate, 49 | github: IGithub, 50 | plus: IPlus, 51 | history: IHistory, 52 | send: ISend, 53 | tagFilled: ITagFilled, 54 | set: ISet, 55 | exit: IExit, 56 | checkOutlined: ICheckOutlined, 57 | IMoreOutlined: ICheckOutlined, 58 | IOct: IOct, 59 | ICloseCircle: ICloseCircle, 60 | }; 61 | -------------------------------------------------------------------------------- /src/components/icons/menu.vue: -------------------------------------------------------------------------------- 1 | 15 | 32 | -------------------------------------------------------------------------------- /src/components/icons/moreOutlined.vue: -------------------------------------------------------------------------------- 1 | 15 | 31 | -------------------------------------------------------------------------------- /src/components/icons/oct.vue: -------------------------------------------------------------------------------- 1 | 15 | 37 | -------------------------------------------------------------------------------- /src/components/icons/plus.vue: -------------------------------------------------------------------------------- 1 | 15 | 48 | -------------------------------------------------------------------------------- /src/components/icons/question.vue: -------------------------------------------------------------------------------- 1 | 15 | 41 | -------------------------------------------------------------------------------- /src/components/icons/refreshRight.vue: -------------------------------------------------------------------------------- 1 | 15 | 29 | -------------------------------------------------------------------------------- /src/components/icons/send.vue: -------------------------------------------------------------------------------- 1 | 15 | 37 | -------------------------------------------------------------------------------- /src/components/icons/syntax.vue: -------------------------------------------------------------------------------- 1 | 15 | 28 | -------------------------------------------------------------------------------- /src/components/icons/tag.vue: -------------------------------------------------------------------------------- 1 | 15 | 28 | -------------------------------------------------------------------------------- /src/components/icons/tagFilled.vue: -------------------------------------------------------------------------------- 1 | 15 | 41 | -------------------------------------------------------------------------------- /src/components/icons/transform.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 36 | -------------------------------------------------------------------------------- /src/components/icons/update.vue: -------------------------------------------------------------------------------- 1 | 15 | 27 | -------------------------------------------------------------------------------- /src/components/icons/userFilled.vue: -------------------------------------------------------------------------------- 1 | 15 | 42 | -------------------------------------------------------------------------------- /src/components/talk/message/GroupNoticeMessage.vue: -------------------------------------------------------------------------------- 1 | 13 | 24 | 25 | 61 | -------------------------------------------------------------------------------- /src/components/talk/message/ImageMessage.vue: -------------------------------------------------------------------------------- 1 | 32 | 41 | 61 | -------------------------------------------------------------------------------- /src/components/talk/message/RevokeMessage.vue: -------------------------------------------------------------------------------- 1 | 27 | 39 | 72 | -------------------------------------------------------------------------------- /src/components/talk/message/UnknownMessage.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 24 | -------------------------------------------------------------------------------- /src/components/talk/message/index.js: -------------------------------------------------------------------------------- 1 | import { defineAsyncComponent } from 'vue' 2 | 3 | export function setComponents(app) { 4 | // 动态导出当前目录下的组件 5 | const modules = import.meta.glob(['./*.vue', './system/*.vue']) 6 | 7 | for (const [key, value] of Object.entries(modules)) { 8 | const name = key.slice(key.lastIndexOf('/') + 1, key.lastIndexOf('.')) 9 | app.component(name, defineAsyncComponent(value)) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/talk/message/system/SysGroupCancelMutedMessage.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | -------------------------------------------------------------------------------- /src/components/talk/message/system/SysGroupCreateMessage.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 30 | -------------------------------------------------------------------------------- /src/components/talk/message/system/SysGroupJoinMessage.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 31 | -------------------------------------------------------------------------------- /src/components/talk/message/system/SysGroupMemberCancelMutedMessage.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 30 | -------------------------------------------------------------------------------- /src/components/talk/message/system/SysGroupMemberKickedMessage.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 31 | -------------------------------------------------------------------------------- /src/components/talk/message/system/SysGroupMemberMutedMessage.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 30 | -------------------------------------------------------------------------------- /src/components/talk/message/system/SysGroupMemberQuitMessage.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | -------------------------------------------------------------------------------- /src/components/talk/message/system/SysGroupMutedMessage.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | -------------------------------------------------------------------------------- /src/components/talk/message/system/SysGroupTransferMessage.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | -------------------------------------------------------------------------------- /src/components/talk/message/system/SysTextMessage.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | 46 | -------------------------------------------------------------------------------- /src/components/talk/message/system/sys-message.less: -------------------------------------------------------------------------------- 1 | .im-message-sys-text { 2 | display: flex; 3 | justify-content: center; 4 | 5 | .sys-text { 6 | margin: 10px auto; 7 | background-color: #f5f5f5; 8 | font-size: 11px; 9 | line-height: 30px; 10 | padding: 0 8px; 11 | word-wrap: break-word; 12 | color: #979191; 13 | user-select: none; 14 | font-weight: 300; 15 | display: inline-block; 16 | border-radius: 3px; 17 | max-width: 80%; 18 | text-align: center; 19 | 20 | span { 21 | margin: 0 5px; 22 | } 23 | 24 | a { 25 | color: #939596; 26 | cursor: pointer; 27 | font-size: 12px; 28 | font-weight: 400; 29 | 30 | &:hover { 31 | color: #1890ff; 32 | } 33 | } 34 | } 35 | } 36 | 37 | html[data-theme='dark'] { 38 | .im-message-sys-text { 39 | .sys-text { 40 | background: unset; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/talk/message/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface TextExtra { 2 | lang: string 3 | code: string 4 | } 5 | 6 | export interface CodeExtra { 7 | lang: string 8 | code: string 9 | } 10 | 11 | export interface FileExtra { 12 | drive: number 13 | name: string 14 | path: string 15 | suffix: string 16 | size: number 17 | } 18 | 19 | export interface ForwardExtra { 20 | msg_ids: number[] 21 | receiver_id: number 22 | records: { 23 | nickname: string 24 | text: string 25 | }[] 26 | talk_type: number 27 | user_id: number 28 | } 29 | 30 | export interface GroupNoticeExtra { 31 | owner_id: number 32 | owner_name: string 33 | title: number 34 | content: string 35 | } 36 | 37 | export interface Data { 38 | float: string 39 | content: string 40 | } 41 | 42 | export interface VideoExtra { 43 | cover: string 44 | url: string 45 | duration: number 46 | size: number 47 | } 48 | 49 | export interface MixedExtra { 50 | items: { 51 | type: number 52 | content: string 53 | link: string 54 | }[] 55 | } 56 | -------------------------------------------------------------------------------- /src/components/user/Avatar.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/components/user/Avatar.vue -------------------------------------------------------------------------------- /src/constant/default.js: -------------------------------------------------------------------------------- 1 | import avatar from '../assets/image/notify.png' 2 | import notify from '../assets/image/notify.png' 3 | 4 | export const GenderOptions = [ 5 | { 6 | label: '未知', 7 | value: '0' 8 | }, 9 | { 10 | label: '男', 11 | value: '1' 12 | }, 13 | { 14 | label: '女', 15 | value: '2' 16 | } 17 | ] 18 | 19 | export const defAvatar = avatar 20 | 21 | export const notifyIcon = notify 22 | -------------------------------------------------------------------------------- /src/constant/event-bus.ts: -------------------------------------------------------------------------------- 1 | export const enum ContactConst { 2 | UpdateRemark = 'contact:update-remark' 3 | } 4 | 5 | export const enum EditorConst { 6 | Mention = 'editor:mention', 7 | Quote = 'editor:quote' 8 | } 9 | -------------------------------------------------------------------------------- /src/constant/highlight.js: -------------------------------------------------------------------------------- 1 | export const options = [ 2 | { 3 | label: 'PHP', 4 | value: 'php' 5 | }, 6 | { 7 | label: 'Java', 8 | value: 'java' 9 | }, 10 | { 11 | label: 'Python', 12 | value: 'python' 13 | }, 14 | { 15 | label: 'Golang', 16 | value: 'go' 17 | }, 18 | { 19 | label: 'JavaScript', 20 | value: 'javascript' 21 | }, 22 | { 23 | label: 'TypeScript', 24 | value: 'typescript' 25 | }, 26 | { 27 | label: 'Json', 28 | value: 'json' 29 | }, 30 | { 31 | label: 'Sql', 32 | value: 'sql' 33 | }, 34 | { 35 | label: 'Rust', 36 | value: 'rust' 37 | }, 38 | { 39 | label: 'Markdown', 40 | value: 'markdown' 41 | }, 42 | { 43 | label: 'Nginx', 44 | value: 'nginx' 45 | }, 46 | { 47 | label: 'Yaml', 48 | value: 'yaml' 49 | }, 50 | { 51 | label: 'Protobuf', 52 | value: 'protobuf' 53 | }, 54 | { 55 | label: 'Shell', 56 | value: 'shell' 57 | }, 58 | { 59 | label: 'Makefile', 60 | value: 'makefile' 61 | }, 62 | { 63 | label: 'Ini', 64 | value: 'ini' 65 | } 66 | ] 67 | -------------------------------------------------------------------------------- /src/constant/theme.js: -------------------------------------------------------------------------------- 1 | // 主题配置 2 | export const overrides = { 3 | common: { 4 | primaryColor: '#1890ff', 5 | primaryColorHover: '#1890ff', 6 | primaryColorPressed: '#1890ff', 7 | primaryColorSuppl: '#1890ff', 8 | bodyColor: '#ffffff' 9 | }, 10 | 11 | Dialog: { 12 | borderRadius: '10px' 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/directive/copy.js: -------------------------------------------------------------------------------- 1 | export default { 2 | created(el, binding) { 3 | el.addEventListener('copy', function (event) { 4 | //这里直接监听元素的粘贴事件 5 | binding.value(event) 6 | }) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/directive/drag.js: -------------------------------------------------------------------------------- 1 | export default { 2 | created(el, binding) { 3 | // 因为拖拽还包括拖动时的经过事件,离开事件,和进入事件,放下事件, 4 | // 浏览器对于拖拽的默认事件的处理是打开拖进来的资源, 5 | // 所以要先对这三个事件进行默认事件的禁止 6 | el.addEventListener('dragenter', function (event) { 7 | event.stopPropagation() 8 | event.preventDefault() 9 | }) 10 | el.addEventListener('dragover', function (event) { 11 | event.stopPropagation() 12 | event.preventDefault() 13 | }) 14 | el.addEventListener('dragleave', function (event) { 15 | event.stopPropagation() 16 | event.preventDefault() 17 | }) 18 | el.addEventListener('drop', function (event) { 19 | // 这里阻止默认事件,并绑定事件的对象,用来在组件上返回事件对象 20 | event.stopPropagation() 21 | event.preventDefault() 22 | binding.value(event) 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/directive/focus.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mounted(el) { 3 | setTimeout(() => { 4 | el.focus() 5 | }, 0) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/directive/index.ts: -------------------------------------------------------------------------------- 1 | import dropsize from './dropsize' 2 | import focus from './focus' 3 | // import paste from './paste' 4 | import loading from './loading' 5 | // import copy from './copy' 6 | 7 | const directives = { 8 | dropsize, 9 | focus, 10 | // paste, 11 | loading 12 | // copy 13 | } 14 | 15 | export function setupDirective(app: any) { 16 | for (const key in directives) { 17 | app.directive(key, directives[key]) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/directive/loading.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import Loading from './inner/loading.vue' 3 | 4 | export default { 5 | mounted(el, binding) { 6 | const app = createApp(Loading) 7 | const instance = app.mount(document.createElement('div')) 8 | el.instance = instance 9 | 10 | if (binding.value) { 11 | if (el.style.position == '') { 12 | el.dataset['position'] = true 13 | } 14 | 15 | if (el.style.overflow == '') { 16 | el.dataset['overflow'] = true 17 | } 18 | 19 | appendEl(el) 20 | } 21 | }, 22 | updated(el, binding) { 23 | if (binding.value !== binding.oldValue) { 24 | binding.value ? appendEl(el) : removeEl(el) 25 | } 26 | } 27 | } 28 | 29 | // 插入元素 30 | const appendEl = (el) => { 31 | // 给父元素加个定位,让loading元素定位 32 | el.style.position = 'relative' 33 | el.style.overflow = 'hidden' 34 | 35 | el?.appendChild(el.instance.$el) 36 | } 37 | 38 | // 移除元素 39 | const removeEl = (el) => { 40 | if (el.dataset['position']) { 41 | el.style.position = '' 42 | } 43 | 44 | if (el.dataset['overflow']) { 45 | el.style.overflow = '' 46 | } 47 | 48 | delete el.dataset['position'] 49 | delete el.dataset['overflow'] 50 | 51 | let $el = el.instance.$el 52 | if (el?.contains($el)) { 53 | el?.removeChild($el) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/directive/paste.js: -------------------------------------------------------------------------------- 1 | export default { 2 | created(el, binding) { 3 | el.addEventListener('paste', function (event) { 4 | //这里直接监听元素的粘贴事件 5 | binding.value(event) 6 | }) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/enums/breakpointEnum.ts: -------------------------------------------------------------------------------- 1 | export enum sizeEnum { 2 | XS = 'XS', 3 | SM = 'SM', 4 | MD = 'MD', 5 | LG = 'LG', 6 | XL = 'XL', 7 | XXL = 'XXL', 8 | } 9 | 10 | export enum screenEnum { 11 | XS = 480, 12 | SM = 576, 13 | MD = 768, 14 | LG = 992, 15 | XL = 1200, 16 | XXL = 1600, 17 | } 18 | 19 | const screenMap = new Map(); 20 | 21 | screenMap.set(sizeEnum.XS, screenEnum.XS); 22 | screenMap.set(sizeEnum.SM, screenEnum.SM); 23 | screenMap.set(sizeEnum.MD, screenEnum.MD); 24 | screenMap.set(sizeEnum.LG, screenEnum.LG); 25 | screenMap.set(sizeEnum.XL, screenEnum.XL); 26 | screenMap.set(sizeEnum.XXL, screenEnum.XXL); 27 | 28 | export { screenMap }; 29 | -------------------------------------------------------------------------------- /src/enums/cacheEnum.ts: -------------------------------------------------------------------------------- 1 | // token key 2 | export const TOKEN_KEY = 'TOKEN'; 3 | 4 | // user info key 5 | export const USER_INFO_KEY = 'USER__INFO__'; 6 | 7 | // role info key 8 | export const ROLES_KEY = 'ROLES__KEY__'; 9 | 10 | // project config key 11 | export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__'; 12 | 13 | // lock info 14 | export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__'; 15 | 16 | // base global local key 17 | export const BASE_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__'; 18 | 19 | // base global session key 20 | export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__'; 21 | -------------------------------------------------------------------------------- /src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: 请求结果集 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 200, 6 | ERROR = -1, 7 | TIMEOUT = 10042, 8 | TYPE = 'success', 9 | } 10 | 11 | /** 12 | * @description: 请求方法 13 | */ 14 | export enum RequestEnum { 15 | GET = 'GET', 16 | POST = 'POST', 17 | PATCH = 'PATCH', 18 | PUT = 'PUT', 19 | DELETE = 'DELETE', 20 | } 21 | 22 | /** 23 | * @description: 常用的contentTyp类型 24 | */ 25 | export enum ContentTypeEnum { 26 | // json 27 | JSON = 'application/json;charset=UTF-8', 28 | // json 29 | TEXT = 'text/plain;charset=UTF-8', 30 | // form-data 一般配合qs 31 | FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8', 32 | // form-data 上传 33 | FORM_DATA = 'multipart/form-data;charset=UTF-8', 34 | } 35 | -------------------------------------------------------------------------------- /src/enums/pageEnum.ts: -------------------------------------------------------------------------------- 1 | export enum PageEnum { 2 | // 登录 3 | BASE_LOGIN = '/login', 4 | BASE_LOGIN_NAME = 'Login', 5 | //重定向 6 | REDIRECT = '/redirect', 7 | REDIRECT_NAME = 'Redirect', 8 | // 首页 9 | BASE_HOME = '/dashboard', 10 | //首页跳转默认路由 11 | BASE_HOME_REDIRECT = '/dashboard/console', 12 | // 错误 13 | ERROR_PAGE_NAME = 'ErrorPage', 14 | } 15 | -------------------------------------------------------------------------------- /src/enums/permissionsEnum.ts: -------------------------------------------------------------------------------- 1 | export interface PermissionsEnum { 2 | value: string; 3 | label: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/enums/roleEnum.ts: -------------------------------------------------------------------------------- 1 | export enum RoleEnum { 2 | // 管理员 3 | ADMIN = 'admin', 4 | 5 | // 普通用户 6 | NORMAL = 'normal', 7 | } 8 | -------------------------------------------------------------------------------- /src/event/socket/base.js: -------------------------------------------------------------------------------- 1 | import { useUserStore, useDialogueStore } from '@/store' 2 | import router from '@/router' 3 | 4 | class Base { 5 | /** 6 | * 初始化 7 | */ 8 | constructor() {} 9 | 10 | /** 11 | * 获取当前登录用户的ID 12 | */ 13 | getAccountId() { 14 | return useUserStore().uid 15 | } 16 | 17 | getTalkParams() { 18 | let dialogueStore = useDialogueStore() 19 | 20 | let { talk_type, receiver_id } = dialogueStore.talk 21 | 22 | return { 23 | talk_type, 24 | receiver_id, 25 | index_name: dialogueStore.index_name 26 | } 27 | } 28 | 29 | /** 30 | * 判断消息是否来自当前对话 31 | * 32 | * @param {Number} talk_type 聊天消息类型[1:私信;2:群聊;] 33 | * @param {Number} sender_id 发送者ID 34 | * @param {Number} receiver_id 接收者ID 35 | */ 36 | isTalk(talk_type, sender_id, receiver_id) { 37 | let params = this.getTalkParams() 38 | 39 | if (talk_type != params.talk_type) { 40 | return false 41 | } else if (params.receiver_id == receiver_id || params.receiver_id == sender_id) { 42 | return true 43 | } 44 | 45 | return false 46 | } 47 | 48 | /** 49 | * 判断用户是否打开对话页 50 | */ 51 | isTalkPage() { 52 | return ['/message', '/'].includes(router.currentRoute.value.path) 53 | } 54 | } 55 | 56 | export default Base 57 | -------------------------------------------------------------------------------- /src/event/socket/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atorber/chat-studio/e744e1d2087de267e999c87fa342e63f7cbcdb5c/src/event/socket/index.js -------------------------------------------------------------------------------- /src/event/socket/keyboard.js: -------------------------------------------------------------------------------- 1 | import Base from './base' 2 | 3 | import { useDialogueStore } from '@/store' 4 | 5 | /** 6 | * 键盘输入事件 7 | */ 8 | class Keyboard extends Base { 9 | /** 10 | * @var resource 资源 11 | */ 12 | resource 13 | 14 | /** 15 | * 初始化构造方法 16 | * 17 | * @param {Object} resource Socket消息 18 | */ 19 | constructor(resource) { 20 | super() 21 | 22 | this.resource = resource 23 | 24 | this.handle() 25 | } 26 | 27 | handle() { 28 | let params = this.getTalkParams() 29 | 30 | // 判断当前是否正在对话 31 | if (!params.index_name) { 32 | return false 33 | } 34 | 35 | // 判断是否是私信 36 | if (params.talk_type != 1) { 37 | return false 38 | } 39 | 40 | // 判断消息是否来当前对话 41 | if (params.receiver_id != this.resource.sender_id) { 42 | return false 43 | } 44 | 45 | useDialogueStore().triggerKeyboard() 46 | } 47 | } 48 | 49 | export default Keyboard 50 | -------------------------------------------------------------------------------- /src/event/socket/login.js: -------------------------------------------------------------------------------- 1 | import Base from './base' 2 | import { useTalkStore, useDialogueStore } from '@/store' 3 | 4 | /** 5 | * 好友状态事件 6 | */ 7 | class Login extends Base { 8 | /** 9 | * @var resource 资源 10 | */ 11 | resource 12 | 13 | /** 14 | * 初始化构造方法 15 | * 16 | * @param {Object} resource Socket消息 17 | */ 18 | constructor(resource) { 19 | super() 20 | 21 | this.resource = resource 22 | 23 | this.handle() 24 | } 25 | 26 | handle() { 27 | useTalkStore().updateItem({ 28 | index_name: `1_${this.resource.user_id}`, 29 | is_online: this.resource.status 30 | }) 31 | 32 | if (this.isTalk(1, this.resource.user_id, this.getAccountId())) { 33 | useDialogueStore().setOnlineStatus(this.resource.status == 1) 34 | } 35 | } 36 | } 37 | 38 | export default Login 39 | -------------------------------------------------------------------------------- /src/event/socket/revoke.js: -------------------------------------------------------------------------------- 1 | import Base from './base' 2 | import { useDialogueStore } from '@/store' 3 | 4 | /** 5 | * 好友状态事件 6 | */ 7 | class Revoke extends Base { 8 | /** 9 | * @var resource 资源 10 | */ 11 | resource 12 | 13 | /** 14 | * 发送者ID 15 | */ 16 | sender_id = 0 17 | 18 | /** 19 | * 接收者ID 20 | */ 21 | receiver_id = 0 22 | 23 | /** 24 | * 聊天类型[1:私聊;2:群聊;] 25 | */ 26 | talk_type = 0 27 | 28 | /** 29 | * 初始化构造方法 30 | * 31 | * @param {Object} resource Socket消息 32 | */ 33 | constructor(resource) { 34 | super() 35 | 36 | this.sender_id = resource.sender_id 37 | this.receiver_id = resource.receiver_id 38 | this.talk_type = resource.talk_type 39 | this.record_id = resource.record_id 40 | 41 | this.handle() 42 | } 43 | 44 | /** 45 | * 判断消息发送者是否来自于我 46 | * @returns 47 | */ 48 | isCurrSender() { 49 | return this.sender_id == this.getAccountId() 50 | } 51 | 52 | /** 53 | * 获取对话索引 54 | * 55 | * @return String 56 | */ 57 | getIndexName() { 58 | if (this.talk_type == 2) { 59 | return `${this.talk_type}_${this.receiver_id}` 60 | } 61 | 62 | let receiver_id = this.isCurrSender() ? this.receiver_id : this.sender_id 63 | 64 | return `${this.talk_type}_${receiver_id}` 65 | } 66 | 67 | handle() { 68 | // 判断当前是否正在和好友对话 69 | if (!this.isTalk(this.talk_type, this.receiver_id, this.sender_id)) { 70 | return 71 | } 72 | 73 | useDialogueStore().updateDialogueRecord({ 74 | id: this.record_id, 75 | is_revoke: 1 76 | }) 77 | } 78 | } 79 | 80 | export default Revoke 81 | -------------------------------------------------------------------------------- /src/hooks/event/useWindowSizeFn copy.ts: -------------------------------------------------------------------------------- 1 | import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'; 2 | import { useDebounceFn } from '@vueuse/core'; 3 | 4 | interface WindowSizeOptions { 5 | once?: boolean; 6 | immediate?: boolean; 7 | listenerOptions?: AddEventListenerOptions | boolean; 8 | } 9 | 10 | export function useWindowSizeFn(fn: Fn, wait = 150, options?: WindowSizeOptions) { 11 | let handler = () => { 12 | fn(); 13 | }; 14 | const handleSize = useDebounceFn(handler, wait); 15 | handler = handleSize; 16 | 17 | const start = () => { 18 | if (options && options.immediate) { 19 | handler(); 20 | } 21 | window.addEventListener('resize', handler); 22 | }; 23 | 24 | const stop = () => { 25 | window.removeEventListener('resize', handler); 26 | }; 27 | 28 | tryOnMounted(() => { 29 | start(); 30 | }); 31 | 32 | tryOnUnmounted(() => { 33 | stop(); 34 | }); 35 | return [start, stop]; 36 | } 37 | -------------------------------------------------------------------------------- /src/hooks/event/useWindowSizeFn.ts: -------------------------------------------------------------------------------- 1 | import { tryOnMounted, tryOnUnmounted, useDebounceFn } from '@vueuse/core'; 2 | 3 | interface WindowSizeOptions { 4 | once?: boolean; 5 | immediate?: boolean; 6 | listenerOptions?: AddEventListenerOptions | boolean; 7 | } 8 | 9 | export function useWindowSizeFn(fn: Fn, wait = 150, options?: WindowSizeOptions) { 10 | let handler = () => { 11 | fn(); 12 | }; 13 | const handleSize = useDebounceFn(handler, wait); 14 | handler = handleSize; 15 | 16 | const start = () => { 17 | if (options && options.immediate) { 18 | handler(); 19 | } 20 | window.addEventListener('resize', handler); 21 | }; 22 | 23 | const stop = () => { 24 | window.removeEventListener('resize', handler); 25 | }; 26 | 27 | tryOnMounted(() => { 28 | start(); 29 | }); 30 | 31 | tryOnUnmounted(() => { 32 | stop(); 33 | }); 34 | return [start, stop]; 35 | } 36 | -------------------------------------------------------------------------------- /src/hooks/setting/useDesignSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { useDesignSettingStore } from '@/store/modules/designSetting'; 3 | 4 | export function useDesignSetting() { 5 | const designStore = useDesignSettingStore(); 6 | 7 | const getDarkTheme = computed(() => designStore.darkTheme); 8 | 9 | const getAppTheme = computed(() => designStore.appTheme); 10 | 11 | const getAppThemeList = computed(() => designStore.appThemeList); 12 | 13 | return { 14 | getDarkTheme, 15 | getAppTheme, 16 | getAppThemeList, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/hooks/useAccessPrompt.ts: -------------------------------------------------------------------------------- 1 | export const useAccessPrompt = () => { 2 | // 3秒后获取用户浏览器权限 3 | setTimeout(() => { 4 | window['$notification'].create({ 5 | title: '友情提示', 6 | content: '此站点仅供演示、学习所用,请勿进行非法操作、上传或发布违法资讯。', 7 | duration: 30000 8 | }) 9 | }, 3000) 10 | } 11 | -------------------------------------------------------------------------------- /src/hooks/useClickEvent.ts: -------------------------------------------------------------------------------- 1 | import { isElectronMode, electron } from '@/utils/common' 2 | 3 | export const useClickEvent = () => { 4 | const push = (el: any) => { 5 | const href = el.getAttribute('href') 6 | 7 | if (href) { 8 | if (isElectronMode() && el.getAttribute('alt') === 'link') { 9 | return electron().openLink(href) 10 | } 11 | 12 | return window.open(href) 13 | } 14 | } 15 | 16 | document.body.addEventListener('click', (event: any) => { 17 | const target = event.target 18 | 19 | if (target.nodeName.toLocaleLowerCase() === 'a') { 20 | // 判断是否匹配目标元素 21 | if (event.preventDefault) { 22 | // 对捕获到的 a 标签进行处理 23 | event.preventDefault() 24 | } else { 25 | window.event.returnValue = false 26 | } 27 | 28 | console.log('====') 29 | // 处理完 a 标签的内容,重新触发跳转,根据原来 a 标签页 target 来判断是否需要新窗口打开 30 | push(target) 31 | } 32 | }) 33 | 34 | return {} 35 | } 36 | -------------------------------------------------------------------------------- /src/hooks/useConnectStatus.ts: -------------------------------------------------------------------------------- 1 | import { watchEffect } from 'vue' 2 | import { useRouter } from 'vue-router' 3 | import { useSettingsStore } from '@/store' 4 | import { isLoggedIn } from '@/utils/auth' 5 | import ws from '@/connect' 6 | import { useUserStore } from '@/store' 7 | 8 | export const useConnectStatus = () => { 9 | const settingsStore = useSettingsStore() 10 | const router = useRouter() 11 | const userStore = useUserStore() 12 | watchEffect(() => { 13 | if (settingsStore.isLeaveWeb) { 14 | return 15 | } 16 | 17 | const pathname = router.currentRoute.value.path 18 | 19 | const paths = ['/auth/login', '/auth/register', '/auth/forget'] 20 | 21 | if (!paths.includes(pathname) && isLoggedIn()) { 22 | userStore.loadSetting() 23 | !ws.isConnect() && ws.connect('useConnectStatus...') 24 | } 25 | }) 26 | 27 | return {} 28 | } 29 | -------------------------------------------------------------------------------- /src/hooks/useEventBus.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted } from 'vue' 2 | import { bus } from '@/utils/event-bus' 3 | 4 | interface Event { 5 | name: any 6 | event: Function 7 | } 8 | 9 | export const useEventBus = (items: Event[]) => { 10 | if (!items.length) return 11 | 12 | onMounted(() => { 13 | for (const item of items) { 14 | bus.subscribe(item.name, item.event) 15 | } 16 | }) 17 | 18 | onUnmounted(() => { 19 | for (const item of items) { 20 | bus.unsubscribe(item.name, item.event) 21 | } 22 | }) 23 | 24 | const emit = (channel: string, data: any) => { 25 | bus.emit(channel, data) 26 | } 27 | 28 | return { emit } 29 | } 30 | -------------------------------------------------------------------------------- /src/hooks/useFriendsMenu.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | 3 | interface IDropdown { 4 | options: any[] 5 | show: boolean 6 | x: number 7 | y: number 8 | item: any 9 | } 10 | 11 | export function useFriendsMenu() { 12 | const dropdown: IDropdown = reactive({ 13 | options: [], 14 | show: false, 15 | x: 0, 16 | y: 0, 17 | item: {} 18 | }) 19 | 20 | const showDropdownMenu = (e: any, item: any) => { 21 | dropdown.item = Object.assign({}, item) 22 | 23 | dropdown.options = [] 24 | dropdown.options.push({ label: '删除好友', key: 'delete' }) 25 | 26 | dropdown.x = e.clientX 27 | dropdown.y = e.clientY 28 | dropdown.show = true 29 | } 30 | 31 | const closeDropdownMenu = () => { 32 | dropdown.show = false 33 | dropdown.item = {} 34 | } 35 | 36 | return { dropdown, showDropdownMenu, closeDropdownMenu } 37 | } 38 | -------------------------------------------------------------------------------- /src/hooks/useGroupListMenu.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | 3 | interface IDropdown { 4 | options: any[] 5 | show: boolean 6 | x: number 7 | y: number 8 | item: any 9 | } 10 | 11 | export function useGroupListMenu() { 12 | const dropdown: IDropdown = reactive({ 13 | options: [], 14 | show: false, 15 | x: 0, 16 | y: 0, 17 | item: {} 18 | }) 19 | 20 | const showDropdownMenu = (e: any, item: any) => { 21 | dropdown.item = Object.assign({}, item) 22 | 23 | dropdown.options = [] 24 | 25 | dropdown.options.push({ label: '添加好友', key: 'delete2' }) 26 | dropdown.options.push({ label: '移出群聊', key: 'delete1' }) 27 | dropdown.options.push({ label: '禁止发言', key: 'delete3' }) 28 | dropdown.options.push({ label: '权限分配', key: 'delete4' }) 29 | dropdown.options.push({ label: '@群成员', key: 'delete5' }) 30 | 31 | dropdown.x = e.clientX 32 | dropdown.y = e.clientY 33 | dropdown.show = true 34 | } 35 | 36 | const closeDropdownMenu = () => { 37 | dropdown.show = false 38 | dropdown.item = {} 39 | } 40 | 41 | return { dropdown, showDropdownMenu, closeDropdownMenu } 42 | } 43 | -------------------------------------------------------------------------------- /src/hooks/useNotifyAuth.ts: -------------------------------------------------------------------------------- 1 | import { useSettingsStore } from '@/store' 2 | import { applyNotificationAuth } from '@/utils/notification' 3 | 4 | export const useNotifyAuth = () => { 5 | applyNotificationAuth((value: boolean) => { 6 | useSettingsStore().isWebNotify = value 7 | }) 8 | 9 | return {} 10 | } 11 | -------------------------------------------------------------------------------- /src/hooks/useSmsLock.ts: -------------------------------------------------------------------------------- 1 | import { ref, onUnmounted } from 'vue' 2 | import SmsLock from '@/plugins/sms-lock' 3 | 4 | export function useSmsLock(key: string, time = 60) { 5 | const lockTime = ref(0) 6 | 7 | // 初始化短信按钮锁 8 | const lock = new SmsLock(key, time, (time) => { 9 | lockTime.value = time 10 | }) 11 | 12 | const start = () => { 13 | return lock.start() 14 | } 15 | 16 | onUnmounted(() => { 17 | lock.clear() 18 | }) 19 | 20 | return { 21 | lockTime, 22 | start 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/hooks/useThemeMode.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | import { useSettingsStore } from '@/store' 3 | import { overrides } from '@/constant/theme' 4 | import { darkTheme } from 'naive-ui' 5 | 6 | export function useThemeMode() { 7 | const settingsStore = useSettingsStore() 8 | 9 | const getDarkTheme = computed(() => { 10 | const theme = settingsStore.darkTheme ? 'dark' : 'light' 11 | 12 | document.getElementsByTagName('html')[0].dataset.theme = theme 13 | 14 | return settingsStore.darkTheme ? darkTheme : undefined 15 | }) 16 | 17 | const getThemeOverride = computed(() => { 18 | if (settingsStore.darkTheme) { 19 | overrides.common.bodyColor = '#202124' 20 | overrides.common.baseColor = '#ffffff' 21 | } 22 | 23 | return overrides 24 | }) 25 | 26 | return { getDarkTheme, getThemeOverride } 27 | } 28 | -------------------------------------------------------------------------------- /src/hooks/useUnreadMessage.ts: -------------------------------------------------------------------------------- 1 | import { watchEffect } from 'vue' 2 | import { useTalkStore } from '@/store' 3 | import { isElectronMode, electron } from '@/utils/common' 4 | 5 | export const useUnreadMessage = () => { 6 | const useTalk = useTalkStore() 7 | const el = document.getElementsByTagName('title')[0] 8 | const title = el.innerText 9 | 10 | watchEffect(() => { 11 | if (isElectronMode()) { 12 | electron().setBadge(useTalk.talkUnreadNum) 13 | } else { 14 | setInterval(() => { 15 | if (useTalk.talkUnreadNum > 0) { 16 | el.innerText = 17 | el.innerText == title ? `您有新的消息未读(${useTalk.talkUnreadNum})` : title 18 | } else { 19 | el.innerText = title 20 | } 21 | }, 1000) 22 | } 23 | }) 24 | 25 | return {} 26 | } 27 | -------------------------------------------------------------------------------- /src/hooks/useUserModal.ts: -------------------------------------------------------------------------------- 1 | import { ref, provide } from 'vue' 2 | 3 | export function useUserModal() { 4 | const isShow = ref(false) 5 | const uid = ref(0) 6 | 7 | const show = (id: number) => { 8 | uid.value = id 9 | isShow.value = true 10 | } 11 | 12 | const close = () => { 13 | uid.value = 0 14 | isShow.value = false 15 | } 16 | 17 | provide('$user', show) 18 | 19 | return { isShow, uid, show, close } 20 | } 21 | -------------------------------------------------------------------------------- /src/hooks/useVisibilityChange.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted } from 'vue' 2 | import { useRouter } from 'vue-router' 3 | import { isLoggedIn } from '@/utils/auth' 4 | import { useSettingsStore } from '@/store' 5 | 6 | let once = false 7 | const paths = ['/auth/login', '/auth/register', '/auth/forget'] 8 | 9 | export function useVisibilityChange() { 10 | onMounted(() => { 11 | document.addEventListener('visibilitychange', handle) 12 | }) 13 | 14 | onUnmounted(() => { 15 | document.removeEventListener('visibilitychange', handle) 16 | }) 17 | 18 | return {} 19 | } 20 | 21 | function handle() { 22 | useSettingsStore().isLeaveWeb = document.visibilityState === 'hidden' 23 | 24 | if (document.visibilityState === 'hidden') { 25 | return 26 | } 27 | 28 | if (isLoggedIn() || once) return 29 | 30 | if (paths.includes(useRouter().currentRoute.value.path)) return 31 | 32 | once = true 33 | 34 | window['$dialog'].info({ 35 | title: '友情提示', 36 | content: '当前登录已失效,请重新登录?', 37 | positiveText: '立即登录?', 38 | maskClosable: false, 39 | onPositiveClick: () => { 40 | once = false 41 | useRouter().push('/auth/login') 42 | } 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /src/hooks/web/usePermission.ts: -------------------------------------------------------------------------------- 1 | import { useUserStore } from '@/store/modules/user'; 2 | 3 | export function usePermission() { 4 | const userStore = useUserStore(); 5 | 6 | /** 7 | * 检查权限 8 | * @param accesses 9 | */ 10 | function _somePermissions(accesses: string[]) { 11 | return userStore.getPermissions.some((item) => { 12 | const { value }: any = item; 13 | return accesses.includes(value); 14 | }); 15 | } 16 | 17 | /** 18 | * 判断是否存在权限 19 | * 可用于 v-if 显示逻辑 20 | * */ 21 | function hasPermission(accesses: string[]): boolean { 22 | if (!accesses || !accesses.length) return true; 23 | return _somePermissions(accesses); 24 | } 25 | 26 | /** 27 | * 是否包含指定的所有权限 28 | * @param accesses 29 | */ 30 | function hasEveryPermission(accesses: string[]): boolean { 31 | const permissionsList = userStore.getPermissions; 32 | if (Array.isArray(accesses)) { 33 | return permissionsList.every((access: any) => accesses.includes(access.value)); 34 | } 35 | throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`); 36 | } 37 | 38 | /** 39 | * 是否包含其中某个权限 40 | * @param accesses 41 | * @param accessMap 42 | */ 43 | function hasSomePermission(accesses: string[]): boolean { 44 | const permissionsList = userStore.getPermissions; 45 | if (Array.isArray(accesses)) { 46 | return permissionsList.some((access: any) => accesses.includes(access.value)); 47 | } 48 | throw new Error(`[hasSomePermission]: ${accesses} should be a array !`); 49 | } 50 | 51 | return { hasPermission, hasEveryPermission, hasSomePermission }; 52 | } 53 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import '@/assets/css/define/theme.less' 2 | import '@/assets/css/define/global.less' 3 | import '@/assets/css/dropsize.less' 4 | import './styles/tailwind.css'; 5 | import './plugins/highlight' 6 | import { createApp } from 'vue' 7 | import { createPinia } from 'pinia' 8 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' 9 | import router from './router' 10 | import App from './App.vue' 11 | import * as plugins from './plugins' 12 | import { setupNaive } from '@/plugins'; 13 | import naive from 'naive-ui' 14 | 15 | async function bootstrap() { 16 | const pinia = createPinia() 17 | pinia.use(piniaPluginPersistedstate) 18 | 19 | const app = createApp(App) 20 | 21 | // 注册全局常用的 naive-ui 组件 22 | setupNaive(app); 23 | app.use(pinia) 24 | app.use(router) 25 | app.use(naive) 26 | 27 | plugins.setHljsVuePlugin(app) 28 | plugins.setupNaive(app) 29 | plugins.setMdEditor(app) 30 | plugins.setComponents(app) 31 | plugins.setupDirective(app) 32 | 33 | // https://www.naiveui.com/en-US/os-theme/docs/style-conflict#About-Tailwind's-Preflight-Style-Override 34 | const meta = document.createElement('meta'); 35 | meta.name = 'naive-ui-style'; 36 | document.head.appendChild(meta); 37 | 38 | app.mount('#app', true) 39 | } 40 | 41 | bootstrap() 42 | -------------------------------------------------------------------------------- /src/plugins/core.js: -------------------------------------------------------------------------------- 1 | import { setComponents as Components } from '@/components/talk/message' 2 | import Avatar from '@/components/base/Avatar.vue' 3 | 4 | // 注册全局消息组件 5 | export function setComponents(app) { 6 | Components(app) 7 | 8 | app.component('im-avatar', Avatar) 9 | } 10 | -------------------------------------------------------------------------------- /src/plugins/directive.js: -------------------------------------------------------------------------------- 1 | import dropsize from '@/directive/dropsize' 2 | import focus from '@/directive/focus' 3 | import paste from '@/directive/paste' 4 | import drag from '@/directive/drag' 5 | import loading from '@/directive/loading' 6 | import copy from '@/directive/copy' 7 | 8 | export function setupDirective(app) { 9 | app.directive('dropsize', dropsize) 10 | app.directive('focus', focus) 11 | app.directive('paste', paste) 12 | app.directive('drag', drag) 13 | app.directive('loading', loading) 14 | app.directive('copy', copy) 15 | } 16 | -------------------------------------------------------------------------------- /src/plugins/hljs.js: -------------------------------------------------------------------------------- 1 | import hljsVuePlugin from '@highlightjs/vue-plugin' 2 | import 'highlight.js/styles/github.css' 3 | import 'highlight.js/lib/common' 4 | 5 | export function setHljsVuePlugin(app) { 6 | app.use(hljsVuePlugin) 7 | } 8 | -------------------------------------------------------------------------------- /src/plugins/im-sdk.js: -------------------------------------------------------------------------------- 1 | class ImSDK { 2 | /** 3 | * 发送文本消息 4 | * 5 | * @param {*} data 6 | */ 7 | sendText(data = {}) {} 8 | 9 | /** 10 | * 发送图片消息 11 | * 12 | * @param {*} data 13 | */ 14 | sendImage(data = {}) {} 15 | 16 | /** 17 | * 发送图片消息 18 | * 19 | * @param {*} data 20 | */ 21 | sendFile(data = {}) {} 22 | 23 | /** 24 | * 发送代码消息 25 | * 26 | * @param {*} data 27 | */ 28 | sendCode(data = {}) {} 29 | 30 | /** 31 | * 发送投票消息 32 | * 33 | * @param {*} data 34 | */ 35 | sendVote(data = {}) {} 36 | 37 | /** 38 | * 发送位置消息 39 | * 40 | * @param {*} data 41 | */ 42 | sendLocation(data = {}) {} 43 | 44 | /** 45 | * 发送表情包 46 | * 47 | * @param {*} data 48 | */ 49 | sendEmoticon(data = {}) {} 50 | 51 | /** 52 | * 发送键盘消息 53 | */ 54 | sendKeyboardMsg() {} 55 | } 56 | 57 | export default new ImSDK() 58 | -------------------------------------------------------------------------------- /src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export * from './directive' 2 | export * from './highlight' 3 | export * from './hljs' 4 | export * from './core' 5 | export * from './md-editor' 6 | export * from './naive-ui' 7 | export * from './sms-lock' 8 | // export * from './ws-socket' 9 | -------------------------------------------------------------------------------- /src/plugins/md-editor.js: -------------------------------------------------------------------------------- 1 | import VueMarkdownEditor from '@kangc/v-md-editor' 2 | import '@kangc/v-md-editor/lib/style/base-editor.css' 3 | import githubTheme from '@kangc/v-md-editor/lib/theme/github.js' 4 | import '@kangc/v-md-editor/lib/theme/style/github.css' 5 | import hljs from 'highlight.js' 6 | 7 | VueMarkdownEditor.use(githubTheme, { 8 | Hljs: hljs 9 | }) 10 | 11 | export function setMdEditor(app) { 12 | app.use(VueMarkdownEditor) 13 | } 14 | -------------------------------------------------------------------------------- /src/plugins/naive-ui.ts: -------------------------------------------------------------------------------- 1 | import { 2 | // create naive ui 3 | create, 4 | // component 5 | NButton, 6 | NLayout, 7 | NLayoutSider, 8 | NLayoutHeader, 9 | NLayoutContent, 10 | NLayoutFooter, 11 | NAvatar, 12 | NIcon, 13 | NInput, 14 | NSpin, 15 | NEmpty, 16 | NModal, 17 | NTag, 18 | NSpace 19 | } from 'naive-ui' 20 | 21 | // 按需全局安装组件 22 | const naive = create({ 23 | components: [ 24 | NButton, 25 | NLayout, 26 | NLayoutSider, 27 | NLayoutHeader, 28 | NLayoutContent, 29 | NLayoutFooter, 30 | NAvatar, 31 | NIcon, 32 | NInput, 33 | NSpin, 34 | NEmpty, 35 | NModal, 36 | NTag, 37 | NSpace 38 | ] 39 | }) 40 | 41 | export function setupNaive(app) { 42 | app.use(naive) 43 | } 44 | -------------------------------------------------------------------------------- /src/plugins/sms-lock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 短信倒计时锁 3 | */ 4 | class SmsLock { 5 | // 计时器 6 | timer = null 7 | 8 | // 倒计时默认60秒 9 | lockTime = 60 10 | 11 | // 锁标记名称 12 | lockName = '' 13 | 14 | // 回调方法 15 | callBack = () => {} 16 | 17 | /** 18 | * 实例化构造方法 19 | * 20 | * @param {String} purpose 唯一标识 21 | * @param {Number} time 22 | */ 23 | constructor(purpose, lockTime = 60, fn = (time) => {}) { 24 | this.lockTime = lockTime 25 | 26 | this.lockName = `SMSLOCK_${purpose}` 27 | 28 | this.callBack = fn 29 | 30 | this.compute() 31 | } 32 | 33 | // 开始计时 34 | start() { 35 | // 设置本地缓存 36 | localStorage.setItem(this.lockName, this.getCurrentTime() + this.lockTime) 37 | 38 | this.compute() 39 | } 40 | 41 | compute() { 42 | this.clear() 43 | 44 | const time = this.getExpireTime() 45 | 46 | if (time === null) return 47 | 48 | if (time <= this.getCurrentTime()) { 49 | this.callBack(0) 50 | localStorage.removeItem(this.lockName) 51 | return 52 | } 53 | 54 | const t = time - this.getCurrentTime() 55 | 56 | this.callBack(t) 57 | 58 | this.timer = setTimeout(() => this.compute(), 1000) 59 | } 60 | 61 | // 获取当前时间 62 | getCurrentTime() { 63 | return Math.floor(new Date().getTime() / 1000) 64 | } 65 | 66 | // 获取过期时间 67 | getExpireTime() { 68 | return localStorage.getItem(this.lockName) 69 | } 70 | 71 | // 清除计时器 72 | clear() { 73 | clearTimeout(this.timer) 74 | } 75 | } 76 | 77 | export default SmsLock 78 | -------------------------------------------------------------------------------- /src/router/modules/app.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | path: '/app', 3 | name: 'app', 4 | redirect: '/app/notice', 5 | component: () => import('@/views/app/layout.vue'), 6 | children: [ 7 | { 8 | path: '/app/notice', 9 | name: 'notice', 10 | meta: { auth: true }, 11 | component: () => import('@/views/notice/index.vue') 12 | }, 13 | { 14 | path: '/app/groupnotice', 15 | name: 'groupnotice', 16 | meta: { auth: true }, 17 | component: () => import('@/views/groupnotice/index.vue') 18 | }, 19 | { 20 | path: '/app/keyword', 21 | meta: { auth: true }, 22 | component: () => import('@/views/keyword/index.vue') 23 | }, 24 | { 25 | path: '/app/whitelist', 26 | meta: { auth: true }, 27 | component: () => import('@/views/whitelist/white.vue') 28 | }, 29 | { 30 | path: '/app/blacklist', 31 | meta: { auth: true }, 32 | component: () => import('@/views/whitelist/black.vue') 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /src/router/modules/auth.js: -------------------------------------------------------------------------------- 1 | export default { 2 | path: '/auth', 3 | name: 'auth', 4 | redirect: '/auth/register', 5 | component: () => import('@/views/auth/layout.vue'), 6 | children: [ 7 | { 8 | path: '/auth/login', 9 | meta: { auth: false }, 10 | component: () => import('@/views/auth/login.vue') 11 | }, 12 | { 13 | path: '/auth/register', 14 | meta: { auth: false }, 15 | component: () => import('@/views/auth/register.vue') 16 | }, 17 | { 18 | path: '/auth/forget', 19 | meta: { auth: false }, 20 | component: () => import('@/views/auth/forget.vue') 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/router/modules/chatbot.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | path: '/chatbot', 3 | name: 'ChatBot', 4 | redirect: '/chatbot/list', 5 | component: () => import('@/views/chatbot/layout.vue'), 6 | children: [ 7 | { 8 | path: '/chatbot/list', 9 | meta: { auth: true }, 10 | component: () => import('@/views/chatbot/index.vue') 11 | }, 12 | { 13 | path: '/chatbot/whitelist', 14 | meta: { auth: true }, 15 | component: () => import('@/views/chatbot/white.vue') 16 | }, 17 | { 18 | path: '/chatbot/prompt', 19 | meta: { auth: true }, 20 | component: () => import('@/views/chatbot/prompt.vue') 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/router/modules/contact.js: -------------------------------------------------------------------------------- 1 | export default { 2 | path: '/contact', 3 | name: 'contact', 4 | redirect: '/contact/friend', 5 | component: () => import('@/views/contact/layout.vue'), 6 | children: [ 7 | { 8 | path: '/contact/organize', 9 | meta: { auth: true }, 10 | component: () => import('@/views/contact/organize.vue') 11 | }, 12 | { 13 | path: '/contact/friend', 14 | meta: { auth: true }, 15 | component: () => import('@/views/contact/friends.vue') 16 | }, 17 | { 18 | path: '/contact/apply', 19 | meta: { auth: true }, 20 | component: () => import('@/views/contact/apply.vue') 21 | }, 22 | { 23 | path: '/contact/group', 24 | meta: { auth: true }, 25 | component: () => import('@/views/contact/groups.vue') 26 | }, 27 | { 28 | path: '/contact/group/open', 29 | meta: { auth: true }, 30 | component: () => import('@/views/contact/open-group.vue') 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /src/router/modules/qa.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | path: '/qa', 3 | name: 'qa', 4 | redirect: '/qa/list', 5 | component: () => import('@/views/qa/layout.vue'), 6 | children: [ 7 | { 8 | path: '/qa/list', 9 | meta: { auth: true }, 10 | component: () => import('@/views/qa/index.vue') 11 | }, 12 | { 13 | path: '/qa/whitelist', 14 | meta: { auth: true }, 15 | component: () => import('@/views/qa/white.vue') 16 | }, 17 | { 18 | path: '/qa/config', 19 | meta: { auth: true }, 20 | component: () => import('@/views/qa/detail.vue') 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/router/modules/setting.js: -------------------------------------------------------------------------------- 1 | export default { 2 | path: '/settings', 3 | name: 'settings', 4 | redirect: '/settings/config', 5 | component: () => import('@/views/setting/layout.vue'), 6 | children: [ 7 | { 8 | path: '/settings/detail', 9 | meta: { auth: true }, 10 | component: () => import('@/views/setting/detail.vue') 11 | }, 12 | { 13 | path: '/settings/security', 14 | meta: { auth: true }, 15 | component: () => import('@/views/setting/security.vue') 16 | }, 17 | { 18 | path: '/settings/binding', 19 | meta: { auth: true }, 20 | component: () => import('@/views/setting/binding.vue') 21 | }, 22 | { 23 | path: '/settings/personalize', 24 | meta: { auth: true }, 25 | component: () => import('@/views/setting/personalize.vue') 26 | }, 27 | { 28 | path: '/settings/notification', 29 | meta: { auth: true }, 30 | component: () => import('@/views/setting/notification.vue') 31 | }, 32 | { 33 | path: '/settings/config', 34 | meta: { auth: true }, 35 | component: () => import('@/views/setting/config.vue') 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /src/router/modules/statistic.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | path: '/statistic', 3 | name: 'statistic', 4 | redirect: '/statistic/list', 5 | component: () => import('@/views/statistic/layout.vue'), 6 | children: [ 7 | { 8 | path: '/statistic/list', 9 | meta: { auth: true }, 10 | component: () => import('@/views/statistic/statistic.vue') 11 | }, 12 | { 13 | path: '/statistic/order', 14 | meta: { auth: true }, 15 | component: () => import('@/views/statistic/order.vue') 16 | }, 17 | { 18 | path: '/statistic/whitelist', 19 | meta: { auth: true }, 20 | component: () => import('@/views/statistic/white.vue') 21 | }, 22 | { 23 | path: '/statistic/config', 24 | meta: { auth: true }, 25 | component: () => import('@/views/statistic/detail.vue') 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/router/modules/whitelist.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | path: '/whitelist', 3 | name: 'whitelist', 4 | redirect: '/whitelist/white', 5 | component: () => import('@/views/whitelist/layout.vue'), 6 | children: [ 7 | { 8 | path: '/whitelist/white', 9 | meta: { auth: true }, 10 | component: () => import('@/views/whitelist/white.vue') 11 | }, 12 | { 13 | path: '/whitelist/black', 14 | meta: { auth: true }, 15 | component: () => import('@/views/whitelist/black.vue') 16 | }, 17 | { 18 | path: '/whitelist/security', 19 | meta: { auth: true }, 20 | component: () => import('@/views/notice/index.vue') 21 | }, 22 | { 23 | path: '/whitelist/binding', 24 | meta: { auth: true }, 25 | component: () => import('@/views/whitelist/binding.vue') 26 | }, 27 | { 28 | path: '/whitelist/personalize', 29 | meta: { auth: true }, 30 | component: () => import('@/views/whitelist/personalize.vue') 31 | }, 32 | { 33 | path: '/whitelist/notification', 34 | meta: { auth: true }, 35 | component: () => import('@/views/whitelist/notification.vue') 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /src/settings/componentSetting.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | table: { 3 | apiSetting: { 4 | // 当前页的字段名 5 | pageField: 'page', 6 | // 每页数量字段名 7 | sizeField: 'pageSize', 8 | // 接口返回的数据字段名 9 | listField: 'items', 10 | // 接口返回总页数字段名 11 | totalField: 'pageCount', 12 | //总数字段名 13 | countField: 'itemCount', 14 | }, 15 | //默认分页数量 16 | defaultPageSize: 10, 17 | //可切换每页数量集合 18 | pageSizes: [10, 20, 30, 40, 50], 19 | }, 20 | upload: { 21 | //考虑接口规范不同 22 | apiSetting: { 23 | // 集合字段名 24 | infoField: 'data', 25 | // 图片地址字段名 26 | imgField: 'photo', 27 | }, 28 | //最大上传图片大小 29 | maxSize: 2, 30 | //图片上传类型 31 | fileType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/svg+xml'], 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /src/settings/designSetting.ts: -------------------------------------------------------------------------------- 1 | // app theme preset color 2 | export const appThemeList: string[] = [ 3 | '#2d8cf0', 4 | '#0960bd', 5 | '#0084f4', 6 | '#009688', 7 | '#536dfe', 8 | '#ff5c93', 9 | '#ee4f12', 10 | '#0096c7', 11 | '#9c27b0', 12 | '#ff9800', 13 | '#FF3D68', 14 | '#00C1D4', 15 | '#71EFA3', 16 | '#171010', 17 | '#78DEC7', 18 | '#1768AC', 19 | '#FB9300', 20 | '#FC5404', 21 | ]; 22 | 23 | const setting = { 24 | //深色主题 25 | darkTheme: false, 26 | //系统主题色 27 | appTheme: '#2d8cf0', 28 | //系统内置主题色列表 29 | appThemeList, 30 | }; 31 | 32 | export default setting; 33 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { useUserStore } from '@/store/modules/user' 2 | import { useSettingsStore } from '@/store/modules/settings' 3 | import { useTalkStore } from '@/store/modules/talk' 4 | import { useEditorStore } from '@/store/modules/editor' 5 | import { useDialogueStore } from '@/store/modules/dialogue' 6 | import { useEditorDraftStore } from '@/store/modules/editor-draft' 7 | import { useUploadsStore } from '@/store/modules/uploads' 8 | import { useNoteStore } from '@/store/modules/note' 9 | import { usePluginStore } from '@/store/modules/plugin' 10 | 11 | import type { App } from 'vue'; 12 | import { createPinia } from 'pinia'; 13 | 14 | export { 15 | useUserStore, 16 | useSettingsStore, 17 | useEditorStore, 18 | useDialogueStore, 19 | useEditorDraftStore, 20 | useUploadsStore, 21 | useTalkStore, 22 | useNoteStore, 23 | usePluginStore, 24 | } 25 | 26 | const store = createPinia(); 27 | 28 | export function setupStore(app: App) { 29 | app.use(store); 30 | } 31 | 32 | export { store }; 33 | -------------------------------------------------------------------------------- /src/store/modules/designSetting.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { store } from '@/store'; 3 | import designSetting from '@/settings/designSetting'; 4 | 5 | const { darkTheme, appTheme, appThemeList } = designSetting; 6 | 7 | interface DesignSettingState { 8 | //深色主题 9 | darkTheme: boolean; 10 | //系统风格 11 | appTheme: string; 12 | //系统内置风格 13 | appThemeList: string[]; 14 | } 15 | 16 | export const useDesignSettingStore = defineStore({ 17 | id: 'app-design-setting', 18 | state: (): DesignSettingState => ({ 19 | darkTheme, 20 | appTheme, 21 | appThemeList, 22 | }), 23 | getters: { 24 | getDarkTheme(): boolean { 25 | return this.darkTheme; 26 | }, 27 | getAppTheme(): string { 28 | return this.appTheme; 29 | }, 30 | getAppThemeList(): string[] { 31 | return this.appThemeList; 32 | }, 33 | }, 34 | actions: {}, 35 | }); 36 | 37 | // Need to be used outside the setup 38 | export function useDesignSetting() { 39 | return useDesignSettingStore(store); 40 | } 41 | -------------------------------------------------------------------------------- /src/store/modules/editor-draft.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | // 编辑器草稿 4 | export const useEditorDraftStore = defineStore('editor-draft', { 5 | // 开启数据持久化 6 | persist: true, 7 | state: () => { 8 | return { 9 | items: {} 10 | } 11 | }, 12 | actions: {} 13 | }) 14 | -------------------------------------------------------------------------------- /src/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { storage } from '@/utils/storage' 3 | 4 | export const useSettingsStore = defineStore('settings', { 5 | state: () => { 6 | return { 7 | isPromptTone: storage.get('isPromptTone', true), // 新消息提示音 8 | isKeyboard: storage.get('isKeyboard', true), // 是否推送键盘输入事件 9 | isLeaveWeb: false, // 是否离开网页 10 | isWebNotify: false, // 是否同意浏览器通知 11 | isFullScreen: storage.get('isFullScreen', true), // 是否客户端全屏 12 | darkTheme: storage.get('darkTheme', false) 13 | } 14 | }, 15 | actions: { 16 | setPromptTone(value) { 17 | this.isPromptTone = value 18 | storage.set('isPromptTone', value, null) 19 | }, 20 | setKeyboard(value) { 21 | this.isKeyboard = value 22 | storage.set('isKeyboard', value, null) 23 | }, 24 | setFullScreen(value) { 25 | this.isFullScreen = value 26 | storage.set('isFullScreen', value, null) 27 | }, 28 | setDarkTheme(value) { 29 | this.darkTheme = value 30 | storage.set('darkTheme', value, null) 31 | } 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /src/styles/index.less: -------------------------------------------------------------------------------- 1 | @import 'transition/index.less'; 2 | -------------------------------------------------------------------------------- /src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | /*! @import */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | -------------------------------------------------------------------------------- /src/styles/transition/base.less: -------------------------------------------------------------------------------- 1 | .transition-default() { 2 | &-enter-active, 3 | &-leave-active { 4 | transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1) !important; 5 | } 6 | 7 | &-move { 8 | transition: transform 0.4s; 9 | } 10 | } 11 | 12 | .expand-transition { 13 | .transition-default(); 14 | } 15 | 16 | .expand-x-transition { 17 | .transition-default(); 18 | } 19 | -------------------------------------------------------------------------------- /src/styles/transition/index.less: -------------------------------------------------------------------------------- 1 | @import './base.less'; 2 | @import './fade.less'; 3 | @import './scale.less'; 4 | @import './slide.less'; 5 | @import './scroll.less'; 6 | @import './zoom.less'; 7 | 8 | .collapse-transition { 9 | transition: 0.2s height ease-in-out, 0.2s padding-top ease-in-out, 0.2s padding-bottom ease-in-out; 10 | } 11 | -------------------------------------------------------------------------------- /src/styles/transition/scale.less: -------------------------------------------------------------------------------- 1 | .scale-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave, 6 | &-leave-to { 7 | opacity: 0; 8 | transform: scale(0); 9 | } 10 | } 11 | 12 | .scale-rotate-transition { 13 | .transition-default(); 14 | 15 | &-enter-from, 16 | &-leave, 17 | &-leave-to { 18 | opacity: 0; 19 | transform: scale(0) rotate(-45deg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/styles/transition/scroll.less: -------------------------------------------------------------------------------- 1 | .scroll-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | opacity: 0; 7 | } 8 | 9 | &-enter-from { 10 | transform: translateY(-15px); 11 | } 12 | 13 | &-leave-to { 14 | transform: translateY(15px); 15 | } 16 | } 17 | 18 | .scroll-y-reverse-transition { 19 | .transition-default(); 20 | 21 | &-enter-from, 22 | &-leave-to { 23 | opacity: 0; 24 | } 25 | 26 | &-enter-from { 27 | transform: translateY(15px); 28 | } 29 | 30 | &-leave-to { 31 | transform: translateY(-15px); 32 | } 33 | } 34 | 35 | .scroll-x-transition { 36 | .transition-default(); 37 | 38 | &-enter-from, 39 | &-leave-to { 40 | opacity: 0; 41 | } 42 | 43 | &-enter-from { 44 | transform: translateX(-15px); 45 | } 46 | 47 | &-leave-to { 48 | transform: translateX(15px); 49 | } 50 | } 51 | 52 | .scroll-x-reverse-transition { 53 | .transition-default(); 54 | 55 | &-enter-from, 56 | &-leave-to { 57 | opacity: 0; 58 | } 59 | 60 | &-enter-from { 61 | transform: translateX(15px); 62 | } 63 | 64 | &-leave-to { 65 | transform: translateX(-15px); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/styles/transition/slide.less: -------------------------------------------------------------------------------- 1 | .slide-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | opacity: 0; 7 | transform: translateY(-15px); 8 | } 9 | } 10 | 11 | .slide-y-reverse-transition { 12 | .transition-default(); 13 | 14 | &-enter-from, 15 | &-leave-to { 16 | opacity: 0; 17 | transform: translateY(15px); 18 | } 19 | } 20 | 21 | .slide-x-transition { 22 | .transition-default(); 23 | 24 | &-enter-from, 25 | &-leave-to { 26 | opacity: 0; 27 | transform: translateX(-15px); 28 | } 29 | } 30 | 31 | .slide-x-reverse-transition { 32 | .transition-default(); 33 | 34 | &-enter-from, 35 | &-leave-to { 36 | opacity: 0; 37 | transform: translateX(15px); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/styles/transition/zoom.less: -------------------------------------------------------------------------------- 1 | // zoom-out 2 | .zoom-out-enter-active, 3 | .zoom-out-leave-active { 4 | transition: opacity 0.1 ease-in-out, transform 0.15s ease-out; 5 | } 6 | 7 | .zoom-out-enter-from, 8 | .zoom-out-leave-to { 9 | opacity: 0; 10 | transform: scale(0); 11 | } 12 | 13 | // zoom-fade 14 | .zoom-fade-enter-active, 15 | .zoom-fade-leave-active { 16 | transition: transform 0.2s, opacity 0.3s ease-out; 17 | } 18 | 19 | .zoom-fade-enter-from { 20 | opacity: 0; 21 | transform: scale(0.92); 22 | } 23 | 24 | .zoom-fade-leave-to { 25 | opacity: 0; 26 | transform: scale(1.06); 27 | } 28 | -------------------------------------------------------------------------------- /src/styles/var.less: -------------------------------------------------------------------------------- 1 | @import 'transition/index.less'; 2 | -------------------------------------------------------------------------------- /src/types/global.ts: -------------------------------------------------------------------------------- 1 | export interface StateDropdown { 2 | options: any[] 3 | show: boolean 4 | dropdownX: number 5 | dropdownY: number 6 | item: any 7 | } 8 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare interface Fn { 2 | (...arg: T[]): R; 3 | } 4 | 5 | declare interface PromiseFn { 6 | (...arg: T[]): Promise; 7 | } 8 | 9 | declare type RefType = T | null; 10 | 11 | declare type LabelValueOptions = { 12 | label: string; 13 | value: any; 14 | disabled: boolean; 15 | [key: string]: string | number | boolean; 16 | }[]; 17 | 18 | declare type EmitType = (event: string, ...args: any[]) => void; 19 | 20 | declare type TargetContext = '_self' | '_blank'; 21 | 22 | declare interface ComponentElRef { 23 | $el: T; 24 | } 25 | 26 | declare type ComponentRef = ComponentElRef | null; 27 | 28 | declare type ElRef = Nullable; 29 | -------------------------------------------------------------------------------- /src/types/modules.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare module '*.vue' { 3 | import { DefineComponent } from 'vue'; 4 | const Component: DefineComponent<{}, {}, any>; 5 | export default Component; 6 | } 7 | 8 | declare module 'virtual:*' { 9 | const result: any; 10 | export default result; 11 | } 12 | -------------------------------------------------------------------------------- /src/types/utils.d.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, Ref } from 'vue'; 2 | 3 | export type DynamicProps = { 4 | [P in keyof T]: Ref | T[P] | ComputedRef; 5 | }; 6 | -------------------------------------------------------------------------------- /src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import { storage } from './storage' 2 | 3 | const AccessToken = 'AUTH_TOKEN' 4 | 5 | /** 6 | * 验证是否登录 7 | * 8 | * @returns token 9 | */ 10 | export function isLoggedIn() { 11 | return getAccessToken() != '' 12 | } 13 | 14 | /** 15 | * 获取登录授权 Token 16 | * 17 | * @returns token 18 | */ 19 | export function getAccessToken() { 20 | return storage.get(AccessToken) || '' 21 | } 22 | 23 | /** 24 | * 设置登录授权 Token 25 | * 26 | * @returns token 27 | */ 28 | export function setAccessToken(token = '', expire = 60 * 60 * 2) { 29 | return storage.set(AccessToken, token, expire) || '' 30 | } 31 | 32 | /** 33 | * 删除登录授权 Token 34 | */ 35 | export function delAccessToken() { 36 | storage.remove(AccessToken) 37 | } 38 | -------------------------------------------------------------------------------- /src/utils/crypto-use-crypto-js.mjs: -------------------------------------------------------------------------------- 1 | import CryptoJS from 'crypto-js'; 2 | 3 | // 加密函数 4 | export function encrypt(payload, keyBase64) { 5 | const key = CryptoJS.enc.Base64.parse(keyBase64); 6 | const iv = CryptoJS.lib.WordArray.random(16); // 生成一个16字节的随机IV 7 | const encrypted = CryptoJS.AES.encrypt(payload, key, { iv }); 8 | return JSON.stringify({ data: encrypted.ciphertext.toString(CryptoJS.enc.Hex), iv: iv.toString(CryptoJS.enc.Hex) }); 9 | } 10 | 11 | // 解密函数 12 | export function decrypt(message, keyBase64) { 13 | message = JSON.parse(message); 14 | const key = CryptoJS.enc.Base64.parse(keyBase64); 15 | const iv = CryptoJS.enc.Hex.parse(message.iv); 16 | const encryptedText = CryptoJS.enc.Hex.parse(message.data); 17 | const cipherParams = CryptoJS.lib.CipherParams.create({ 18 | ciphertext: encryptedText, 19 | }); 20 | const decrypted = CryptoJS.AES.decrypt(cipherParams, key, { iv }); 21 | return decrypted.toString(CryptoJS.enc.Utf8); 22 | } 23 | 24 | // 生成密钥 25 | export function getKey() { 26 | return CryptoJS.lib.WordArray.random(32).toString(CryptoJS.enc.Base64); 27 | } 28 | 29 | // 使用基础字符串生成密钥 30 | export function getKeyByBasicString(basicString) { 31 | const hash = CryptoJS.SHA256(basicString); 32 | return hash.toString(CryptoJS.enc.Base64); 33 | } 34 | 35 | console.debug(getKeyByBasicString('123456')) 36 | -------------------------------------------------------------------------------- /src/utils/event-bus.ts: -------------------------------------------------------------------------------- 1 | class EventBus { 2 | private channels = {} 3 | 4 | // 定义订阅方法 5 | subscribe(channel: string, callback: Function) { 6 | // 如果频道不存在,则创建一个新的频道 7 | if (!this.channels[channel]) { 8 | this.channels[channel] = [] 9 | } 10 | 11 | // 将回调函数添加到频道的订阅者列表中 12 | this.channels[channel].push(callback) 13 | } 14 | 15 | // 定义发布方法 16 | emit(channel: string, data: any) { 17 | // 如果频道不存在,则直接返回 18 | if (!this.channels[channel]) { 19 | return 20 | } 21 | 22 | // 遍历频道的订阅者列表,并依次调用回调函数 23 | this.channels[channel].forEach((callback: Function) => { 24 | callback(data) 25 | }) 26 | } 27 | 28 | unsubscribe(channel: string, callback: Function) { 29 | // 如果频道不存在,则创建一个新的频道 30 | if (!this.channels[channel]) { 31 | this.channels[channel] = [] 32 | } 33 | 34 | for (const index in this.channels[channel]) { 35 | if (this.channels[channel][index] === callback) { 36 | this.channels[channel].splice(index, 1) 37 | } 38 | } 39 | } 40 | } 41 | 42 | export default EventBus 43 | 44 | export const bus = new EventBus() 45 | -------------------------------------------------------------------------------- /src/utils/log.ts: -------------------------------------------------------------------------------- 1 | const projectName = import.meta.env.VITE_GLOB_APP_TITLE; 2 | 3 | export function warn(message: string) { 4 | console.warn(`[${projectName} warn]:${message}`); 5 | } 6 | 7 | export function error(message: string) { 8 | throw new Error(`[${projectName} error]:${message}`); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/notification.js: -------------------------------------------------------------------------------- 1 | const WebNotification = window.Notification || window.mozNotification || window.webkitNotification 2 | 3 | // 申请获取浏览器权限 4 | export function applyNotificationAuth(fn) { 5 | const notification = WebNotification 6 | 7 | if (notification) { 8 | notification.requestPermission((result) => { 9 | return fn('granted' === result) // granted(允许) || denied(拒绝) 10 | }) 11 | } else { 12 | console.warn('浏览器不支持通知!') 13 | return fn(false) 14 | } 15 | } 16 | 17 | // 显示浏览器通知 18 | export function WebNotify(title = '', options = {}) { 19 | const notification = new WebNotification(title, options) 20 | 21 | notification.onclick = function (event) { 22 | notification.close() 23 | console.log(event) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/propTypes.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties, VNodeChild } from 'vue'; 2 | import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types'; 3 | 4 | export type VueNode = VNodeChild | JSX.Element; 5 | 6 | type PropTypes = VueTypesInterface & { 7 | readonly style: VueTypeValidableDef; 8 | readonly VNodeChild: VueTypeValidableDef; 9 | }; 10 | 11 | const propTypes = createTypes({ 12 | func: undefined, 13 | bool: undefined, 14 | string: undefined, 15 | number: undefined, 16 | object: undefined, 17 | integer: undefined, 18 | }) as PropTypes; 19 | 20 | propTypes.extend([ 21 | { 22 | name: 'style', 23 | getter: true, 24 | type: [String, Object], 25 | default: undefined, 26 | }, 27 | { 28 | name: 'VNodeChild', 29 | getter: true, 30 | type: undefined, 31 | }, 32 | ]); 33 | export { propTypes }; 34 | -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | class Storage { 2 | // 缓存前缀 3 | prefix = '' 4 | 5 | // 缓存驱动 6 | storage = localStorage 7 | 8 | constructor(prefix = '', storage) { 9 | this.prefix = prefix 10 | this.storage = storage 11 | } 12 | 13 | cacheKey(key) { 14 | return `${this.prefix}_${key}`.toUpperCase() 15 | } 16 | 17 | get(key, def = '') { 18 | const item = this.storage.getItem(this.cacheKey(key)) 19 | 20 | if (!item) return def 21 | 22 | try { 23 | const { value, expire } = JSON.parse(item) 24 | 25 | // 在有效期内直接返回 26 | if (expire === null || expire >= Date.now()) { 27 | return value 28 | } 29 | 30 | this.remove(key) 31 | } catch (e) { 32 | console.warn(e) 33 | } 34 | 35 | return def 36 | } 37 | 38 | /** 39 | * 设置缓存 40 | * 41 | * @param {String} key // 缓存KEY 42 | * @param {Any} value // 缓存值 43 | * @param {Number|null} expire // 缓存时间单位秒 44 | */ 45 | set(key, value, expire = 60 * 60 * 24) { 46 | this.storage.setItem( 47 | this.cacheKey(key), 48 | JSON.stringify({ 49 | value, 50 | expire: expire !== null ? new Date().getTime() + expire * 1000 : null 51 | }) 52 | ) 53 | } 54 | 55 | remove(key) { 56 | this.storage.removeItem(this.cacheKey(key)) 57 | } 58 | 59 | clear() { 60 | this.storage.clear() 61 | } 62 | } 63 | 64 | export default Storage 65 | 66 | export const storage = new Storage('im', localStorage) 67 | -------------------------------------------------------------------------------- /src/utils/util.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue' 2 | import { MessageApi, NIcon } from 'naive-ui' 3 | 4 | /** 5 | * 防抖函数 6 | * 7 | * @returns MessageApi 8 | */ 9 | export const message = function (): MessageApi { 10 | return window.$message 11 | } 12 | 13 | export const renderIcon = (icon: any) => { 14 | return () => { 15 | return h(NIcon, null, { 16 | default: () => h(icon) 17 | }) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 检测是否是字邮箱地址 3 | * 4 | * @param {String} value 5 | */ 6 | export const isEmail = (value) => { 7 | return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(value) 8 | } 9 | 10 | /** 11 | * 检测是否是手机号 12 | * 13 | * @param {String} value 14 | */ 15 | export const isMobile = (value) => { 16 | return /^1[0-9]{10}$/.test(value) 17 | } 18 | 19 | /** 20 | * 检测是否为url 21 | * 22 | * @param {String} value 23 | */ 24 | export const isURL = (value) => { 25 | return /^http[s]?:\/\/.*/.test(value) 26 | } 27 | 28 | /** 29 | * 检测是否为数字类型 30 | * 31 | * @param {*} value 32 | */ 33 | export const isNumber = (value) => { 34 | return Object.prototype.toString.call(value).slice(8, -1) === 'Number' 35 | } 36 | 37 | /** 38 | * 检测是否为 Booleanl 类型 39 | * 40 | * @param {*} value 41 | */ 42 | export const isBoolean = (value) => { 43 | return Object.prototype.toString.call(value).slice(8, -1) === 'Boolean' 44 | } 45 | 46 | /** 47 | * 检测是非是微信浏览器 48 | */ 49 | export const isWeiXin = () => { 50 | let ua = navigator.userAgent.toLowerCase() 51 | return ua.match(/microMessenger/i) == 'micromessenger' 52 | } 53 | -------------------------------------------------------------------------------- /src/views/app/CellColumns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | 4 | export const columns = [ 5 | { 6 | title: 'id', 7 | key: 'id', 8 | width: 100, 9 | }, 10 | { 11 | title: '编码', 12 | key: 'no', 13 | width: 100, 14 | }, 15 | { 16 | title: '名称', 17 | key: 'name', 18 | editComponent: 'NInput', 19 | // 默认必填校验 20 | editRule: true, 21 | edit: true, 22 | width: 200, 23 | }, 24 | { 25 | title: '头像', 26 | key: 'avatar', 27 | width: 100, 28 | render(row) { 29 | return h(NAvatar, { 30 | size: 48, 31 | src: row.avatar, 32 | }); 33 | }, 34 | }, 35 | { 36 | title: '地址', 37 | key: 'address', 38 | editComponent: 'NSelect', 39 | editComponentProps: { 40 | options: [ 41 | { 42 | label: '广东省', 43 | value: 1, 44 | }, 45 | { 46 | label: '浙江省', 47 | value: 2, 48 | }, 49 | ], 50 | }, 51 | edit: true, 52 | width: 200, 53 | ellipsis: false, 54 | }, 55 | { 56 | title: '开始日期', 57 | key: 'beginTime', 58 | edit: true, 59 | width: 160, 60 | editComponent: 'NDatePicker', 61 | editComponentProps: { 62 | type: 'datetime', 63 | format: 'yyyy-MM-dd HH:mm:ss', 64 | valueFormat: 'yyyy-MM-dd HH:mm:ss', 65 | }, 66 | ellipsis: false, 67 | }, 68 | { 69 | title: '结束日期', 70 | key: 'endTime', 71 | width: 160, 72 | }, 73 | { 74 | title: '创建时间', 75 | key: 'date', 76 | width: 160, 77 | }, 78 | { 79 | title: '停留时间', 80 | key: 'time', 81 | width: 80, 82 | }, 83 | ]; 84 | -------------------------------------------------------------------------------- /src/views/app/basicColumns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar, NTag } from 'naive-ui'; 3 | 4 | export const columns = [ 5 | { 6 | title: 'id', 7 | key: 'id', 8 | width: 100, 9 | }, 10 | { 11 | title: '编码', 12 | key: 'no', 13 | width: 100, 14 | }, 15 | { 16 | title: '名称', 17 | key: 'name', 18 | width: 100, 19 | }, 20 | { 21 | title: '头像', 22 | key: 'avatar', 23 | width: 100, 24 | render(row) { 25 | return h(NAvatar, { 26 | size: 48, 27 | src: row.avatar, 28 | }); 29 | }, 30 | }, 31 | { 32 | title: '地址', 33 | key: 'address', 34 | width: 150, 35 | }, 36 | { 37 | title: '开始日期', 38 | key: 'beginTime', 39 | width: 160, 40 | }, 41 | { 42 | title: '结束日期', 43 | key: 'endTime', 44 | width: 160, 45 | }, 46 | { 47 | title: '状态', 48 | key: 'status', 49 | width: 100, 50 | render(row) { 51 | return h( 52 | NTag, 53 | { 54 | type: row.status ? 'success' : 'error', 55 | }, 56 | { 57 | default: () => (row.status ? '启用' : '禁用'), 58 | } 59 | ); 60 | }, 61 | }, 62 | { 63 | title: '创建时间', 64 | key: 'date', 65 | width: 160, 66 | }, 67 | { 68 | title: '停留时间', 69 | key: 'time', 70 | width: 80, 71 | }, 72 | ]; 73 | -------------------------------------------------------------------------------- /src/views/app/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | import { BasicColumn } from '@/components/Table'; 4 | export interface ListData { 5 | skillname?: string; // 定义名字属性,可选 6 | title?: string; 7 | question1?: string; 8 | question2?: number; 9 | answer?: string; 10 | state?: string; 11 | syncStatus?: string; 12 | lastOperationTime: string; 13 | action?: string; 14 | recordId?: string; 15 | } 16 | export const columns: BasicColumn[] = [ 17 | { 18 | title: '序号', 19 | key: 'recordId', 20 | width: 150, 21 | }, 22 | { 23 | title: '分类', 24 | key: 'skillname', 25 | width: 100, 26 | }, 27 | { 28 | title: '标准问题', 29 | key: 'title', 30 | width: 200, 31 | }, 32 | { 33 | title: '相似问题1', 34 | key: 'question1', 35 | width: 200, 36 | }, 37 | { 38 | title: '相似问题2', 39 | key: 'question2', 40 | width: 200, 41 | }, 42 | { 43 | title: '回答内容', 44 | key: 'answer', 45 | width: 160, 46 | }, 47 | { 48 | title: '状态', 49 | key: 'state', 50 | width: 100, 51 | }, 52 | { 53 | title: '同步状态', 54 | key: 'syncStatus', 55 | width: 100, 56 | }, 57 | { 58 | title: '更新时间', 59 | key: 'lastOperationTime', 60 | width: 160, 61 | render(row) { 62 | return new Date(row.lastOperationTime).toLocaleString(); 63 | }, 64 | } 65 | ]; -------------------------------------------------------------------------------- /src/views/app/editCell.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/views/app/layout.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 47 | -------------------------------------------------------------------------------- /src/views/auth/layout.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 25 | 26 | 37 | -------------------------------------------------------------------------------- /src/views/chatbot/CellColumns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | 4 | export const columns = [ 5 | { 6 | title: 'id', 7 | key: 'id', 8 | width: 100, 9 | }, 10 | { 11 | title: '编码', 12 | key: 'no', 13 | width: 100, 14 | }, 15 | { 16 | title: '名称', 17 | key: 'name', 18 | editComponent: 'NInput', 19 | // 默认必填校验 20 | editRule: true, 21 | edit: true, 22 | width: 200, 23 | }, 24 | { 25 | title: '头像', 26 | key: 'avatar', 27 | width: 100, 28 | render(row) { 29 | return h(NAvatar, { 30 | size: 48, 31 | src: row.avatar, 32 | }); 33 | }, 34 | }, 35 | { 36 | title: '地址', 37 | key: 'address', 38 | editComponent: 'NSelect', 39 | editComponentProps: { 40 | options: [ 41 | { 42 | label: '广东省', 43 | value: 1, 44 | }, 45 | { 46 | label: '浙江省', 47 | value: 2, 48 | }, 49 | ], 50 | }, 51 | edit: true, 52 | width: 200, 53 | ellipsis: false, 54 | }, 55 | { 56 | title: '开始日期', 57 | key: 'beginTime', 58 | edit: true, 59 | width: 160, 60 | editComponent: 'NDatePicker', 61 | editComponentProps: { 62 | type: 'datetime', 63 | format: 'yyyy-MM-dd HH:mm:ss', 64 | valueFormat: 'yyyy-MM-dd HH:mm:ss', 65 | }, 66 | ellipsis: false, 67 | }, 68 | { 69 | title: '结束日期', 70 | key: 'endTime', 71 | width: 160, 72 | }, 73 | { 74 | title: '创建时间', 75 | key: 'date', 76 | width: 160, 77 | }, 78 | { 79 | title: '停留时间', 80 | key: 'time', 81 | width: 80, 82 | }, 83 | ]; 84 | -------------------------------------------------------------------------------- /src/views/chatbot/basicColumns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar, NTag } from 'naive-ui'; 3 | 4 | export const columns = [ 5 | { 6 | title: 'id', 7 | key: 'id', 8 | width: 100, 9 | }, 10 | { 11 | title: '编码', 12 | key: 'no', 13 | width: 100, 14 | }, 15 | { 16 | title: '名称', 17 | key: 'name', 18 | width: 100, 19 | }, 20 | { 21 | title: '头像', 22 | key: 'avatar', 23 | width: 100, 24 | render(row) { 25 | return h(NAvatar, { 26 | size: 48, 27 | src: row.avatar, 28 | }); 29 | }, 30 | }, 31 | { 32 | title: '地址', 33 | key: 'address', 34 | width: 150, 35 | }, 36 | { 37 | title: '开始日期', 38 | key: 'beginTime', 39 | width: 160, 40 | }, 41 | { 42 | title: '结束日期', 43 | key: 'endTime', 44 | width: 160, 45 | }, 46 | { 47 | title: '状态', 48 | key: 'status', 49 | width: 100, 50 | render(row) { 51 | return h( 52 | NTag, 53 | { 54 | type: row.status ? 'success' : 'error', 55 | }, 56 | { 57 | default: () => (row.status ? '启用' : '禁用'), 58 | } 59 | ); 60 | }, 61 | }, 62 | { 63 | title: '创建时间', 64 | key: 'date', 65 | width: 160, 66 | }, 67 | { 68 | title: '停留时间', 69 | key: 'time', 70 | width: 80, 71 | }, 72 | ]; 73 | -------------------------------------------------------------------------------- /src/views/chatbot/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | import { BasicColumn } from '@/components/Table'; 4 | export interface ListData { 5 | id: string; // 定义名字属性,可选 6 | name: string; 7 | desc: string; 8 | type: string; 9 | model: string; 10 | prompt: string; 11 | quota: string; 12 | endpoint: string; 13 | key: string; 14 | recordId?: string; 15 | } 16 | export const columns: BasicColumn[] = [ 17 | // { 18 | // title: '序号', 19 | // key: 'recordId', 20 | // width: 120, 21 | // }, 22 | { 23 | title: '机器人ID', 24 | key: 'id', 25 | width: 100, 26 | }, 27 | { 28 | title: '类型', 29 | key: 'type', 30 | width: 100, 31 | }, 32 | { 33 | title: '模型', 34 | key: 'model', 35 | width: 100, 36 | }, 37 | { 38 | title: '系统提示词', 39 | key: 'prompt', 40 | width: 200, 41 | }, 42 | { 43 | title: '描述', 44 | key: 'desc', 45 | width: 200, 46 | }, 47 | // { 48 | // title: '配额', 49 | // key: 'quota', 50 | // width: 100, 51 | // }, 52 | { 53 | title: '接入点', 54 | key: 'endpoint', 55 | width: 160, 56 | }, 57 | { 58 | title: '密钥', 59 | key: 'key', 60 | width: 160, 61 | } 62 | ]; -------------------------------------------------------------------------------- /src/views/chatbot/columnsPromt.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | import { BasicColumn } from '@/components/Table'; 4 | export interface ListData { 5 | id: string; // 定义名字属性,可选 6 | name: string; 7 | desc: string; 8 | type: string; 9 | model: string; 10 | prompt: string; 11 | quota: string; 12 | endpoint: string; 13 | key: string; 14 | recordId?: string; 15 | } 16 | export const columns: BasicColumn[] = [ 17 | // { 18 | // title: '序号', 19 | // key: 'recordId', 20 | // width: 120, 21 | // }, 22 | { 23 | title: '机器人ID', 24 | key: 'id', 25 | width: 100, 26 | }, 27 | { 28 | title: '描述', 29 | key: 'desc', 30 | width: 200, 31 | }, 32 | { 33 | title: '类型', 34 | key: 'type', 35 | width: 100, 36 | }, 37 | { 38 | title: '模型', 39 | key: 'model', 40 | width: 100, 41 | }, 42 | { 43 | title: '系统提示词', 44 | key: 'prompt', 45 | width: 200, 46 | }, 47 | { 48 | title: '配额', 49 | key: 'quota', 50 | width: 100, 51 | }, 52 | { 53 | title: '接入点', 54 | key: 'endpoint', 55 | width: 160, 56 | }, 57 | { 58 | title: '密钥', 59 | key: 'key', 60 | width: 160, 61 | } 62 | ]; -------------------------------------------------------------------------------- /src/views/chatbot/columnsUser.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | import { BasicColumn } from '@/components/Table'; 4 | export interface ListData { 5 | id: string; // 定义名字属性,可选 6 | botname: string; 7 | wxid: string; 8 | name: string; 9 | prompt: string; 10 | quota: string; 11 | state: string; 12 | info: string; 13 | recordId?: string; 14 | } 15 | export const columns: BasicColumn[] = [ 16 | // { 17 | // title: '序号', 18 | // key: 'recordId', 19 | // width: 120, 20 | // }, 21 | { 22 | title: '用户名称', 23 | key: 'name', 24 | width: 100, 25 | }, 26 | { 27 | title: '备注名称', 28 | key: 'alias', 29 | width: 200, 30 | }, 31 | { 32 | title: '用户ID', 33 | key: 'wxid', 34 | width: 200, 35 | }, 36 | 37 | { 38 | title: '机器人ID', 39 | key: 'id', 40 | width: 100, 41 | }, 42 | { 43 | title: '机器人名称', 44 | key: 'botname', 45 | width: 100, 46 | }, 47 | { 48 | title: '用户提示词', 49 | key: 'prompt', 50 | width: 200, 51 | }, 52 | { 53 | title: '配额', 54 | key: 'quota', 55 | width: 100, 56 | }, 57 | { 58 | title: '启动状态', 59 | key: 'state', 60 | width: 160, 61 | }, 62 | { 63 | title: '备注', 64 | key: 'info', 65 | width: 160, 66 | } 67 | ]; -------------------------------------------------------------------------------- /src/views/chatbot/layout.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /src/views/contact/apply.vue: -------------------------------------------------------------------------------- 1 | 15 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/views/contact/layout.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 52 | -------------------------------------------------------------------------------- /src/views/example/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/views/keyword/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | import { BasicColumn } from '@/components/Table'; 4 | export interface ListData { 5 | name?: string; // 定义名字属性,可选 6 | desc?: string; 7 | type?: string; 8 | details?: number; 9 | recordId?: string; 10 | } 11 | export const columns: BasicColumn[] = [ 12 | { 13 | title: '序号', 14 | key: 'recordId', 15 | width: 150, 16 | }, 17 | { 18 | title: '类型', 19 | key: 'type', 20 | width: 200, 21 | }, 22 | { 23 | title: '关键词', 24 | key: 'name', 25 | width: 200, 26 | }, 27 | { 28 | title: '说明', 29 | key: 'desc', 30 | width: 200, 31 | }, 32 | { 33 | title: '详细说明', 34 | key: 'details', 35 | width: 200, 36 | } 37 | ]; -------------------------------------------------------------------------------- /src/views/message/inner/IndexAmicable.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 26 | -------------------------------------------------------------------------------- /src/views/message/inner/Skeleton.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 29 | -------------------------------------------------------------------------------- /src/views/message/inner/panel/SkipBottom.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 28 | 29 | 61 | -------------------------------------------------------------------------------- /src/views/note/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/views/notice/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | import { BasicColumn } from '@/components/Table'; 4 | export interface ListData { 5 | alias: string 6 | id: string 7 | name: string 8 | type: string 9 | desc: string 10 | time: number 11 | cycle: string 12 | state: string 13 | syncStatus: string 14 | lastOperationTime: number 15 | recordId: string 16 | } 17 | export const columns: BasicColumn[] = [ 18 | // { 19 | // title: '序号', 20 | // key: 'recordId', 21 | // width: 150, 22 | // }, 23 | { 24 | title: '内容', 25 | key: 'desc', 26 | width: 300, 27 | }, 28 | { 29 | title: '类型', 30 | key: 'type', 31 | width: 100, 32 | }, 33 | { 34 | title: '昵称/群名称', 35 | key: 'name', 36 | width: 100, 37 | }, 38 | { 39 | title: '好友ID/群ID', 40 | key: 'id', 41 | width: 100, 42 | }, 43 | { 44 | title: '好友备注', 45 | key: 'alias', 46 | width: 100, 47 | }, 48 | { 49 | title: '周期', 50 | key: 'cycle', 51 | width: 100, 52 | }, 53 | { 54 | title: '状态', 55 | key: 'state', 56 | width: 100, 57 | }, 58 | { 59 | title: '同步状态', 60 | key: 'syncStatus', 61 | width: 100, 62 | }, 63 | { 64 | title: '更新时间', 65 | key: 'lastOperationTime', 66 | width: 160, 67 | render(row) { 68 | return new Date(row.lastOperationTime as number).toLocaleString(); 69 | }, 70 | } 71 | ]; -------------------------------------------------------------------------------- /src/views/plugin/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | import { BasicColumn } from '@/components/Table'; 4 | export interface ListData { 5 | desc: string 6 | time: number 7 | cycle: string 8 | state: string 9 | syncStatus: string 10 | lastOperationTime: number 11 | action: string 12 | recordId: string 13 | } 14 | export const columns: BasicColumn[] = [ 15 | { 16 | title: '序号', 17 | key: 'recordId', 18 | width: 150, 19 | }, 20 | { 21 | title: '内容', 22 | key: 'desc', 23 | width: 300, 24 | }, 25 | { 26 | title: '周期', 27 | key: 'cycle', 28 | width: 100, 29 | }, 30 | { 31 | title: '状态', 32 | key: 'state', 33 | width: 100, 34 | }, 35 | { 36 | title: '同步状态', 37 | key: 'syncStatus', 38 | width: 100, 39 | }, 40 | { 41 | title: '更新时间', 42 | key: 'lastOperationTime', 43 | width: 160, 44 | render(row) { 45 | return new Date(row.lastOperationTime as number).toLocaleString(); 46 | }, 47 | } 48 | ]; -------------------------------------------------------------------------------- /src/views/plugin/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 32 | 33 | 42 | -------------------------------------------------------------------------------- /src/views/qa/CellColumns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | 4 | export const columns = [ 5 | { 6 | title: 'id', 7 | key: 'id', 8 | width: 100, 9 | }, 10 | { 11 | title: '编码', 12 | key: 'no', 13 | width: 100, 14 | }, 15 | { 16 | title: '名称', 17 | key: 'name', 18 | editComponent: 'NInput', 19 | // 默认必填校验 20 | editRule: true, 21 | edit: true, 22 | width: 200, 23 | }, 24 | { 25 | title: '头像', 26 | key: 'avatar', 27 | width: 100, 28 | render(row) { 29 | return h(NAvatar, { 30 | size: 48, 31 | src: row.avatar, 32 | }); 33 | }, 34 | }, 35 | { 36 | title: '地址', 37 | key: 'address', 38 | editComponent: 'NSelect', 39 | editComponentProps: { 40 | options: [ 41 | { 42 | label: '广东省', 43 | value: 1, 44 | }, 45 | { 46 | label: '浙江省', 47 | value: 2, 48 | }, 49 | ], 50 | }, 51 | edit: true, 52 | width: 200, 53 | ellipsis: false, 54 | }, 55 | { 56 | title: '开始日期', 57 | key: 'beginTime', 58 | edit: true, 59 | width: 160, 60 | editComponent: 'NDatePicker', 61 | editComponentProps: { 62 | type: 'datetime', 63 | format: 'yyyy-MM-dd HH:mm:ss', 64 | valueFormat: 'yyyy-MM-dd HH:mm:ss', 65 | }, 66 | ellipsis: false, 67 | }, 68 | { 69 | title: '结束日期', 70 | key: 'endTime', 71 | width: 160, 72 | }, 73 | { 74 | title: '创建时间', 75 | key: 'date', 76 | width: 160, 77 | }, 78 | { 79 | title: '停留时间', 80 | key: 'time', 81 | width: 80, 82 | }, 83 | ]; 84 | -------------------------------------------------------------------------------- /src/views/qa/basicColumns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar, NTag } from 'naive-ui'; 3 | 4 | export const columns = [ 5 | { 6 | title: 'id', 7 | key: 'id', 8 | width: 100, 9 | }, 10 | { 11 | title: '编码', 12 | key: 'no', 13 | width: 100, 14 | }, 15 | { 16 | title: '名称', 17 | key: 'name', 18 | width: 100, 19 | }, 20 | { 21 | title: '头像', 22 | key: 'avatar', 23 | width: 100, 24 | render(row) { 25 | return h(NAvatar, { 26 | size: 48, 27 | src: row.avatar, 28 | }); 29 | }, 30 | }, 31 | { 32 | title: '地址', 33 | key: 'address', 34 | width: 150, 35 | }, 36 | { 37 | title: '开始日期', 38 | key: 'beginTime', 39 | width: 160, 40 | }, 41 | { 42 | title: '结束日期', 43 | key: 'endTime', 44 | width: 160, 45 | }, 46 | { 47 | title: '状态', 48 | key: 'status', 49 | width: 100, 50 | render(row) { 51 | return h( 52 | NTag, 53 | { 54 | type: row.status ? 'success' : 'error', 55 | }, 56 | { 57 | default: () => (row.status ? '启用' : '禁用'), 58 | } 59 | ); 60 | }, 61 | }, 62 | { 63 | title: '创建时间', 64 | key: 'date', 65 | width: 160, 66 | }, 67 | { 68 | title: '停留时间', 69 | key: 'time', 70 | width: 80, 71 | }, 72 | ]; 73 | -------------------------------------------------------------------------------- /src/views/qa/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | import { BasicColumn } from '@/components/Table'; 4 | export interface ListData { 5 | skillname?: string; // 定义名字属性,可选 6 | title?: string; 7 | question1?: string; 8 | question2?: number; 9 | answer?: string; 10 | state?: string; 11 | syncStatus?: string; 12 | lastOperationTime: string; 13 | action?: string; 14 | recordId?: string; 15 | } 16 | export const columns: BasicColumn[] = [ 17 | // { 18 | // title: '序号', 19 | // key: 'recordId', 20 | // width: 150, 21 | // }, 22 | { 23 | title: '分类', 24 | key: 'skillname', 25 | width: 100, 26 | }, 27 | { 28 | title: '标准问题', 29 | key: 'title', 30 | width: 200, 31 | }, 32 | { 33 | title: '相似问题1', 34 | key: 'question1', 35 | width: 200, 36 | }, 37 | { 38 | title: '相似问题2', 39 | key: 'question2', 40 | width: 200, 41 | }, 42 | { 43 | title: '回答内容', 44 | key: 'answer', 45 | width: 160, 46 | }, 47 | { 48 | title: '状态', 49 | key: 'state', 50 | width: 100, 51 | }, 52 | { 53 | title: '同步状态', 54 | key: 'syncStatus', 55 | width: 100, 56 | }, 57 | { 58 | title: '更新时间', 59 | key: 'lastOperationTime', 60 | width: 160, 61 | render(row) { 62 | return new Date(row.lastOperationTime).toLocaleString(); 63 | }, 64 | } 65 | ]; -------------------------------------------------------------------------------- /src/views/qa/layout.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /src/views/setting/binding.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 38 | 39 | 58 | -------------------------------------------------------------------------------- /src/views/setting/layout.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /src/views/statistic/binding.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 38 | 39 | 58 | -------------------------------------------------------------------------------- /src/views/statistic/layout.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /src/views/whitelist/binding.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 38 | 39 | 58 | -------------------------------------------------------------------------------- /src/views/whitelist/layout.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /src/vue-shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ['./index.html', './src/**/*.{vue,ts,tsx}'], 3 | important: true, 4 | theme: { 5 | extend: {}, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strictFunctionTypes": false, 10 | "jsx": "preserve", 11 | "baseUrl": ".", 12 | "allowJs": true, 13 | "sourceMap": true, 14 | "esModuleInterop": true, 15 | "resolveJsonModule": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "experimentalDecorators": true, 19 | "noEmit": true, 20 | "allowImportingTsExtensions": true, 21 | "types": [ 22 | "vite/client" 23 | ], 24 | "noImplicitAny": false, 25 | "skipLibCheck": true, 26 | "paths": { 27 | "@/*": [ 28 | "src/*" 29 | ], 30 | "/#/*": [ 31 | "types/*" 32 | ] 33 | } 34 | }, 35 | "include": [ 36 | "env.d.ts", 37 | "src/**/*.ts", 38 | "src/**/*.d.ts", 39 | "src/**/*.tsx", 40 | "src/**/*.vue", 41 | "assets/**/*.jpg", 42 | "vite-env.d.ts", 43 | "src/vue-shim.d.ts" 44 | ], 45 | "exclude": [ 46 | "node_modules", 47 | "dist", 48 | "**/*.js" 49 | ] 50 | } -------------------------------------------------------------------------------- /vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv } from 'vite' 2 | import { fileURLToPath, URL } from 'node:url' 3 | import vue from '@vitejs/plugin-vue' 4 | import vueJsx from '@vitejs/plugin-vue-jsx' 5 | import compressPlugin from 'vite-plugin-compression' 6 | import type { UserConfig, ConfigEnv } from 'vite'; 7 | import { resolve } from 'path'; 8 | 9 | function pathResolve(dir: string) { 10 | return resolve(process.cwd(), '.', dir); 11 | } 12 | 13 | // https://vitejs.dev/config/ 14 | export default defineConfig(({ mode }) => { 15 | // 根据当前工作目录中的 `mode` 加载 .env 文件 16 | // 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。 17 | const env = loadEnv(mode, process.cwd(), 'VITE') 18 | 19 | return { 20 | base: env.VITE_BASE, 21 | resolve: { 22 | alias:[ 23 | { 24 | find: /\/#\//, 25 | replacement: pathResolve('types') + '/', 26 | }, 27 | { 28 | find: '@', 29 | replacement: pathResolve('src') + '/', 30 | }, 31 | // { 32 | // '@': fileURLToPath(new URL('./src', import.meta.url)) 33 | // } 34 | ] , 35 | dedupe: ['vue'], 36 | extensions: ['.js', '.json', 'jsx', '.vue', '.ts'], // 使用路径别名时想要省略的后缀名,可以自己 增减 37 | }, 38 | root: process.cwd(), 39 | assetsInclude: ['./src/assets'], 40 | plugins: [vue(), vueJsx({}), compressPlugin()], 41 | define: { 42 | __APP_ENV__: env.APP_ENV, 43 | }, 44 | build:{ 45 | chunkSizeWarningLimit:1000 46 | }, 47 | } 48 | }) 49 | --------------------------------------------------------------------------------