新增前端vue

This commit is contained in:
2025-11-26 13:55:01 +08:00
parent ae391f1b94
commit ffd5a6ad66
781 changed files with 83348 additions and 0 deletions

28
web-vue/.editorconfig Normal file
View File

@@ -0,0 +1,28 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
max_line_length = 100
[*.{ts,mts,tsx,vue}]
ij_typescript_use_chained_calls_group_indents = false
ij_typescript_use_double_quotes = false
ij_typescript_use_explicit_js_extension = auto
ij_typescript_use_import_type = auto
ij_typescript_use_path_mapping = always
ij_typescript_use_public_modifier = false
ij_typescript_use_semicolon_after_statement = true
[*.{yml,yaml,json}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

View File

@@ -0,0 +1,22 @@
### 是什么问题、该问题是怎么引起的?
1.
### 重现步骤、期望结果、截图、代码
1.
```
这里贴你的代码块
```
### 实际结果、报错信息、截图
1.
```
这里贴错误信息
```
### 环境版本:
- 浏览器版本Chrome xx、Firefox xx、其它
- 平台版本JeeSite 4.x.x、5.x.xpackage.json里查看

36
web-vue/.gitignore vendored Normal file
View File

@@ -0,0 +1,36 @@
node_modules
Thumbs.db
.DS_Store
.vercel
.turbo
.cache
dist
upgrade
# Test files
tests/server/static
tests/server/static/upload
coverage
# Local files
.local
.env.local
.env.*.local
.eslintcache
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
vite.config.ts.timestamp*
# IDE files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.iml

5
web-vue/.npmrc Normal file
View File

@@ -0,0 +1,5 @@
@jeesite:registry=https://maven.jeesite.net/repository/npm-package/
registry=https://registry.npmmirror.com
package-manager-strict=false
auto-install-peers = true
git-checks=false

14
web-vue/.prettierignore Normal file
View File

@@ -0,0 +1,14 @@
dist
public
node_modules
.local
.npmrc
.output.js
*.sh
*.md
*.svg
*.html
*.json
*-lock.yaml

32
web-vue/.prettierrc.mjs Normal file
View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
export default {
printWidth: 120,
tabWidth: 2,
useTabs: false,
semi: true,
vueIndentScriptAndStyle: true,
singleQuote: true,
quoteProps: 'as-needed',
bracketSpacing: true,
trailingComma: 'all',
jsxSingleQuote: false,
arrowParens: 'always',
insertPragma: false,
requirePragma: false,
proseWrap: 'preserve',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
plugins: ['prettier-plugin-packagejson'],
overrides: [
{
files: '.*rc',
options: {
parser: 'json',
},
},
],
};

6
web-vue/.stylelintignore Normal file
View File

@@ -0,0 +1,6 @@
dist
public
node_modules
*.sh
*.md

223
web-vue/LICENSE Normal file
View File

@@ -0,0 +1,223 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (C) 2013-Now, http://jeesite.com (thinkgem@163.com).
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
============================================================================
授权许可补充协议条款:
1. 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款。
2. 不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
3. 在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议、版权声明和其他原作者
规定需要包含的说明(请尊重原作者的著作权,不要删除或修改文件中的`Copyright`和`@author`信息)
更不要,全局替换源代码中的 jeesite 或 ThinkGem 等字样,否则你将违反本协议条款承担责任。
4. 基于本软件的作品,只能使用 JeeSite5 作为后台服务,除外情况不可商用且不允许二次分发或开源。
5. 您若套用本软件的一些代码或功能参考,请保留源文件中的版权和作者,需要在您的软件介绍明显位置
说明出处,举例:本软件基于 JeeSite Vue 快速开发平台并附带链接http://jeesite.com
6. 任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
7. 如果你对本软件有改进,希望可以贡献给我们,共同进步。
8. 本项目已申请软件著作权,请尊重开源,感谢阅读。
版权所有:济南卓源软件有限公司
官方网址http://jeesite.com
技术服务http://s.jeesite.com

819
web-vue/README.md Normal file
View File

@@ -0,0 +1,819 @@
<p align="center">
<img alt="JeeSite" src="https://jeesite.com/assets/images/logo.png" width="120" height="120" style="margin-bottom: 10px;">
</p>
<h3 align="center" style="margin:30px 0 30px;font-weight:bold;font-size:30px;">
JeeSite Vue3 前端源码<br>
使用 Turborepo、Monorepo、pnpm<br>
快速构建、模块化、代码复用、高效管理
</h3>
<p align="center">
<a href="https://v3.cn.vuejs.org/" target="__blank"><img alt="TypeScript-Vue3" src="https://img.shields.io/badge/TypeScript-Vue3-green.svg"></a>
<a href="https://www.antdv.com/" target="__blank"><img alt="Ant Design Vue-V4.x" src="https://img.shields.io/badge/Ant Design Vue-V4.x-brightgreen.svg"></a>
<a href="https://jeesite.com" target="__blank"><img alt="JeeSite-V5.x" src="https://img.shields.io/badge/JeeSite-V5.x-blue.svg"></a>
<a href="https://gitee.com/thinkgem/jeesite5" target="__blank"><img alt="star" src="https://gitee.com/thinkgem/jeesite5/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/thinkgem/jeesite-vue" target="__blank"><img alt="star" src="https://gitee.com/thinkgem/jeesite-vue/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/thinkgem/jeesite" target="__blank"><img alt="star" src="https://gitee.com/thinkgem/jeesite/badge/star.svg?theme=gvp"></a>
<a href="https://gitcode.com/thinkgem/jeesite" target="__blank"><img alt="star" src="https://gitcode.com/thinkgem/jeesite/star/badge.svg"></a>
</p>
------
<div align="center">
如果你喜欢 JeeSite请给她一个 ⭐️ Star您的支持将是我们前行的动力。
</div>
------
1. 单仓多包 pnpm + Turborepo 涡轮增压,提升编译速度,方便统一管理脚本任务
2. 按功能模块进行拆分为不同的包,方便进行团队开发源码管理,可根据需要进行发包
3. 模块之间松耦合,单依赖,公共模块,公共组件,公共工具,方便代码复用
4. 可方便从传统架构版本,升级到 Monorepo 模块化、分包架构
## 技术交流
* 官方网站:<https://jeesite.com>
* 使用文档:<https://jeesite.com/docs>
* 问题反馈:[http://jeesite.net](https://gitee.com/thinkgem/jeesite5/issues/new) &nbsp;|&nbsp;
[gitcode](https://gitcode.com/thinkgem/jeesite/issues/create/choose) &nbsp;|&nbsp;
[新手必读](https://gitee.com/thinkgem/jeesite5/issues/I18ARR)
* 联系我们:<http://s.jeesite.com>
* 关注微信公众号,了解最新动态:
<p style="padding-left:40px">
  <img alt="JeeSite微信公众号" src="https://jeesite.com/assets/images/mp.png" width="200">
</p>
* QQ 群:`127515876``209330483``223507718``709534275``730390092``1373527``183903863(外包)`
* 微信群如果二维码过期请尝试点击图片并F5刷新或者添加客服微信 jeesitex 邀请您进群
<p style="padding-left:40px"><a href="https://jeesite.com/assets/images/wxg_cur.png" target="_blank">
  <img alt="JeeSite微信群" src="https://jeesite.com/assets/images/wxg_cur.png" width="200"/></a>
</p>
* 后端源码仓库地址:
[Gitee](https://gitee.com/thinkgem/jeesite5)、
[GitCode](https://gitcode.com/thinkgem/jeesite5)、
[GitHub](https://github.com/thinkgem/jeesite5)
* 前端源码仓库地址:
[Gitee](https://gitee.com/thinkgem/jeesite-vue)、
[GitCode](https://gitcode.com/thinkgem/jeesite-vue)、
[GitHub](https://github.com/thinkgem/jeesite-vue)
* 源码合集仓库地址:
[GVP](https://gitee.com/thinkgem/jeesite/tree/main)、
[G-Star](https://gitcode.com/thinkgem/jeesite/tree/main)、
[GitHub](https://github.com/thinkgem/jeesite/tree/main)
## 平台介绍
* JeeSite 快速开发平台,低代码,轻量级,不仅仅是一个后台开发框架,它是一个企业级快速开发解决方案,后端基于经典组合 Spring Boot、Shiro、MyBatis前端采用分离版 Vue3、Vite、Monorepo、Ant Design Vue、TypeScript、Vben Admin 最先进技术栈,或者 Beetl、Bootstrap、AdminLTE 经典开发模式。
* 提供在线数据源管理、数据表建模、代码生成等功能,可自动创建业务模块代码工程和微服务模块代码工程,自动生成前端代码和后端代码;包括核心功能模块如:组织机构、用户、角色、岗位、管理员、权限审计、菜单及按钮权限、数据权限、模块管理、系统参数、字典管理、系统监控、数据监控等;扩展功能如:工作流引擎、内容管理、消息推送、单点登录、第三方登录、在线作业调度、对象存储、可视化数据大屏、报表设计器、在线文件预览、国际化、全文检索、统一认证服务等。
* 本平台采用松耦合设计,真正的轻量级,微内核,快速部署,插件架构,模块增减便捷,支持扩展 SaaS 架构、集群部署、读写分离、分库分表、Spring Cloud 微服务架构;并内置了众多账号安全设置、密码策略、系统访问限制等安全解决方案,支持等保评测。
* 本平台专注于为初级研发人员提供强大的支持,使他们能够高效、快速地开发出复杂的业务功能,同时为中高级人员腾出宝贵的时间,专注于更具战略性和创新性的任务。我们致力于让开发者能够全心投入业务逻辑中,而将繁琐的技术细节交由平台来封装处理。这不仅降低了技术实现的难度,还确保了系统架构的稳定性和安全性,进而帮助企业节省人力成本、缩短项目周期,并提高整体软件的安全性和质量。
* 2013 年发布以来已被广大爱好者用到了企业、政府、医疗、金融、互联网等各个领域中,拥有:精良架构、易于扩展、大众思维的设计模式,工匠精神,用心打磨每一个细节,深入开发者的内心,并荣获开源中国《最受欢迎中国开源软件》多次奖项,期间也帮助了不少刚毕业的大学生,教师作为入门教材,快速的去实践。
* 2019 年换代升级,我们结合了多年总结和经验,以及各方面的应用案例,对架构完成了一次全部重构,也纳入很多新的思想。不管是从开发者模式、底层架构、逻辑处理还是到用户界面,用户交互体验上都有很大的进步,在不忘学习成本、提高开发效率的情况下,安全方面也做和很多工作,包括:身份认证、密码策略、安全审计、日志收集等众多安全选项供您选择。努力为大中小微企业打造全方位企业级快速开发解决方案。
* 2021 年终发布 Vue3 的前后分离版本,使得 JeeSite 拥有同一个后台服务 Web 来支撑分离版和全栈版两套前端技术栈。
* 对接 OpenAPI、Ollama、DeepSeek 等热门 AI 大模型,凭借检索增强生成 RAG 技术,为企业知识库打造专属智能对话。
* 提供大模型 Tool 本地工具调用及 MCP 服务端和客户端工具调用,助力大模型与您的业务深度融合,实现高效交互。
* 支持国产化软件和硬件环境,如国产芯片、操作系统、数据库、中间件、国密算法等。
## 核心优势
* JeeSite 非常易于二次开发,可控性高,整体架构清晰、技术稳定而先进、源代码书写规范、经典技术会的人多、易于维护、易于扩展、安全稳定。
* JeeSite 功能全,知识点非常多,也非常少。因为她使用的都是一些通用的技术,通俗的设计风格,大多数基础知识点,多数人都能掌握,所以每一个 JeeSite 的功能点都非常容易掌握。只要您学会使用这些功能和组件的应用,就可以顺利地完成系统开发了。
* JeeSite 是一个低代码开发平台具有较高的封装度、扩展性封装不是限制您去做一些事情而是在便捷的同时也具有较好的扩展性在不具备一些功能的情况下JeeSite 提供了扩展接口,提供了原生调用方法。
* 大家都在用 Spring也在学习 Spring 的优点Spring 提供了较好的扩展性,可又有多少人去修改它的源代码呢,退一步说,大家去修改了 Spring 的源码反而会对未来升级造成很大困扰您说不是呢这样的例子很多所以不要纠结我们非常注重这一点JeeSite 也一样具备强大的扩展性。为你解决升级的困扰。
* 为什么说 JeeSite 比较易于学习JeeSite 很好的把握了设计的 “度”,避免过度设计的情况。过度设计是在产品设计过程中忽略了产品和用户的实际需求,反而带来了不必要的复杂性,而忽略了系统的学习、开发和维护成本。
------
* 至今 JeeSite 平台架构已经非常稳定,我们持续升级,并不失架构的先进性。
* JeeSite 精益求精,用心打磨每一个细节,界面 UI 操作便捷,体验性好。
* JeeSite 是一个专业的平台,是一个可以让您,用着省心的平台。
* 社区版基于 Apache License 2.0 开源协议,永久免费使用。
### 架构特点及安全方面的优势:<https://jeesite.com/docs/feature/>
## Vue 前端简介
基于 Vue3、Vite、Ant-Design-Vue、TypeScript 和 Vue Vben Admin 等前沿技术栈构建,本软件采用最先进的技术架构,
帮助初学者快速上手并融入团队开发。内置组织机构、角色用户、菜单授权、数据权限、系统参数等核心模块,结合强大的组件封装
与数据驱动视图设计,为微小、中大型项目提供开箱即用的解决方案和丰富的示例,助力高效开发。
用户界面专为信息化管理后台量身打造,在界面设计上精益求精,每一处细节都彰显着精致,为用户带来优雅且直观的操作体验。
它提供多样化的菜单布局、智能的页签管理、高效的树表操作体验、强大的表格组件、灵活的表单组件设计,具备强大的扩展能力,
同时支持黑暗布局风格,为用户提供高效、灵活且美观的操作体验,满足各类管理后台的复杂需求。
另外我们还支持 Turborepo + Monorepo 快速构建、模块化、代码复用、支持分包开发
详见:<https://gitee.com/thinkgem/jeesite-vue/tree/monorepo/>
## 前端技术特点
定义众多组件,非常贴心的组件属性及小功能,符合 JeeSite 以往的设计思想,列表和表单以数据驱动视图,
极大简化了业务功能开发, 注释分解详见【[源码解析](https://jeesite.com/docs/vue-crud-view)】
为什么做数据驱动视图?前端向下兼容一直是最大的问题,有了一套相应的标准,会对框架升级帮助很大。
比如你可以非常小的成本,业务代码改动非常小的情况下,去升级前端;数据驱动视图可以为未来自定义拖拽表单做更好的铺垫,
数据存储结构更清晰化,更利于维护。
提示:请仔细阅读源码解析,表单视图和列表视图上的注释哦,支持复杂表单以及多表单联合使用。
## 演示地址
1. 地址:<http://vue.jeesite.com/>
## 学习准备
- [VSCode](https://code.visualstudio.com/) - 推荐 IDE 集成开发工具
- [Node.js 20](https://nodejs.org/dist/latest-v20.x/) 和 [git](https://git-scm.com/) - 开发环境
- [Vite](https://vitejs.dev/) - 熟悉 Vite 特性
- [Vue-v3](https://cn.vuejs.org/) - 熟悉 Vue 基础语法
- [TypeScript](https://www.typescriptlang.org/) - 熟悉 TS 基本语法
- [ES6+](http://es6.ruanyifeng.com/) - 熟悉 ES6 基本语法
- [Vue-Router-v4](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用
- [Vue-Vben-Admin](https://jeesite.com/front/vben-admin/) - 熟悉 UI 及表单列表及常用组件使用
- [Ant-Design-Vue](https://antdv.com/components/overview-cn/) - 熟悉 UI 基本使用
## 安装使用
- 如果没有安装 Node.js 20+,下载地址:<https://nodejs.org>
```bash
# 验证
node -v
# 配置国内源
npm config set registry https://registry.npmmirror.com
```
- 如果没有安装 Pnpm 执行安装
```bash
npm i -g pnpm
# 验证
pnpm -v
# 配置国内源
pnpm config set registry https://registry.npmmirror.com
```
- 获取源代码
```bash
git clone https://gitee.com/thinkgem/jeesite-vue.git
cd jeesite-vue
```
注意:不要放到中文或带空格的目录下。
- 安装依赖
```bash
pnpm install
```
- 开发环境运行访问(方式一)
```bash
pnpm dev
```
开发环境会加载文件较多,便于调试,请耐心等待。
- 编译打包后运行访问(方式二)
```bash
pnpm preview
```
编译打包后,会整合这些文件,所以访问性能会大大提高,生产环境可以开启 gzip
- 打包发布程序
```bash
pnpm build
```
打包完成后,会在根目录生成 dist 文件夹,发布 nginx。
详见文档:<https://jeesite.com/docs/vue-install-deploy/#部署到正式服务器>
### 后端服务
- 安装后台服务 [JeeSite v5.x](https://gitee.com/thinkgem/jeesite5/tree/v5.springboot3/)
- 打开 [.env.development](https://jeesite.com/docs/vue-settings/#env-development-详解) 文件,修改后台接口:
```bash
# 代理设置,可配置多个,不能换行,格式:[访问接口的根路径, 代理地址, 是否保持Host头]
# VITE_PROXY = [["/js","https://vue.jeesite.com/js",true]]
VITE_PROXY = [["/js","http://127.0.0.1:8980/js",false]]
# 访问接口的根路径例如https://vue.jeesite.com
VITE_GLOB_API_URL =
# 访问接口的前缀,在根路径之后
VITE_GLOB_API_URL_PREFIX = /js
```
### 如果您使用的 VSCode 的话,推荐安装以下插件:
* [UnoCSS](https://marketplace.visualstudio.com/items?itemName=antfu.unocss) - UnoCSS 提示插件
* [Iconify](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) - Iconify 图标插件
* [I18n-ally](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) - i18n 插件
* [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) - Vue3 开发必备Vetur禁用
* [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - 脚本代码检查
* [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - 代码格式化
* [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - CSS 格式化
* [DotENV](https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv) - .env 文件高亮
## 常见问题
* Vue 版本的浏览器支持情况支持所有现代浏览器Vue3 已不再支持 IE 浏览器。
* 为什么使用抽屉作为表单组件,因为抽屉空间更大,可以展示更多内容,且操作更友好。
* 如何将表单抽屉改为弹窗,替换 list 和 form 页面的 Drawer 为 Modal 即可V5.6增加了路由表单和弹窗表单的代码生成。
* 打不开代码生成工具怎么办?提示 404请检查 .env.development 中的代理配置 VITE_PROXY 最后一个参数是否保持Host头本地服务 127.0.0.1 应设置为 false远程服务设置为 true。
* 更多文档详见:<https://jeesite.com/docs/vue-faq/#常见问题>
## 软件截图
<img src="https://oscimg.oschina.net/oscnet/up-db83c334daab05d89a0930d8497816da6a4.png"/>
<img src="https://oscimg.oschina.net/oscnet/up-685134ad2a721e0a7818efe4201d476e332.png"/>
<img src="https://oscimg.oschina.net/oscnet/up-3ac1f31bdb399431cd0f6af479acc7f2e58.png"/>
<img src="https://oscimg.oschina.net/oscnet/up-6a4c84696b589ba2a3bd034c2b9026b40e1.png"/>
<img src="https://oscimg.oschina.net/oscnet/up-b85f44704e4a8c1e3a50f1c10e7e413341a.png"/>
<img src="https://oscimg.oschina.net/oscnet/up-f03d07d58b351e1f2fc9931ea2dba550429.png"/>
<img src="https://oscimg.oschina.net/oscnet/up-a36a005290cb95bc625e0933c867edc7e6f.png"/>
## 附录
### 表单视图
```html
<template>
<!-- 弹出抽屉组件如果想改为弹窗Drawer 换为 Modal 即可快速替换 -->
<BasicDrawer
v-bind="$attrs" -- 传递来自父组件的属性
:showFooter="true" -- 显示弹窗底部按钮组
:okAuth="'test:testData:edit'" -- 提交按钮权限控制按钮是否显示
@register="registerDrawer" -- 弹窗后的回调方法
@ok="handleSubmit" -- 提交按钮调用方法
width="60%" -- 弹窗宽度支持按比例
>
<!-- 弹窗标题 -->
<template #title>
<Icon :icon="getTitle.icon" class="pr-1 m-1" /> -- 图标
<span> {{ getTitle.value }} </span> -- 标题名称
</template>
<!-- 表单组件 -->
<BasicForm @register="registerForm">
<!-- 定义表单控件插槽、个性化表单控件,如:这是一个表单子表插槽 -->
<template #testDataChildList>
<BasicTable
@register="registerTestDataChildTable"
@row-click="handleTestDataChildRowClick"
/>
<!-- 子表新增按钮 -->
<a-button class="mt-2" @click="handleTestDataChildAdd">
<Icon icon="i-ant-design:plus-circle-outlined" /> {{ t('新增') }}
</a-button>
</template>
</BasicForm>
</BasicDrawer>
</template>
<!-- script name: 当前组件名称(与路由名一致,如果不一致会页面缓存失效)-->
<script lang="ts" setup name="ViewsTestTestDataForm">
// 导入当前用到的对象,部分省略
import { ref, unref, computed } from 'vue';
import { officeTreeData } from '/@/api/sys/office';
// 页面事件定义
const emit = defineEmits(['success', 'register']);
// 国际化方法调用,参数是国际化编码的根路径
const { t } = useI18n('test.testData');
// 消息弹窗方法
const { showMessage } = useMessage();
// 路由meta信息
const { meta } = unref(router.currentRoute);
// 当前页面数据记录
const record = ref<Recordable>({});
// 当前页面标题定义,来自菜单管理定义
const getTitle = computed(() => ({
icon: meta.icon || 'ant-design:book-outlined',
value: record.value.isNewRecord ? t('新增数据') : t('编辑数据'),
}));
// 输入表单控件定义
const inputFormSchemas: FormSchema[] = [
{
label: t('单行文本'), // 控件前面的页签
field: 'testInput', // 字段提交参数名
component: 'Input', // 控件类型(可自定义,更多查看 componentMap.ts
componentProps: { // 组件属性定义
maxlength: 200,
},
required: true, // 表单验证,是否必填(快速定义)
rules: [ // 如果不只是必填,需要通过 rules 定义,举例:
{ required: true },
{ min: 4, max: 20, message: t('请输入长度在 4 到 20 个字符之间') },
{ pattern: /^[\u0391-\uFFE5\w]+$/, message: t('不能输入特殊字符') },
{
validator(_rule, value) {
return new Promise((resolve, reject) => {
if (!value || value === '') return resolve();
// 远程验证,访问后台校验数据是否重复
checkTestInput(record.value.testInput || '', value)
.then((res) => (res ? resolve() : reject(t('数据已存在'))))
.catch((err) => reject(err.message || t('验证失败')));
});
},
trigger: 'blur', // 如果是远程验证,可以减少请求频率
},
],
colProps: { lg: 24, md: 24 }, // 栅格布局(遵循 Ant Design 风格)
},
{
label: t('下拉框'),
field: 'testSelect',
component: 'Select', // 选择框还有 RadioGroup、CheckboxGroup
componentProps: {
dictType: 'sys_menu_type', // 下拉框选项数据(支持直接指定字典类型)
allowClear: true, // 启用空选项,可清空选择
mode: 'multiple', // 下拉框模块,启用多选
},
},
{
label: t('日期选择'),
field: 'testDate',
component: 'DatePicker',
componentProps: {
format: 'YYYY-MM-DD', // 日期选择
showTime: false, // 关闭时间选择
},
},
{
label: t('日期时间'),
field: 'testDatetime',
component: 'DatePicker',
componentProps: {
format: 'YYYY-MM-DD HH:mm', // 日期时间选择
showTime: { format: 'HH:mm' }, // 设置时间的格式
},
},
{
label: t('用户选择'),
field: 'testUser.userCode',
fieldLabel: 'testUser.userName', //【支持返回,如下拉框或树选择的节点名】
component: 'TreeSelect', // 树选择控件
componentProps: {
api: officeTreeData, // 数据源 API 定义,支持 ztree 格式
params: { isLoadUser: true, userIdPrefix: '' }, // API 参数
canSelectParent: false, // 是否允许选择父级
allowClear: true,
},
},
{
label: t('子表数据'),
field: 'testDataChildList',
component: 'Input',
colProps: { lg: 24, md: 24 },
slot: 'testDataChildList', // 指定插槽、个性化控件内容
},
];
// 当前表单的参数定义
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
labelWidth: 120, // 控件前面的标签宽度
schemas: inputFormSchemas, // 控件定义列表
baseColProps: { lg: 12, md: 24 }, // 控件默认栅格布局方式(响应式)
});
// 当前表单子表格定义
const [registerTestDataChildTable, testDataChildTable] = useTable({
actionColumn: { // 子表的操作列定义
width: 60, // 操作列宽度
actions: (record: Recordable) => [
{
icon: 'i-ant-design:delete-outlined',
color: 'error',
popConfirm: { // 是否需要启用确认框
title: '是否确认删除',
confirm: handleTestDataChildDelete.bind(this, record),
},
auth: 'sys:empUser:edit', // 按钮权限(可控制按钮是否显示)
},
],
},
rowKey: 'id', // 子表主键名
pagination: false,// 关闭分页
bordered: true, // 开启表格边框
size: 'small', // 单元格间距
inset: true, // 是否内嵌(去除一些边距)
});
// 当前表单子表自动定义
async function setTestDataChildTableData(_res: Recordable) {
testDataChildTable.setColumns([
{
title: t('单行文本'),
dataIndex: 'testInput',
width: 230,
align: 'left',
editRow: true, // 是否启用编辑
editComponent: 'Input', // 编辑控件(可自定义,更多查看 componentMap.ts
editRule: true, // 控件验证(是否必填)
},
{
title: t('下拉框'),
dataIndex: 'testSelect',
width: 130,
align: 'left',
dictType: 'sys_menu_type', // 指定字典类型,自动显示字典标签
editRow: true,
editComponent: 'Select',
editComponentProps: { // 控件属性
dictType: 'sys_menu_type', // 下拉框的字段类型
allowClear: true,
},
editRule: false,
},
// 更多组件控件不举例了,同表单控件 ...
]);
// 设定子表数据
testDataChildTable.setTableData(record.value.testDataChildList || []);
}
// 点击行,启用编辑
function handleTestDataChildRowClick(record: Recordable) {
record.onEdit?.(true, false);
}
// 添加编辑行,可指定初始数据
function handleTestDataChildAdd() {
testDataChildTable.insertTableDataRecord({
id: new Date().getTime(),
isNewRecord: true,
editable: true,
});
}
// 删除编辑行方法
function handleTestDataChildDelete(record: Recordable) {
testDataChildTable.deleteTableDataRecord(record);
}
// 获取子表数据(支持返回删除未提交的数据)
async function getTestDataChildList() {
let testDataChildListValid = true;
let testDataChildList: Recordable[] = [];
for (const record of testDataChildTable.getDataSource()) {
// 验证控件内容并取消行的编辑状态如果验证失败返回false
if (!(await record.onEdit?.(false, true))) {
testDataChildListValid = false;
}
testDataChildList.push({
...record,
id: !!record.isNewRecord ? '' : record.id,
});
}
for (const record of testDataChildTable.getDelDataSource()) {
if (!!record.isNewRecord) continue;
testDataChildList.push({
...record,
status: '1',
});
}
// 子表验证事件,抛出异常消息
if (!testDataChildListValid) {
throw { errorFields: [{ name: ['testDataChildList'] }] };
}
return testDataChildList;
}
// 弹窗后的回调事件,进行一些表单数据初始化等操作
const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
resetFields(); // 重置表单数据
setDrawerProps({ loading: true }); // 显示加载框
const res = await testDataForm(data); // 查询表单数据
record.value = (res.testData || {}) as Recordable;
setFieldsValue(record.value); // 设置字段值
setTestDataChildTableData(res); // 设置子表数据(没有子表可不写)
setDrawerProps({ loading: false }); // 隐藏加载框
});
// 表单提交按钮方法
async function handleSubmit() {
try {
const data = await validate(); // 验证表单,并返回数据
setDrawerProps({ confirmLoading: true }); // 显示提交加载中
// 设置提交的参数QueryString后台 Controller 的 get 接受)
const params: any = {
isNewRecord: record.value.isNewRecord,
id: record.value.id,
};
// 获取并设置子表数据
data.testDataChildList = await getTestDataChildList();
// console.log('submit', params, data, record);
// 将数据提交给后台(如果失败跳转到 catch
const res = await testDataSave(params, data);
showMessage(res.message); // 显示提交结果
setTimeout(closeDrawer); // 隐藏抽屉弹窗
emit('success', data); // 触发事件,列表数据刷新
} catch (error: any) {
if (error && error.errorFields) {
showMessage(t('您填写的信息有误,请根据提示修正。'));
}
console.log('error', error);
} finally {
setDrawerProps({ confirmLoading: false }); // 隐藏提交加载中
}
}
</script>
```
### 列表视图
```html
<template>
<div>
<!-- 表格组件 -->
<BasicTable @register="registerTable">
<!-- 表格标题插槽 -->
<template #tableTitle>
<Icon :icon="getTitle.icon" class="m-1 pr-1" />
<span> {{ getTitle.value }} </span>
</template>
<!-- 表格右侧按钮插槽,其中 v-auth 是按钮权限控制 -->
<template #toolbar>
<a-button type="primary" @click="handleForm({})" v-auth="'test:testData:edit'">
<Icon icon="i-fluent:add-12-filled" /> {{ t('新增') }}
</a-button>
</template>
<!-- 首列插槽 -->
<template #firstColumn="{ record }">
<a @click="handleForm({ id: record.id })">
{{ record.testInput }}
</a>
</template>
</BasicTable>
<!-- 点击表格行进入的输入表单弹窗 -->
<InputForm @register="registerDrawer" @success="handleSuccess" />
</div>
</template>
<!-- script name: 当前组件名称(与路由名一致,如果不一致会页面缓存失效)-->
<script lang="ts" setup name="ViewsTestTestDataList">
// 导入当前用到的对象,部分省略
import InputForm from './form.vue';
// 国际化方法调用,参数是国际化编码的根路径
const { t } = useI18n('test.testData');
// 消息弹窗方法
const { showMessage } = useMessage();
// 路由meta信息
const { meta } = unref(router.currentRoute);
// 当前页面标题定义,来自菜单管理定义
const getTitle = {
icon: meta.icon || 'ant-design:book-outlined',
value: meta.title || t('数据管理'),
};
// 表格搜索表单控件定义
const searchForm: FormProps = {
baseColProps: { lg: 6, md: 8 }, // 表单栅格布局
labelWidth: 90, // 表单标签宽度
schemas: [
{
label: t('单行文本'), // 表单标签
field: 'testInput', // 字段提交参数名
component: 'Input', // 表单控件
},
{
label: t('下拉框'),
field: 'testSelect',
component: 'Select', // 选择框还有 RadioGroup、CheckboxGroup
componentProps: {
dictType: 'sys_menu_type', // 下拉框选项数据(支持直接指定字典类型)
allowClear: true, // 启用空选项,可清空选择
mode: 'multiple', // 下拉框模块,启用多选
},
},
// 更多控件,再次不展示了,和上一节表单视图一致
],
};
// 表格列定义
const tableColumns: BasicColumn[] = [
{
title: t('单行文本'), // 表头标题
dataIndex: 'testInput', // 表列实体属性名
key: 'a.test_input', // 排序数据库字段名
sorter: true, // 点击表头是否可排序
width: 230, // 列宽
align: 'left', // 列的对齐方式
// 个性化列,可定义插槽(如样式,增加控件等)
slot: 'firstColumn',
},
{
title: t('下拉框'),
dataIndex: 'testSelect',
key: 'a.test_select',
sorter: true,
width: 130,
align: 'center',
dictType: 'sys_menu_type', // 字典列,快速显示字典标签
},
];
// 表格操作列定义
const actionColumn: BasicColumn = {
width: 160, // 操作列宽
actions: (record: Recordable) => [
{
icon: 'i-clarity:note-edit-line',
title: t('编辑数据'),
onClick: handleForm.bind(this, { id: record.id }),
// 按钮权限控制,指定权限字符串
auth: 'test:testData:edit',
},
{
icon: 'i-ant-design:stop-outlined',
color: 'error',
title: t('停用数据'),
// 是否需要启用确认框
popConfirm: {
title: t('是否确认停用数据'),
confirm: handleDisable.bind(this, { id: record.id }),
},
// 按钮权限控制,指定权限字符串
auth: 'test:testData:edit',
// 控制按钮是否显示区别show 是显示或隐藏ifShow 是显示或移除)
show: () => record.status === '0',
ifShow: () => record.status === '0',
},
],
// 操作列更多按钮定义
dropDownActions: (record: Recordable) => [
{
icon: 'i-ant-design:reload-outlined',
label: t('重置密码'),
onClick: handleResetpwd.bind(this, { userCode: record.userCode }),
auth: 'sys:empUser:resetpwd',
},
],
};
// 点击首列或编辑按钮是的抽屉弹窗定义
const [registerDrawer, { openDrawer }] = useDrawer();
// 表格定义
const [registerTable, { reload }] = useTable({
api: testDataListData, // 表格数据源 API
beforeFetch: (params) => {
return params; // API 提交之前的参数修改
},
columns: tableColumns, // 表格列
actionColumn: actionColumn,// 操作列
formConfig: searchForm, // 搜索表单
showTableSetting: true, // 是否显示右上角的设置按钮
useSearchForm: true, // 是否显示搜索表单
canResize: true, // 是否自适应表单高度
});
// 弹窗操作方法
function handleForm(record: Recordable) {
openDrawer(true, record);
}
// 操作列停用按钮方法
async function handleDisable(record: Recordable) {
const res = await testDataDisable(record);
showMessage(res.message);
handleSuccess();
}
// 刷新表格数据(含表单回调)
function handleSuccess() {
reload();
}
</script>
```
## 更多介绍
* 架构特点:<https://jeesite.com/docs/feature/>
* 内置功能:<https://jeesite.com/docs/function/>
* 目录结构:<https://jeesite.com/docs/catalog/>
* 参数配置:<https://jeesite.com/docs/config/>
* 开发规范:<https://jeesite.com/docs/standard/>
* 数表设计:<https://jeesite.com/docs/treetable/>
## 学习文档
* 库表生成、代码生成:<https://jeesite.com/docs/code-gen/>
* 菜单权限、按钮权限:<https://jeesite.com/docs/permi-shiro/>
* 数据权限、库事务:<https://jeesite.com/docs/service-datascope/#数据权限>
* 表结构、数据字典:<https://jeesite.com/docs/code-gen/#表结构数据字典>
* 持久层框架、SQL<https://jeesite.com/docs/dao-mybatis/>
* 后端常用工具:<https://jeesite.com/docs/sys-utils/>
**分离版**
* 版本介绍:<https://jeesite.com/docs/jeesite-vue/>
* 源码解析:<https://jeesite.com/docs/vue-crud-view/>
* 表单组件:<https://jeesite.com/docs/vue-basic-form/>
* 表格组件:<https://jeesite.com/docs/vue-basic-table/>
* 参数配置:<https://jeesite.com/docs/vue-settings/>
* 常用组件:<https://jeesite.com/docs/vue-comp/>
* 前端权限:<https://jeesite.com/docs/vue-auth/>
* 图标组件:<https://jeesite.com/docs/vue-icon/>
* 前端样式库:<https://jeesite.com/docs/vue-style/>
* 多语言国际化:<https://jeesite.com/docs/vue-i18n/>
**经典版**
* 表单组件:<https://jeesite.com/docs/views-beetl/>
* 表格组件:<https://jeesite.com/docs/datagrid/>
* 常用工具:<https://jeesite.com/docs/jeesite-js/>
* 自定义主题:<https://jeesite.com/docs/custom-views/>
## 更多文档
* AI、CMS、RAG、Tool、MCP 人工智能助手:<https://jeesite.com/docs/ai-cms>
* BPM 业务流程引擎Flowable<http://jeesite.com/docs/bpm/>
* CMS 多站点内容管理模块:<https://jeesite.com/docs/cms/>
* 消息推送消息提醒:<https://jeesite.com/docs/msg-push-use/>
* 对象存储模块:<https://jeesite.com/docs/oss-client>
* 单点登录模块:<https://jeesite.com/docs/sso-cas>
* 在线任务调度:<https://jeesite.com/docs/job/>
* 大屏设计器:<https://jeesite.com/docs/visual/>
* 报表设计器:<https://jeesite.com/docs/ureport/>
* 文件管理分享:<https://jeesite.com/docs/filemanager/>
* 文件在线预览:<https://jeesite.com/docs/filepreview/>
* 集群、高可用架构:<https://jeesite.com/docs/cluster/>
* SaaS 多租户架构:<https://jeesite.com/docs/saas-corp-use/>
* 读写分离分片分表:<https://jeesite.com/docs/sharding/>
* Spring监控系统<https://jeesite.com/docs/webadmin/>
* 分布式跨应用事务:<https://jeesite.com/docs/seata/>
* 追踪系统集成:<https://jeesite.com/docs/skywalking/>
* ELK 日志收集:<https://jeesite.com/docs/elk-log/>
* MybatisPlus: <https://gitee.com/thinkgem/jeesite-mybatisplus>
* 接口快速开发:<https://gitee.com/thinkgem/jeesite-magic-api>
* 内外网中间件:<https://my.oschina.net/thinkgem/blog/4624519>
* 统一认证平台:<https://jeesite.com/docs/oauth2-server>
## 授权许可协议条款
1. 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款。
2. 不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
3. 在使用本软件时,由于它集成了众多第三方开源软件,请共同遵守这些开源软件的使用许可条款规定。
4. 在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议、版权声明和其他原作者
规定需要包含的说明(请尊重原作者的著作权,不要删除或修改文件中的`Copyright``@author`信息)
更不要,全局替换源代码中的 jeesite 或 ThinkGem 等字样,否则你将违反本协议条款承担责任。
5. 基于本软件的作品,只能使用 JeeSite5 作为后台服务,除外情况不可商用且不允许二次分发或开源。
6. 您若套用本软件的一些代码或功能参考,请保留源文件中的版权和作者,需要在您的软件介绍明显位置
说明出处,举例:本软件基于 JeeSite Vue 快速开发平台并附带链接http://jeesite.com
7. 任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
8. 如果你对本软件有改进,希望可以贡献给我们,共同进步。
9. 本项目已申请软件著作权,请尊重开源,感谢阅读。
## 技术支持与服务
* 本软件免费,我们也提供了相应的收费服务,因为:
* 没有资金的支撑就很难得到发展,特别是一个好的产品,如果 JeeSite 帮助了您,请为我们点赞。支持我们,您可以获得更多回馈,我们会把公益事业做的更好,开放更多资源,回报社区和社会。请给我们一些动力吧,在此非常感谢已支持我们的朋友!
* **联系我们**:请访问技术支持与服务页面:<http://s.jeesite.com>
## 专业版增加的功能
1. 主题标签页的三种风格自由切换
2. 业务流程、流程设计、流程办理
3. 文件管理、上传秒传、文件预览
4. 高级折叠表单和个性化本地存储
5. 表格个性化设置参数本地存储
6. 租户管理功能、租户切换
7. 动态设置页面字体大小
8. 页签右键,在新窗口打开
9. 消息推送、消息提醒
10. 语言国际化、本地化
11. 快速升级到 Monorepo 脚本
12. 更多功能详见文档

9
web-vue/bin/build.bat Normal file
View File

@@ -0,0 +1,9 @@
@echo off
%~d0
cd %~dp0
cd..
npm run build
cd bin
pause

5
web-vue/bin/build.sh Normal file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
cd ..
pnpm build
cd bin

9
web-vue/bin/install.bat Normal file
View File

@@ -0,0 +1,9 @@
@echo off
%~d0
cd %~dp0
cd..
npm run install
cd bin
pause

5
web-vue/bin/install.sh Normal file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
cd..
pnpm install
cd bin

9
web-vue/bin/startup.bat Normal file
View File

@@ -0,0 +1,9 @@
@echo off
%~d0
cd %~dp0
cd..
pnpm dev
cd bin
pause

5
web-vue/bin/startup.sh Normal file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
cd ..
pnpm dev
cd bin

177
web-vue/eslint.config.mjs Normal file
View File

@@ -0,0 +1,177 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import globals from 'globals';
import vuePlugin from 'eslint-plugin-vue';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import prettierPlugin from 'eslint-plugin-prettier';
import tsParser from '@typescript-eslint/parser';
import vueParser from 'vue-eslint-parser';
import babelParser from '@babel/eslint-parser';
export default [
// 基础忽略配置
{
ignores: [
'**/node_modules/',
'**/dist/',
'**/public/',
'**/build/',
'**/.git/',
'**/.vscode/',
'**/.idea/',
'**/.husky/',
'**/.local/',
'**/.turbo/',
'**/Dockerfile',
'**/*.sh',
'**/*.md',
'**/*.woff',
'**/*.ttf',
'**/*.d.ts',
'**/__snapshots__/',
],
},
// 公共基础配置
{
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
...globals.browser,
...globals.node,
defineOptions: 'readonly',
},
parserOptions: {
requireConfigFile: false,
},
},
rules: {
// 通用规则
'no-case-declarations': 'off',
'no-extra-boolean-cast': 'off',
'no-undef': 'off',
'space-before-function-paren': 'off',
},
},
// Vue 文件配置
{
files: ['**/*.vue'],
plugins: {
vue: vuePlugin,
},
languageOptions: {
parser: vueParser,
parserOptions: {
parser: {
ts: tsParser,
tsx: tsParser,
js: babelParser,
'<template>': 'espree',
},
ecmaFeatures: {
jsx: true,
},
},
},
rules: {
// ...vuePlugin.configs['vue3-recommended'].rules,
// 'vue/script-setup-uses-vars': 'error',
'vue/no-reserved-component-names': 'off',
'vue/custom-event-name-casing': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'off',
},
},
// TypeScript 配置
{
files: ['**/*.ts', '**/*.tsx'],
plugins: {
'@typescript-eslint': tsPlugin,
},
languageOptions: {
parser: tsParser,
parserOptions: {
// project: './tsconfig.json',
// jsxPragma: 'React',
ecmaFeatures: {
jsx: true, // 启用 JSX 支持
},
},
},
rules: {
...tsPlugin.configs.recommended.rules,
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-unsafe-function-type': 'off',
},
},
// JavaScript 配置
{
files: ['**/*.js'],
languageOptions: {
parser: babelParser,
parserOptions: {
requireConfigFile: false,
babelOptions: {
presets: ['@babel/preset-env'],
plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]],
},
},
},
},
// Prettier 集成配置
{
plugins: {
prettier: prettierPlugin,
},
rules: {
'prettier/prettier': [
'error',
{
printWidth: 120,
semi: true,
singleQuote: true,
trailingComma: 'all',
},
],
'property-sort-order': 'off',
},
},
];

119
web-vue/package.json Normal file
View File

@@ -0,0 +1,119 @@
{
"name": "@jeesite/root",
"version": "5.14.0",
"type": "module",
"private": true,
"scripts": {
"bootstrap": "pnpm install",
"dev": "cd web && pnpm run dev",
"build": "turbo build --concurrency 20",
"build:tomcat": "cd web && pnpm build:tomcat",
"build:preview": "pnpm run preview",
"report": "cd web && pnpm report",
"preview": "cd web && pnpm preview",
"preview:dist": "cd web && pnpm preview:dist",
"type:check": "turbo type:check --concurrency 20",
"lint:eslint": "eslint --cache --max-warnings 0 \"./**/*.{ts,tsx,mjs,vue}\" --fix",
"lint:prettier": "prettier --ignore-unknown --check --cache --write \"./**/*.{vue,tsx,less,scss}\"",
"lint:stylelint": "stylelint \"./**/*.{vue,less,scss,css}\" --fix --custom-syntax postcss-html --cache --cache-location node_modules/.cache/stylelint/",
"lint:all": "pnpm bootstrap && pnpm type:check && pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
"reinstall:force": "rimraf pnpm-lock.yaml node_modules && pnpm store prune && pnpm i --force && pnpm run dev --force",
"reinstall": "turbo uninstall --concurrency 20 && rimraf pnpm-lock.yaml node_modules && pnpm bootstrap",
"update": "turbo update --concurrency 20 && ncu -u && pnpm reinstall",
"postinstall": "pnpm -r run stub --if-present",
"preinstall": "npx only-allow pnpm",
"serve": "pnpm dev"
},
"dependencies": {
"@ant-design/colors": "8.0.0",
"@ant-design/icons-vue": "7.0.1",
"@jeesite/assets": "workspace:*",
"@jeesite/cms": "workspace:*",
"@jeesite/core": "workspace:*",
"@jeesite/dbm": "workspace:*",
"@jeesite/dfm": "workspace:*",
"@jeesite/test": "workspace:*",
"@jeesite/types": "workspace:*",
"@jeesite/vite": "workspace:*",
"@jeesite/web": "workspace:*",
"ant-design-vue": "4.2.6",
"axios": "1.12.2",
"dayjs": "1.11.18",
"lodash-es": "4.17.21",
"vue": "3.5.22",
"vue-eslint-parser": "10.2.0",
"vue-router": "4.5.1",
"vue-tsc": "3.1.1",
"vue-types": "6.0.0"
},
"devDependencies": {
"@babel/eslint-parser": "7.28.4",
"@iconify/json": "2.2.394",
"@iconify/utils": "3.0.2",
"@stylistic/stylelint-plugin": "4.0.0",
"@types/node": "24.7.2",
"@typescript-eslint/eslint-plugin": "8.46.0",
"@typescript-eslint/parser": "8.46.0",
"@unocss/eslint-config": "66.5.3",
"@unocss/preset-wind3": "66.5.3",
"@vue/compiler-sfc": "3.5.22",
"@vue/runtime-core": "3.5.22",
"@vue/shared": "3.5.22",
"autoprefixer": "10.4.21",
"cross-env": "10.1.0",
"eslint": "9.37.0",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-prettier": "5.5.4",
"eslint-plugin-vue": "10.5.0",
"globals": "16.4.0",
"less": "4.4.2",
"monaco-editor": "0.54.0",
"npm-check-updates": "19.0.0",
"pkg-types": "2.3.0",
"postcss": "8.5.6",
"postcss-html": "1.8.0",
"postcss-less": "6.0.0",
"prettier": "3.6.2",
"prettier-plugin-packagejson": "2.5.19",
"rimraf": "6.0.1",
"stylelint": "16.25.0",
"stylelint-config-recommended": "17.0.0",
"stylelint-config-recommended-less": "3.0.1",
"stylelint-config-recommended-vue": "1.6.1",
"stylelint-config-standard": "39.0.1",
"stylelint-config-standard-less": "3.0.1",
"stylelint-less": "3.0.1",
"stylelint-prettier": "5.0.3",
"turbo": "2.5.8",
"typescript": "5.9.3",
"unocss": "66.5.3",
"vite": "7.1.9"
},
"keywords": [
"typescript",
"jeesite",
"antdv",
"vite",
"vue"
],
"resolutions": {
"bin-wrapper": "npm:bin-wrapper-china"
},
"homepage": "https://jeesite.com",
"repository": {
"type": "git",
"url": "https://gitee.com/thinkgem/jeesite-vue.git"
},
"bugs": {
"url": "https://gitee.com/thinkgem/jeesite-vue/issues"
},
"author": {
"name": "ThinkGem",
"email": "thinkgem@163.com",
"url": "https://gitee.com/thinkgem"
},
"engines": {
"node": ">=20.19 || >=22.12"
},
"packageManager": "pnpm@10.18.2"
}

View File

@@ -0,0 +1,23 @@
- 官方网站:<https://jeesite.com>
- 使用文档:<https://jeesite.com/docs>
- 后端代码:<https://gitee.com/thinkgem/jeesite5>
- 前端代码:<https://gitee.com/thinkgem/jeesite-vue>
------
<div align="center">
如果你喜欢 JeeSite请给她一个 ⭐️ Star您的支持将是我们前行的动力。
</div>
------
- 问题反馈:<https://gitee.com/thinkgem/jeesite-vue/issues> [【新手必读】](https://gitee.com/thinkgem/jeesite5/issues/I18ARR)
- 需求收集:<https://gitee.com/thinkgem/jeesite-vue/issues/new>
- QQ 群:`127515876``209330483``223507718``709534275``730390092``1373527``183903863(外包)`
- 微信群:添加客服微信 <http://s.jeesite.com> 邀请您进群
- 关注微信公众号,了解最新动态:
<p style="padding-left:40px">
<img alt="JeeSite微信公众号" src="https://jeesite.com/assets/images/mp.png" width="220" height="220">
</p>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 499.712 499.712" style="enable-background: new 0 0 499.712 499.712;" xml:space="preserve">
<path style="fill: #FFD93B;" d="M146.88,375.528c126.272,0,228.624-102.368,228.624-228.64c0-55.952-20.16-107.136-53.52-146.88
C425.056,33.096,499.696,129.64,499.696,243.704c0,141.392-114.608,256-256,256c-114.064,0-210.608-74.64-243.696-177.712
C39.744,355.368,90.944,375.528,146.88,375.528z"/>
<path style="fill: #F4C534;" d="M401.92,42.776c34.24,43.504,54.816,98.272,54.816,157.952c0,141.392-114.608,256-256,256
c-59.68,0-114.448-20.576-157.952-54.816c46.848,59.472,119.344,97.792,200.928,97.792c141.392,0,256-114.608,256-256
C499.712,162.12,461.392,89.64,401.92,42.776z"/>
<g>
<polygon style="fill: #FFD83B;" points="128.128,99.944 154.496,153.4 213.472,161.96 170.8,203.56 180.864,262.296
128.128,234.568 75.376,262.296 85.44,203.56 42.768,161.96 101.744,153.4"/>
<polygon style="fill: #FFD83B;" points="276.864,82.84 290.528,110.552 321.104,114.984 298.976,136.552 304.208,166.984
276.864,152.616 249.52,166.984 254.752,136.552 232.624,114.984 263.2,110.552"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 60 60" style="enable-background: new 0 0 60 60;" xml:space="preserve">
<g>
<path style="fill: #F0C419;" d="M30,0c-0.552,0-1,0.448-1,1v6c0,0.552,0.448,1,1,1s1-0.448,1-1V1C31,0.448,30.552,0,30,0z"/>
<path style="fill: #F0C419;" d="M30,52c-0.552,0-1,0.448-1,1v6c0,0.552,0.448,1,1,1s1-0.448,1-1v-6C31,52.448,30.552,52,30,52z"/>
<path style="fill: #F0C419;" d="M59,29h-6c-0.552,0-1,0.448-1,1s0.448,1,1,1h6c0.552,0,1-0.448,1-1S59.552,29,59,29z"/>
<path style="fill: #F0C419;" d="M8,30c0-0.552-0.448-1-1-1H1c-0.552,0-1,0.448-1,1s0.448,1,1,1h6C7.552,31,8,30.552,8,30z"/>
<path style="fill: #F0C419;" d="M46.264,14.736c0.256,0,0.512-0.098,0.707-0.293l5.736-5.736c0.391-0.391,0.391-1.023,0-1.414
s-1.023-0.391-1.414,0l-5.736,5.736c-0.391,0.391-0.391,1.023,0,1.414C45.752,14.639,46.008,14.736,46.264,14.736z"/>
<path style="fill: #F0C419;" d="M13.029,45.557l-5.736,5.736c-0.391,0.391-0.391,1.023,0,1.414C7.488,52.902,7.744,53,8,53
s0.512-0.098,0.707-0.293l5.736-5.736c0.391-0.391,0.391-1.023,0-1.414S13.42,45.166,13.029,45.557z"/>
<path style="fill: #F0C419;" d="M46.971,45.557c-0.391-0.391-1.023-0.391-1.414,0s-0.391,1.023,0,1.414l5.736,5.736
C51.488,52.902,51.744,53,52,53s0.512-0.098,0.707-0.293c0.391-0.391,0.391-1.023,0-1.414L46.971,45.557z"/>
<path style="fill: #F0C419;" d="M8.707,7.293c-0.391-0.391-1.023-0.391-1.414,0s-0.391,1.023,0,1.414l5.736,5.736
c0.195,0.195,0.451,0.293,0.707,0.293s0.512-0.098,0.707-0.293c0.391-0.391,0.391-1.023,0-1.414L8.707,7.293z"/>
<path style="fill: #F0C419;" d="M50.251,21.404c0.162,0.381,0.532,0.61,0.921,0.61c0.13,0,0.263-0.026,0.39-0.08l2.762-1.172
c0.508-0.216,0.746-0.803,0.53-1.311s-0.804-0.746-1.311-0.53l-2.762,1.172C50.272,20.309,50.035,20.896,50.251,21.404z"/>
<path style="fill: #F0C419;" d="M9.749,38.596c-0.216-0.508-0.803-0.746-1.311-0.53l-2.762,1.172
c-0.508,0.216-0.746,0.803-0.53,1.311c0.162,0.381,0.532,0.61,0.921,0.61c0.13,0,0.263-0.026,0.39-0.08l2.762-1.172
C9.728,39.691,9.965,39.104,9.749,38.596z"/>
<path style="fill: #F0C419;" d="M54.481,38.813L51.7,37.688c-0.511-0.207-1.095,0.041-1.302,0.553
c-0.207,0.512,0.041,1.095,0.553,1.302l2.782,1.124c0.123,0.049,0.25,0.073,0.374,0.073c0.396,0,0.771-0.236,0.928-0.626
C55.241,39.603,54.994,39.02,54.481,38.813z"/>
<path style="fill: #F0C419;" d="M5.519,21.188L8.3,22.312c0.123,0.049,0.25,0.073,0.374,0.073c0.396,0,0.771-0.236,0.928-0.626
c0.207-0.512-0.041-1.095-0.553-1.302l-2.782-1.124c-0.513-0.207-1.095,0.04-1.302,0.553C4.759,20.397,5.006,20.98,5.519,21.188z"
/>
<path style="fill: #F0C419;" d="M39.907,50.781c-0.216-0.508-0.803-0.745-1.311-0.53c-0.508,0.216-0.746,0.803-0.53,1.311
l1.172,2.762c0.162,0.381,0.532,0.61,0.921,0.61c0.13,0,0.263-0.026,0.39-0.08c0.508-0.216,0.746-0.803,0.53-1.311L39.907,50.781z"
/>
<path style="fill: #F0C419;" d="M21.014,9.829c0.13,0,0.263-0.026,0.39-0.08c0.508-0.216,0.746-0.803,0.53-1.311l-1.172-2.762
c-0.215-0.509-0.802-0.747-1.311-0.53c-0.508,0.216-0.746,0.803-0.53,1.311l1.172,2.762C20.254,9.6,20.625,9.829,21.014,9.829z"/>
<path style="fill: #F0C419;" d="M21.759,50.398c-0.511-0.205-1.095,0.04-1.302,0.553l-1.124,2.782
c-0.207,0.512,0.041,1.095,0.553,1.302c0.123,0.049,0.25,0.073,0.374,0.073c0.396,0,0.771-0.236,0.928-0.626l1.124-2.782
C22.519,51.188,22.271,50.605,21.759,50.398z"/>
<path style="fill: #F0C419;" d="M38.615,9.675c0.396,0,0.771-0.236,0.928-0.626l1.124-2.782c0.207-0.512-0.041-1.095-0.553-1.302
c-0.511-0.207-1.095,0.041-1.302,0.553L37.688,8.3c-0.207,0.512,0.041,1.095,0.553,1.302C38.364,9.651,38.491,9.675,38.615,9.675z"
/>
</g>
<circle style="fill: #F0C419;" cx="30" cy="30" r="20"/>
<circle style="fill: #EDE21B;" cx="30" cy="30" r="15"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,23 @@
{
"name": "@jeesite/assets",
"version": "5.14.0",
"private": true,
"type": "module",
"scripts": {
"uninstall": "rimraf node_modules",
"update": "ncu -u"
},
"homepage": "https://jeesite.com",
"repository": {
"type": "git",
"url": "https://gitee.com/thinkgem/jeesite-vue.git"
},
"bugs": {
"url": "https://gitee.com/thinkgem/jeesite-vue/issues"
},
"author": {
"name": "ThinkGem",
"email": "thinkgem@163.com",
"url": "https://gitee.com/thinkgem"
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="6395" height="1080" viewBox="0 0 6395 1080">
<defs>
<clipPath id="clip-path">
<rect id="Rectangle_73" data-name="Rectangle 73" width="6395" height="1079" transform="translate(-5391)" fill="#fff"/>
</clipPath>
<linearGradient id="linear-gradient" x1="0.631" y1="0.5" x2="0.958" y2="0.488" gradientUnits="objectBoundingBox">
<stop offset="0" stop-color="#2e364a"/>
<stop offset="1" stop-color="#2c344a"/>
</linearGradient>
</defs>
<g id="Web_1920_1" data-name="Web 1920 1" clip-path="url(#clip-Web_1920_1)">
<g id="Mask_Group_1" data-name="Mask Group 1" transform="translate(5391)" clip-path="url(#clip-path)">
<g id="Group_118" data-name="Group 118" transform="translate(-419.333 -1.126)">
<path id="Path_142" data-name="Path 142" d="M6271.734-6.176s-222.478,187.809-55.349,583.254c44.957,106.375,81.514,205.964,84.521,277,8.164,192.764-156.046,268.564-156.046,268.564l-653.53-26.8L5475.065-21.625Z" transform="translate(-4876.383)" fill="#2d3750"/>
<path id="Union_6" data-name="Union 6" d="M-2631.1,1081.8v-1.6H-8230.9V.022h5599.8V0h759.7s-187.845,197.448-91.626,488.844c49.167,148.9,96.309,256.289,104.683,362.118,7.979,100.852-57.98,201.711-168.644,254.286-65.858,31.29-144.552,42.382-223.028,42.383C-2441.2,1147.632-2631.1,1081.8-2631.1,1081.8Z" transform="translate(3259.524 0.803)" fill="url(#linear-gradient)"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="6395" height="1079" viewBox="0 0 6395 1079">
<defs>
<clipPath id="clip-path">
<rect width="6395" height="1079" transform="translate(-5391)" fill="#fff"/>
</clipPath>
<linearGradient id="linear-gradient" x1="0.747" y1="0.222" x2="0.973" y2="0.807" gradientUnits="objectBoundingBox">
<stop offset="0" stop-color="#1e58cc"/>
<stop offset="1" stop-color="#1951be"/>
</linearGradient>
</defs>
<g id="Mask_Group_1" data-name="Mask Group 1" transform="translate(5391)" clip-path="url(#clip-path)">
<g id="Group_118" data-name="Group 118" transform="translate(-419.333 -1.126)">
<path id="Path_142" data-name="Path 142" d="M6271.734-6.176s-222.478,187.809-55.349,583.254c44.957,106.375,81.514,205.964,84.521,277,8.164,192.764-156.046,268.564-156.046,268.564l-653.53-26.8L5475.065-21.625Z" transform="translate(-4876.383 0)" fill="#eaecf3"/>
<path id="Union_6" data-name="Union 6" d="M-2631.1,1081.8v-1.6H-8230.9V.022H-2631.1V0H-1871.4s-187.845,197.448-91.626,488.844c49.167,148.9,96.309,256.289,104.683,362.118,7.979,100.852-57.98,201.711-168.644,254.286-65.858,31.29-144.552,42.382-223.028,42.383C-2441.2,1147.632-2631.1,1081.8-2631.1,1081.8Z" transform="translate(3259.524 0.803)" fill="url(#linear-gradient)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,98 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="a622e68e-7a65-46e9-94a9-d455de519afc" data-name="Layer 1" width="971.44" height="502" viewBox="0 0 971.44 502">
<defs>
<linearGradient id="341b0e5e-a21f-44db-b85f-76180f33f0d3" x1="599.5" y1="668.05" x2="599.5" y2="199" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="gray" stop-opacity="0.25"/>
<stop offset="0.54" stop-color="gray" stop-opacity="0.12"/>
<stop offset="1" stop-color="gray" stop-opacity="0.1"/>
</linearGradient>
<linearGradient id="9c19d1ba-0c1d-4cca-8c15-e6f3831a5e67" x1="485.72" y1="258.88" x2="485.72" y2="71.12" xlink:href="#341b0e5e-a21f-44db-b85f-76180f33f0d3"/>
<linearGradient id="fe76f7c7-2126-4e48-920d-21143a22d340" x1="132" y1="515" x2="303" y2="515" xlink:href="#341b0e5e-a21f-44db-b85f-76180f33f0d3"/>
<linearGradient id="2cf89a04-5a05-413b-983a-d2bc296cbb5e" x1="933" y1="568.28" x2="1031" y2="568.28" xlink:href="#341b0e5e-a21f-44db-b85f-76180f33f0d3"/>
</defs>
<title>responsive</title>
<g opacity="0.7">
<path d="M852.69,199H346.31A16.37,16.37,0,0,0,330,215.42V563.94a16.37,16.37,0,0,0,16.31,16.42H520.47v60.16h-7.94a8.3,8.3,0,0,0-8.27,8.33v12.07h16.21v7.14H678.53v-7.14h16.21V648.85a8.3,8.3,0,0,0-8.27-8.33H679V640h-.51V580.36H852.69A16.37,16.37,0,0,0,869,563.94V215.42A16.37,16.37,0,0,0,852.69,199Z" transform="translate(-114.28 -199)" fill="url(#341b0e5e-a21f-44db-b85f-76180f33f0d3)"/>
</g>
<rect x="407.72" y="371" width="156" height="92" fill="#bdbdbd"/>
<g opacity="0.1">
<path d="M525.07,579H675.24c1.81-7.87,3.26-13,3.26-13h-157S523.11,571.11,525.07,579Z" transform="translate(-114.28 -199)"/>
</g>
<path d="M235.82,3h499.8a16.1,16.1,0,0,1,16.1,16.1V327a0,0,0,0,1,0,0h-532a0,0,0,0,1,0,0V19.1A16.1,16.1,0,0,1,235.82,3Z" fill="#535461"/>
<path d="M849.9,576H350.1A16.1,16.1,0,0,1,334,559.9V526H866v33.9A16.1,16.1,0,0,1,849.9,576Z" transform="translate(-114.28 -199)" fill="#bdbdbd"/>
<circle cx="485.72" cy="352" r="9" fill="#535461"/>
<path d="M399.89,436H571.55a8.17,8.17,0,0,1,8.17,8.17V456a0,0,0,0,1,0,0h-188a0,0,0,0,1,0,0V444.17A8.17,8.17,0,0,1,399.89,436Z" fill="#bdbdbd"/>
<g opacity="0.5">
<rect x="320.72" y="71.12" width="330" height="187.76" rx="4.5" ry="4.5" fill="url(#9c19d1ba-0c1d-4cca-8c15-e6f3831a5e67)"/>
</g>
<rect x="324.95" y="72.5" width="321.54" height="183.96" rx="4.5" ry="4.5" fill="#fff"/>
<g opacity="0.5">
<rect x="414.52" y="98.91" width="35.44" height="31.9" rx="4.5" ry="4.5" fill="#0960bd"/>
</g>
<rect x="460.59" y="98.91" width="95.69" height="3.54" rx="1.77" ry="1.77" fill="#e0e0e0"/>
<rect x="460.59" y="109.55" width="79.54" height="3.54" rx="1.77" ry="1.77" fill="#e0e0e0"/>
<g opacity="0.5">
<rect x="414.52" y="148.53" width="35.44" height="31.9" rx="4.5" ry="4.5" fill="#0960bd"/>
</g>
<rect x="460.59" y="148.53" width="95.69" height="3.54" rx="1.77" ry="1.77" fill="#e0e0e0"/>
<rect x="460.59" y="159.16" width="95.69" height="3.54" rx="1.77" ry="1.77" fill="#e0e0e0"/>
<g opacity="0.5">
<rect x="414.52" y="198.15" width="35.44" height="31.9" rx="4.5" ry="4.5" fill="#0960bd"/>
</g>
<rect x="460.59" y="198.15" width="95.69" height="3.54" rx="1.77" ry="1.77" fill="#e0e0e0"/>
<rect x="460.59" y="208.78" width="96.33" height="3.54" rx="1.59" ry="1.59" fill="#e0e0e0"/>
<line x1="485.72" y1="42" x2="485.72" y2="20" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/>
<line x1="485.72" y1="79" x2="485.72" y2="50.13" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/>
<circle cx="485.72" cy="79" r="4" fill="#0960bd"/>
<circle cx="485.72" cy="46" r="4" fill="none" stroke="#fff" stroke-miterlimit="10"/>
<line x1="485.72" y1="42" x2="485.72" y2="20" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/>
<line x1="485.72" y1="79" x2="485.72" y2="50.13" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/>
<circle cx="485.72" cy="79" r="4" fill="#0960bd"/>
<circle cx="485.72" cy="46" r="4" fill="none" stroke="#fff" stroke-miterlimit="10"/>
<line x1="485.72" y1="279" x2="485.72" y2="310" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/>
<line x1="485.72" y1="251" x2="485.72" y2="279.87" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/>
<circle cx="485.72" cy="251" r="4" fill="#0960bd"/>
<line x1="305.72" y1="168.5" x2="274.22" y2="168.5" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/>
<line x1="333.22" y1="168.5" x2="304.35" y2="168.5" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/>
<circle cx="333.22" cy="168.5" r="4" fill="#0960bd"/>
<g opacity="0.1">
<rect x="408.22" y="435.5" width="156" height="3"/>
</g>
<g opacity="0.7">
<path d="M293.48,566.06H221.08l1-8.14c20.46-18.37,33.69-67.31,33.69-67.31a6.78,6.78,0,0,0-.87.18c-12,2.42-20.54,7.35-26.51,13.28l2.54-21.66c37.8-8.14,52.79-58.14,52.79-58.14-24.12,5.35-39.16,13.63-48.5,21.49l3.72-31.82c25.56,8.77,52-37.82,52-37.82l-1-.21.5-.32-.76.27c-28.25-6.09-43.35,10.06-48.25,16.77l.37-3.12q-1.12,3-2.18,5.88h0l0,.08q-3,8.13-5.49,16.06l0,0h0q-2.17,6.77-4.06,13.4l0-.06s-1.17-28.46-31.18-35.95c0,0,3.15,62.07,26.93,51.91h0c-2.2,9-4,17.66-5.56,26.07h0q-1.49,8.21-2.6,16l-.14.16.14-.12-.06.41v0h0q-1,7.07-1.7,13.78c.46-8.62-1.11-33.52-30.45-56.92,0,0-39,68.54,27.5,82,.15.13.3.26.44.38l-.1-.31.6.13.27-3.52a369.39,369.39,0,0,0,.23,44.1h0c.07,1,.14,2,.21,2.95H141.37c-27.94,57.79,15.52,89.46,15.52,89.46h120C323.49,596.66,293.48,566.06,293.48,566.06Zm-78-65.68h0v0Z" transform="translate(-114.28 -199)" fill="url(#fe76f7c7-2126-4e48-920d-21143a22d340)"/>
</g>
<path d="M217,588s-19-83,23-190" transform="translate(-114.28 -199)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="3" opacity="0.6"/>
<path d="M143,563H290s29,37-16,92H158S116,617,143,563Z" transform="translate(-114.28 -199)" fill="#a76611"/>
<path d="M237.89,403.5s14.61-26,49.61-18c0,0-28.93,49.26-55,33.13Z" transform="translate(-114.28 -199)" fill="#4db6ac"/>
<path d="M228.63,431.09S227.5,404.5,198.5,397.5c0,0,3,58,26,48.5Z" transform="translate(-114.28 -199)" fill="#4db6ac"/>
<path d="M219.15,470.36s5.35-27.86,61.35-39.86c0,0-17.86,57.62-63.93,55.31Z" transform="translate(-114.28 -199)" fill="#4db6ac"/>
<path d="M214.61,501.63s5.89-29.13-29.11-56.13c0,0-38,64.67,27.48,76.83Z" transform="translate(-114.28 -199)" fill="#4db6ac"/>
<path d="M213.56,541.67S209.5,500.5,253.5,492.5c0,0-16.07,57.49-40,67.74Z" transform="translate(-114.28 -199)" fill="#4db6ac"/>
<path d="M233,419s38-29,54-34" transform="translate(-114.28 -199)" fill="none" stroke="#535461" stroke-miterlimit="10" opacity="0.3"/>
<path d="M216.5,485.5s46-49,64-55" transform="translate(-114.28 -199)" fill="none" stroke="#535461" stroke-miterlimit="10" opacity="0.3"/>
<path d="M198.5,397.5s28,38,26,48" transform="translate(-114.28 -199)" fill="none" stroke="#535461" stroke-miterlimit="10" opacity="0.3"/>
<path d="M185.5,445.5s15,68,27,77" transform="translate(-114.28 -199)" fill="none" stroke="#535461" stroke-miterlimit="10" opacity="0.3"/>
<path d="M213.5,560.5s24-66,40-68" transform="translate(-114.28 -199)" fill="none" stroke="#535461" stroke-miterlimit="10" opacity="0.3"/>
<g opacity="0.1">
<path d="M290,563H143c-.33.67-.65,1.34-1,2H285s28.29,36.11-14.4,90H274C319,600,290,563,290,563Z" transform="translate(-114.28 -199)"/>
</g>
<rect y="455.6" width="971.44" height="32.93" fill="#e0e0e0"/>
<rect x="41.16" y="488.53" width="889.11" height="13.47" fill="#e0e0e0"/>
<rect x="41.16" y="488.53" width="889.11" height="4.49" opacity="0.1"/>
<line x1="690.22" y1="168.5" x2="696.22" y2="168.5" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/>
<line x1="637.22" y1="168.5" x2="682.1" y2="168.5" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/>
<circle cx="637.22" cy="168.5" r="4" fill="#0960bd"/>
<circle cx="686.22" cy="168.5" r="4" fill="none" stroke="#fff" stroke-miterlimit="10"/>
<g opacity="0.7">
<path d="M1027,643.88l.1-.15q.31-.48.61-1l.11-.19q.29-.49.55-1l.09-.17c.2-.39.39-.78.56-1.19h0a23.79,23.79,0,0,0,.94-2.51l.1-.33c.09-.31.18-.62.26-.93l.1-.44q.1-.42.18-.85c0-.16.06-.32.09-.48s.09-.56.13-.85,0-.33.06-.49.06-.61.08-.92c0-.14,0-.29,0-.43,0-.45,0-.91,0-1.36V548h-13.85V507.52h-17V548H988.39V489.86h-17V548H965V481.55h-17V548H933V630.6c0,13.48,11.21,24.4,25,24.4H1006a25.19,25.19,0,0,0,20.24-10.06l0,0Q1026.61,644.41,1027,643.88Z" transform="translate(-114.28 -199)" fill="url(#2cf89a04-5a05-413b-983a-d2bc296cbb5e)"/>
</g>
<rect x="835.72" y="321" width="16" height="100" fill="#535461"/>
<rect x="835.72" y="288" width="16" height="33" fill="#3ad29f"/>
<rect x="857.72" y="329" width="16" height="100" fill="#535461"/>
<rect x="857.72" y="296" width="16" height="33" fill="#4d8af0"/>
<rect x="884.72" y="346" width="16" height="100" fill="#535461"/>
<rect x="884.72" y="313" width="16" height="33" fill="#f55f44"/>
<path d="M821.72,352h92a0,0,0,0,1,0,0v79.5a23.5,23.5,0,0,1-23.5,23.5h-45a23.5,23.5,0,0,1-23.5-23.5V352A0,0,0,0,1,821.72,352Z" fill="#0a949e"/>
<g opacity="0.1">
<path d="M936,551v4h88v79.5a23.39,23.39,0,0,1-5,14.49,23.45,23.45,0,0,0,9-18.49V551Z" transform="translate(-114.28 -199)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595306944988" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1820" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M1464.3 279.7" p-id="1821" fill="#ffffff"></path><path d="M512 960c-60.5 0-119.1-11.9-174.4-35.2-53.4-22.6-101.3-54.9-142.4-96s-73.4-89-96-142.4C75.9 631.1 64 572.5 64 512s11.9-119.1 35.2-174.4c22.6-53.4 54.9-101.3 96-142.4s89-73.4 142.4-96C392.9 75.9 451.5 64 512 64s119.1 11.9 174.4 35.2c53.4 22.6 101.3 54.9 142.4 96s73.4 89 96 142.4C948.1 392.9 960 451.5 960 512c0 19.1-15.5 34.6-34.6 34.6s-34.6-15.5-34.6-34.6c0-51.2-10-100.8-29.8-147.4-19.1-45.1-46.4-85.6-81.2-120.4C745 209.4 704.5 182 659.4 163c-46.7-19.7-96.3-29.8-147.4-29.8-51.2 0-100.8 10-147.4 29.8-45.1 19.1-85.6 46.4-120.4 81.2S182 319.5 163 364.6c-19.7 46.7-29.8 96.3-29.8 147.4 0 51.2 10 100.8 29.8 147.4 19.1 45.1 46.4 85.6 81.2 120.4C279 814.6 319.5 842 364.6 861c46.7 19.7 96.3 29.8 147.4 29.8 64.6 0 128.4-16.5 184.4-47.8 54.4-30.4 100.9-74.1 134.6-126.6 10.3-16.1 31.7-20.8 47.8-10.4 16.1 10.3 20.8 31.7 10.4 47.8-39.8 62-94.8 113.7-159.1 149.6-66.2 37-141.7 56.6-218.1 56.6z" p-id="1822" fill="#ffffff"></path><path d="M924 552c-19.8 0-36-16.2-36-36V228c0-19.8 16.2-36 36-36s36 16.2 36 36v288c0 19.8-16.2 36-36 36zM275.4 575.5c9.5-2.5 19.1 2.9 22.3 12.2 3.5 10.2 9.9 17.7 19.1 22.6 7.1 3.9 15.1 5.8 24 5.8 16.6 0 30.8-6.9 42.5-20.8 11.7-13.8 20-32.7 24.9-75.1-7.7 12.2-17.3 20.8-28.7 25.8-11.4 5-23.7 7.4-36.8 7.4-26.7 0-47.7-8.3-63.3-24.9-15.5-16.6-23.3-37.9-23.3-64.1 0-25.1 7.7-47.1 23-66.2 15.3-19 37.9-28.6 67.8-28.6 40.3 0 68.1 18.1 83.4 54.4 8.5 19.9 12.7 44.9 12.7 74.9 0 33.8-5.1 63.8-15.3 89.9-16.9 43.5-45.5 65.2-85.8 65.2-27 0-47.6-7.1-61.6-21.2-10-10.1-16.4-22-19.3-35.8-2-9.6 4-19.1 13.5-21.6l0.9 0.1z m103-74.4c9.4-7.5 14.1-20.6 14.1-39.3 0-16.8-4.2-29.3-12.7-37.5S360.6 412 347.5 412c-14 0-25.2 4.7-33.4 14.1-8.2 9.4-12.4 22-12.4 37.7 0 14.9 3.6 26.7 10.9 35.5 7.2 8.8 18.8 13.1 34.6 13.1 11.4 0 21.8-3.8 31.2-11.3zM646.6 414.4c12.4 22.8 18.5 54 18.5 93.7 0 37.6-5.6 68.7-16.8 93.3-16.2 35.3-42.8 52.9-79.6 52.9-33.2 0-57.9-14.4-74.2-43.3-13.5-24.1-20.3-56.4-20.3-97 0-31.4 4.1-58.4 12.2-80.9 15.2-42 42.7-63 82.5-63 35.9 0 61.8 14.8 77.7 44.3z m-40.2 173.3c9.4-13.9 14-39.9 14-78 0-27.4-3.4-50-10.1-67.7-6.8-17.7-19.9-26.6-39.4-26.6-17.9 0-31 8.4-39.3 25.2-8.3 16.8-12.4 41.6-12.4 74.3 0 24.6 2.6 44.4 7.9 59.4 8.1 22.8 22 34.3 41.6 34.3 15.7 0 28.3-7 37.7-20.9zM803.3 387.2c11.2 11.3 16.8 25 16.8 41.2 0 16.7-5.8 30.7-17.5 41.8C791 481.4 777.4 487 762 487c-17.1 0-31.2-5.8-42.1-17.4-10.9-11.6-16.4-25.1-16.4-40.6 0-16.5 5.8-30.4 17.3-41.7 11.5-11.3 25.3-17 41.2-17 16.3 0 30.1 5.7 41.3 16.9zM739.5 451c6.2 6.2 13.7 9.3 22.5 9.3 8.4 0 15.8-3.1 22.1-9.3 6.3-6.2 9.4-13.7 9.4-22.6 0-8.5-3.1-15.9-9.3-22.1-6.2-6.2-13.6-9.3-22.2-9.3s-16.1 3.1-22.4 9.3c-6.3 6.2-9.4 13.7-9.4 22.6-0.1 8.4 3 15.8 9.3 22.1z" p-id="1823" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595307154239" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7317" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M316 672h60c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8zM512 622c22.1 0 40-17.9 40-39 0-23.1-17.9-41-40-41s-40 17.9-40 41c0 21.1 17.9 39 40 39zM512 482c22.1 0 40-17.9 40-39 0-23.1-17.9-41-40-41s-40 17.9-40 41c0 21.1 17.9 39 40 39z" p-id="7318" fill="#ffffff"></path><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32z m-40 728H184V184h656v656z" p-id="7319" fill="#ffffff"></path><path d="M648 672h60c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8z" p-id="7320" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 996 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595307195033" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8116" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M887.081 904.791a25.8 25.8 0 0 1-18.376-7.619L705.618 734.075l-4.163 3.369c-58.255 47.18-131.522 73.16-206.32 73.16-181.07 0-328.377-147.308-328.377-328.367 0-181.068 147.308-328.376 328.377-328.376 181.063 0 328.376 147.308 328.376 328.376 0 77.072-27.412 152.07-77.169 211.17l-3.522 4.173 162.719 162.744a25.846 25.846 0 0 1 7.639 18.432 26.081 26.081 0 0 1-26.051 26.045l-0.046-0.01zM495.13 205.957c-152.336 0-276.27 123.935-276.27 276.27 0 152.33 123.934 276.27 276.27 276.27 152.34 0 276.275-123.94 276.275-276.27 0-152.335-123.935-276.27-276.275-276.27z" fill="#ffffff" p-id="8117"></path><path d="M626.545 508.355h-262.83a26.127 26.127 0 0 1 0-52.255h262.83a26.127 26.127 0 0 1 0 52.255z" fill="#ffffff" p-id="8118"></path><path d="M495.13 639.77a26.127 26.127 0 0 1-26.128-26.128v-262.83a26.127 26.127 0 0 1 52.255 0v262.835a26.127 26.127 0 0 1-26.127 26.123z" fill="#ffffff" p-id="8119"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595306911635" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1352" width="48" height="48" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M924.8 337.6c-22.6-53.4-54.9-101.3-96-142.4s-89-73.4-142.4-96C631.1 75.9 572.5 64 512 64S392.9 75.9 337.6 99.2c-53.4 22.6-101.3 54.9-142.4 96-22.4 22.4-42.2 46.8-59.2 73.1V228c0-19.8-16.2-36-36-36s-36 16.2-36 36v288c0 19.8 16.2 36 36 36s36-16.2 36-36v-50.2c4.2-34.8 13.2-68.7 27-101.2 19.1-45.1 46.4-85.6 81.2-120.4C279 209.4 319.5 182 364.6 163c46.7-19.7 96.3-29.8 147.4-29.8 51.2 0 100.8 10 147.4 29.8 45.1 19.1 85.6 46.4 120.4 81.2C814.6 279 842 319.5 861 364.6c19.7 46.7 29.8 96.3 29.8 147.4 0 51.2-10 100.8-29.8 147.4-19.1 45.1-46.4 85.6-81.2 120.4C745 814.6 704.5 842 659.4 861c-46.7 19.7-96.3 29.8-147.4 29.8-64.6 0-128.4-16.5-184.4-47.8-54.4-30.4-100.9-74.1-134.6-126.6-10.3-16.1-31.7-20.8-47.8-10.4-16.1 10.3-20.8 31.7-10.4 47.8 39.8 62 94.8 113.7 159.1 149.6 66.2 37 141.7 56.6 218.1 56.6 60.5 0 119.1-11.9 174.4-35.2 53.4-22.6 101.3-54.9 142.4-96 41.1-41.1 73.4-89 96-142.4C948.1 631.1 960 572.5 960 512s-11.9-119.1-35.2-174.4z" p-id="1353" fill="#ffffff"></path><path d="M275.4 575.5c9.5-2.5 19.1 2.9 22.3 12.2 3.5 10.2 9.9 17.7 19.1 22.6 7.1 3.9 15.1 5.8 24 5.8 16.6 0 30.8-6.9 42.5-20.8 11.7-13.8 20-32.7 24.9-75.1-7.7 12.2-17.3 20.8-28.7 25.8-11.4 5-23.7 7.4-36.8 7.4-26.7 0-47.7-8.3-63.3-24.9-15.5-16.6-23.3-37.9-23.3-64.1 0-25.1 7.7-47.1 23-66.2 15.3-19 37.9-28.6 67.8-28.6 40.3 0 68.1 18.1 83.4 54.4 8.5 19.9 12.7 44.9 12.7 74.9 0 33.8-5.1 63.8-15.3 89.9-16.9 43.5-45.5 65.2-85.8 65.2-27 0-47.6-7.1-61.6-21.2-10-10.1-16.4-22-19.3-35.8-2-9.6 4-19.1 13.5-21.6l0.9 0.1z m103-74.4c9.4-7.5 14.1-20.6 14.1-39.3 0-16.8-4.2-29.3-12.7-37.5S360.6 412 347.5 412c-14 0-25.2 4.7-33.4 14.1-8.2 9.4-12.4 22-12.4 37.7 0 14.9 3.6 26.7 10.9 35.5 7.2 8.8 18.8 13.1 34.6 13.1 11.4 0 21.8-3.8 31.2-11.3zM646.6 414.4c12.4 22.8 18.5 54 18.5 93.7 0 37.6-5.6 68.7-16.8 93.3-16.2 35.3-42.8 52.9-79.6 52.9-33.2 0-57.9-14.4-74.2-43.3-13.5-24.1-20.3-56.4-20.3-97 0-31.4 4.1-58.4 12.2-80.9 15.2-42 42.7-63 82.5-63 35.9 0 61.8 14.8 77.7 44.3z m-40.2 173.3c9.4-13.9 14-39.9 14-78 0-27.4-3.4-50-10.1-67.7-6.8-17.7-19.9-26.6-39.4-26.6-17.9 0-31 8.4-39.3 25.2-8.3 16.8-12.4 41.6-12.4 74.3 0 24.6 2.6 44.4 7.9 59.4 8.1 22.8 22 34.3 41.6 34.3 15.7 0 28.3-7 37.7-20.9zM803.3 387.2c11.2 11.3 16.8 25 16.8 41.2 0 16.7-5.8 30.7-17.5 41.8C791 481.4 777.4 487 762 487c-17.1 0-31.2-5.8-42.1-17.4-10.9-11.6-16.4-25.1-16.4-40.6 0-16.5 5.8-30.4 17.3-41.7 11.5-11.3 25.3-17 41.2-17 16.3 0 30.1 5.7 41.3 16.9zM739.5 451c6.2 6.2 13.7 9.3 22.5 9.3 8.4 0 15.8-3.1 22.1-9.3 6.3-6.2 9.4-13.7 9.4-22.6 0-8.5-3.1-15.9-9.3-22.1-6.2-6.2-13.6-9.3-22.2-9.3s-16.1 3.1-22.4 9.3c-6.3 6.2-9.4 13.7-9.4 22.6-0.1 8.4 3 15.8 9.3 22.1z" p-id="1354" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595308005241" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9878" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M750.3 198.7C598 46.4 351.1 46.4 198.7 198.7s-152.3 399.2 0 551.5C345.1 896.6 578.8 902.3 732 767.3l172.1 172.1 35.4-35.4-172.1-171.9c135-153.2 129.3-387-17.1-533.4z m39.3 403.8c-17.1 42.1-42.2 80-74.7 112.4-32.5 32.5-70.3 57.6-112.4 74.7-40.7 16.5-83.8 24.9-128 24.9s-87.2-8.4-128-24.9c-42.1-17.1-80-42.2-112.4-74.7s-57.6-70.3-74.7-112.4c-16.5-40.7-24.9-83.8-24.9-128s8.4-87.2 24.9-128c17.1-42.1 42.2-80 74.7-112.4s70.3-57.6 112.4-74.7c40.7-16.5 83.8-24.9 128-24.9s87.2 8.4 128 24.9c42.1 17.1 80 42.2 112.4 74.7 32.5 32.5 57.6 70.3 74.7 112.4 16.5 40.7 24.9 83.8 24.9 128s-8.4 87.3-24.9 128zM671 502H271v-50h400v50z" fill="#ffffff" p-id="9879"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@jeesite/assets/*": ["./*"]
}
},
"include": [
"./**/*.ts",
"./**/*.tsx",
"./**/*.vue"
],
"exclude": [
"node_modules",
"vite.config.ts",
"dist"
]
}

View File

@@ -0,0 +1,23 @@
- 官方网站:<https://jeesite.com>
- 使用文档:<https://jeesite.com/docs>
- 后端代码:<https://gitee.com/thinkgem/jeesite5>
- 前端代码:<https://gitee.com/thinkgem/jeesite-vue>
------
<div align="center">
如果你喜欢 JeeSite请给她一个 ⭐️ Star您的支持将是我们前行的动力。
</div>
------
- 问题反馈:<https://gitee.com/thinkgem/jeesite-vue/issues> [【新手必读】](https://gitee.com/thinkgem/jeesite5/issues/I18ARR)
- 需求收集:<https://gitee.com/thinkgem/jeesite-vue/issues/new>
- QQ 群:`127515876``209330483``223507718``709534275``730390092``1373527``183903863(外包)`
- 微信群:添加客服微信 <http://s.jeesite.com> 邀请您进群
- 关注微信公众号,了解最新动态:
<p style="padding-left:40px">
<img alt="JeeSite微信公众号" src="https://jeesite.com/assets/images/mp.png" width="220" height="220">
</p>

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { AxiosProgressEvent, GenericAbortSignal } from 'axios';
const { adminPath } = useGlobSetting();
export const cmsChatMessage = (params?: Recordable | any) =>
defHttp.get<Recordable[]>({ url: adminPath + '/cms/chat/message', params });
export const cmsChatList = (params?: Recordable | any) =>
defHttp.get<Recordable[]>({ url: adminPath + '/cms/chat/list', params });
export const cmsChatSave = (params?: Recordable | any) =>
defHttp.post<Recordable>({ url: adminPath + '/cms/chat/save', params });
export const cmsChatDelete = (params?: Recordable | any) =>
defHttp.get<Recordable>({ url: adminPath + '/cms/chat/delete', params });
export const cmsChatStream = (
params?: Recordable | any,
signal?: GenericAbortSignal,
onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void,
) =>
defHttp.post<Recordable>({
url: adminPath + '/cms/chat/stream',
params,
signal,
onDownloadProgress,
responseType: 'stream',
headers: {
'x-ajax': 'event-stream',
},
});

View File

@@ -0,0 +1,2 @@
import './node_modules/@jeesite/cms-lib/dist/style.css';
export { ChatMessage } from './node_modules/@jeesite/cms-lib/dist';

View File

@@ -0,0 +1,31 @@
{
"name": "@jeesite/cms",
"version": "5.14.0",
"private": true,
"type": "module",
"scripts": {
"type:check": "vue-tsc --noEmit --skipLibCheck",
"uninstall": "rimraf node_modules",
"update": "ncu -u"
},
"dependencies": {
"@jeesite/cms-lib": "5.14.0-rc.1",
"qs": "6.14.0"
},
"devDependencies": {
"@types/qs": "6.14.0"
},
"homepage": "https://jeesite.com",
"repository": {
"type": "git",
"url": "https://gitee.com/thinkgem/jeesite-vue.git"
},
"bugs": {
"url": "https://gitee.com/thinkgem/jeesite-vue/issues"
},
"author": {
"name": "ThinkGem",
"email": "thinkgem@163.com",
"url": "https://gitee.com/thinkgem"
}
}

View File

@@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@jeesite/cms/*": ["./*"]
}
},
"include": [
"./**/*.ts",
"./**/*.tsx",
"./**/*.vue"
],
"exclude": [
"node_modules",
"vite.config.ts",
"dist"
]
}

View File

@@ -0,0 +1,240 @@
<!--
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
-->
<template>
<PageWrapper :sidebarWidth="230" :contentFullHeight="true">
<template #sidebar>
<div class="p-2 pt-1">
<a-button type="primary" class="w-full" @click="handleAdd" :disabled="loading">
<Icon icon="i-ant-design:plus-outlined" /> {{ t('新建对话') }}
</a-button>
</div>
<ScrollContainer class="jeesite-cms-ai p-2 bg-white rounded-2 h-full">
<Menu class="jeesite-cms-ai-menu" v-model:selectedKeys="conversationIds" :disabled="loading">
<template v-for="(item, index) in chatList" :key="item.id">
<Menu.Item @click="handleSelect(item)">
<div class="flex justify-end">
<span v-if="item.edit" class="flex-1 mr-2">
<a-input
v-model:value="item.title"
size="small"
class="mr-2"
@blur="handleEdit(item, false, $event)"
/>
</span>
<span v-else class="flex-1 truncate">{{ item.title }}</span>
<span v-if="item.id == conversationIds[0]" class="c-gray">
<Icon icon="i-ant-design:edit" class="pt-3" @click="handleEdit(item, true)" />
<Popconfirm :title="t('是否确认删除该对话吗?')" @confirm="handleDelete(item, index)">
<Icon icon="i-ant-design:delete" class="pt-3" />
</Popconfirm>
</span>
</div>
</Menu.Item>
</template>
</Menu>
<div class="h-10"></div>
</ScrollContainer>
</template>
<div class="h-full rounded-2 flex flex-col overflow-hidden">
<div v-if="messages.length == 0" class="h-[90%] flex justify-center items-center text-center">
<div class="text-xl c-gray-4">
{{ t('我是你的 AI 助手,我可以帮你解答一些问题') }}
<div v-if="userStore.getPageCacheByKey('demoMode')" class="text-sm mt-20 line-height-loose">
提示当前对接的是 DeepSeek 蒸馏过的 7B 超小模型仅作为演示使用AI 回答结果可能不够理想<br />
可在自己本地部署或对接其它大模型此外当前向量库中只含了几篇关于 jeesite 的文章<br />
知识库文章来源可进入菜单查看扩展功能 -> 内容管理 -> 内容发布<br />
提问举例jeesite 简介jeesite 优势jeesite 技术栈体验一下
</div>
</div>
</div>
<ChatMessage
ref="messageRef"
v-model:value="messages"
:chatStreamApi="cmsChatStream"
:conversationId="conversationIds[0]"
:inputMessage="inputMessageRef?.value"
v-model:loading="loading"
/>
<div class="pl-14 pr-16 w-full flex justify-end mt-3">
<div class="flex-1 rounded-2 p-3 pb-1 bg-white text-[15px] leading-7">
<textarea
ref="inputMessageRef"
class="outline-none no-scrollbar resize-none w-full h-full border-none bg-transparent"
rows="1"
:placeholder="t('你有什么想知道的快来问问我Shift+Enter 换行Enter 发送。')"
@input="handleInput"
@keypress="handleEnter"
></textarea>
</div>
<a-button
style="width: 110px; height: 100%; margin-left: 10px"
type="primary"
:loading="loading"
@click="handleSend"
>
<Icon icon="i-fa:send" /> {{ t('发送') }}
</a-button>
</div>
<div class="pt-2 pr-8 c-gray-4 text-xs text-center">
{{ t('服务生成的所有内容均由人工智能模型生成,准确和完整性无法保证,不代表我们的态度或观点。') }}
</div>
</div>
</PageWrapper>
</template>
<script lang="ts" setup name="ViewsCmsChatIndex">
import { nextTick, onMounted, ref } from 'vue';
import { Menu, Popconfirm } from 'ant-design-vue';
import { Icon } from '@jeesite/core/components/Icon';
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
import { useUserStore } from '@jeesite/core/store/modules/user';
import { PageWrapper } from '@jeesite/core/components/Page';
import { ScrollContainer } from '@jeesite/core/components/Container';
import { cmsChatDelete, cmsChatList, cmsChatMessage, cmsChatSave, cmsChatStream } from '@jeesite/cms/api/cms/chat';
import { ChatMessage } from '@jeesite/cms';
const { t } = useI18n('cms.chat');
const { showMessage } = useMessage();
const conversationIds = ref<string[]>([]);
const conversationTitle = ref<string>('');
const userStore = useUserStore();
const loading = ref(false);
const messageRef = ref<InstanceType<typeof ChatMessage>>();
const inputMessageRef = ref<HTMLTextAreaElement>();
const messages = ref<Recordable[]>([]);
const chatList = ref<Recordable[]>([]);
onMounted(async () => {
chatList.value = await cmsChatList();
if (chatList.value.length == 0) {
const res = await cmsChatSave();
chatList.value.unshift(res);
}
await nextTick(async () => {
setTimeout(async () => {
await handleSelect(chatList.value[0]);
}, 100);
});
});
async function handleAdd() {
if (chatList.value.length > 0) {
await handleSelect(chatList.value[0]);
if (messages.value.length == 0) {
showMessage(t('当前已是新对话'));
return;
}
}
const res = await cmsChatSave();
chatList.value.unshift(res);
await handleSelect(chatList.value[0]);
}
async function handleSelect(item: Recordable) {
conversationIds.value = [item.id];
conversationTitle.value = item.title;
messages.value = await cmsChatMessage({ id: item.id });
messageRef.value?.scrollBottom();
}
async function handleEdit(item: Recordable, edit: boolean, event?: Event) {
item.edit = edit;
if (!edit) {
const res = await cmsChatSave(item);
showMessage(res.message);
}
}
async function handleDelete(item: Recordable, idx: number) {
const res = await cmsChatDelete({ id: item.id });
chatList.value.splice(idx, 1);
showMessage(res.message);
if (idx == 0 && chatList.value[idx]) {
await handleSelect(chatList.value[idx]);
} else if (chatList.value[idx - 1]) {
await handleSelect(chatList.value[idx - 1]);
}
}
function handleInput() {
if (inputMessageRef.value) {
inputMessageRef.value.style.height = 'auto';
const height = inputMessageRef.value.scrollHeight;
inputMessageRef.value.style.height = (height > 300 ? 300 : height) + 'px';
}
}
function handleEnter(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
if (!loading.value) {
handleSend();
}
}
}
async function handleSend() {
if (inputMessageRef.value && inputMessageRef.value.value) {
if (!conversationIds.value[0]) {
await handleAdd();
}
loading.value = true;
const params = {
id: conversationIds.value[0],
message: inputMessageRef.value.value,
};
inputMessageRef.value.value = '';
inputMessageRef.value.style.height = 'auto';
try {
chatList.value
.filter((item) => item.id == params.id)
.forEach((item) => {
if (item.title.startsWith('新对话')) {
item.title = params.message.substring(0, 30);
cmsChatSave(item);
}
});
await messageRef.value?.sendMessage(params);
} finally {
loading.value = false;
}
} else {
showMessage(t('请填写你的问题'));
}
}
</script>
<style lang="less">
.jeesite-cms-ai {
&-menu.ant-menu.ant-menu-light {
border-right: 0 !important;
padding: 2px !important;
.ant-menu-item {
padding-right: 8px;
color: #333 !important;
&-selected {
background-color: #f0f5ff !important;
color: #333 !important;
}
}
}
}
html[data-theme='dark'] {
.jeesite-cms-ai {
.ant-menu-light .ant-menu-item {
color: #ddd !important;
&-selected {
background-color: #333 !important;
color: #ddd !important;
}
}
}
}
</style>

View File

@@ -0,0 +1,23 @@
- 官方网站:<https://jeesite.com>
- 使用文档:<https://jeesite.com/docs>
- 后端代码:<https://gitee.com/thinkgem/jeesite5>
- 前端代码:<https://gitee.com/thinkgem/jeesite-vue>
------
<div align="center">
如果你喜欢 JeeSite请给她一个 ⭐️ Star您的支持将是我们前行的动力。
</div>
------
- 问题反馈:<https://gitee.com/thinkgem/jeesite-vue/issues> [【新手必读】](https://gitee.com/thinkgem/jeesite5/issues/I18ARR)
- 需求收集:<https://gitee.com/thinkgem/jeesite-vue/issues/new>
- QQ 群:`127515876``209330483``223507718``709534275``730390092``1373527``183903863(外包)`
- 微信群:添加客服微信 <http://s.jeesite.com> 邀请您进群
- 关注微信公众号,了解最新动态:
<p style="padding-left:40px">
<img alt="JeeSite微信公众号" src="https://jeesite.com/assets/images/mp.png" width="220" height="220">
</p>

View File

@@ -0,0 +1 @@
// export * from './sys';

View File

@@ -0,0 +1,56 @@
import type { Result } from '@jeesite/types/axios';
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
export interface Page<T> {
pageNo: number;
pageSize: number;
orderBy: string;
count: number;
list: T[];
}
export interface BasicModel<T> extends Result, Recordable {
id: string;
page: Page<T>;
isNewRecord: boolean;
dataMap: Map<string, any>;
createBy?: string;
createDate?: string;
updateBy?: string;
updateDate?: string;
status?: string;
}
export interface TreeModel<T> extends BasicModel<T> {
parentCode?: string; // 父级编码
parentCodes?: string; // 所有父级编号
treeNames?: string; // 全节点名
treeSort?: string; // 排序号
treeSorts?: string; // 所有排序号
treeLeaf?: string; // 是否叶子节点
treeLevel?: number; // 树层次级别从0开始
childList?: T[]; // 子项列表
isRoot?: boolean; // 是否根节点
isTreeLeaf?: boolean; // 是否叶子
isLoading?: boolean; // 是否加载中
}
export interface TreeDataModel {
id: string;
pId: string;
name: string;
value?: string;
title?: string;
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { BasicModel, Page } from '@jeesite/core/api/model/baseModel';
const { adminPath } = useGlobSetting();
export interface MsgInner extends BasicModel<MsgInner> {
msgTitle?: string; // 消息标题
contentLevel?: string; // 内容级别1普通 2一般 3紧急
contentType?: string; // 内容类型1公告 2新闻 3会议 4其它
msgContent?: string; // 消息内容
receiveType?: string; // 接受者类型0全部 1用户 2部门 3角色 4岗位
receiveCodes?: string; // 接受者字符串
receiveNames?: string; // 接受者名称字符串
sendUserCode?: string; // 发送者用户编码
sendUserName?: string; // 发送者用户姓名
sendDate?: string; // 发送时间
isAttac?: string; // 是否有附件
notifyTypes?: string; // 通知类型PC APP 短信 邮件 微信)多选
}
export const msgInnerList = (params?: MsgInner | any) =>
defHttp.get<MsgInner>({ url: adminPath + '/msg/msgInner/list', params });
export const msgInnerListData = (params?: MsgInner | any) =>
defHttp.post<Page<MsgInner>>({ url: adminPath + '/msg/msgInner/listData', params });
export const msgInnerForm = (params?: MsgInner | any) =>
defHttp.get<MsgInner>({ url: adminPath + '/msg/msgInner/form', params });
export const msgInnerView = (params?: MsgInner | any) =>
defHttp.get<MsgInner>({ url: adminPath + '/msg/msgInner/view', params });
export const msgInnerSave = (params?: any, data?: MsgInner | any) =>
defHttp.postJson<MsgInner>({ url: adminPath + '/msg/msgInner/save', params, data });
export const msgInnerDelete = (params?: MsgInner | any) =>
defHttp.get<MsgInner>({ url: adminPath + '/msg/msgInner/delete', params });

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { encryptByBase64 } from '@jeesite/core/utils/cipher';
export const getLoginValidCode = (params?: any) =>
defHttp.post({ url: '/account/getLoginValidCode', params }, { errorMessageMode: 'none' });
export const loginByValidCode = (params?: any) =>
defHttp.post({ url: '/account/loginByValidCode', params }, { errorMessageMode: 'none' });
export const getFpValidCode = (params?: any) =>
defHttp.post({ url: '/account/getFpValidCode', params }, { errorMessageMode: 'none' });
export const getPwdQuestion = (params?: any) =>
defHttp.post({ url: '/account/getPwdQuestion', params }, { errorMessageMode: 'none' });
export const savePwdByValidCode = (params?: any) => {
if (params.password || params.confirmPassword) {
params.password = encryptByBase64(params.password);
params.confirmPassword = encryptByBase64(params.confirmPassword);
}
return defHttp.post({ url: '/account/savePwdByValidCode', params }, { errorMessageMode: 'none' });
};
export const savePwdByPwdQuestion = (params?: any) => {
if (params.pwdQuestionAnswer || params.pwdQuestionAnswer2 || params.pwdQuestionAnswer3) {
params.pwdQuestionAnswer = encryptByBase64(params.pwdQuestionAnswer);
params.pwdQuestionAnswer2 = encryptByBase64(params.pwdQuestionAnswer2);
params.pwdQuestionAnswer3 = encryptByBase64(params.pwdQuestionAnswer3);
}
if (params.password || params.confirmPassword) {
params.password = encryptByBase64(params.password);
params.confirmPassword = encryptByBase64(params.confirmPassword);
}
return defHttp.post({ url: '/account/savePwdByPwdQuestion', params }, { errorMessageMode: 'none' });
};
export const getRegValidCode = (params?: any) =>
defHttp.post({ url: '/account/getRegValidCode', params }, { errorMessageMode: 'none' });
export const saveRegByValidCode = (params?: any) => {
if (params.password || params.confirmPassword) {
params.password = encryptByBase64(params.password);
params.confirmPassword = encryptByBase64(params.confirmPassword);
}
return defHttp.post({ url: '/account/saveRegByValidCode', params }, { errorMessageMode: 'none' });
};

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { TreeDataModel, TreeModel, Page } from '@jeesite/core/api/model/baseModel';
const { adminPath } = useGlobSetting();
export interface Area extends TreeModel<Area> {
areaCode?: string; // 区域编码
areaName?: string; // 区域名称
areaType?: string; // 区域类型
}
export const areaList = (params?: Area | any) => defHttp.get<Area>({ url: adminPath + '/sys/area/list', params });
export const areaListData = (params?: Area | any) =>
defHttp.post<Page<Area>>({ url: adminPath + '/sys/area/listPageData', params });
export const areaForm = (params?: Area | any) => defHttp.get<Area>({ url: adminPath + '/sys/area/form', params });
export const areaCreateNextNode = (params?: Area | any) =>
defHttp.get<Area>({ url: adminPath + '/sys/area/createNextNode', params });
export const areaSave = (params?: any, data?: Area | any) =>
defHttp.postJson<Area>({ url: adminPath + '/sys/area/save', params, data });
export const areaDisable = (params?: Area | any) => defHttp.get<Area>({ url: adminPath + '/sys/area/disable', params });
export const areaEnable = (params?: Area | any) => defHttp.get<Area>({ url: adminPath + '/sys/area/enable', params });
export const areaDelete = (params?: Area | any) => defHttp.get<Area>({ url: adminPath + '/sys/area/delete', params });
export const areaTreeData = (params?: any) =>
defHttp.get<TreeDataModel[]>({ url: adminPath + '/sys/area/treeData', params });

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { TreeDataModel, TreeModel } from '@jeesite/core/api/model/baseModel';
const { adminPath } = useGlobSetting();
export interface Company extends TreeModel<Company> {
companyCode?: string; // 公司编码
viewCode?: string; // 公司代码
companyName?: string; // 公司名称
fullName?: string; // 公司全称
areaCode?: string; // 区域编码
extend?: any; // 扩展字段
}
export const companyList = (params?: Company | any) =>
defHttp.get<Company>({ url: adminPath + '/sys/company/list', params });
export const companyListData = (params?: Company | any) =>
defHttp.post<Company[]>({ url: adminPath + '/sys/company/listData', params });
export const companyForm = (params?: Company | any) =>
defHttp.get<Company>({ url: adminPath + '/sys/company/form', params });
export const companyCreateNextNode = (params?: Company | any) =>
defHttp.get<Company>({ url: adminPath + '/sys/company/createNextNode', params });
export const companySave = (params?: any, data?: Company | any) =>
defHttp.postJson<Company>({ url: adminPath + '/sys/company/save', params, data });
export const companyDisable = (params?: Company | any) =>
defHttp.get<Company>({ url: adminPath + '/sys/company/disable', params });
export const companyEnable = (params?: Company | any) =>
defHttp.get<Company>({ url: adminPath + '/sys/company/enable', params });
export const companyDelete = (params?: Company | any) =>
defHttp.get<Company>({ url: adminPath + '/sys/company/delete', params });
export const companyTreeData = (params?: any) =>
defHttp.get<TreeDataModel[]>({ url: adminPath + '/sys/company/treeData', params });

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { BasicModel, Page } from '@jeesite/core/api/model/baseModel';
const { adminPath } = useGlobSetting();
export interface Config extends BasicModel<Config> {
configName?: string; // 名称
configKey?: string; // 参数键
configValue?: string; // 参数值
isSys?: string; // 系统内置1是 0否
}
export const configList = (params?: Config | any) =>
defHttp.get<Config>({ url: adminPath + '/sys/config/list', params });
export const configListData = (params?: Config | any) =>
defHttp.post<Page<Config>>({ url: adminPath + '/sys/config/listData', params });
export const configForm = (params?: Config | any) =>
defHttp.get<Config>({ url: adminPath + '/sys/config/form', params });
export const checkConfigKey = (oldConfigKey: string, configKey: string) =>
defHttp.get<Config>({
url: adminPath + '/sys/config/checkConfigKey',
params: { oldConfigKey, configKey },
});
export const configSave = (params?: any, data?: Config | any) =>
defHttp.postJson<Config>({ url: adminPath + '/sys/config/save', params, data });
export const configDelete = (params?: Config | any) =>
defHttp.get<Config>({ url: adminPath + '/sys/config/delete', params });

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { Page, TreeDataModel } from '@jeesite/core/api/model/baseModel';
import { User } from '@jeesite/core/api/sys/user';
const { adminPath } = useGlobSetting();
export const corpAdminList = (params?: User | any) =>
defHttp.get<User>({ url: adminPath + '/sys/corpAdmin/list', params });
export const corpAdminListData = (params?: User | any) =>
defHttp.post<Page<User>>({ url: adminPath + '/sys/corpAdmin/listData', params });
export const corpAdminForm = (params?: User | any) =>
defHttp.get<User>({ url: adminPath + '/sys/corpAdmin/form', params });
export const corpAdminSave = (params?: any, data?: User | any) =>
defHttp.postJson<User>({ url: adminPath + '/sys/corpAdmin/save', params, data });
export const corpAdminDisable = (params?: User | any) =>
defHttp.get<User>({ url: adminPath + '/sys/corpAdmin/disable', params });
export const corpAdminEnable = (params?: User | any) =>
defHttp.get<User>({ url: adminPath + '/sys/corpAdmin/enable', params });
export const corpAdminResetpwd = (params?: User | any) =>
defHttp.get<User>({ url: adminPath + '/sys/corpAdmin/resetpwd', params });
export const corpAdminDelete = (params?: User | any) =>
defHttp.get<User>({ url: adminPath + '/sys/corpAdmin/delete', params });
export const corpAdminTreeData = (params?: any) =>
defHttp.get<TreeDataModel[]>({ url: adminPath + '/sys/corpAdmin/treeData', params });
export const switchCorp = (corpCode: string) =>
defHttp.get<User>({
url: adminPath + '/sys/corpAdmin/switch/' + corpCode,
});

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { TreeDataModel, TreeModel } from '@jeesite/core/api/model/baseModel';
const { adminPath } = useGlobSetting();
export interface DictData extends TreeModel<DictData> {
dictCode?: string; // 字典编码
dictLabelRaw?: string; // 字典标签
dictValue?: string; // 字典键值
dictIcon?: string; // 字典图标
dictType?: string; // 字典类型
isSys?: string; // 系统内置1是 0否
description?: string; // 字典描述
cssStyle?: string; // css样式color:red)
cssClass?: string; // css类名red
}
export interface DictDataTree extends TreeDataModel {
icon?: string; // 字典图标
cssStyle?: string; // css样式color:red)
cssClass?: string; // css类名red
}
export const dictDataList = (params?: DictData | any) =>
defHttp.get<DictData>({ url: adminPath + '/sys/dictData/list', params });
export const dictDataListData = (params?: DictData | any) =>
defHttp.post<DictData[]>({ url: adminPath + '/sys/dictData/listData', params });
export const dictDataForm = (params?: DictData | any) =>
defHttp.get<DictData>({ url: adminPath + '/sys/dictData/form', params });
export const dictDataCreateNextNode = (params?: DictData | any) =>
defHttp.get<DictData>({ url: adminPath + '/sys/dictData/createNextNode', params });
export const dictDataSave = (params?: any, data?: DictData | any) =>
defHttp.postJson<DictData>({ url: adminPath + '/sys/dictData/save', params, data });
export const dictDataDisable = (params?: DictData | any) =>
defHttp.get<DictData>({ url: adminPath + '/sys/dictData/disable', params });
export const dictDataEnable = (params?: DictData | any) =>
defHttp.get<DictData>({ url: adminPath + '/sys/dictData/enable', params });
export const dictDataDelete = (params?: DictData | any) =>
defHttp.get<DictData>({ url: adminPath + '/sys/dictData/delete', params });
export const dictDataTreeData = (params?: any) =>
defHttp.get<DictDataTree[]>({ url: adminPath + '/sys/dictData/treeData', params });

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { BasicModel, Page, TreeDataModel } from '@jeesite/core/api/model/baseModel';
const { adminPath } = useGlobSetting();
export interface DictType extends BasicModel<DictType> {
dictName?: string; // 字典名称
dictType?: string; // 字典类型
isSys?: string; // 是否系统字典
}
export const dictTypeList = (params?: DictType | any) =>
defHttp.get<DictType>({ url: adminPath + '/sys/dictType/list', params });
export const dictTypeListData = (params?: DictType | any) =>
defHttp.post<Page<DictType>>({ url: adminPath + '/sys/dictType/listData', params });
export const dictTypeForm = (params?: DictType | any) =>
defHttp.get<DictType>({ url: adminPath + '/sys/dictType/form', params });
export const dictTypeSave = (params?: any, data?: DictType | any) =>
defHttp.postJson<DictType>({ url: adminPath + '/sys/dictType/save', params, data });
export const checkDictType = (oldDictType: string, dictType: string) =>
defHttp.get<DictType>({
url: adminPath + '/sys/dictType/checkDictType',
params: { oldDictType, dictType },
});
export const dictTypeDelete = (params?: DictType | any) =>
defHttp.get<DictType>({ url: adminPath + '/sys/dictType/delete', params });
export const dictTypeTreeData = (params?: any) =>
defHttp.get<TreeDataModel[]>({ url: adminPath + '/sys/dictType/treeData', params });

View File

@@ -0,0 +1,96 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { BasicModel, Page, TreeDataModel } from '@jeesite/core/api/model/baseModel';
import { User } from '@jeesite/core/api/sys/user';
import { UploadApiResult } from '@jeesite/core/api/sys/upload';
import { UploadFileParams } from '@jeesite/types/axios';
import { AxiosProgressEvent } from 'axios';
import { Office } from '@jeesite/core/api/sys/office';
import { Company } from '@jeesite/core/api/sys/company';
const { ctxPath, adminPath } = useGlobSetting();
export interface EmpUser extends User {
employee?: Employee;
}
export interface Employee extends BasicModel<Employee> {
empCode?: string;
empNo?: string;
empName?: string;
empNameEn?: string;
office?: Office;
company?: Company;
}
export const empUserIndex = (params?: EmpUser | any) =>
defHttp.get<EmpUser>({ url: adminPath + '/sys/empUser/index', params });
export const empUserList = (params?: EmpUser | any) =>
defHttp.get<EmpUser>({ url: adminPath + '/sys/empUser/list', params });
export const empUserListData = (params?: EmpUser | any) =>
defHttp.post<Page<EmpUser>>({ url: adminPath + '/sys/empUser/listData', params });
export const empUserForm = (params?: EmpUser | any) =>
defHttp.get<EmpUser>({ url: adminPath + '/sys/empUser/form', params });
export const empUserSave = (params?: any, data?: EmpUser | any) =>
defHttp.postJson<EmpUser>({ url: adminPath + '/sys/empUser/save', params, data });
export const checkEmpNo = (oldEmpNo: string, empNo: string) =>
defHttp.get<EmpUser>({
url: adminPath + '/sys/empUser/checkEmpNo',
params: { oldEmpNo, 'employee.empNo': empNo },
});
export const empUserImportData = (
params: UploadFileParams,
onUploadProgress: (progressEvent: AxiosProgressEvent) => void,
) =>
defHttp.uploadFile<UploadApiResult>(
{
url: ctxPath + adminPath + '/sys/empUser/importData',
onUploadProgress,
},
params,
);
export const empUserDisable = (params?: EmpUser | any) =>
defHttp.get<EmpUser>({ url: adminPath + '/sys/empUser/disable', params });
export const empUserEnable = (params?: EmpUser | any) =>
defHttp.get<EmpUser>({ url: adminPath + '/sys/empUser/enable', params });
export const resetpwd = (params?: EmpUser | any) =>
defHttp.get<EmpUser>({ url: adminPath + '/sys/empUser/resetpwd', params });
export const empUserDelete = (params?: EmpUser | any) =>
defHttp.get<EmpUser>({ url: adminPath + '/sys/empUser/delete', params }, { errorMessageMode: 'none' });
export const formAuthDataScope = (params?: EmpUser | any) =>
defHttp.get<EmpUser>({ url: adminPath + '/sys/empUser/formAuthDataScope', params });
export const ctrlDataTreeData = (params?: any) => {
const { url, ...params2 } = params;
return defHttp.get<EmpUser>({ url: adminPath + url, params: params2 });
};
export const saveAuthDataScope = (params?: EmpUser | any) =>
defHttp.post<EmpUser>({ url: adminPath + '/sys/empUser/saveAuthDataScope', params });
export const empUserTreeData = (params?: any) =>
defHttp.get<TreeDataModel[]>({ url: adminPath + '/sys/empUser/treeData', params });
export const empUserOfficeListData = (params?: any) =>
defHttp.get<Recordable[]>({ url: adminPath + '/sys/empUser/officeListData', params });
export const empUserSwitchOffice = (officeCode: string) =>
defHttp.get<User>({
url: adminPath + '/sys/empUser/switchOffice' + (officeCode ? '/' + officeCode : ''),
});

View File

@@ -0,0 +1,18 @@
export * from './area';
export * from './company';
export * from './config';
export * from './corpAdmin';
export * from './dictData';
export * from './dictType';
export * from './empUser';
export * from './log';
export * from './login';
export * from './menu';
export * from './module';
export * from './office';
export * from './online';
export * from './post';
export * from './role';
export * from './secAdmin';
export * from './upload';
export * from './user';

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { BasicModel, Page } from '@jeesite/core/api/model/baseModel';
const { adminPath } = useGlobSetting();
export interface Log extends BasicModel<Log> {
logTitle?: string; // 日志标题
requestUri?: string; // 请求URI
logType?: string; // 日志类型
createBy?: string; // 操作用户编码
createByName?: string; // 操作用户名称
requestMethod?: string; // 操作方式
requestParams?: string; // 操作提交的数据
diffModifyData?: string; // 新旧数据比较结果
bizType?: string; // 业务类型
bizKey?: string; // 业务主键
remoteAddr?: string; // 客户端IP
serverAddr?: string; // 请求服务器地址
isException?: string; // 是否异常
exceptionInfo?: string; // 异常信息
userAgent?: string; // 用户代理
deviceName?: string; // 设备名称
browserName?: string; // 浏览器名称
executeTime?: number; // 响应时间
}
export const logList = (params?: Log | any) => defHttp.get<Log>({ url: adminPath + '/sys/log/list', params });
export const logListData = (params?: Log | any) =>
defHttp.post<Page<Log>>({ url: adminPath + '/sys/log/listData', params });
export const logForm = (params?: Log | any) => defHttp.get<Log>({ url: adminPath + '/sys/log/form', params });

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { UserInfo } from '@jeesite/types/store';
import { ErrorMessageMode } from '@jeesite/types/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { encryptByBase64 } from '@jeesite/core/utils/cipher';
import { Menu } from '@jeesite/core/router/types';
import { useAppStore } from '@jeesite/core/store/modules/app';
const { adminPath } = useGlobSetting();
export interface LoginParams {
username: string;
password: string;
validCode?: string;
rememberMe?: boolean;
}
export interface LoginResult {
result: string;
message: string;
sessionid: string;
isValidCodeLogin: boolean;
user: UserInfo;
demoMode: boolean;
useCorpModel: boolean;
currentCorpCode: string;
currentCorpName: string;
modifyPasswordTip: string;
modifyPasswordMsg: string;
msgEnabled: boolean;
sysCode: string;
roleCode: string;
postCode: string;
title: string;
company: string;
version: string;
year: string;
lang: string;
}
export interface AuthInfo {
stringPermissions: string[];
roles: string[];
}
export const loginApi = (params: LoginParams, mode: ErrorMessageMode = 'none') => {
params.username = encryptByBase64(params.username);
params.password = encryptByBase64(params.password);
if (params.validCode) {
params.validCode = encryptByBase64(params.validCode);
}
return defHttp.post<LoginResult>(
{ url: adminPath + '/login', params, timeout: 20 * 1000 },
{ errorMessageMode: mode },
);
};
export const switchSys = (sysCode: string) => {
const params = sysCode ? '/' + sysCode : sysCode;
return defHttp.get({ url: adminPath + '/switch' + params });
};
export const switchRole = (roleCode: string) => {
const params = roleCode ? '/' + roleCode : roleCode;
return defHttp.get({ url: adminPath + '/switchRole' + params });
};
export const switchPost = (postCode: string) => {
const params = postCode ? '/' + postCode : postCode;
return defHttp.get({ url: adminPath + '/switchPost' + params });
};
export const switchSkin = (name = '') => {
if (name == '') {
const appStore = useAppStore();
if (appStore.getDarkMode === 'dark') {
name = 'skin-dark';
} else {
const themeColor = appStore.getProjectConfig.themeColor;
name = themeColor == '#1890ff' ? 'skin-blue-light3' : 'skin-blue3';
}
}
return defHttp.get({ url: adminPath + '/switchSkin/' + name });
};
export const userInfoApi = (mode: ErrorMessageMode = 'message') =>
defHttp.get<LoginResult>({ url: adminPath + '/index', timeout: 10 * 1000 }, { errorMessageMode: mode });
export const authInfoApi = () => defHttp.get<AuthInfo>({ url: adminPath + '/authInfo' });
export const menuRouteApi = () => defHttp.get<Menu[]>({ url: adminPath + '/menuRoute' });
export const logoutApi = () => defHttp.get({ url: adminPath + '/logout' });

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { TreeDataModel, TreeModel } from '@jeesite/core/api/model/baseModel';
const { adminPath } = useGlobSetting();
export interface Menu extends TreeModel<Menu> {
menuCode?: string; // 菜单编码
menuNameRaw?: string; // 菜单名称
menuType?: string; // 菜单类型1菜单 2权限
menuUrl?: string; // 菜单链接
menuTarget?: string; // 目标窗口
menuIcon?: string; // 菜单图标
menuColor?: string; // 菜单颜色
menuTitle?: string; // 菜单标题
permission?: string; // 权限标识
weight?: number; // 菜单权重(权重越大,表示菜单的重要性越大)
isShow?: string; // 是否显示1显示 0隐藏
sysCode?: string; // 归属系统default:主导航菜单、mobileApp:APP菜单
moduleCodes?: string; // 归属模块(多个用逗号隔开)
}
export const menuIndex = (params?: Menu | any) => defHttp.get<Menu>({ url: adminPath + '/sys/menu/index', params });
export const menuList = (params?: Menu | any) => defHttp.get<Menu>({ url: adminPath + '/sys/menu/list', params });
export const menuListData = (params?: Menu | any) =>
defHttp.post<Menu[]>({ url: adminPath + '/sys/menu/listData', params });
export const menuForm = (params?: Menu | any) => defHttp.get<Menu>({ url: adminPath + '/sys/menu/form', params });
export const menuCreateNextNode = (params?: Menu | any) =>
defHttp.get<Menu>({ url: adminPath + '/sys/menu/createNextNode', params });
export const menuSave = (params?: any, data?: Menu | any) =>
defHttp.postJson<Menu>({ url: adminPath + '/sys/menu/save', params, data });
export const menuDisable = (params?: Menu | any) => defHttp.get<Menu>({ url: adminPath + '/sys/menu/disable', params });
export const menuEnable = (params?: Menu | any) => defHttp.get<Menu>({ url: adminPath + '/sys/menu/enable', params });
export const menuDelete = (params?: Menu | any) => defHttp.get<Menu>({ url: adminPath + '/sys/menu/delete', params });
export const menuTreeData = (params?: any) =>
defHttp.get<TreeDataModel[]>({ url: adminPath + '/sys/menu/treeData', params });

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { BasicModel, Page } from '@jeesite/core/api/model/baseModel';
const { adminPath } = useGlobSetting();
export interface Module extends BasicModel<Module> {
moduleCode?: string; // 模块编码
moduleName?: string; // 模块名称
description?: string; // 模块描述
mainClassName?: string; // 主类全名
currentVersion?: string; // 当前版本
upgradeInfo?: string; // 升级信息
}
export const moduleList = (params?: Module | any) =>
defHttp.get<Module>({ url: adminPath + '/sys/module/list', params });
export const moduleListData = (params?: Module | any) =>
defHttp.post<Page<Module>>({ url: adminPath + '/sys/module/listData', params });
export const moduleSelectData = (params?: Module | any) =>
defHttp.post<Recordable[]>({ url: adminPath + '/sys/module/selectData', params });
export const moduleForm = (params?: Module | any) =>
defHttp.get<Module>({ url: adminPath + '/sys/module/form', params });
export const moduleSave = (params?: any, data?: Module | any) =>
defHttp.postJson<Module>({ url: adminPath + '/sys/module/save', params, data });
export const moduleDisable = (params?: Module | any) =>
defHttp.get<Module>({ url: adminPath + '/sys/module/disable', params });
export const moduleEnable = (params?: Module | any) =>
defHttp.get<Module>({ url: adminPath + '/sys/module/enable', params });
export const moduleDelete = (params?: Module | any) =>
defHttp.get<Module>({ url: adminPath + '/sys/module/delete', params });

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { TreeDataModel, TreeModel } from '@jeesite/core/api/model/baseModel';
import { UploadApiResult } from '@jeesite/core/api/sys/upload';
import { UploadFileParams } from '@jeesite/types/axios';
import { AxiosProgressEvent } from 'axios';
const { ctxPath, adminPath } = useGlobSetting();
export interface Office extends TreeModel<Office> {
officeCode?: string; // 机构编码
viewCode?: string; // 机构代码
officeName?: string; // 机构名称
fullName?: string; // 机构全称
officeType?: string; // 机构类型
leader?: string; // 负责人
phone?: string; // 办公电话
address?: string; // 联系地址
zipCode?: string; // 邮政编码
email?: string; // 电子邮箱
extend?: any; // 扩展字段
companyCode?: string; // 根据公司查询机构,组织机构所属公司
}
export const officeList = (params?: Office | any) =>
defHttp.get<Office>({ url: adminPath + '/sys/office/list', params });
export const officeListData = (params?: Office | any) =>
defHttp.post<Office[]>({ url: adminPath + '/sys/office/listData', params });
export const officeForm = (params?: Office | any) =>
defHttp.get<Office>({ url: adminPath + '/sys/office/form', params });
export const officeCreateNextNode = (params?: Office | any) =>
defHttp.get<Office>({ url: adminPath + '/sys/office/createNextNode', params });
export const officeSave = (params?: any, data?: Office | any) =>
defHttp.postJson<Office>({ url: adminPath + '/sys/office/save', params, data });
export const officeImportData = (
params: UploadFileParams,
onUploadProgress: (progressEvent: AxiosProgressEvent) => void,
) =>
defHttp.uploadFile<UploadApiResult>(
{
url: ctxPath + adminPath + '/sys/office/importData',
onUploadProgress,
},
params,
);
export const officeDisable = (params?: Office | any) =>
defHttp.get<Office>({ url: adminPath + '/sys/office/disable', params });
export const officeEnable = (params?: Office | any) =>
defHttp.get<Office>({ url: adminPath + '/sys/office/enable', params });
export const officeDelete = (params?: Office | any) =>
defHttp.get<Office>({ url: adminPath + '/sys/office/delete', params });
export const officeTreeData = (params?: any) =>
defHttp.get<TreeDataModel[]>({ url: adminPath + '/sys/office/treeData', params });

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import type { Result } from '@jeesite/types/axios';
const { adminPath } = useGlobSetting();
export interface Online {
id?: string;
startTimestamp?: string;
lastAccessTime?: string;
timeout?: string;
userCode?: string;
userName?: string;
userType?: string;
deviceType?: string;
host?: string;
}
export const onlineList = (params?: Online | any) =>
defHttp.get<Online>({ url: adminPath + '/sys/online/list', params });
export const onlineListData = (params?: Online | any) =>
defHttp.post<Online[]>({ url: adminPath + '/sys/online/listData', params });
export const onlineTickOut = (params?: Online | any) =>
defHttp.post<Result>({ url: adminPath + '/sys/online/tickOut', params });
export const onlineCount = () =>
defHttp.post<any>({ url: adminPath + '/sys/online/count?__notUpdateSession=true' }, { errorMessageMode: 'none' });

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { TreeDataModel, BasicModel, Page } from '@jeesite/core/api/model/baseModel';
const { adminPath } = useGlobSetting();
export interface Post extends BasicModel<Post> {
postCode?: string; // 岗位编码
postName?: string; // 岗位名称
postType?: string; // 岗位分类(高管、中层、基层)
postSort?: number; // 岗位排序(升序)
viewCode?: string; // 岗位代码
}
export const postList = (params?: Post | any) => defHttp.get<Post>({ url: adminPath + '/sys/post/list', params });
export const postListData = (params?: Post | any) =>
defHttp.post<Page<Post>>({ url: adminPath + '/sys/post/listData', params });
export const postForm = (params?: Post | any) => defHttp.get<Post>({ url: adminPath + '/sys/post/form', params });
export const postDisable = (params?: Post | any) => defHttp.get<Post>({ url: adminPath + '/sys/post/disable', params });
export const postEnable = (params?: Post | any) => defHttp.get<Post>({ url: adminPath + '/sys/post/enable', params });
export const postSave = (params?: any, data?: Post | any) =>
defHttp.postJson<Post>({ url: adminPath + '/sys/post/save', params, data });
export const postDelete = (params?: Post | any) => defHttp.get<Post>({ url: adminPath + '/sys/post/delete', params });
export const postTreeData = (params?: any) =>
defHttp.get<TreeDataModel[]>({ url: adminPath + '/sys/post/treeData', params });

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { Page, TreeDataModel, BasicModel } from '@jeesite/core/api/model/baseModel';
const { adminPath } = useGlobSetting();
export interface Role extends BasicModel<Role> {
roleCode?: string; // 角色编码
roleName?: string; // 角色名称
roleType?: string; // 角色分类(高管、中层、基层、其它)
roleSort?: number; // 角色排序(升序)
isSys?: string; // 系统内置1是 0否
userType?: string; // 用户类型employee员工 member会员
dataScope?: string; // 数据范围设置0未设置 1全部数据 2自定义数据
bizScope?: string; // 适应业务范围(不同的功能,不同的数据权限支持)
sysCodes?: string; // 包含系统
extend?: any; // 扩展字段
viewCode?: string; // 角色代码
userCode?: string; // 根据用户编号查询授权的角色列表
}
export const roleList = (params?: Role | any) => defHttp.get<Role>({ url: adminPath + '/sys/role/list', params });
export const roleListData = (params?: Role | any) =>
defHttp.post<Page<Role>>({ url: adminPath + '/sys/role/listData', params });
export const roleForm = (params?: Role | any) => defHttp.get<Role>({ url: adminPath + '/sys/role/form', params });
export const roleMenuTreeData = (params?: any) =>
defHttp.get<Recordable>({ url: adminPath + '/sys/role/menuTreeData', params });
export const menuTreeDataByRoleCode = (params?: any) =>
defHttp.get<Recordable>({ url: adminPath + '/sys/role/menuTreeDataByRoleCode', params });
export const roleSave = (params?: any, data?: Role | any) =>
defHttp.postJson<Role>({ url: adminPath + '/sys/role/save', params, data });
export const checkRoleName = (oldRoleName: string, roleName: string) =>
defHttp.get<Role>({
url: adminPath + '/sys/role/checkRoleName',
params: { oldRoleName, roleName },
});
export const roleDisable = (params?: Role | any) => defHttp.get<Role>({ url: adminPath + '/sys/role/disable', params });
export const roleEnable = (params?: Role | any) => defHttp.get<Role>({ url: adminPath + '/sys/role/enable', params });
export const roleDelete = (params?: Role | any) => defHttp.get<Role>({ url: adminPath + '/sys/role/delete', params });
export const roleFormAuthDataScope = (params?: Role | any) =>
defHttp.get<Role>({ url: adminPath + '/sys/role/formAuthDataScope', params });
export const roleCtrlDataTreeData = (params?: any) => {
const { url, ...params2 } = params;
return defHttp.get<Role>({ url: adminPath + url, params: params2 });
};
export const roleSaveAuthDataScope = (params?: Role | any) =>
defHttp.post<Role>({ url: adminPath + '/sys/role/saveAuthDataScope', params });
export const formAuthUser = (params?: Role | any) =>
defHttp.get<Role>({ url: adminPath + '/sys/role/formAuthUser', params });
export const saveAuthUser = (params?: Role | any) =>
defHttp.post<Role>({ url: adminPath + '/sys/role/saveAuthUser', params });
export const deleteAuthUser = (params?: Role | any) =>
defHttp.post<Role>({ url: adminPath + '/sys/role/deleteAuthUser', params });
export const roleTreeData = (params?: any) =>
defHttp.get<TreeDataModel[]>({ url: adminPath + '/sys/role/treeData', params });

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { Page } from '@jeesite/core/api/model/baseModel';
import { User } from '@jeesite/core/api/sys/user';
const { adminPath } = useGlobSetting();
export const secAdminList = (params?: User | any) =>
defHttp.get<User>({ url: adminPath + '/sys/secAdmin/list', params });
export const secAdminListData = (params?: User | any) =>
defHttp.post<Page<User>>({ url: adminPath + '/sys/secAdmin/listData', params });
export const secAdminForm = (params?: User | any) =>
defHttp.get<User>({ url: adminPath + '/sys/secAdmin/form', params });
export const secAdminSave = (params?: any) => defHttp.post<User>({ url: adminPath + '/sys/secAdmin/save', params });
export const secAdminDelete = (params?: User | any) =>
defHttp.get<User>({ url: adminPath + '/sys/secAdmin/delete', params });

View File

@@ -0,0 +1,82 @@
import { defHttp } from '@jeesite/core/utils/http/axios';
import { UploadFileParams } from '@jeesite/types/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { BasicModel } from '@jeesite/core/api/model/baseModel';
import { AxiosProgressEvent } from 'axios';
const { ctxPath, adminPath } = useGlobSetting();
export interface UploadApiResult {
code: string;
// url: string;
result: string;
message: string;
fileEntityId: string;
fileUploadId: string;
fileUpload: FileUpload;
}
export interface FileEntity extends BasicModel<FileEntity> {
fileId: string;
fileMd5: string;
filePath: string;
fileContentType: string;
fileExtension: string;
fileSize: number;
fileMeta: string;
fileMetaMap: any;
filePreview: string;
}
export interface FileUpload extends BasicModel<FileUpload> {
fileEntity: FileEntity;
fileName: string;
fileType: string;
fileSort: number;
bizKey: string;
bizType: string;
bizKeyIsLike: string;
fileUrl?: string;
}
export interface UploadParams {
maxFileSize: number;
imageAllowSuffixes: string;
mediaAllowSuffixes: string;
fileAllowSuffixes: string;
imageMaxWidth?: number;
imageMaxHeight?: number;
checkmd5?: boolean;
chunked?: boolean;
chunkSize?: number;
threads?: number;
}
/**
* @description: Upload interface
*/
export function uploadFile(
params: UploadFileParams,
onUploadProgress: (progressEvent: ProgressEvent | AxiosProgressEvent) => void,
apiUploadUrl?: string,
) {
if (params.file != undefined) {
return defHttp.uploadFile<UploadApiResult>(
{
url: apiUploadUrl || ctxPath + adminPath + '/file/upload',
onUploadProgress,
},
params,
);
} else {
return defHttp.post(
{ url: apiUploadUrl || ctxPath + adminPath + '/file/upload', params },
{ errorMessageMode: 'none', apiUrl: '', urlPrefix: '' },
);
}
}
export const uploadFileList = (params?: FileUpload | any, apiFileListUrl?: string) =>
defHttp.get<FileUpload[]>(
{ url: apiFileListUrl || ctxPath + adminPath + '/file/fileList', params },
{ errorMessageMode: 'none', apiUrl: '', urlPrefix: '' },
);
export const uploadParams = () => defHttp.get<UploadParams>({ url: adminPath + '/file/params' });

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
*/
import { defHttp } from '@jeesite/core/utils/http/axios';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { BasicModel, Page } from '@jeesite/core/api/model/baseModel';
import { encryptByBase64 } from '@jeesite/core/utils/cipher';
const { adminPath } = useGlobSetting();
export interface User extends BasicModel<User> {
userCode?: string; // 用户编码
loginCode?: string; // 登录账号
userName?: string; // 用户昵称
password?: string; // 登录密码
email?: string; // 电子邮箱
mobile?: string; // 手机号码
phone?: string; // 办公电话
sex?: string; // 用户性别
avatar?: string; // 头像路径
sign?: string; // 个性签名
wxOpenid?: string; // 绑定的微信号
mobileImei?: string; // 绑定的手机串号
userType?: string; // 用户类型none未设置 employee员工 member会员
refCode?: string; // 用户类型引用编号
refName?: string; // 用户类型引用姓名
mgrType?: string; // 管理员类型0非管理员 1系统管理员 2二级管理员
lastLoginIp?: string; // 最后登陆IP
lastLoginDate?: string; // 最后登陆时间
freezeDate?: string; // 冻结时间
freezeCause?: string; // 冻结原因
userWeight?: number; // 用户权重(降序)
avatarBase64?: string; // 头像Base64数据修改头像时用
oldLastLoginIp?: string; // 上次登陆IP
oldLastLoginDate?: string; // 上次登陆日期
roleCode?: string; // 根据角色查询用户条件
isAll?: string; // 不过滤数据权限,查询全部用户
ctrlPermi?: string; // 权限控制类型(拥有权限、管理权限)
}
export const userListData = (params?: User | any) =>
defHttp.post<Page<User>>({ url: adminPath + '/sys/user/listData', params });
export const checkLoginCode = (oldLoginCode: string, loginCode: string) =>
defHttp.get<User>({
url: adminPath + '/sys/user/checkLoginCode',
params: { oldLoginCode, loginCode },
});
export const userInfo = (params?: any) => defHttp.post<User>({ url: adminPath + '/sys/user/info', params });
export const infoSaveBase = (params?: any) => defHttp.post<User>({ url: adminPath + '/sys/user/infoSaveBase', params });
export const infoSavePwd = (params?: any) => {
params.oldPassword = encryptByBase64(params.oldPassword);
params.newPassword = encryptByBase64(params.newPassword);
params.confirmNewPassword = encryptByBase64(params.confirmNewPassword);
return defHttp.post<User>({ url: adminPath + '/sys/user/infoSavePwd', params });
};
export const infoSavePqa = (params?: any) => {
params.validPassword = encryptByBase64(params.validPassword);
params.oldPwdQuestionAnswer = encryptByBase64(params.oldPwdQuestionAnswer);
params.oldPwdQuestionAnswer2 = encryptByBase64(params.oldPwdQuestionAnswer2);
params.oldPwdQuestionAnswer3 = encryptByBase64(params.oldPwdQuestionAnswer3);
params.pwdQuestionAnswer = encryptByBase64(params.pwdQuestionAnswer);
params.pwdQuestionAnswer2 = encryptByBase64(params.pwdQuestionAnswer2);
params.pwdQuestionAnswer3 = encryptByBase64(params.pwdQuestionAnswer3);
return defHttp.post<User>({ url: adminPath + '/sys/user/infoSavePqa', params });
};

View File

@@ -0,0 +1,15 @@
import { withInstall } from '@jeesite/core/utils';
import appLogo from './src/AppLogo.vue';
import appProvider from './src/AppProvider.vue';
import appSearch from './src/search/AppSearch.vue';
import appLocalePicker from './src/AppLocalePicker.vue';
import appDarkModeToggle from './src/AppDarkModeToggle.vue';
export { useAppProviderContext } from './src/useAppContext';
export const AppLogo = withInstall(appLogo);
export const AppProvider = withInstall(appProvider);
export const AppSearch = withInstall(appSearch);
export const AppLocalePicker = withInstall(appLocalePicker);
export const AppDarkModeToggle = withInstall(appDarkModeToggle);

View File

@@ -0,0 +1,79 @@
<template>
<div v-if="getShowDarkModeToggle" :class="getClass" @click="toggleDarkMode">
<div :class="`${prefixCls}-inner`"></div>
<Icon icon="i-svg:sun" size="14" />
<Icon icon="i-svg:moon" size="14" />
</div>
</template>
<script lang="ts" setup>
import { computed, unref } from 'vue';
import { Icon } from '@jeesite/core/components/Icon';
import { useDesign } from '@jeesite/core/hooks/web/useDesign';
import { useRootSetting } from '@jeesite/core/hooks/setting/useRootSetting';
import { updateHeaderBgColor, updateSidebarBgColor } from '@jeesite/core/logics/theme/updateBackground';
import { updateDarkTheme } from '@jeesite/core/logics/theme/dark';
import { ThemeEnum } from '@jeesite/core/enums/appEnum';
const { prefixCls } = useDesign('dark-switch');
const { getDarkMode, setDarkMode, getShowDarkModeToggle } = useRootSetting();
const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK);
const getClass = computed(() => [
prefixCls,
{
[`${prefixCls}--dark`]: unref(isDark),
},
]);
function toggleDarkMode() {
const darkMode = getDarkMode.value === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK;
setDarkMode(darkMode);
updateDarkTheme(darkMode);
updateHeaderBgColor();
updateSidebarBgColor();
}
</script>
<style lang="less">
@prefix-cls: ~'jeesite-dark-switch';
html[data-theme='dark'] {
.@{prefix-cls} {
border: 1px solid rgb(196 188 188);
}
}
.@{prefix-cls} {
z-index: 10 !important;
position: relative !important;
display: flex;
width: 50px;
height: 26px;
padding: 0 6px;
margin-left: auto;
cursor: pointer;
background-color: #151515;
border-radius: 30px;
justify-content: space-between;
align-items: center;
&-inner {
position: absolute;
z-index: 1;
width: 18px;
height: 18px;
background-color: #fff;
border-radius: 50%;
transition:
transform 0.5s,
background-color 0.5s;
will-change: transform;
}
&--dark {
.@{prefix-cls}-inner {
transform: translateX(calc(100% + 2px));
}
}
}
</style>

View File

@@ -0,0 +1,76 @@
<!--
* @Author: Vben
* @Description: Multi-language switching component
-->
<template>
<Dropdown
placement="bottom"
:trigger="['click']"
:dropMenuList="localeList"
:selectedKeys="selectedKeys"
@menu-event="handleMenuEvent"
overlayClassName="app-locale-picker-overlay"
>
<span class="flex cursor-pointer items-center">
<Icon icon="i-ion:language" />
<span v-if="showText" class="ml-1">{{ getLocaleText }}</span>
</span>
</Dropdown>
</template>
<script lang="ts" setup>
import type { LocaleType } from '@jeesite/types/config';
import type { DropMenu } from '@jeesite/core/components/Dropdown';
import { ref, watchEffect, unref, computed } from 'vue';
import { Dropdown } from '@jeesite/core/components/Dropdown';
import { Icon } from '@jeesite/core/components/Icon';
import { useLocale } from '@jeesite/core/locales/useLocale';
import { localeList } from '@jeesite/core/settings/localeSetting';
const props = defineProps({
/**
* Whether to display text
*/
showText: { type: Boolean, default: true },
/**
* Whether to refresh the interface when changing
*/
reload: { type: Boolean },
});
const selectedKeys = ref<string[]>([]);
const { changeLocale, getLocale } = useLocale();
const getLocaleText = computed(() => {
const key = selectedKeys.value[0];
if (!key) {
return '';
}
return localeList.find((item) => item.event === key)?.text;
});
watchEffect(() => {
selectedKeys.value = [unref(getLocale)];
});
async function toggleLocale(lang: LocaleType | string) {
await changeLocale(lang as LocaleType);
selectedKeys.value = [lang as string];
props.reload && location.reload();
}
function handleMenuEvent(menu: DropMenu) {
if (unref(getLocale) === menu.event) {
return;
}
toggleLocale(menu.event as string);
}
</script>
<style lang="less">
.app-locale-picker-overlay {
.ant-dropdown-menu-item {
min-width: 160px;
}
}
</style>

View File

@@ -0,0 +1,98 @@
<!--
* @Author: Vben
* @Description: logo component
-->
<template>
<div class="anticon" :class="getAppLogoClass" @click="goHome">
<img src="@jeesite/assets/images/logo.png" v-show="!!!showTitle" />
<div class="ml-2 truncate md:opacity-100" :class="getTitleClass" v-show="showTitle">
{{ getTitle }}
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, unref } from 'vue';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { useGo } from '@jeesite/core/hooks/web/usePage';
import { useMenuSetting } from '@jeesite/core/hooks/setting/useMenuSetting';
import { useDesign } from '@jeesite/core/hooks/web/useDesign';
import { PageEnum } from '@jeesite/core/enums/pageEnum';
import { useUserStore } from '@jeesite/core/store/modules/user';
const props = defineProps({
/**
* The theme of the current parent component
*/
theme: { type: String, validator: (v: string) => ['light', 'dark'].includes(v) },
/**
* Whether to show title
*/
showTitle: { type: Boolean, default: true },
/**
* The title is also displayed when the menu is collapsed
*/
alwaysShowTitle: { type: Boolean },
});
const { prefixCls } = useDesign('app-logo');
const { getCollapsedShowTitle } = useMenuSetting();
const userStore = useUserStore();
const go = useGo();
const getTitle = computed(() => {
const { title } = useGlobSetting();
return userStore.getPageCacheByKey('title', title);
});
const getAppLogoClass = computed(() => [
prefixCls,
props.theme,
{ 'collapsed-show-title': unref(getCollapsedShowTitle) },
]);
const getTitleClass = computed(() => [
`${prefixCls}__title`,
{
'xs:opacity-0': !props.alwaysShowTitle,
},
]);
function goHome() {
go(userStore.getUserInfo.homePath || PageEnum.BASE_HOME);
}
</script>
<style lang="less">
@prefix-cls: ~'jeesite-app-logo';
.@{prefix-cls} {
display: flex;
align-items: center;
padding-left: 7px;
cursor: pointer;
transition: all 0.2s ease;
&.light {
// border-bottom: 1px solid @border-color-base;
border-bottom: 0;
}
&.collapsed-show-title {
padding-left: 20px;
}
&.light &__title {
color: #555;
}
&.dark &__title {
color: @white;
}
&__title {
font-size: 20px;
// font-weight: bold;
font-family: Arial, 'Microsoft YaHei', sans-serif;
transition: all 0.5s;
}
}
</style>

View File

@@ -0,0 +1,78 @@
<script lang="ts">
import { defineComponent, toRefs, ref, unref } from 'vue';
import { createAppProviderContext } from './useAppContext';
import { createBreakpointListen } from '@jeesite/core/hooks/event/useBreakpoint';
// import { useAppStore } from '@jeesite/core/store/modules/app';
// import { MenuModeEnum, MenuTypeEnum } from '@jeesite/core/enums/menuEnum';
export default defineComponent({
name: 'AppProvider',
inheritAttrs: false,
props: {
prefixCls: {
type: String,
default: 'jeesite',
},
},
setup(props, { slots }) {
const isMobile = ref(false);
// const isSetState = ref(false);
// const appStore = useAppStore();
// Monitor screen breakpoint information changes
createBreakpointListen(({ screenMap, sizeEnum, width }) => {
const lgWidth = screenMap.get(sizeEnum.LG);
if (lgWidth) {
isMobile.value = width.value - 1 < lgWidth;
}
// handleRestoreState();
});
const { prefixCls } = toRefs(props);
// Inject variables into the global
createAppProviderContext({ prefixCls, isMobile });
// /**
// * Used to maintain the state before the window changes
// */
// function handleRestoreState() {
// if (unref(isMobile)) {
// if (!unref(isSetState)) {
// isSetState.value = true;
// const {
// menuSetting: {
// type: menuType,
// mode: menuMode,
// collapsed: menuCollapsed,
// split: menuSplit,
// },
// } = appStore.getProjectConfig;
// appStore.setProjectConfig({
// menuSetting: {
// mode: MenuModeEnum.INLINE,
// type: MenuTypeEnum.MIX,
// split: false,
// },
// });
// appStore.setBeforeMiniInfo({ menuMode, menuCollapsed, menuType, menuSplit });
// }
// } else {
// if (unref(isSetState)) {
// isSetState.value = false;
// const { menuMode, menuCollapsed, menuType, menuSplit } = appStore.getBeforeMiniInfo;
// appStore.setProjectConfig({
// menuSetting: {
// type: menuType,
// mode: menuMode,
// collapsed: menuCollapsed,
// split: menuSplit,
// },
// });
// }
// }
// }
return () => slots.default?.();
},
});
</script>

View File

@@ -0,0 +1,34 @@
<script lang="tsx">
import { defineComponent, ref, unref } from 'vue';
import { Tooltip } from 'ant-design-vue';
import { Icon } from '@jeesite/core/components/Icon';
import AppSearchModal from './AppSearchModal.vue';
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
export default defineComponent({
name: 'AppSearch',
setup() {
const showModal = ref(false);
const { t } = useI18n();
function changeModal(show: boolean) {
showModal.value = show;
}
return () => {
return (
<div class="p-1" onClick={changeModal.bind(null, true)}>
<Tooltip>
{{
title: () => t('common.searchText'),
// default: () => <SearchOutlined />,
default: () => <Icon icon="i-ant-design:search-outlined" />,
}}
</Tooltip>
<AppSearchModal onClose={changeModal.bind(null, false)} open={unref(showModal)} />
</div>
);
};
},
});
</script>

View File

@@ -0,0 +1,58 @@
<template>
<div :class="`${prefixCls}`">
<AppSearchKeyItem :class="`${prefixCls}-item`" icon="i-ant-design:enter-outlined" />
<span>{{ t('component.app.toSearch') }}</span>
<AppSearchKeyItem :class="`${prefixCls}-item`" icon="i-ion:arrow-up-outline" />
<AppSearchKeyItem :class="`${prefixCls}-item`" icon="i-ion:arrow-down-outline" />
<span>{{ t('component.app.toNavigate') }}</span>
<AppSearchKeyItem :class="`${prefixCls}-item`" icon="i-mdi:keyboard-esc" />
<span>{{ t('common.closeText') }}</span>
</div>
</template>
<script lang="ts" setup>
import AppSearchKeyItem from './AppSearchKeyItem.vue';
import { useDesign } from '@jeesite/core/hooks/web/useDesign';
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
const { prefixCls } = useDesign('app-search-footer');
const { t } = useI18n();
</script>
<style lang="less">
@prefix-cls: ~'jeesite-app-search-footer';
.@{prefix-cls} {
position: relative;
display: flex;
height: 44px;
padding: 0 16px;
font-size: 12px;
color: #666;
background-color: @component-background;
border-top: 1px solid @border-color-base;
border-radius: 0 0 16px 16px;
align-items: center;
flex-shrink: 0;
&-item {
display: flex;
width: 20px;
height: 18px;
padding-bottom: 2px;
margin-right: 0.4em;
background-color: linear-gradient(-225deg, #d5dbe4, #f8f8f8);
border-radius: 2px;
box-shadow:
inset 0 -2px 0 0 #cdcde6,
inset 0 0 1px 1px #fff,
0 1px 2px 1px rgb(30 35 90 / 40%);
align-items: center;
justify-content: center;
&:nth-child(2),
&:nth-child(3),
&:nth-child(6) {
margin-left: 14px;
}
}
}
</style>

View File

@@ -0,0 +1,11 @@
<template>
<span :class="$attrs.class">
<Icon :icon="icon" />
</span>
</template>
<script lang="ts" setup>
import { Icon } from '@jeesite/core/components/Icon';
defineProps({
icon: String,
});
</script>

View File

@@ -0,0 +1,269 @@
<template>
<Teleport to="body">
<transition name="zoom-fade" mode="out-in">
<div :class="getClass" @click.stop v-if="open">
<div :class="`${prefixCls}-content`" v-click-outside="handleClose">
<div :class="`${prefixCls}-input__wrapper`">
<a-input
:class="`${prefixCls}-input`"
:placeholder="t('common.searchText')"
ref="inputRef"
allow-clear
@change="handleSearch"
>
<template #prefix>
<Icon icon="i-ant-design:search-outlined" class="text-gray-500" />
</template>
</a-input>
<span :class="`${prefixCls}-cancel`" @click="handleClose">
{{ t('common.cancelText') }}
</span>
</div>
<div :class="`${prefixCls}-not-data`" v-show="getIsNotData">
{{ t('component.app.searchNotData') }}
</div>
<ul :class="`${prefixCls}-list`" v-show="!getIsNotData" ref="scrollWrap">
<li
:ref="setRefs(index)"
v-for="(item, index) in searchResult"
:key="item.path"
:data-index="index"
@mouseenter="handleMouseenter"
@click="handleEnter"
:class="[
`${prefixCls}-list__item`,
{
[`${prefixCls}-list__item--active`]: activeIndex === index,
},
]"
>
<div :class="`${prefixCls}-list__item-icon`">
<Icon :icon="item.icon || 'mdi:form-select'" :size="20" />
</div>
<div :class="`${prefixCls}-list__item-text`">
{{ item.name }}
</div>
<div :class="`${prefixCls}-list__item-enter`">
<Icon icon="i-ant-design:enter-outlined" :size="20" />
</div>
</li>
</ul>
<AppSearchFooter />
</div>
</div>
</transition>
</Teleport>
</template>
<script lang="ts" setup>
import { computed, unref, ref, watch, nextTick } from 'vue';
import AppSearchFooter from './AppSearchFooter.vue';
import { Icon } from '@jeesite/core/components/Icon';
// @ts-ignore
import vClickOutside from '@jeesite/core/directives/clickOutside';
import { useDesign } from '@jeesite/core/hooks/web/useDesign';
import { useRefs } from '@jeesite/core/hooks/core/useRefs';
import { useMenuSearch } from './useMenuSearch';
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
import { useAppInject } from '@jeesite/core/hooks/web/useAppInject';
const props = defineProps({
open: { type: Boolean },
});
const emit = defineEmits(['close']);
const scrollWrap = ref(null);
const inputRef = ref<Nullable<HTMLElement>>(null);
const { t } = useI18n();
const { prefixCls } = useDesign('app-search-modal');
const { refs, setRefs } = useRefs();
const { getIsMobile } = useAppInject();
const { handleSearch, searchResult, keyword, activeIndex, handleEnter, handleMouseenter } = useMenuSearch(
refs,
scrollWrap,
emit,
);
const getIsNotData = computed(() => !keyword || unref(searchResult).length === 0);
const getClass = computed(() => {
return [
prefixCls,
{
[`${prefixCls}--mobile`]: unref(getIsMobile),
},
];
});
watch(
() => props.open,
(open: boolean) => {
open &&
nextTick(() => {
unref(inputRef)?.focus();
});
},
);
function handleClose() {
searchResult.value = [];
emit('close');
}
</script>
<style lang="less">
@prefix-cls: ~'jeesite-app-search-modal';
@footer-prefix-cls: ~'jeesite-app-search-footer';
.@{prefix-cls} {
position: fixed;
top: 0;
left: 0;
z-index: 800;
display: flex;
width: 100%;
height: 100%;
padding-top: 50px;
background-color: rgb(0 0 0 / 25%);
justify-content: center;
&--mobile {
padding: 0;
> div {
width: 100%;
}
.@{prefix-cls}-input {
width: calc(100% - 38px);
}
.@{prefix-cls}-cancel {
display: inline-block;
}
.@{prefix-cls}-content {
width: 100%;
height: 100%;
border-radius: 0;
}
.@{footer-prefix-cls} {
display: none;
}
.@{prefix-cls}-list {
height: calc(100% - 80px);
max-height: unset;
&__item {
&-enter {
opacity: 0 !important;
}
}
}
}
&-content {
position: relative;
width: 632px;
margin: 0 auto auto;
background-color: @component-background;
border-radius: 16px;
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 25%);
flex-direction: column;
}
&-input__wrapper {
display: flex;
padding: 14px 14px 0;
justify-content: space-between;
align-items: center;
}
&-input {
width: 100%;
height: 48px;
font-size: 1.5em;
color: #1c1e21;
border-radius: 6px;
span[role='img'] {
color: #999;
}
}
&-cancel {
display: none;
font-size: 1em;
color: #666;
}
&-not-data {
display: flex;
width: 100%;
height: 100px;
font-size: 0.9;
color: rgb(150 159 175);
align-items: center;
justify-content: center;
}
&-list {
max-height: 472px;
padding: 0 14px;
padding-bottom: 20px;
margin: 0 auto;
margin-top: 14px;
overflow: auto;
&__item {
position: relative;
display: flex;
width: 100%;
height: 56px;
padding-bottom: 4px;
padding-left: 14px;
margin-top: 8px;
font-size: 14px;
color: @text-color-base;
cursor: pointer;
background-color: @component-background;
border-radius: 4px;
box-shadow: 0 1px 3px 0 #d4d9e1;
align-items: center;
> div:first-child,
> div:last-child {
display: flex;
align-items: center;
}
&--active {
color: #fff;
background-color: @primary-color;
.@{prefix-cls}-list__item-enter {
opacity: 1;
}
}
&-icon {
width: 30px;
}
&-text {
flex: 1;
}
&-enter {
width: 30px;
opacity: 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,166 @@
import type { Menu } from '@jeesite/core/router/types';
import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue';
import { getMenus } from '@jeesite/core/router/menus';
import { cloneDeep } from 'lodash-es';
import { filter, forEach } from '@jeesite/core/utils/helper/treeHelper';
import { useGo } from '@jeesite/core/hooks/web/usePage';
import { useScrollTo } from '@jeesite/core/hooks/event/useScrollTo';
import { onKeyStroke, useDebounceFn } from '@vueuse/core';
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
export interface SearchResult {
name: string;
path: string;
icon?: string;
}
// Translate special characters
function transform(c: string) {
const code: string[] = ['$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|'];
return code.includes(c) ? `\\${c}` : c;
}
function createSearchReg(key: string) {
const keys = [...key].map((item) => transform(item));
const str = ['', ...keys, ''].join('.*');
return new RegExp(str);
}
export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>, emit: EmitType) {
const searchResult = ref<SearchResult[]>([]);
const keyword = ref('');
const activeIndex = ref(-1);
let menuList: Menu[] = [];
const { t } = useI18n();
const go = useGo();
const handleSearch = useDebounceFn(search, 200);
onBeforeMount(async () => {
const list = await getMenus();
menuList = cloneDeep(list);
forEach(menuList, (item) => {
item.name = t(item.name);
});
});
function search(e: ChangeEvent) {
e?.stopPropagation();
const key = e.target.value || '';
keyword.value = key.trim();
if (!key) {
searchResult.value = [];
return;
}
const reg = createSearchReg(unref(keyword));
const filterMenu = filter(menuList, (item) => {
return reg.test(item.name) && !item.hideMenu;
});
searchResult.value = handlerSearchResult(filterMenu, reg);
activeIndex.value = 0;
}
function handlerSearchResult(filterMenu: Menu[], reg: RegExp, parent?: Menu) {
const ret: SearchResult[] = [];
filterMenu.forEach((item) => {
const { name, path, icon, children, hideMenu, meta } = item;
if (!hideMenu && reg.test(name) && (!children?.length || meta?.hideChildrenInMenu)) {
ret.push({
name: parent?.name ? `${parent.name} > ${name}` : name,
path,
icon,
});
}
if (!meta?.hideChildrenInMenu && Array.isArray(children) && children.length) {
ret.push(...handlerSearchResult(children, reg, item));
}
});
return ret;
}
// Activate when the mouse moves to a certain line
function handleMouseenter(e: any) {
const index = e.target.dataset.index;
activeIndex.value = Number(index);
}
// Arrow key up
function handleUp() {
if (!searchResult.value.length) return;
activeIndex.value--;
if (activeIndex.value < 0) {
activeIndex.value = searchResult.value.length - 1;
}
handleScroll();
}
// Arrow key down
function handleDown() {
if (!searchResult.value.length) return;
activeIndex.value++;
if (activeIndex.value > searchResult.value.length - 1) {
activeIndex.value = 0;
}
handleScroll();
}
// When the keyboard up and down keys move to an invisible place
// the scroll bar needs to scroll automatically
function handleScroll() {
const refList = unref(refs);
if (!refList || !Array.isArray(refList) || refList.length === 0 || !unref(scrollWrap)) {
return;
}
const index = unref(activeIndex);
const currentRef = refList[index];
if (!currentRef) {
return;
}
const wrapEl = unref(scrollWrap);
if (!wrapEl) {
return;
}
const scrollHeight = currentRef.offsetTop + currentRef.offsetHeight;
const wrapHeight = wrapEl.offsetHeight;
const { start } = useScrollTo({
el: wrapEl,
duration: 100,
to: scrollHeight - wrapHeight,
});
start();
}
// enter keyboard event
async function handleEnter() {
if (!searchResult.value.length) {
return;
}
const result = unref(searchResult);
const index = unref(activeIndex);
if (result.length === 0 || index < 0) {
return;
}
const to = result[index];
handleClose();
await nextTick();
await go(to.path);
}
// close search modal
function handleClose() {
searchResult.value = [];
emit('close');
}
// enter search
onKeyStroke('Enter', handleEnter);
// Monitor keyboard arrow keys
onKeyStroke('ArrowUp', handleUp);
onKeyStroke('ArrowDown', handleDown);
// esc close
onKeyStroke('Escape', handleClose);
return { handleSearch, searchResult, keyword, activeIndex, handleMouseenter, handleEnter };
}

View File

@@ -0,0 +1,17 @@
import { InjectionKey, Ref } from 'vue';
import { createContext, useContext } from '@jeesite/core/hooks/core/useContext';
export interface AppProviderContextProps {
prefixCls: Ref<string>;
isMobile: Ref<boolean>;
}
const key: InjectionKey<AppProviderContextProps> = Symbol();
export function createAppProviderContext(context: AppProviderContextProps) {
return createContext<AppProviderContextProps>(context, key);
}
export function useAppProviderContext() {
return useContext<AppProviderContextProps>(key);
}

View File

@@ -0,0 +1,4 @@
import { withInstall } from '@jeesite/core/utils';
import authority from './src/Authority.vue';
export const Authority = withInstall(authority);

View File

@@ -0,0 +1,45 @@
<!--
Access control component for fine-grained access control.
-->
<script lang="ts">
import type { PropType } from 'vue';
import { defineComponent } from 'vue';
import { RoleEnum } from '@jeesite/core/enums/roleEnum';
import { usePermission } from '@jeesite/core/hooks/web/usePermission';
import { getSlot } from '@jeesite/core/utils/helper/tsxHelper';
export default defineComponent({
name: 'Authority',
props: {
/**
* Specified role is open
* When the permission mode is the role mode, the value value can pass the role value.
* When the permission mode is background, the value value can pass the code permission value
* @default ''
*/
value: {
type: [Number, Array, String] as PropType<RoleEnum | RoleEnum[] | string | string[]>,
default: '',
},
},
setup(props, { slots }) {
const { hasPermission } = usePermission();
/**
* Render role button
*/
function renderAuth() {
const { value } = props;
if (!value) {
return getSlot(slots);
}
return hasPermission(value) ? getSlot(slots) : null;
}
return () => {
// Role-based value control
return renderAuth();
};
},
});
</script>

View File

@@ -0,0 +1,8 @@
import { withInstall } from '@jeesite/core/utils';
import basicArrow from './src/BasicArrow.vue';
import basicTitle from './src/BasicTitle.vue';
import basicHelp from './src/BasicHelp.vue';
export const BasicArrow = withInstall(basicArrow);
export const BasicTitle = withInstall(basicTitle);
export const BasicHelp = withInstall(basicHelp);

View File

@@ -0,0 +1,117 @@
<!--
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author VbenThinkGem
-->
<template>
<span :class="getClass" @click="handleClick" @dblclick="handleDblClick">
<Spin v-if="props.loading" size="small" :style="$attrs.iconStyle" />
<Icon v-else :icon="getIcon" :style="$attrs.iconStyle" />
</span>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { Spin } from 'ant-design-vue';
import { Icon } from '@jeesite/core/components/Icon';
import { useDesign } from '@jeesite/core/hooks/web/useDesign';
const props = defineProps({
/**
* Arrow expand state
*/
expand: { type: Boolean },
/**
* Arrow up by default
*/
up: { type: Boolean },
/**
* Arrow down by default
*/
down: { type: Boolean },
/**
* Cancel padding/margin for inline
*/
inset: { type: Boolean },
/**
* 是否是叶子节点
*/
leaf: { type: Boolean },
/**
* 是否双箭头图标
*/
double: { type: Boolean },
/**
* 是否加载状态
*/
loading: { type: Boolean, defaultValue: false },
// eslint check
onClick: { type: Function, default: (_e: Event) => {} },
onDblclick: { type: Function, default: (_e: Event) => {} },
});
const emit = defineEmits(['click', 'dblclick']);
const { prefixCls } = useDesign('basic-arrow');
const getIcon = computed(() => {
const { leaf, double } = props;
return leaf ? 'i-radix-icons:dot' : double ? 'i-ant-design:double-right-outlined' : 'i-ion:chevron-forward';
});
// get component class
const getClass = computed(() => {
const { expand, up, down, inset } = props;
return [
prefixCls,
{
[`${prefixCls}--active`]: expand,
up,
inset,
down,
},
];
});
function handleClick(event) {
emit('click', event);
}
function handleDblClick(event) {
emit('dblclick', event);
}
</script>
<style lang="less">
@prefix-cls: ~'jeesite-basic-arrow';
.@{prefix-cls} {
display: inline-block;
cursor: pointer;
transform: rotate(0deg);
transition: all 0.3s ease 0.1s;
transform-origin: center center;
&--active {
transform: rotate(90deg);
}
&.inset {
line-height: 0;
}
&.up {
transform: rotate(-90deg);
}
&.down {
transform: rotate(90deg);
}
&.up.@{prefix-cls}--active {
transform: rotate(90deg);
}
&.down.@{prefix-cls}--active {
transform: rotate(-90deg);
}
}
</style>

View File

@@ -0,0 +1,119 @@
<script lang="tsx">
import type { CSSProperties, PropType } from 'vue';
import { defineComponent, computed, unref } from 'vue';
import { Tooltip } from 'ant-design-vue';
import { getPopupContainer } from '@jeesite/core/utils';
import { isString, isArray } from '@jeesite/core/utils/is';
import { getSlot } from '@jeesite/core/utils/helper/tsxHelper';
import { useDesign } from '@jeesite/core/hooks/web/useDesign';
import { Icon } from '@jeesite/core/components/Icon';
const props = {
/**
* Help text max-width
* @default: 600px
*/
maxWidth: { type: String, default: '600px' },
/**
* Whether to display the serial number
* @default: false
*/
showIndex: { type: Boolean },
/**
* Help text font color
* @default: #ffffff
*/
color: { type: String, default: '#ffffff' },
/**
* Help text font size
* @default: 14px
*/
fontSize: { type: String, default: '14px' },
/**
* Help text list
*/
placement: { type: String, default: 'right' },
/**
* Help text list
*/
text: { type: [Array, String] as PropType<string[] | string> },
};
export default defineComponent({
name: 'BasicHelp',
components: { Icon, Tooltip },
props,
setup(props, { slots }) {
const { prefixCls } = useDesign('basic-help');
const getTooltipStyle = computed((): CSSProperties => ({ color: props.color, fontSize: props.fontSize }));
const getOverlayStyle = computed((): CSSProperties => ({ maxWidth: props.maxWidth }));
function renderTitle() {
const textList = props.text;
if (isString(textList)) {
return <p>{textList}</p>;
}
if (isArray(textList)) {
return textList.map((text, index) => {
return (
<p key={text}>
<>
{props.showIndex ? `${index + 1}. ` : ''}
{text}
</>
</p>
);
});
}
return null;
}
return () => {
return (
<Tooltip
overlayClassName={`${prefixCls}__wrap`}
title={<div style={unref(getTooltipStyle)}>{renderTitle()}</div>}
autoAdjustOverflow={true}
overlayStyle={unref(getOverlayStyle)}
placement={props.placement as 'right'}
getPopupContainer={() => getPopupContainer()}
>
<span class={prefixCls}>{getSlot(slots) || <Icon icon="i-ant-design:question-circle-outlined" />}</span>
</Tooltip>
);
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'jeesite-basic-help';
.@{prefix-cls} {
display: inline-block;
font-size: 13px;
color: @text-color-help-dark;
vertical-align: middle;
cursor: pointer;
&:hover {
color: @primary-color;
}
&__wrap {
p {
margin-bottom: 0;
}
}
}
.ant-form-item-label .@{prefix-cls} {
vertical-align: baseline;
margin-left: -7px;
margin-right: -4px;
opacity: 0.8;
}
</style>

View File

@@ -0,0 +1,76 @@
<template>
<span :class="getClass">
<slot></slot>
<BasicHelp :class="`${prefixCls}-help`" v-if="helpMessage" :text="helpMessage" />
</span>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue';
import { useSlots, computed } from 'vue';
import BasicHelp from './BasicHelp.vue';
import { useDesign } from '@jeesite/core/hooks/web/useDesign';
const props = defineProps({
/**
* Help text list or string
* @default: ''
*/
helpMessage: {
type: [String, Array] as PropType<string | string[]>,
default: '',
},
/**
* Whether the color block on the left side of the title
* @default: false
*/
span: { type: Boolean },
/**
* Whether to default the text, that is, not bold
* @default: false
*/
normal: { type: Boolean },
});
const { prefixCls } = useDesign('basic-title');
const slots = useSlots();
const getClass = computed(() => [
prefixCls,
{ [`${prefixCls}-show-span`]: props.span && slots.default },
{ [`${prefixCls}-normal`]: props.normal },
]);
</script>
<style lang="less">
@prefix-cls: ~'jeesite-basic-title';
.@{prefix-cls} {
position: relative;
display: flex;
padding-left: 3px;
font-size: 16px;
font-weight: 500;
line-height: 24px;
color: fade(@text-color-base, 75);
//cursor: pointer;
user-select: none;
&-normal {
font-size: 14px;
font-weight: 500;
}
&-show-span::before {
position: absolute;
top: 4px;
left: 0;
width: 3px;
height: 16px;
margin-right: 4px;
background-color: @primary-color;
content: '';
}
&-help {
margin-left: 10px;
}
}
</style>

View File

@@ -0,0 +1,9 @@
import { withInstall } from '@jeesite/core/utils';
import type { ExtractPropTypes } from 'vue';
import button from './src/BasicButton.vue';
import popConfirmButton from './src/PopConfirmButton.vue';
import { buttonProps } from './src/props';
export const Button = withInstall(button);
export const PopConfirmButton = withInstall(popConfirmButton);
export declare type ButtonProps = Partial<ExtractPropTypes<typeof buttonProps>>;

View File

@@ -0,0 +1,35 @@
<template>
<Button v-bind="getBindValue" :class="getButtonClass" @click="onClick">
<template #default="data">
<Icon :icon="preIcon" v-if="preIcon" :size="iconSize" />
<slot v-bind="data || {}"></slot>
<Icon :icon="postIcon" v-if="postIcon" :size="iconSize" />
</template>
</Button>
</template>
<script lang="ts" setup name="AButton">
import { computed, unref } from 'vue';
import { Button } from 'ant-design-vue';
import Icon from '@jeesite/core/components/Icon/src/Icon.vue';
import { buttonProps } from './props';
import { useAttrs } from '@jeesite/core/hooks/core/useAttrs';
defineOptions({
inheritAttrs: false,
});
const props = defineProps(buttonProps);
const attrs = useAttrs({ excludeDefaultKeys: false });
const getButtonClass = computed(() => {
const { color, disabled } = props;
return [
{
[`ant-btn-${color}`]: !!color,
[`is-disabled`]: disabled,
},
];
});
const getBindValue = computed(() => ({ ...unref(attrs), ...props }));
</script>

View File

@@ -0,0 +1,55 @@
<script lang="ts">
import { computed, defineComponent, h, unref } from 'vue';
import BasicButton from './BasicButton.vue';
import { Popconfirm } from 'ant-design-vue';
import { extendSlots } from '@jeesite/core/utils/helper/tsxHelper';
import { omit } from 'lodash-es';
import { useAttrs } from '@jeesite/core/hooks/core/useAttrs';
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
const props = {
/**
* Whether to enable the drop-down menu
* @default: true
*/
enable: {
type: Boolean,
default: true,
},
};
export default defineComponent({
name: 'PopButton',
inheritAttrs: false,
props,
setup(props, { slots }) {
const { t } = useI18n();
const attrs = useAttrs();
// get inherit binding value
const getBindValues = computed(() => {
return Object.assign(
{
okText: t('common.okText'),
cancelText: t('common.cancelText'),
placement: 'left',
},
{ ...props, ...unref(attrs) },
);
});
return () => {
const bindValues = omit(unref(getBindValues), 'icon') as Recordable;
const btnBind = omit(bindValues, 'title') as Recordable;
if (btnBind.disabled) btnBind.color = '';
const Button = h(BasicButton, btnBind, extendSlots(slots));
// If it is not enabled, it is a normal button
if (!props.enable) {
return Button;
}
return h(Popconfirm, bindValues, { default: () => Button });
};
},
});
</script>

View File

@@ -0,0 +1,19 @@
export const buttonProps = {
color: { type: String, validator: (v) => ['error', 'warning', 'success', ''].includes(v) },
loading: { type: Boolean },
disabled: { type: Boolean },
/**
* Text before icon.
*/
preIcon: { type: String },
/**
* Text after icon.
*/
postIcon: { type: String },
/**
* preIcon and postIcon icon size.
* @default: 14
*/
iconSize: { type: Number, default: 14 },
onClick: { type: Function as PropType<(...args) => any>, default: null },
};

View File

@@ -0,0 +1,4 @@
import { withInstall } from '@jeesite/core/utils';
import cardList from './src/CardList.vue';
export const CardList = withInstall(cardList);

View File

@@ -0,0 +1,159 @@
<template>
<div class="bg-white">
<List
:grid="{ gutter: 5, xs: 1, sm: 2, md: 4, lg: 4, xl: 6, xxl: 6 }"
:data-source="data"
:pagination="paginationProp"
>
<template #header>
<div class="flex space-x-2">
<BasicForm @register="registerForm" />
<slot name="header"></slot>
<Tooltip :overlayStyle="{ maxWidth: '500px' }">
<template #title>
<div class="w-50">每页显示数量</div>
<Slider id="slider" class="w-90" v-bind="sliderProp" v-model:value="grid" @change="sliderChange" />
</template>
<a-button><TableOutlined /></a-button>
</Tooltip>
<Tooltip @click="fetch">
<template #title>刷新</template>
<a-button><RedoOutlined /></a-button>
</Tooltip>
</div>
</template>
<template #renderItem="{ item }">
<ListItem style="padding: 10px; margin: 10px 0 0">
<Card>
<template #actions>
<EditOutlined @click="showMessage('你点击了编辑图标')" />
<Dropdown
:trigger="['hover']"
:dropMenuList="[
{
text: '删除',
event: '1',
popConfirm: {
title: t('是否确认删除'),
confirm: handleDelete.bind(null, item.id),
},
},
]"
popconfirm
>
<EllipsisOutlined />
</Dropdown>
</template>
<Avatar :src="getAvatar(item)" />
<span class="pl-2">{{ item.userName }}</span>
</Card>
</ListItem>
</template>
</List>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import { EditOutlined, EllipsisOutlined, RedoOutlined, TableOutlined } from '@ant-design/icons-vue';
import { List, Card, Image, Typography, Tooltip, Slider, Avatar } from 'ant-design-vue';
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
import { Dropdown } from '@jeesite/core/components/Dropdown';
import { BasicForm, useForm } from '@jeesite/core/components/Form';
import { propTypes } from '@jeesite/core/utils/propTypes';
import { isFunction } from '@jeesite/core/utils/is';
import { useSlider, grid } from './data';
import { useGlobSetting } from '@jeesite/core/hooks/setting';
import { useMessage } from '@jeesite/core/hooks/web/useMessage';
const { t } = useI18n();
const { showMessage } = useMessage();
const ListItem = List.Item;
// 获取slider属性
const sliderProp = computed(() => useSlider(1));
// 组件接收参数
const props = defineProps({
// 请求API的参数
params: propTypes.object.def({}),
// api
api: propTypes.func,
});
// 暴露内部方法
const emit = defineEmits(['getMethod', 'delete']);
// 数据
const data = ref([]);
// 表单
const [registerForm, { validate }] = useForm({
schemas: [{ field: 'loginCode', component: 'Input', label: '账号' }],
labelWidth: 80,
autoSubmitOnEnter: true,
showActionButtonGroup: true,
submitFunc: handleSubmit,
});
//表单提交
async function handleSubmit() {
const data = await validate();
await fetch(data);
}
function sliderChange(n) {
pageSize.value = n;
fetch();
}
// 自动请求并暴露内部方法
onMounted(() => {
fetch();
emit('getMethod', fetch);
});
async function fetch(p = {}) {
const { api, params } = props;
if (api && isFunction(api)) {
const res = await api({ ...params, pageNo: page.value, pageSize: pageSize.value, ...p });
data.value = res.list;
total.value = res.count;
}
}
//分页相关
const page = ref(1);
const pageSize = ref(36);
const total = ref(0);
const paginationProp = ref({
showSizeChanger: false,
showQuickJumper: true,
pageSize,
current: page,
total,
showTotal: (total: number) => `${total}`,
onChange: pageChange,
onShowSizeChange: pageSizeChange,
});
function getAvatar(item: Recordable) {
const { ctxPath } = useGlobSetting();
let url = item.avatarUrl || '/ctxPath/static/images/user1.jpg';
url = url.replace('/ctxPath/', ctxPath + '/');
return url;
}
function pageChange(p: number, pz: number) {
page.value = p;
pageSize.value = pz;
fetch();
}
function pageSizeChange(_current, size: number) {
pageSize.value = size;
fetch();
}
async function handleDelete(id: number) {
emit('delete', id);
}
</script>

View File

@@ -0,0 +1,25 @@
import { ref } from 'vue';
// 每行个数
export const grid = ref(20);
// slider属性
export const useSlider = (min = 6, max = 20) => {
// 每行显示个数滑动条
const getMarks = () => {
const l = {};
for (let i = min; i < max + 1; i++) {
l[i] = {
style: {
color: '#fff',
},
label: i,
};
}
return l;
};
return {
min,
max,
marks: getMarks(),
step: 1,
};
};

View File

@@ -0,0 +1,4 @@
import { withInstall } from '@jeesite/core/utils';
import clickOutSide from './src/ClickOutSide.vue';
export const ClickOutSide = withInstall(clickOutSide);

View File

@@ -0,0 +1,19 @@
<template>
<div ref="wrap">
<slot></slot>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { onClickOutside } from '@vueuse/core';
const emit = defineEmits(['mounted', 'clickOutside']);
const wrap = ref<ElRef>(null);
onClickOutside(wrap, () => {
emit('clickOutside');
});
onMounted(() => {
emit('mounted');
});
</script>

View File

@@ -0,0 +1,8 @@
import { withInstall } from '@jeesite/core/utils';
import codeEditor from './src/CodeEditor.vue';
import jsonPreview from './src/json-preview/JsonPreview.vue';
export const CodeEditor = withInstall(codeEditor);
export const JsonPreview = withInstall(jsonPreview);
export * from './src/typing';

View File

@@ -0,0 +1,59 @@
<template>
<div class="h-full">
<CodeMirrorEditor
:value="getValue"
@change="handleValueChange"
:mode="mode"
:readonly="readonly"
:bordered="bordered"
:config="config"
/>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import CodeMirrorEditor from './codemirror/CodeMirror.vue';
import { isString } from '@jeesite/core/utils/is';
import { MODE } from './typing';
import type { EditorConfiguration } from 'codemirror';
const props = defineProps({
value: { type: [Object, String] as PropType<Record<string, any> | string> },
mode: {
type: String as PropType<MODE>,
default: MODE.JSON,
validator(value: any) {
// 这个值必须匹配下列字符串中的一个
return Object.values(MODE).includes(value);
},
},
readonly: { type: Boolean },
autoFormat: { type: Boolean, default: true },
bordered: { type: Boolean, default: false },
config: { type: Object as PropType<EditorConfiguration>, default: () => {} },
});
const emit = defineEmits(['change', 'update:value', 'format-error']);
const getValue = computed(() => {
const { value, mode, autoFormat } = props;
if (!autoFormat || mode !== MODE.JSON) {
return value as string;
}
let result = value;
if (isString(value)) {
try {
result = JSON.parse(value);
} catch (e) {
emit('format-error', value);
return value as string;
}
}
return JSON.stringify(result, null, 2);
});
function handleValueChange(v) {
emit('update:value', v);
emit('change', v);
}
</script>

View File

@@ -0,0 +1,132 @@
<template>
<div
class="relative w-full overflow-hidden !h-full"
:class="{ 'ant-input': props.bordered, 'css-dev-only-do-not-override-kqecok': props.bordered }"
ref="el"
></div>
</template>
<script lang="ts" setup>
import { type PropType, ref, onMounted, onUnmounted, watchEffect, watch, unref, nextTick } from 'vue';
import { useWindowSizeFn } from '@jeesite/core/hooks/event/useWindowSizeFn';
import { useDebounceFn } from '@vueuse/core';
import { useAppStore } from '@jeesite/core/store/modules/app';
import CodeMirror from 'codemirror';
import type { EditorConfiguration } from 'codemirror';
import { MODE, parserDynamicImport } from './../typing';
// css
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/idea.css';
import 'codemirror/theme/material-palenight.css';
// 代码段折叠功能
import 'codemirror/addon/fold/foldgutter.css';
import 'codemirror/addon/fold/foldcode.js';
import 'codemirror/addon/fold/foldgutter';
import 'codemirror/addon/fold/brace-fold';
import 'codemirror/addon/fold/comment-fold';
import 'codemirror/addon/fold/markdown-fold';
import 'codemirror/addon/fold/xml-fold';
import 'codemirror/addon/fold/indent-fold';
const props = defineProps({
mode: {
type: String as PropType<MODE>,
default: MODE.JSON,
validator(value: any) {
// 这个值必须匹配下列字符串中的一个
return Object.values(MODE).includes(value);
},
},
value: { type: String, default: '' },
readonly: { type: Boolean, default: false },
bordered: { type: Boolean, default: false },
config: { type: Object as PropType<EditorConfiguration>, default: () => {} },
});
const emit = defineEmits(['change']);
const el = ref();
let editor: Nullable<CodeMirror.Editor>;
const debounceRefresh = useDebounceFn(refresh, 100);
const appStore = useAppStore();
watch(
() => props.value,
async (value) => {
await nextTick();
const oldValue = editor?.getValue();
if (value !== oldValue) {
editor?.setValue(value ? value : '');
}
},
{ flush: 'post' },
);
watchEffect(async () => {
await parserDynamicImport(props.mode)();
editor?.setOption('mode', props.mode);
});
watch(
() => appStore.getDarkMode,
async () => {
setTheme();
},
{
immediate: true,
},
);
function setTheme() {
unref(editor)?.setOption('theme', appStore.getDarkMode === 'light' ? 'idea' : 'material-palenight');
}
function refresh() {
editor?.refresh();
}
async function init() {
const addonOptions = {
autoCloseBrackets: true,
autoCloseTags: true,
foldGutter: true,
gutters: ['CodeMirror-lint-markers', 'CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
};
editor = CodeMirror(el.value!, {
value: '',
mode: props.mode,
readOnly: props.readonly,
tabSize: 2,
theme: 'material-palenight',
lineWrapping: true,
lineNumbers: true,
...addonOptions,
...props.config,
});
editor?.setValue(props.value);
setTheme();
editor?.on('change', () => {
emit('change', editor?.getValue());
});
}
onMounted(async () => {
await nextTick();
init();
useWindowSizeFn(debounceRefresh);
});
onUnmounted(() => {
editor = null;
});
</script>
<style>
.CodeMirror {
height: 100%;
}
</style>

View File

@@ -0,0 +1,16 @@
<template>
<vue-json-pretty :path="'res'" :deep="4" :showLength="true" :data="data" />
</template>
<script lang="ts" setup>
import VueJsonPretty from 'vue-json-pretty';
import 'vue-json-pretty/lib/styles.css';
defineProps({
data: Object,
});
</script>
<style lang="less">
.vjs-value-string {
color: @text-color-base;
}
</style>

View File

@@ -0,0 +1,247 @@
export enum MODE {
JSON = 'application/json',
APL = 'apl',
ASCIIARMOR = 'asciiarmor',
ASTERISK = 'asterisk',
BRAINFUCK = 'brainfuck',
CLIKE = 'clike',
CLOJURE = 'clojure',
CMAKE = 'cmake',
COBOL = 'cobol',
COFFEESCRIPT = 'coffeescript',
COMMONLISP = 'commonlisp',
CRYSTAL = 'crystal',
CSS = 'css',
CYPHER = 'cypher',
D = 'd',
DART = 'dart',
DIFF = 'diff',
DJANGO = 'django',
DOCKERFILE = 'dockerfile',
DTD = 'dtd',
DYLAN = 'dylan',
EBNF = 'ebnf',
ECL = 'ecl',
EIFFEL = 'eiffel',
ELM = 'elm',
ERLANG = 'erlang',
FACTOR = 'factor',
FCL = 'fcl',
FORTH = 'forth',
FORTRAN = 'fortran',
GAS = 'gas',
GFM = 'gfm',
GHERKIN = 'gherkin',
GO = 'go',
GROOVY = 'groovy',
HAML = 'haml',
HANDLEBARS = 'handlebars',
HASKELL = 'haskell',
HAXE = 'haxe',
HTMLEMBEDDED = 'htmlembedded',
HTMLMIXED = 'htmlmixed',
HTTP = 'http',
IDL = 'idl',
JAVASCRIPT = 'javascript',
JINJA2 = 'jinja2',
JSX = 'jsx',
JULIA = 'julia',
LIVESCRIPT = 'livescript',
LUA = 'lua',
MARKDOWN = 'markdown',
MATHEMATICA = 'mathematica',
MBOX = 'mbox',
MIRC = 'mirc',
MLLIKE = 'mllike',
MODELICA = 'modelica',
MSCGEN = 'mscgen',
MUMPS = 'mumps',
NGINX = 'nginx',
NSIS = 'nsis',
NTRIPLES = 'ntriples',
OCTAVE = 'octave',
OZ = 'oz',
PASCAL = 'pascal',
PEGJS = 'pegjs',
PERL = 'perl',
PHP = 'php',
PIG = 'pig',
POWERSHELL = 'powershell',
PROPERTIES = 'properties',
PROTOBUF = 'protobuf',
PUG = 'pug',
PUPPET = 'puppet',
PYTHON = 'python',
Q = 'q',
R = 'r',
RPM = 'rpm',
RST = 'rst',
RUBY = 'ruby',
RUST = 'rust',
SAS = 'sas',
SASS = 'sass',
SCHEME = 'scheme',
SHELL = 'shell',
SIEVE = 'sieve',
SLIM = 'slim',
SMALLTALK = 'smalltalk',
SMARTY = 'smarty',
SOLR = 'solr',
SOY = 'soy',
SPARQL = 'sparql',
SPREADSHEET = 'spreadsheet',
SQL = 'sql',
STEX = 'stex',
STYLUS = 'stylus',
SWIFT = 'swift',
TCL = 'tcl',
TEXTILE = 'textile',
TIDDLYWIKI = 'tiddlywiki',
TIKI = 'tiki',
TOML = 'toml',
TORNADO = 'tornado',
TROFF = 'troff',
TTCN = 'ttcn',
TURTLE = 'turtle',
TWIG = 'twig',
VB = 'vb',
VBSCRIPT = 'vbscript',
VELOCITY = 'velocity',
VERILOG = 'verilog',
VHDL = 'vhdl',
VUE = 'vue',
WAST = 'wast',
WEBIDL = 'webidl',
XML = 'xml',
XQUERY = 'xquery',
YACAS = 'yacas',
YAML = 'yaml',
Z80 = 'z80',
}
/**
* @description: DynamicImport codemirror
*/
export function parserDynamicImport(str: MODE): () => Promise<any> {
const dynamicArray = {
// adapt before demo
'application/json': async () => await import('codemirror/mode/javascript/javascript'),
apl: async () => await import('codemirror/mode/apl/apl'),
asciiarmor: async () => await import('codemirror/mode/asciiarmor/asciiarmor'),
asterisk: async () => await import('codemirror/mode/asterisk/asterisk'),
brainfuck: async () => await import('codemirror/mode/brainfuck/brainfuck'),
clike: async () => await import('codemirror/mode/clike/clike'),
clojure: async () => await import('codemirror/mode/clojure/clojure'),
cmake: async () => await import('codemirror/mode/cmake/cmake'),
cobol: async () => await import('codemirror/mode/cobol/cobol'),
coffeescript: async () => await import('codemirror/mode/coffeescript/coffeescript'),
commonlisp: async () => await import('codemirror/mode/commonlisp/commonlisp'),
crystal: async () => await import('codemirror/mode/crystal/crystal'),
css: async () => await import('codemirror/mode/css/css'),
cypher: async () => await import('codemirror/mode/cypher/cypher'),
d: async () => await import('codemirror/mode/d/d'),
dart: async () => await import('codemirror/mode/dart/dart'),
diff: async () => await import('codemirror/mode/diff/diff'),
django: async () => await import('codemirror/mode/django/django'),
dockerfile: async () => await import('codemirror/mode/dockerfile/dockerfile'),
dtd: async () => await import('codemirror/mode/dtd/dtd'),
dylan: async () => await import('codemirror/mode/dylan/dylan'),
ebnf: async () => await import('codemirror/mode/ebnf/ebnf'),
ecl: async () => await import('codemirror/mode/ecl/ecl'),
eiffel: async () => await import('codemirror/mode/eiffel/eiffel'),
elm: async () => await import('codemirror/mode/elm/elm'),
erlang: async () => await import('codemirror/mode/erlang/erlang'),
factor: async () => await import('codemirror/mode/factor/factor'),
fcl: async () => await import('codemirror/mode/fcl/fcl'),
forth: async () => await import('codemirror/mode/forth/forth'),
fortran: async () => await import('codemirror/mode/fortran/fortran'),
gas: async () => await import('codemirror/mode/gas/gas'),
gfm: async () => await import('codemirror/mode/gfm/gfm'),
gherkin: async () => await import('codemirror/mode/gherkin/gherkin'),
go: async () => await import('codemirror/mode/go/go'),
groovy: async () => await import('codemirror/mode/groovy/groovy'),
haml: async () => await import('codemirror/mode/haml/haml'),
handlebars: async () => await import('codemirror/mode/handlebars/handlebars'),
haskell: async () => await import('codemirror/mode/haskell/haskell'),
haxe: async () => await import('codemirror/mode/haxe/haxe'),
htmlembedded: async () => await import('codemirror/mode/htmlembedded/htmlembedded'),
htmlmixed: async () => await import('codemirror/mode/htmlmixed/htmlmixed'),
http: async () => await import('codemirror/mode/http/http'),
idl: async () => await import('codemirror/mode/idl/idl'),
javascript: async () => await import('codemirror/mode/javascript/javascript'),
jinja2: async () => await import('codemirror/mode/jinja2/jinja2'),
jsx: async () => await import('codemirror/mode/jsx/jsx'),
julia: async () => await import('codemirror/mode/julia/julia'),
livescript: async () => await import('codemirror/mode/livescript/livescript'),
lua: async () => await import('codemirror/mode/lua/lua'),
markdown: async () => await import('codemirror/mode/markdown/markdown'),
mathematica: async () => await import('codemirror/mode/mathematica/mathematica'),
mbox: async () => await import('codemirror/mode/mbox/mbox'),
mirc: async () => await import('codemirror/mode/mirc/mirc'),
mllike: async () => await import('codemirror/mode/mllike/mllike'),
modelica: async () => await import('codemirror/mode/modelica/modelica'),
mscgen: async () => await import('codemirror/mode/mscgen/mscgen'),
mumps: async () => await import('codemirror/mode/mumps/mumps'),
nginx: async () => await import('codemirror/mode/nginx/nginx'),
nsis: async () => await import('codemirror/mode/nsis/nsis'),
ntriples: async () => await import('codemirror/mode/ntriples/ntriples'),
octave: async () => await import('codemirror/mode/octave/octave'),
oz: async () => await import('codemirror/mode/oz/oz'),
pascal: async () => await import('codemirror/mode/pascal/pascal'),
pegjs: async () => await import('codemirror/mode/pegjs/pegjs'),
perl: async () => await import('codemirror/mode/perl/perl'),
php: async () => await import('codemirror/mode/php/php'),
pig: async () => await import('codemirror/mode/pig/pig'),
powershell: async () => await import('codemirror/mode/powershell/powershell'),
properties: async () => await import('codemirror/mode/properties/properties'),
protobuf: async () => await import('codemirror/mode/protobuf/protobuf'),
pug: async () => await import('codemirror/mode/pug/pug'),
puppet: async () => await import('codemirror/mode/puppet/puppet'),
python: async () => await import('codemirror/mode/python/python'),
q: async () => await import('codemirror/mode/q/q'),
r: async () => await import('codemirror/mode/r/r'),
rpm: async () => await import('codemirror/mode/rpm/rpm'),
rst: async () => await import('codemirror/mode/rst/rst'),
ruby: async () => await import('codemirror/mode/ruby/ruby'),
rust: async () => await import('codemirror/mode/rust/rust'),
sas: async () => await import('codemirror/mode/sas/sas'),
sass: async () => await import('codemirror/mode/sass/sass'),
scheme: async () => await import('codemirror/mode/scheme/scheme'),
shell: async () => await import('codemirror/mode/shell/shell'),
sieve: async () => await import('codemirror/mode/sieve/sieve'),
slim: async () => await import('codemirror/mode/slim/slim'),
smalltalk: async () => await import('codemirror/mode/smalltalk/smalltalk'),
smarty: async () => await import('codemirror/mode/smarty/smarty'),
solr: async () => await import('codemirror/mode/solr/solr'),
soy: async () => await import('codemirror/mode/soy/soy'),
sparql: async () => await import('codemirror/mode/sparql/sparql'),
spreadsheet: async () => await import('codemirror/mode/spreadsheet/spreadsheet'),
sql: async () => await import('codemirror/mode/sql/sql'),
stex: async () => await import('codemirror/mode/stex/stex'),
stylus: async () => await import('codemirror/mode/stylus/stylus'),
swift: async () => await import('codemirror/mode/swift/swift'),
tcl: async () => await import('codemirror/mode/tcl/tcl'),
textile: async () => await import('codemirror/mode/textile/textile'),
tiddlywiki: async () => await import('codemirror/mode/tiddlywiki/tiddlywiki'),
tiki: async () => await import('codemirror/mode/tiki/tiki'),
toml: async () => await import('codemirror/mode/toml/toml'),
tornado: async () => await import('codemirror/mode/tornado/tornado'),
troff: async () => await import('codemirror/mode/troff/troff'),
ttcn: async () => await import('codemirror/mode/ttcn/ttcn'),
turtle: async () => await import('codemirror/mode/turtle/turtle'),
twig: async () => await import('codemirror/mode/twig/twig'),
vb: async () => await import('codemirror/mode/vb/vb'),
vbscript: async () => await import('codemirror/mode/vbscript/vbscript'),
velocity: async () => await import('codemirror/mode/velocity/velocity'),
verilog: async () => await import('codemirror/mode/verilog/verilog'),
vhdl: async () => await import('codemirror/mode/vhdl/vhdl'),
vue: async () => await import('codemirror/mode/vue/vue'),
wast: async () => await import('codemirror/mode/wast/wast'),
webidl: async () => await import('codemirror/mode/webidl/webidl'),
xml: async () => await import('codemirror/mode/xml/xml'),
xquery: async () => await import('codemirror/mode/xquery/xquery'),
yacas: async () => await import('codemirror/mode/yacas/yacas'),
yaml: async () => await import('codemirror/mode/yaml/yaml'),
z80: async () => await import('codemirror/mode/z80/z80'),
};
return dynamicArray[str];
}

View File

@@ -0,0 +1,3 @@
import CollapseForm from './src/CollapseForm.vue';
export { CollapseForm };

View File

@@ -0,0 +1,155 @@
<!--
* Copyright (c) 2013-Now http://jeesite.com All rights reserved.
* No deletion without permission, or be held responsible to law.
* @author ThinkGem
-->
<template>
<div class="jeesite-collapse-form-page">
<ScrollContainer ref="contentRef" :style="{ height: contentHeight + 'px' }" v-loading="props.loading">
<div v-for="item in configList" :key="item.value">
<Collapse
:class="item.value"
:default-active-key="configList.filter((i) => i.open || true).map((i) => i.value)"
>
<Collapse.Panel :key="item.value" :header="item.label">
<slot :name="item.value"></slot>
</Collapse.Panel>
</Collapse>
</div>
</ScrollContainer>
<div class="jeesite-collapse-form-actions">
<slot v-if="$slots.actions" name="actions"></slot>
<template v-else>
<a-button type="default" @click="handleClose" v-auth="props.okAuth">
<Icon icon="i-ant-design:close-outlined" /> {{ t('common.closeText') }}
</a-button>
<a-button type="primary" @click="handleSubmit" :loading="props.loading || props.okLoading">
<Icon icon="i-ant-design:check-outlined" /> {{ t('common.okText') }}
</a-button>
</template>
</div>
</div>
</template>
<script lang="ts" setup name="CollapseForm">
import { nextTick, ref } from 'vue';
import { Collapse } from 'ant-design-vue';
import { Icon } from '@jeesite/core/components/Icon';
import { useI18n } from '@jeesite/core/hooks/web/useI18n';
import { propTypes } from '@jeesite/core/utils/propTypes';
import { ScrollContainer } from '@jeesite/core/components/Container';
import { useWindowSizeFn } from '@jeesite/core/hooks/event/useWindowSizeFn';
import { onMountedOrActivated } from '@jeesite/core/hooks/core/onMountedOrActivated';
import { useLayoutHeight } from '@jeesite/core/layouts/default/content/useContentViewHeight';
const props = defineProps({
config: propTypes.array.def([]),
okAuth: propTypes.string,
loading: propTypes.bool,
okLoading: propTypes.bool,
});
const emit = defineEmits(['close', 'ok']);
const { t } = useI18n();
const configList = ref<any[]>(props.config);
const contentRef = ref<ComponentRef>();
const contentHeight = ref<number>(200);
const { headerHeightRef } = useLayoutHeight();
function calcContentHeight() {
const parentElement = contentRef.value?.$el.parentElement;
const actionsElement = parentElement?.querySelector('.jeesite-collapse-form-actions');
if (parentElement && actionsElement) {
contentHeight.value = document.body.clientHeight - headerHeightRef.value - actionsElement.scrollHeight - 32;
}
}
onMountedOrActivated(() => {
nextTick(() => {
calcContentHeight();
});
});
useWindowSizeFn(
() => {
calcContentHeight();
},
50,
{ immediate: true },
);
function handleClose() {
emit('close');
}
function handleSubmit() {
emit('ok');
}
</script>
<style lang="less">
.jeesite-collapse-form {
&-page {
.scrollbar {
border-radius: 4px !important;
&__view > div {
margin-bottom: 5px;
&:last-child {
margin-bottom: 0;
}
}
}
.ant-collapse {
border: 0 !important;
&-item {
border: 0 !important;
border-radius: 4px !important;
}
&-header {
font-size: 16px;
padding: 8px 16px !important;
border: 0 !important;
border-radius: 4px !important;
background-color: @component-background;
.ant-collapse-expand-icon {
padding-top: 3px;
}
}
&-content {
border: 0 !important;
padding-top: 5px !important;
border-radius: 0 0 4px 4px !important;
}
&-item-active {
.ant-collapse-header {
border-radius: 4px 4px 0 0 !important;
}
}
}
}
&-actions {
padding: 10px;
margin-top: 5px;
margin-bottom: 0;
text-align: center;
border-radius: 4px !important;
background-color: @component-background;
.ant-btn {
margin-right: 8px;
&-primary {
background: fade(@primary-color, 85);
}
}
}
}
</style>

View File

@@ -0,0 +1,10 @@
import { withInstall } from '@jeesite/core/utils';
import collapseContainer from './src/collapse/CollapseContainer.vue';
import scrollContainer from './src/ScrollContainer.vue';
import lazyContainer from './src/LazyContainer.vue';
export const CollapseContainer = withInstall(collapseContainer);
export const ScrollContainer = withInstall(scrollContainer);
export const LazyContainer = withInstall(lazyContainer);
export * from './src/typing';

Some files were not shown because too many files have changed in this diff Show More