Initial commit: ujcms-cp-v10.1.3
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>master
commit
11cd939666
|
|
@ -0,0 +1,18 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{js,jsx,ts,tsx,vue,hbs}]
|
||||
max_line_length = 180
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
VITE_APP_TITLE=UJCMS后台管理
|
||||
VITE_APP_NAME=UJCMS
|
||||
VITE_PORT=5173
|
||||
VITE_PUBLIC_PATH=./
|
||||
VITE_PROXY_API=http://192.168.10.37:8080
|
||||
VITE_PROXY_UPLOADS=http://192.168.10.37:8080
|
||||
VITE_PROXY_TEMPLATES=http://192.168.10.37:8080
|
||||
VITE_BASE_API=/api
|
||||
VITE_BASE_UPLOADS=/uploads
|
||||
VITE_BASE_TEMPLATES=/templates
|
||||
VITE_I18N_LOCALE=zh-cn
|
||||
VITE_I18N_FALLBACK_LOCALE=zh-cn
|
||||
VITE_USE_MOCK=false
|
||||
|
|
@ -0,0 +1 @@
|
|||
VITE_BASE_API=../api
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
build/*.js
|
||||
src/assets/*
|
||||
public/*
|
||||
dist/*
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true,
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:vue/vue3-recommended", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended", "./.eslintrc-auto-import.json"],
|
||||
"parser": "vue-eslint-parser",
|
||||
"parserOptions": {
|
||||
// "extraFileExtensions": [".vue"],
|
||||
// "project": ["./tsconfig.json"],
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module",
|
||||
"parser": "@typescript-eslint/parser",
|
||||
},
|
||||
"plugins": ["vue", "@typescript-eslint"],
|
||||
"rules": {
|
||||
// "no-param-reassign": ["error", { "props": true, "ignorePropertyModificationsFor": ["state"] }],
|
||||
// "import/prefer-default-export": "off",
|
||||
|
||||
// "@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
// // 避免使用Q_作为查询参数时报错
|
||||
// "@typescript-eslint/camelcase": "off",
|
||||
|
||||
// 允许使用 any 类型
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"globals": {
|
||||
"ElMessageBox": true,
|
||||
"ElMessage": true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
###############################
|
||||
# Git Line Endings #
|
||||
###############################
|
||||
|
||||
# Set default behaviour to automatically normalize line endings.
|
||||
# * text=auto
|
||||
# 文本文件全部使用lf换行,eslint prettier等工具保持一致。
|
||||
* text=auto eol=lf
|
||||
|
||||
# Force batch scripts to always use CRLF line endings so that if a repo is accessed
|
||||
# in Windows via a file share from Linux, the scripts will work.
|
||||
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||
|
||||
# Force bash scripts to always use LF line endings so that if a repo is accessed
|
||||
# in Unix via a file share from Windows, the scripts will work.
|
||||
*.sh text eol=lf
|
||||
|
||||
###############################
|
||||
# Git Large File System (LFS) #
|
||||
###############################
|
||||
|
||||
# # Archives
|
||||
# *.7z filter=lfs diff=lfs merge=lfs -text
|
||||
# *.br filter=lfs diff=lfs merge=lfs -text
|
||||
# *.gz filter=lfs diff=lfs merge=lfs -text
|
||||
# *.tar filter=lfs diff=lfs merge=lfs -text
|
||||
# *.zip filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# # Documents
|
||||
# *.pdf filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# # Images
|
||||
# *.gif filter=lfs diff=lfs merge=lfs -text
|
||||
# *.ico filter=lfs diff=lfs merge=lfs -text
|
||||
# *.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
# *.pdf filter=lfs diff=lfs merge=lfs -text
|
||||
# *.png filter=lfs diff=lfs merge=lfs -text
|
||||
# *.psd filter=lfs diff=lfs merge=lfs -text
|
||||
# *.webp filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# # Fonts
|
||||
# *.woff2 filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# # Other
|
||||
# *.exe filter=lfs diff=lfs merge=lfs -text
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
.history
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# VSCode project files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/ujcms-cp-v10.1.3.iml" filepath="$PROJECT_DIR$/.idea/ujcms-cp-v10.1.3.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PrettierConfiguration">
|
||||
<option name="myConfigurationMode" value="AUTOMATIC" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"editorconfig.editorconfig",
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
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 2021-2024 南昌蓝智科技有限公司
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
# UJCMS-CP
|
||||
|
||||
UJCMS-CP是UJCMS的后台前端项目。使用 Vue 3、Vite、TypeScript、ElementPlus、TailwindCSS、VueRouter、VueI18n 开发。
|
||||
|
||||
需要启动`UJCMS`主项目才可以使用,不可单独运行(无法访问后端接口)。
|
||||
|
||||
如不需要修改`UJCMS`的后台界面,则不必启动此项目。`UJCMS`的`/src/main/webapp/cp`目录已包含本项目编译后的代码,直接运行`UJCMS`主项目即可。
|
||||
|
||||
## 搭建步骤
|
||||
|
||||
* 使用 vscode 开发工具。
|
||||
* 安装 node 环境。Node 20.12+ 版本。
|
||||
* 安装 pnpm。执行:npm install -g pnpm
|
||||
* 使用淘宝 npm 镜像。执行:pnpm set registry https://registry.npmmirror.com/
|
||||
* 安装依赖。执行:pnpm install
|
||||
* 启动程序。执行:pnpm run dev
|
||||
* 访问:http://127.0.0.1:5173
|
||||
* 用户名:admin,密码:password
|
||||
|
||||
## 修改后台标识
|
||||
|
||||
* 修改`.env`文件中的`VITE_APP_TITLE=UJCMS后台管理`配置,可改变浏览器页签上的标题。
|
||||
* 修改`.env`文件中的`VITE_APP_NAME=UJCMS`配置,可改变登录页、后台左侧导航等处的`UJCMS`标识。
|
||||
* 替换`/public/favicon.png`图片,可改变浏览器标签页上显示的图标。
|
||||
* 修改`/src/layout/components/AppSidebar/SidebarLogo.vue`文件中的`svg`图标,可改变后台左侧导航处LOGO图标。
|
||||
|
||||
## 编译及部署
|
||||
|
||||
* 执行:pnpm run build
|
||||
* 编译后的程序在`/dist`目录。
|
||||
* 将`/dist`目录里的文件拷贝至主项目UJCMS的`/src/main/webapp/cp`目录下(先将原目录下的文件删除)。
|
||||
|
||||
## 常见错误
|
||||
|
||||
编译时出现 `Javascript Heap out of memory` 错误,代表内存溢出。可以设置 `NODE_OPTIONS` 环境变量为 `--max-old-space-size=8192`。
|
||||
|
||||
## 前后端分开部署
|
||||
|
||||
通常前端和后端程序部署到同一个应用,即将前端程序复制到主项目UJCMS的`/cp`目录。以演示站点为例,后端接口地址为`https://demo.ujcms.com/api`,前端访问地址则为`https://demo.ujcms.com/cp/`。这样可以避免跨域问题,是最简单的部署方式。
|
||||
|
||||
如果需要将前后端部署到不同域名或端口,如后端接口地址为`http://www.example.com/api`,前端地址为`http://www.frontend.com`。由于前后端域名不同,前端直接访问后端接口会出现跨域错误。这时需要在前端服务器部署反向代理,解决跨域问题。以`nginx`为例:
|
||||
|
||||
```
|
||||
# 代理 api 接口
|
||||
location /api {
|
||||
proxy_pass http://www.example.com;
|
||||
}
|
||||
# 代理上传文件
|
||||
location /uploads {
|
||||
proxy_pass http://www.example.com;
|
||||
}
|
||||
```
|
||||
|
||||
开发模式启动时,情况也类似,后端接口地址为`http://localhost:8080/api`,前端地址为`http://localhost:9520`。前后端端口不同,也属于跨域。但前端开发在状态启动时,会自动开启代理,相关配置在`vite.config.ts`文件中。类似以下代码:
|
||||
|
||||
```
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: env.VITE_PROXY,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/uploads': {
|
||||
target: env.VITE_PROXY,
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
## 菜单和角色权限配置
|
||||
|
||||
如果进行二次开发,需新增功能,可在`/src/router/index.ts`文件中配置菜单。
|
||||
|
||||
并可在`/src/data.ts`文件中配置权限,配置好的权限会在`角色管理 - 权限设置`中的`功能权限`中显示。
|
||||
|
||||
配置内容:
|
||||
|
||||
```
|
||||
export function getPermsTreeData(): any[] {
|
||||
const {
|
||||
global: { t },
|
||||
} = i18n;
|
||||
const perms = [
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const ElMessage: typeof import('element-plus/es')['ElMessage']
|
||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AssignmentProps: typeof import('./src/components/bpmnjs/properties-panel/properties/AssignmentProps.vue')['default']
|
||||
BaseUpload: typeof import('./src/components/Upload/BaseUpload.vue')['default']
|
||||
BreadCrumb: typeof import('./src/components/BreadCrumb/index.vue')['default']
|
||||
ColumnList: typeof import('./src/components/TableList/ColumnList.vue')['default']
|
||||
ColumnSetting: typeof import('./src/components/TableList/ColumnSetting.vue')['default']
|
||||
ConditionProps: typeof import('./src/components/bpmnjs/properties-panel/properties/ConditionProps.vue')['default']
|
||||
DialogForm: typeof import('./src/components/DialogForm.vue')['default']
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
ElAside: typeof import('element-plus/es')['ElAside']
|
||||
ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckboxButton: typeof import('element-plus/es')['ElCheckboxButton']
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElImage: typeof import('element-plus/es')['ElImage']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElMain: typeof import('element-plus/es')['ElMain']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||
ElSpace: typeof import('element-plus/es')['ElSpace']
|
||||
ElStep: typeof import('element-plus/es')['ElStep']
|
||||
ElSteps: typeof import('element-plus/es')['ElSteps']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTree: typeof import('element-plus/es')['ElTree']
|
||||
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
FileListUpload: typeof import('./src/components/Upload/FileListUpload.vue')['default']
|
||||
FlowablePropertiesPannel: typeof import('./src/components/bpmnjs/properties-panel/FlowablePropertiesPannel.vue')['default']
|
||||
FormProps: typeof import('./src/components/bpmnjs/properties-panel/properties/FormProps.vue')['default']
|
||||
ImageCropper: typeof import('./src/components/Upload/ImageCropper.vue')['default']
|
||||
ImageListUpload: typeof import('./src/components/Upload/ImageListUpload.vue')['default']
|
||||
ImageUpload: typeof import('./src/components/Upload/ImageUpload.vue')['default']
|
||||
LabelTip: typeof import('./src/components/LabelTip.vue')['default']
|
||||
ListenerProps: typeof import('./src/components/bpmnjs/properties-panel/properties/ListenerProps.vue')['default']
|
||||
ListMove: typeof import('./src/components/ListMove.vue')['default']
|
||||
MultiInstanceProps: typeof import('./src/components/bpmnjs/properties-panel/properties/MultiInstanceProps.vue')['default']
|
||||
NormalProps: typeof import('./src/components/bpmnjs/properties-panel/properties/NormalProps.vue')['default']
|
||||
QueryForm: typeof import('./src/components/QueryForm/QueryForm.vue')['default']
|
||||
QueryInput: typeof import('./src/components/QueryForm/QueryInput.vue')['default']
|
||||
QueryItem: typeof import('./src/components/QueryForm/QueryItem.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
TimerProps: typeof import('./src/components/bpmnjs/properties-panel/properties/TimerProps.vue')['default']
|
||||
Tinymce: typeof import('./src/components/Tinymce/Tinymce.vue')['default']
|
||||
TuiEditor: typeof import('./src/components/TuiEditor/TuiEditor.vue')['default']
|
||||
UserSelect: typeof import('./src/components/user/UserSelect.vue')['default']
|
||||
UserSelectMulti: typeof import('./src/components/user/UserSelectMulti.vue')['default']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-cn">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>UJCMS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { MockMethod } from 'vite-plugin-mock';
|
||||
export default [
|
||||
{
|
||||
url: '/sample',
|
||||
method: 'get',
|
||||
// response: ({ query, body }: any) => {
|
||||
response: () => {
|
||||
return {
|
||||
code: 0,
|
||||
message: 'ok',
|
||||
data: {
|
||||
total: 2,
|
||||
list: [
|
||||
{
|
||||
id: 100,
|
||||
title: 'Mock测试数据100',
|
||||
},
|
||||
{
|
||||
id: 101,
|
||||
title: 'Mock测试数据101',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
] as MockMethod[];
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,131 @@
|
|||
{
|
||||
"name": "ujcms-cp",
|
||||
"version": "10.1.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint src/**/*.{vue,ts,tsx} --fix",
|
||||
"prettier": "prettier --write src/**/*.{json,js,ts,tsx,css,scss,vue,html,md}",
|
||||
"lint-staged": "lint-staged",
|
||||
"prepare": "husky",
|
||||
"plop": "plop"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-html": "^6.4.9",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@toast-ui/chart": "^4.6.1",
|
||||
"@toast-ui/editor": "^3.2.2",
|
||||
"@toast-ui/editor-plugin-chart": "^3.0.1",
|
||||
"@toast-ui/editor-plugin-code-syntax-highlight": "^3.1.0",
|
||||
"@toast-ui/editor-plugin-table-merged-cell": "^3.1.0",
|
||||
"@toast-ui/editor-plugin-uml": "^3.0.1",
|
||||
"@vueuse/components": "^10.11.1",
|
||||
"@vueuse/core": "^10.11.1",
|
||||
"axios": "^1.7.7",
|
||||
"bpmn-js": "^17.11.1",
|
||||
"codemirror": "^6.0.1",
|
||||
"core-js": "^3.39.0",
|
||||
"cropperjs": "^1.6.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"diagram-js": "^14.11.3",
|
||||
"diagram-js-direct-editing": "^2.1.2",
|
||||
"domutils": "^3.1.0",
|
||||
"echarts": "^5.5.1",
|
||||
"element-plus": "~2.8.8",
|
||||
"entities": "^4.5.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"htmlparser2": "^9.1.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lodash": "^4.17.21",
|
||||
"min-dash": "^4.2.2",
|
||||
"nprogress": "^0.2.0",
|
||||
"path-to-regexp": "^6.3.0",
|
||||
"pinia": "^2.2.6",
|
||||
"pinia-plugin-persistedstate": "^3.2.3",
|
||||
"prismjs": "^1.29.0",
|
||||
"sm-crypto": "^0.3.13",
|
||||
"sortablejs": "1.14.0",
|
||||
"tinymce": "~5.9.2",
|
||||
"vue": "^3.5.13",
|
||||
"vue-codemirror": "^6.1.1",
|
||||
"vue-i18n": "^9.14.1",
|
||||
"vue-router": "^4.4.5",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/lodash": "^4.17.13",
|
||||
"@types/node": "^20.17.6",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/sm-crypto": "^0.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vitejs/plugin-legacy": "^5.4.3",
|
||||
"@vitejs/plugin-vue": "^5.2.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-vue": "^9.31.0",
|
||||
"husky": "^9.1.6",
|
||||
"lint-staged": "^15.2.10",
|
||||
"mockjs": "^1.1.0",
|
||||
"plop": "^4.0.1",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss-import": "^16.1.0",
|
||||
"prettier": "^3.3.3",
|
||||
"sass": "^1.81.0",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"terser": "^5.36.0",
|
||||
"typescript": "^5.6.3",
|
||||
"unplugin-auto-import": "^0.19.0",
|
||||
"unplugin-vue-components": "^0.27.5",
|
||||
"vite": "^5.4.17",
|
||||
"vite-plugin-mock": "^3.0.2",
|
||||
"vue-tsc": "^2.1.10"
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 180,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"arrowParens": "always"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,tsx}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.vue": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"{!(package)*.json,.!(browserslist)*rc}": [
|
||||
"prettier --write--parser json"
|
||||
],
|
||||
"package.json": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{scss,html}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.md": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=20.12"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export const query{{pascalCase name}}{{pascalCase type}} = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/{{kebabCase sub}}/{{kebabCase name}}', { params })).data;
|
||||
export const query{{pascalCase name}} = async (id: string): Promise<any> => (await axios.get(`/backend/{{kebabCase sub}}/{{kebabCase name}}/${id}`)).data;
|
||||
export const create{{pascalCase name}} = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/{{kebabCase sub}}/{{kebabCase name}}', data)).data;
|
||||
export const update{{pascalCase name}} = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/{{kebabCase sub}}/{{kebabCase name}}?_method=put', data)).data;
|
||||
{{#if isList}}
|
||||
export const update{{pascalCase name}}Order = async (fromId: string, toId: string): Promise<any> => (await axios.post('/backend/{{kebabCase sub}}/{{kebabCase name}}/update-order', { fromId, toId })).data;
|
||||
{{/if}}
|
||||
export const delete{{pascalCase name}} = async (data: string[]): Promise<any> => (await axios.post('/backend/{{kebabCase sub}}/{{kebabCase name}}?_method=delete', data)).data;
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { query{{pascalCase name}}, create{{pascalCase name}}, update{{pascalCase name}}, delete{{pascalCase name}} } from '@/api/{{kebabCase path}}';
|
||||
import DialogForm from '@/components/DialogForm.vue';
|
||||
import LabelTip from '@/components/LabelTip.vue';
|
||||
|
||||
defineOptions({
|
||||
name: '{{pascalCase name}}Form',
|
||||
});
|
||||
const visible = defineModel<boolean>({ default: false });
|
||||
defineProps<{ beanId?: string; beanIds: string[] }>();
|
||||
defineEmits({ finished: null });
|
||||
const focus = ref<any>();
|
||||
const values = ref<any>({});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<dialog-form
|
||||
v-model="visible"
|
||||
v-model:values="values"
|
||||
:name="$t('menu.{{camelCase path}}.{{camelCase name}}')"
|
||||
:query-bean="query{{pascalCase name}}"
|
||||
:create-bean="create{{pascalCase name}}"
|
||||
:update-bean="update{{pascalCase name}}"
|
||||
:delete-bean="delete{{pascalCase name}}"
|
||||
:bean-id="beanId"
|
||||
:bean-ids="beanIds"
|
||||
:focus="focus"
|
||||
:init-values="() => ({})"
|
||||
:to-values="(bean) => ({ ...bean })"
|
||||
perms="{{camelCase name}}"
|
||||
@finished="() => $emit('finished')"
|
||||
>
|
||||
<template #default="{}">
|
||||
<el-form-item prop="name" :rules="{ required: true, message: () => $t('v.required') }">
|
||||
<template #label><label-tip message="{{camelCase name}}.name" /></template>
|
||||
<el-input ref="focus" v-model="values.name" maxlength="50"></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</dialog-form>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, onMounted, onBeforeUnmount, ref } from 'vue';
|
||||
import { Plus, Delete, Grid } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Sortable from 'sortablejs';
|
||||
import { perm } from '@/stores/useCurrentUser';
|
||||
import { toParams, resetParams } from '@/utils/common';
|
||||
import { delete{{pascalCase name}}, query{{pascalCase name}}List, update{{pascalCase name}}Order } from '@/api/{{kebabCase path}}';
|
||||
import { ColumnList, ColumnSetting } from '@/components/TableList';
|
||||
import { QueryForm, QueryItem } from '@/components/QueryForm';
|
||||
import {{pascalCase name}}Form from './{{pascalCase name}}Form.vue';
|
||||
|
||||
defineOptions({
|
||||
name: '{{pascalCase name}}List',
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const params = ref<any>({});
|
||||
const sort = ref<any>();
|
||||
const table = ref<any>();
|
||||
const data = ref<any[]>([]);
|
||||
const selection = ref<any[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
const formVisible = ref<boolean>(false);
|
||||
const beanId = ref<string>();
|
||||
const beanIds = computed(() => data.value.map((row) => row.id));
|
||||
const isSorted = ref<boolean>(false);
|
||||
const fetchData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
data.value = await query{{pascalCase name}}List({ ...toParams(params.value), Q_OrderBy: sort.value });
|
||||
isSorted.value = sort.value !== undefined;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
let sortable;
|
||||
const initDragTable = () => {
|
||||
const tbody = document.querySelector('#dataTable .el-table__body-wrapper tbody');
|
||||
sortable = Sortable.create(tbody, {
|
||||
handle: '.drag-handle',
|
||||
onEnd: async function (event: any) {
|
||||
const { oldIndex, newIndex } = event;
|
||||
if (oldIndex !== newIndex) {
|
||||
await update{{pascalCase name}}Order(data.value[oldIndex].id, data.value[newIndex].id);
|
||||
data.value.splice(newIndex, 0, data.value.splice(oldIndex, 1)[0]);
|
||||
ElMessage.success(t('success'));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
onMounted(() => {
|
||||
fetchData();
|
||||
initDragTable();
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
if (sortable !== undefined) {
|
||||
sortable.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
const handleSort = ({ column, prop, order }: { column: any; prop: string; order: string }) => {
|
||||
if (prop && order) {
|
||||
sort.value = (column.sortBy ?? prop) + (order === 'descending' ? '_desc' : '');
|
||||
} else {
|
||||
sort.value = undefined;
|
||||
}
|
||||
fetchData();
|
||||
};
|
||||
const handleSearch = () => fetchData();
|
||||
const handleReset = () => {
|
||||
table.value.clearSort();
|
||||
resetParams(params.value);
|
||||
sort.value = undefined;
|
||||
fetchData();
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
beanId.value = undefined;
|
||||
formVisible.value = true;
|
||||
};
|
||||
const handleEdit = (id: string) => {
|
||||
beanId.value = id;
|
||||
formVisible.value = true;
|
||||
};
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
await delete{{pascalCase name}}(ids);
|
||||
fetchData();
|
||||
ElMessage.success(t('success'));
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-3">
|
||||
<query-form :params="params" @search="handleSearch" @reset="() => handleReset()">
|
||||
<query-item :label="$t('{{camelCase name}}.name')" name="Q_Contains_name"></query-item>
|
||||
</query-form>
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<el-button type="primary" :disabled="perm('{{camelCase name}}:create')" :icon="Plus" @click="() => handleAdd()">\{{ $t('add') }}</el-button>
|
||||
<el-popconfirm :title="$t('confirmDelete')" @confirm="() => handleDelete(selection.map((row) => row.id))">
|
||||
<template #reference>
|
||||
<el-button :disabled="selection.length <= 0 || perm('{{camelCase name}}:delete')" :icon="Delete">\{{ $t('delete') }}</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
<column-setting name="{{camelCase name}}" />
|
||||
</div>
|
||||
<div class="mt-3 app-block">
|
||||
<el-table
|
||||
id="dataTable"
|
||||
ref="table"
|
||||
v-loading="loading"
|
||||
row-key="id"
|
||||
:data
|
||||
@selection-change="(rows) => (selection = rows)"
|
||||
@row-dblclick="(row) => handleEdit(row.id)"
|
||||
@sort-change="handleSort"
|
||||
>
|
||||
<column-list name="{{camelCase name}}">
|
||||
<el-table-column type="selection" width="45"></el-table-column>
|
||||
<el-table-column width="42">
|
||||
<el-icon
|
||||
class="text-lg align-middle text-gray-secondary"
|
||||
:class="isSorted || perm('{{camelCase name}}:update') ? ['cursor-not-allowed', 'text-gray-disabled'] : ['cursor-move', 'text-gray-regular', 'drag-handle']"
|
||||
disalbed
|
||||
>
|
||||
<Grid />
|
||||
</el-icon>
|
||||
</el-table-column>
|
||||
<el-table-column property="id" label="ID" width="180" sortable="custom"></el-table-column>
|
||||
<el-table-column property="name" :label="$t('{{camelCase name}}.name')" sortable="custom" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('table.action')">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" :disabled="perm('{{camelCase name}}:update')" size="small" link @click="() => handleEdit(row.id)">\{{ $t('edit') }}</el-button>
|
||||
<el-popconfirm :title="$t('confirmDelete')" @confirm="() => handleDelete([row.id])">
|
||||
<template #reference>
|
||||
<el-button type="primary" :disabled="perm('{{camelCase name}}:delete')" size="small" link>\{{ $t('delete') }}</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</column-list>
|
||||
</el-table>
|
||||
</div>
|
||||
<{{kebabCase name}}-form v-model="formVisible" :bean-id="beanId" :bean-ids="beanIds" @finished="() => fetchData()" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { Plus, Delete } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { perm } from '@/stores/useCurrentUser';
|
||||
import { pageSizes, pageLayout, toParams, resetParams } from '@/utils/common';
|
||||
import { delete{{pascalCase name}}, query{{pascalCase name}}Page } from '@/api/{{kebabCase path}}';
|
||||
import { ColumnList, ColumnSetting } from '@/components/TableList';
|
||||
import { QueryForm, QueryItem } from '@/components/QueryForm';
|
||||
import {{pascalCase name}}Form from './{{pascalCase name}}Form.vue';
|
||||
|
||||
defineOptions({
|
||||
name: '{{pascalCase name}}List',
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const params = ref<any>({});
|
||||
const sort = ref<any>();
|
||||
const currentPage = ref<number>(1);
|
||||
const pageSize = ref<number>(10);
|
||||
const total = ref<number>(0);
|
||||
const table = ref<any>();
|
||||
const data = ref<any[]>([]);
|
||||
const selection = ref<any[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
const formVisible = ref<boolean>(false);
|
||||
const beanId = ref<string>();
|
||||
const beanIds = computed(() => data.value.map((row) => row.id));
|
||||
const fetchData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const {
|
||||
content,
|
||||
page: { totalElements },
|
||||
} = await query{{pascalCase name}}Page({ ...toParams(params.value), Q_OrderBy: sort.value, page: currentPage.value, pageSize: pageSize.value });
|
||||
data.value = content;
|
||||
total.value = Number(totalElements);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
onMounted(() => {
|
||||
fetchData();
|
||||
});
|
||||
|
||||
const handleSort = ({ column, prop, order }: { column: any; prop: string; order: string }) => {
|
||||
if (prop && order) {
|
||||
sort.value = (column.sortBy ?? prop) + (order === 'descending' ? '_desc' : '');
|
||||
} else {
|
||||
sort.value = undefined;
|
||||
}
|
||||
fetchData();
|
||||
};
|
||||
const handleSearch = () => fetchData();
|
||||
const handleReset = () => {
|
||||
table.value.clearSort();
|
||||
resetParams(params.value);
|
||||
sort.value = undefined;
|
||||
fetchData();
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
beanId.value = undefined;
|
||||
formVisible.value = true;
|
||||
};
|
||||
const handleEdit = (id: string) => {
|
||||
beanId.value = id;
|
||||
formVisible.value = true;
|
||||
};
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
await delete{{pascalCase name}}(ids);
|
||||
fetchData();
|
||||
ElMessage.success(t('success'));
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-3">
|
||||
<query-form :params="params" @search="handleSearch" @reset="() => handleReset()">
|
||||
<query-item :label="$t('{{camelCase name}}.name')" name="Q_Contains_name"></query-item>
|
||||
</query-form>
|
||||
</div>
|
||||
<div>
|
||||
<el-button type="primary" :disabled="perm('{{camelCase name}}:create')" :icon="Plus" @click="() => handleAdd()">\{{ $t('add') }}</el-button>
|
||||
<el-popconfirm :title="$t('confirmDelete')" @confirm="() => handleDelete(selection.map((row) => row.id))">
|
||||
<template #reference>
|
||||
<el-button :disabled="selection.length <= 0 || perm('{{camelCase name}}:delete')" :icon="Delete">\{{ $t('delete') }}</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
<column-setting name="{{camelCase name}}" class="ml-2" />
|
||||
</div>
|
||||
<div class="mt-3 app-block">
|
||||
<el-table
|
||||
ref="table"
|
||||
v-loading="loading"
|
||||
:data="data"
|
||||
@selection-change="(rows) => (selection = rows)"
|
||||
@row-dblclick="(row) => handleEdit(row.id)"
|
||||
@sort-change="handleSort"
|
||||
>
|
||||
<column-list name="{{camelCase name}}">
|
||||
<el-table-column type="selection" width="45"></el-table-column>
|
||||
<el-table-column property="id" label="ID" width="180" sortable="custom"></el-table-column>
|
||||
<el-table-column property="name" :label="$t('{{camelCase name}}.name')" sortable="custom" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('table.action')">
|
||||
<template #default="{row}">
|
||||
<el-button type="primary" :disabled="perm('{{camelCase name}}:update')" size="small" link @click="() => handleEdit(row.id)">\{{ $t('edit') }}</el-button>
|
||||
<el-popconfirm :title="$t('confirmDelete')" @confirm="() => handleDelete([row.id])">
|
||||
<template #reference>
|
||||
<el-button type="primary" :disabled="perm('{{camelCase name}}:delete')" size="small" link>\{{ $t('delete') }}</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</column-list>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:total
|
||||
:page-sizes
|
||||
:layout="pageLayout"
|
||||
class="justify-end px-3 py-2"
|
||||
size="small"
|
||||
background
|
||||
@size-change="() => fetchData()"
|
||||
@current-change="() => fetchData()"
|
||||
></el-pagination>
|
||||
</div>
|
||||
<{{kebabCase name}}-form v-model="formVisible" :bean-id="beanId" :bean-ids="beanIds" @finished="fetchData" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// pnpm run plop core user org page
|
||||
// pnpm run plop <子系统> <分类> <模块> <page|list>
|
||||
/* eslint-disable func-names */
|
||||
export default function (plop) {
|
||||
// controller generator
|
||||
plop.setGenerator('view', {
|
||||
description: 'application views',
|
||||
prompts: [
|
||||
{
|
||||
type: 'input',
|
||||
name: 'sub',
|
||||
message: 'sub:',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'path',
|
||||
message: 'path:',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: 'name:',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'type',
|
||||
message: 'type:',
|
||||
},
|
||||
],
|
||||
actions: (data) => {
|
||||
const actions = [];
|
||||
actions.push({
|
||||
type: 'add',
|
||||
path: 'src/views/{{kebabCase path}}/{{pascalCase name}}Form.vue',
|
||||
templateFile: 'plop-templates/view_form.hbs',
|
||||
});
|
||||
actions.push({
|
||||
type: 'add',
|
||||
path: 'src/views/{{kebabCase path}}/{{pascalCase name}}List.vue',
|
||||
templateFile: `plop-templates/view_${data.type}.hbs`,
|
||||
});
|
||||
actions.push({
|
||||
type: 'append',
|
||||
path: 'src/api/{{kebabCase path}}.ts',
|
||||
templateFile: 'plop-templates/api.hbs',
|
||||
data: { isList: data.type === 'list' },
|
||||
});
|
||||
return actions;
|
||||
},
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
plugins: { 'postcss-import': {}, tailwindcss: {}, autoprefixer: {} },
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 269 B |
|
|
@ -0,0 +1,462 @@
|
|||
tinymce.addI18n('zh_CN',{
|
||||
"Redo": "\u91cd\u505a",
|
||||
"Undo": "\u64a4\u9500",
|
||||
"Cut": "\u526a\u5207",
|
||||
"Copy": "\u590d\u5236",
|
||||
"Paste": "\u7c98\u8d34",
|
||||
"Select all": "\u5168\u9009",
|
||||
"New document": "\u65b0\u6587\u4ef6",
|
||||
"Ok": "\u786e\u5b9a",
|
||||
"Cancel": "\u53d6\u6d88",
|
||||
"Visual aids": "\u7f51\u683c\u7ebf",
|
||||
"Bold": "\u7c97\u4f53",
|
||||
"Italic": "\u659c\u4f53",
|
||||
"Underline": "\u4e0b\u5212\u7ebf",
|
||||
"Strikethrough": "\u5220\u9664\u7ebf",
|
||||
"Superscript": "\u4e0a\u6807",
|
||||
"Subscript": "\u4e0b\u6807",
|
||||
"Clear formatting": "\u6e05\u9664\u683c\u5f0f",
|
||||
"Align left": "\u5de6\u8fb9\u5bf9\u9f50",
|
||||
"Align center": "\u4e2d\u95f4\u5bf9\u9f50",
|
||||
"Align right": "\u53f3\u8fb9\u5bf9\u9f50",
|
||||
"Justify": "\u4e24\u7aef\u5bf9\u9f50",
|
||||
"Bullet list": "\u9879\u76ee\u7b26\u53f7",
|
||||
"Numbered list": "\u7f16\u53f7\u5217\u8868",
|
||||
"Decrease indent": "\u51cf\u5c11\u7f29\u8fdb",
|
||||
"Increase indent": "\u589e\u52a0\u7f29\u8fdb",
|
||||
"Close": "\u5173\u95ed",
|
||||
"Formats": "\u683c\u5f0f",
|
||||
"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u6253\u5f00\u526a\u8d34\u677f\uff0c\u8bf7\u4f7f\u7528Ctrl+X\/C\/V\u7b49\u5feb\u6377\u952e\u3002",
|
||||
"Headers": "\u6807\u9898",
|
||||
"Header 1": "\u6807\u98981",
|
||||
"Header 2": "\u6807\u98982",
|
||||
"Header 3": "\u6807\u98983",
|
||||
"Header 4": "\u6807\u98984",
|
||||
"Header 5": "\u6807\u98985",
|
||||
"Header 6": "\u6807\u98986",
|
||||
"Headings": "\u6807\u9898",
|
||||
"Heading 1": "\u6807\u98981",
|
||||
"Heading 2": "\u6807\u98982",
|
||||
"Heading 3": "\u6807\u98983",
|
||||
"Heading 4": "\u6807\u98984",
|
||||
"Heading 5": "\u6807\u98985",
|
||||
"Heading 6": "\u6807\u98986",
|
||||
"Preformatted": "\u9884\u5148\u683c\u5f0f\u5316\u7684",
|
||||
"Div": "Div",
|
||||
"Pre": "Pre",
|
||||
"Code": "\u4ee3\u7801",
|
||||
"Paragraph": "\u6bb5\u843d",
|
||||
"Blockquote": "\u5f15\u6587\u533a\u5757",
|
||||
"Inline": "\u6587\u672c",
|
||||
"Blocks": "\u57fa\u5757",
|
||||
"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002",
|
||||
"Fonts": "\u5b57\u4f53",
|
||||
"Font Sizes": "\u5b57\u53f7",
|
||||
"Class": "\u7c7b\u578b",
|
||||
"Browse for an image": "\u6d4f\u89c8\u56fe\u50cf",
|
||||
"OR": "\u6216",
|
||||
"Drop an image here": "\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64",
|
||||
"Upload": "\u4e0a\u4f20",
|
||||
"Block": "\u5757",
|
||||
"Align": "\u5bf9\u9f50",
|
||||
"Default": "\u9ed8\u8ba4",
|
||||
"Circle": "\u7a7a\u5fc3\u5706",
|
||||
"Disc": "\u5b9e\u5fc3\u5706",
|
||||
"Square": "\u65b9\u5757",
|
||||
"Lower Alpha": "\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd",
|
||||
"Lower Greek": "\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd",
|
||||
"Lower Roman": "\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd",
|
||||
"Upper Alpha": "\u5927\u5199\u82f1\u6587\u5b57\u6bcd",
|
||||
"Upper Roman": "\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd",
|
||||
"Anchor...": "\u951a\u70b9...",
|
||||
"Name": "\u540d\u79f0",
|
||||
"Id": "\u6807\u8bc6\u7b26",
|
||||
"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u6807\u8bc6\u7b26\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002",
|
||||
"You have unsaved changes are you sure you want to navigate away?": "\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f",
|
||||
"Restore last draft": "\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f",
|
||||
"Special character...": "\u7279\u6b8a\u5b57\u7b26...",
|
||||
"Source code": "\u6e90\u4ee3\u7801",
|
||||
"Insert\/Edit code sample": "\u63d2\u5165\/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b",
|
||||
"Language": "\u8bed\u8a00",
|
||||
"Code sample...": "\u793a\u4f8b\u4ee3\u7801...",
|
||||
"Color Picker": "\u9009\u8272\u5668",
|
||||
"R": "R",
|
||||
"G": "G",
|
||||
"B": "B",
|
||||
"Left to right": "\u4ece\u5de6\u5230\u53f3",
|
||||
"Right to left": "\u4ece\u53f3\u5230\u5de6",
|
||||
"Emoticons": "\u8868\u60c5",
|
||||
"Emoticons...": "\u8868\u60c5\u7b26\u53f7...",
|
||||
"Metadata and Document Properties": "\u5143\u6570\u636e\u548c\u6587\u6863\u5c5e\u6027",
|
||||
"Title": "\u6807\u9898",
|
||||
"Keywords": "\u5173\u952e\u8bcd",
|
||||
"Description": "\u63cf\u8ff0",
|
||||
"Robots": "\u673a\u5668\u4eba",
|
||||
"Author": "\u4f5c\u8005",
|
||||
"Encoding": "\u7f16\u7801",
|
||||
"Fullscreen": "\u5168\u5c4f",
|
||||
"Action": "\u64cd\u4f5c",
|
||||
"Shortcut": "\u5feb\u6377\u952e",
|
||||
"Help": "\u5e2e\u52a9",
|
||||
"Address": "\u5730\u5740",
|
||||
"Focus to menubar": "\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f",
|
||||
"Focus to toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f",
|
||||
"Focus to element path": "\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84",
|
||||
"Focus to contextual toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355",
|
||||
"Insert link (if link plugin activated)": "\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
|
||||
"Save (if save plugin activated)": "\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
|
||||
"Find (if searchreplace plugin activated)": "\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
|
||||
"Plugins installed ({0}):": "\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):",
|
||||
"Premium plugins:": "\u4f18\u79c0\u63d2\u4ef6\uff1a",
|
||||
"Learn more...": "\u4e86\u89e3\u66f4\u591a...",
|
||||
"You are using {0}": "\u4f60\u6b63\u5728\u4f7f\u7528 {0}",
|
||||
"Plugins": "\u63d2\u4ef6",
|
||||
"Handy Shortcuts": "\u5feb\u6377\u952e",
|
||||
"Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf",
|
||||
"Insert\/edit image": "\u63d2\u5165\/\u7f16\u8f91\u56fe\u7247",
|
||||
"Alternative description": "\u66ff\u4ee3\u63cf\u8ff0",
|
||||
"Accessibility": "\u8f85\u52a9\u529f\u80fd",
|
||||
"Image is decorative": "\u56fe\u50cf\u662f\u88c5\u9970\u6027\u7684",
|
||||
"Source": "\u5730\u5740",
|
||||
"Dimensions": "\u5927\u5c0f",
|
||||
"Constrain proportions": "\u4fdd\u6301\u7eb5\u6a2a\u6bd4",
|
||||
"General": "\u666e\u901a",
|
||||
"Advanced": "\u9ad8\u7ea7",
|
||||
"Style": "\u6837\u5f0f",
|
||||
"Vertical space": "\u5782\u76f4\u8fb9\u8ddd",
|
||||
"Horizontal space": "\u6c34\u5e73\u8fb9\u8ddd",
|
||||
"Border": "\u8fb9\u6846",
|
||||
"Insert image": "\u63d2\u5165\u56fe\u7247",
|
||||
"Image...": "\u56fe\u7247...",
|
||||
"Image list": "\u56fe\u7247\u5217\u8868",
|
||||
"Rotate counterclockwise": "\u9006\u65f6\u9488\u65cb\u8f6c",
|
||||
"Rotate clockwise": "\u987a\u65f6\u9488\u65cb\u8f6c",
|
||||
"Flip vertically": "\u5782\u76f4\u7ffb\u8f6c",
|
||||
"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f6c",
|
||||
"Edit image": "\u7f16\u8f91\u56fe\u7247",
|
||||
"Image options": "\u56fe\u7247\u9009\u9879",
|
||||
"Zoom in": "\u653e\u5927",
|
||||
"Zoom out": "\u7f29\u5c0f",
|
||||
"Crop": "\u88c1\u526a",
|
||||
"Resize": "\u8c03\u6574\u5927\u5c0f",
|
||||
"Orientation": "\u65b9\u5411",
|
||||
"Brightness": "\u4eae\u5ea6",
|
||||
"Sharpen": "\u9510\u5316",
|
||||
"Contrast": "\u5bf9\u6bd4\u5ea6",
|
||||
"Color levels": "\u989c\u8272\u5c42\u6b21",
|
||||
"Gamma": "\u4f3d\u9a6c\u503c",
|
||||
"Invert": "\u53cd\u8f6c",
|
||||
"Apply": "\u5e94\u7528",
|
||||
"Back": "\u540e\u9000",
|
||||
"Insert date\/time": "\u63d2\u5165\u65e5\u671f\/\u65f6\u95f4",
|
||||
"Date\/time": "\u65e5\u671f\/\u65f6\u95f4",
|
||||
"Insert\/edit link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
|
||||
"Text to display": "\u663e\u793a\u6587\u5b57",
|
||||
"Url": "\u5730\u5740",
|
||||
"Open link in...": "\u94fe\u63a5\u6253\u5f00\u4f4d\u7f6e...",
|
||||
"Current window": "\u5f53\u524d\u7a97\u53e3",
|
||||
"None": "\u65e0",
|
||||
"New window": "\u5728\u65b0\u7a97\u53e3\u6253\u5f00",
|
||||
"Open link": "\u6253\u5f00\u94fe\u63a5",
|
||||
"Remove link": "\u5220\u9664\u94fe\u63a5",
|
||||
"Anchors": "\u951a\u70b9",
|
||||
"Link...": "\u94fe\u63a5...",
|
||||
"Paste or type a link": "\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5",
|
||||
"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f",
|
||||
"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7f00\u5417\uff1f",
|
||||
"The URL you entered seems to be an external link. Do you want to add the required https:\/\/ prefix?": "\u60a8\u8f93\u5165\u7684 URL \u4f3c\u4e4e\u662f\u4e00\u4e2a\u5916\u90e8\u94fe\u63a5\u3002\u60a8\u60f3\u6dfb\u52a0\u6240\u9700\u7684 https:\/\/ \u524d\u7f00\u5417\uff1f",
|
||||
"Link list": "\u94fe\u63a5\u5217\u8868",
|
||||
"Insert video": "\u63d2\u5165\u89c6\u9891",
|
||||
"Insert\/edit video": "\u63d2\u5165\/\u7f16\u8f91\u89c6\u9891",
|
||||
"Insert\/edit media": "\u63d2\u5165\/\u7f16\u8f91\u5a92\u4f53",
|
||||
"Alternative source": "\u955c\u50cf",
|
||||
"Alternative source URL": "\u66ff\u4ee3\u6765\u6e90\u7f51\u5740",
|
||||
"Media poster (Image URL)": "\u5c01\u9762(\u56fe\u7247\u5730\u5740)",
|
||||
"Paste your embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:",
|
||||
"Embed": "\u5185\u5d4c",
|
||||
"Media...": "\u591a\u5a92\u4f53...",
|
||||
"Nonbreaking space": "\u4e0d\u95f4\u65ad\u7a7a\u683c",
|
||||
"Page break": "\u5206\u9875\u7b26",
|
||||
"Paste as text": "\u7c98\u8d34\u4e3a\u6587\u672c",
|
||||
"Preview": "\u9884\u89c8",
|
||||
"Print...": "\u6253\u5370...",
|
||||
"Save": "\u4fdd\u5b58",
|
||||
"Find": "\u67e5\u627e",
|
||||
"Replace with": "\u66ff\u6362\u4e3a",
|
||||
"Replace": "\u66ff\u6362",
|
||||
"Replace all": "\u5168\u90e8\u66ff\u6362",
|
||||
"Previous": "\u4e0a\u4e00\u4e2a",
|
||||
"Next": "\u4e0b\u4e00\u4e2a",
|
||||
"Find and Replace": "\u67e5\u627e\u548c\u66ff\u6362",
|
||||
"Find and replace...": "\u67e5\u627e\u5e76\u66ff\u6362...",
|
||||
"Could not find the specified string.": "\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.",
|
||||
"Match case": "\u533a\u5206\u5927\u5c0f\u5199",
|
||||
"Find whole words only": "\u5168\u5b57\u5339\u914d",
|
||||
"Find in selection": "\u5728\u9009\u533a\u4e2d\u67e5\u627e",
|
||||
"Spellcheck": "\u62fc\u5199\u68c0\u67e5",
|
||||
"Spellcheck Language": "\u62fc\u5199\u68c0\u67e5\u8bed\u8a00",
|
||||
"No misspellings found.": "\u6ca1\u6709\u53d1\u73b0\u62fc\u5199\u9519\u8bef",
|
||||
"Ignore": "\u5ffd\u7565",
|
||||
"Ignore all": "\u5168\u90e8\u5ffd\u7565",
|
||||
"Finish": "\u5b8c\u6210",
|
||||
"Add to Dictionary": "\u6dfb\u52a0\u5230\u5b57\u5178",
|
||||
"Insert table": "\u63d2\u5165\u8868\u683c",
|
||||
"Table properties": "\u8868\u683c\u5c5e\u6027",
|
||||
"Delete table": "\u5220\u9664\u8868\u683c",
|
||||
"Cell": "\u5355\u5143\u683c",
|
||||
"Row": "\u884c",
|
||||
"Column": "\u5217",
|
||||
"Cell properties": "\u5355\u5143\u683c\u5c5e\u6027",
|
||||
"Merge cells": "\u5408\u5e76\u5355\u5143\u683c",
|
||||
"Split cell": "\u62c6\u5206\u5355\u5143\u683c",
|
||||
"Insert row before": "\u5728\u4e0a\u65b9\u63d2\u5165",
|
||||
"Insert row after": "\u5728\u4e0b\u65b9\u63d2\u5165",
|
||||
"Delete row": "\u5220\u9664\u884c",
|
||||
"Row properties": "\u884c\u5c5e\u6027",
|
||||
"Cut row": "\u526a\u5207\u884c",
|
||||
"Copy row": "\u590d\u5236\u884c",
|
||||
"Paste row before": "\u7c98\u8d34\u5230\u4e0a\u65b9",
|
||||
"Paste row after": "\u7c98\u8d34\u5230\u4e0b\u65b9",
|
||||
"Insert column before": "\u5728\u5de6\u4fa7\u63d2\u5165",
|
||||
"Insert column after": "\u5728\u53f3\u4fa7\u63d2\u5165",
|
||||
"Delete column": "\u5220\u9664\u5217",
|
||||
"Cols": "\u5217",
|
||||
"Rows": "\u884c",
|
||||
"Width": "\u5bbd",
|
||||
"Height": "\u9ad8",
|
||||
"Cell spacing": "\u5355\u5143\u683c\u5916\u95f4\u8ddd",
|
||||
"Cell padding": "\u5355\u5143\u683c\u5185\u8fb9\u8ddd",
|
||||
"Caption": "\u6807\u9898",
|
||||
"Show caption": "\u663e\u793a\u6807\u9898",
|
||||
"Left": "\u5de6\u5bf9\u9f50",
|
||||
"Center": "\u5c45\u4e2d",
|
||||
"Right": "\u53f3\u5bf9\u9f50",
|
||||
"Cell type": "\u5355\u5143\u683c\u7c7b\u578b",
|
||||
"Scope": "\u8303\u56f4",
|
||||
"Alignment": "\u5bf9\u9f50\u65b9\u5f0f",
|
||||
"H Align": "\u6c34\u5e73\u5bf9\u9f50",
|
||||
"V Align": "\u5782\u76f4\u5bf9\u9f50",
|
||||
"Top": "\u9876\u90e8\u5bf9\u9f50",
|
||||
"Middle": "\u5782\u76f4\u5c45\u4e2d",
|
||||
"Bottom": "\u5e95\u90e8\u5bf9\u9f50",
|
||||
"Header cell": "\u8868\u5934\u5355\u5143\u683c",
|
||||
"Row group": "\u884c\u7ec4",
|
||||
"Column group": "\u5217\u7ec4",
|
||||
"Row type": "\u884c\u7c7b\u578b",
|
||||
"Header": "\u8868\u5934",
|
||||
"Body": "\u8868\u4f53",
|
||||
"Footer": "\u8868\u5c3e",
|
||||
"Border color": "\u8fb9\u6846\u989c\u8272",
|
||||
"Insert template...": "\u63d2\u5165\u6a21\u677f...",
|
||||
"Templates": "\u6a21\u677f",
|
||||
"Template": "\u6a21\u677f",
|
||||
"Text color": "\u6587\u5b57\u989c\u8272",
|
||||
"Background color": "\u80cc\u666f\u8272",
|
||||
"Custom...": "\u81ea\u5b9a\u4e49...",
|
||||
"Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272",
|
||||
"No color": "\u65e0",
|
||||
"Remove color": "\u79fb\u9664\u989c\u8272",
|
||||
"Table of Contents": "\u5185\u5bb9\u5217\u8868",
|
||||
"Show blocks": "\u663e\u793a\u533a\u5757\u8fb9\u6846",
|
||||
"Show invisible characters": "\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26",
|
||||
"Word count": "\u5b57\u6570",
|
||||
"Count": "\u8ba1\u6570",
|
||||
"Document": "\u6587\u6863",
|
||||
"Selection": "\u9009\u62e9",
|
||||
"Words": "\u5355\u8bcd",
|
||||
"Words: {0}": "\u5b57\u6570\uff1a{0}",
|
||||
"{0} words": "{0} \u5b57",
|
||||
"File": "\u6587\u4ef6",
|
||||
"Edit": "\u7f16\u8f91",
|
||||
"Insert": "\u63d2\u5165",
|
||||
"View": "\u89c6\u56fe",
|
||||
"Format": "\u683c\u5f0f",
|
||||
"Table": "\u8868\u683c",
|
||||
"Tools": "\u5de5\u5177",
|
||||
"Powered by {0}": "\u7531{0}\u9a71\u52a8",
|
||||
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9",
|
||||
"Image title": "\u56fe\u7247\u6807\u9898",
|
||||
"Border width": "\u8fb9\u6846\u5bbd\u5ea6",
|
||||
"Border style": "\u8fb9\u6846\u6837\u5f0f",
|
||||
"Error": "\u9519\u8bef",
|
||||
"Warn": "\u8b66\u544a",
|
||||
"Valid": "\u6709\u6548",
|
||||
"To open the popup, press Shift+Enter": "\u6309Shitf+Enter\u952e\u6253\u5f00\u5bf9\u8bdd\u6846",
|
||||
"Rich Text Area. Press ALT-0 for help.": "\u7f16\u8f91\u533a\u3002\u6309Alt+0\u952e\u6253\u5f00\u5e2e\u52a9\u3002",
|
||||
"System Font": "\u7cfb\u7edf\u5b57\u4f53",
|
||||
"Failed to upload image: {0}": "\u56fe\u7247\u4e0a\u4f20\u5931\u8d25: {0}",
|
||||
"Failed to load plugin: {0} from url {1}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0} \u6765\u81ea\u94fe\u63a5 {1}",
|
||||
"Failed to load plugin url: {0}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25 \u94fe\u63a5: {0}",
|
||||
"Failed to initialize plugin: {0}": "\u63d2\u4ef6\u521d\u59cb\u5316\u5931\u8d25: {0}",
|
||||
"example": "\u793a\u4f8b",
|
||||
"Search": "\u641c\u7d22",
|
||||
"All": "\u5168\u90e8",
|
||||
"Currency": "\u8d27\u5e01",
|
||||
"Text": "\u6587\u5b57",
|
||||
"Quotations": "\u5f15\u7528",
|
||||
"Mathematical": "\u6570\u5b66",
|
||||
"Extended Latin": "\u62c9\u4e01\u8bed\u6269\u5145",
|
||||
"Symbols": "\u7b26\u53f7",
|
||||
"Arrows": "\u7bad\u5934",
|
||||
"User Defined": "\u81ea\u5b9a\u4e49",
|
||||
"dollar sign": "\u7f8e\u5143\u7b26\u53f7",
|
||||
"currency sign": "\u8d27\u5e01\u7b26\u53f7",
|
||||
"euro-currency sign": "\u6b27\u5143\u7b26\u53f7",
|
||||
"colon sign": "\u5192\u53f7",
|
||||
"cruzeiro sign": "\u514b\u9c81\u8d5b\u7f57\u5e01\u7b26\u53f7",
|
||||
"french franc sign": "\u6cd5\u90ce\u7b26\u53f7",
|
||||
"lira sign": "\u91cc\u62c9\u7b26\u53f7",
|
||||
"mill sign": "\u5bc6\u5c14\u7b26\u53f7",
|
||||
"naira sign": "\u5948\u62c9\u7b26\u53f7",
|
||||
"peseta sign": "\u6bd4\u585e\u5854\u7b26\u53f7",
|
||||
"rupee sign": "\u5362\u6bd4\u7b26\u53f7",
|
||||
"won sign": "\u97e9\u5143\u7b26\u53f7",
|
||||
"new sheqel sign": "\u65b0\u8c22\u514b\u5c14\u7b26\u53f7",
|
||||
"dong sign": "\u8d8a\u5357\u76fe\u7b26\u53f7",
|
||||
"kip sign": "\u8001\u631d\u57fa\u666e\u7b26\u53f7",
|
||||
"tugrik sign": "\u56fe\u683c\u91cc\u514b\u7b26\u53f7",
|
||||
"drachma sign": "\u5fb7\u62c9\u514b\u9a6c\u7b26\u53f7",
|
||||
"german penny symbol": "\u5fb7\u56fd\u4fbf\u58eb\u7b26\u53f7",
|
||||
"peso sign": "\u6bd4\u7d22\u7b26\u53f7",
|
||||
"guarani sign": "\u74dc\u62c9\u5c3c\u7b26\u53f7",
|
||||
"austral sign": "\u6fb3\u5143\u7b26\u53f7",
|
||||
"hryvnia sign": "\u683c\u91cc\u592b\u5c3c\u4e9a\u7b26\u53f7",
|
||||
"cedi sign": "\u585e\u5730\u7b26\u53f7",
|
||||
"livre tournois sign": "\u91cc\u5f17\u5f17\u5c14\u7b26\u53f7",
|
||||
"spesmilo sign": "spesmilo\u7b26\u53f7",
|
||||
"tenge sign": "\u575a\u6208\u7b26\u53f7",
|
||||
"indian rupee sign": "\u5370\u5ea6\u5362\u6bd4",
|
||||
"turkish lira sign": "\u571f\u8033\u5176\u91cc\u62c9",
|
||||
"nordic mark sign": "\u5317\u6b27\u9a6c\u514b",
|
||||
"manat sign": "\u9a6c\u7eb3\u7279\u7b26\u53f7",
|
||||
"ruble sign": "\u5362\u5e03\u7b26\u53f7",
|
||||
"yen character": "\u65e5\u5143\u5b57\u6837",
|
||||
"yuan character": "\u4eba\u6c11\u5e01\u5143\u5b57\u6837",
|
||||
"yuan character, in hong kong and taiwan": "\u5143\u5b57\u6837\uff08\u6e2f\u53f0\u5730\u533a\uff09",
|
||||
"yen\/yuan character variant one": "\u5143\u5b57\u6837\uff08\u5927\u5199\uff09",
|
||||
"Loading emoticons...": "\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7...",
|
||||
"Could not load emoticons": "\u4e0d\u80fd\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7",
|
||||
"People": "\u4eba\u7c7b",
|
||||
"Animals and Nature": "\u52a8\u7269\u548c\u81ea\u7136",
|
||||
"Food and Drink": "\u98df\u7269\u548c\u996e\u54c1",
|
||||
"Activity": "\u6d3b\u52a8",
|
||||
"Travel and Places": "\u65c5\u6e38\u548c\u5730\u70b9",
|
||||
"Objects": "\u7269\u4ef6",
|
||||
"Flags": "\u65d7\u5e1c",
|
||||
"Characters": "\u5b57\u7b26",
|
||||
"Characters (no spaces)": "\u5b57\u7b26(\u65e0\u7a7a\u683c)",
|
||||
"{0} characters": "{0} \u4e2a\u5b57\u7b26",
|
||||
"Error: Form submit field collision.": "\u9519\u8bef: \u8868\u5355\u63d0\u4ea4\u5b57\u6bb5\u51b2\u7a81\u3002",
|
||||
"Error: No form element found.": "\u9519\u8bef: \u6ca1\u6709\u8868\u5355\u63a7\u4ef6\u3002",
|
||||
"Update": "\u66f4\u65b0",
|
||||
"Color swatch": "\u989c\u8272\u6837\u672c",
|
||||
"Turquoise": "\u9752\u7eff\u8272",
|
||||
"Green": "\u7eff\u8272",
|
||||
"Blue": "\u84dd\u8272",
|
||||
"Purple": "\u7d2b\u8272",
|
||||
"Navy Blue": "\u6d77\u519b\u84dd",
|
||||
"Dark Turquoise": "\u6df1\u84dd\u7eff\u8272",
|
||||
"Dark Green": "\u6df1\u7eff\u8272",
|
||||
"Medium Blue": "\u4e2d\u84dd\u8272",
|
||||
"Medium Purple": "\u4e2d\u7d2b\u8272",
|
||||
"Midnight Blue": "\u6df1\u84dd\u8272",
|
||||
"Yellow": "\u9ec4\u8272",
|
||||
"Orange": "\u6a59\u8272",
|
||||
"Red": "\u7ea2\u8272",
|
||||
"Light Gray": "\u6d45\u7070\u8272",
|
||||
"Gray": "\u7070\u8272",
|
||||
"Dark Yellow": "\u6697\u9ec4\u8272",
|
||||
"Dark Orange": "\u6df1\u6a59\u8272",
|
||||
"Dark Red": "\u6df1\u7ea2\u8272",
|
||||
"Medium Gray": "\u4e2d\u7070\u8272",
|
||||
"Dark Gray": "\u6df1\u7070\u8272",
|
||||
"Light Green": "\u6d45\u7eff\u8272",
|
||||
"Light Yellow": "\u6d45\u9ec4\u8272",
|
||||
"Light Red": "\u6d45\u7ea2\u8272",
|
||||
"Light Purple": "\u6d45\u7d2b\u8272",
|
||||
"Light Blue": "\u6d45\u84dd\u8272",
|
||||
"Dark Purple": "\u6df1\u7d2b\u8272",
|
||||
"Dark Blue": "\u6df1\u84dd\u8272",
|
||||
"Black": "\u9ed1\u8272",
|
||||
"White": "\u767d\u8272",
|
||||
"Switch to or from fullscreen mode": "\u5207\u6362\u5168\u5c4f\u6a21\u5f0f",
|
||||
"Open help dialog": "\u6253\u5f00\u5e2e\u52a9\u5bf9\u8bdd\u6846",
|
||||
"history": "\u5386\u53f2",
|
||||
"styles": "\u6837\u5f0f",
|
||||
"formatting": "\u683c\u5f0f\u5316",
|
||||
"alignment": "\u5bf9\u9f50",
|
||||
"indentation": "\u7f29\u8fdb",
|
||||
"Font": "\u5b57\u4f53",
|
||||
"Size": "\u5b57\u53f7",
|
||||
"More...": "\u66f4\u591a...",
|
||||
"Select...": "\u9009\u62e9...",
|
||||
"Preferences": "\u9996\u9009\u9879",
|
||||
"Yes": "\u662f",
|
||||
"No": "\u5426",
|
||||
"Keyboard Navigation": "\u952e\u76d8\u6307\u5f15",
|
||||
"Version": "\u7248\u672c",
|
||||
"Code view": "\u4ee3\u7801\u89c6\u56fe",
|
||||
"Open popup menu for split buttons": "\u6253\u5f00\u5f39\u51fa\u5f0f\u83dc\u5355\uff0c\u7528\u4e8e\u62c6\u5206\u6309\u94ae",
|
||||
"List Properties": "\u5217\u8868\u5c5e\u6027",
|
||||
"List properties...": "\u6807\u9898\u5b57\u4f53\u5c5e\u6027",
|
||||
"Start list at number": "\u4ee5\u6570\u5b57\u5f00\u59cb\u5217\u8868",
|
||||
"Line height": "\u884c\u9ad8",
|
||||
"comments": "\u5907\u6ce8",
|
||||
"Format Painter": "\u683c\u5f0f\u5237",
|
||||
"Insert\/edit iframe": "\u63d2\u5165\/\u7f16\u8f91\u6846\u67b6",
|
||||
"Capitalization": "\u5927\u5199",
|
||||
"lowercase": "\u5c0f\u5199",
|
||||
"UPPERCASE": "\u5927\u5199",
|
||||
"Title Case": "\u9996\u5b57\u6bcd\u5927\u5199",
|
||||
"permanent pen": "\u8bb0\u53f7\u7b14",
|
||||
"Permanent Pen Properties": "\u6c38\u4e45\u7b14\u5c5e\u6027",
|
||||
"Permanent pen properties...": "\u6c38\u4e45\u7b14\u5c5e\u6027...",
|
||||
"case change": "\u6848\u4f8b\u66f4\u6539",
|
||||
"page embed": "\u9875\u9762\u5d4c\u5165",
|
||||
"Advanced sort...": "\u9ad8\u7ea7\u6392\u5e8f...",
|
||||
"Advanced Sort": "\u9ad8\u7ea7\u6392\u5e8f",
|
||||
"Sort table by column ascending": "\u6309\u5217\u5347\u5e8f\u8868",
|
||||
"Sort table by column descending": "\u6309\u5217\u964d\u5e8f\u8868",
|
||||
"Sort": "\u6392\u5e8f",
|
||||
"Order": "\u6392\u5e8f",
|
||||
"Sort by": "\u6392\u5e8f\u65b9\u5f0f",
|
||||
"Ascending": "\u5347\u5e8f",
|
||||
"Descending": "\u964d\u5e8f",
|
||||
"Column {0}": "\u5217{0}",
|
||||
"Row {0}": "\u884c{0}",
|
||||
"Spellcheck...": "\u62fc\u5199\u68c0\u67e5...",
|
||||
"Misspelled word": "\u62fc\u5199\u9519\u8bef\u7684\u5355\u8bcd",
|
||||
"Suggestions": "\u5efa\u8bae",
|
||||
"Change": "\u66f4\u6539",
|
||||
"Finding word suggestions": "\u67e5\u627e\u5355\u8bcd\u5efa\u8bae",
|
||||
"Success": "\u6210\u529f",
|
||||
"Repair": "\u4fee\u590d",
|
||||
"Issue {0} of {1}": "\u5171\u8ba1{1}\u95ee\u9898{0}",
|
||||
"Images must be marked as decorative or have an alternative text description": "\u56fe\u50cf\u5fc5\u987b\u6807\u8bb0\u4e3a\u88c5\u9970\u6027\u6216\u5177\u6709\u66ff\u4ee3\u6587\u672c\u63cf\u8ff0",
|
||||
"Images must have an alternative text description. Decorative images are not allowed.": "\u56fe\u50cf\u5fc5\u987b\u5177\u6709\u66ff\u4ee3\u6587\u672c\u63cf\u8ff0\u3002\u4e0d\u5141\u8bb8\u4f7f\u7528\u88c5\u9970\u56fe\u50cf\u3002",
|
||||
"Or provide alternative text:": "\u6216\u63d0\u4f9b\u5907\u9009\u6587\u672c\uff1a",
|
||||
"Make image decorative:": "\u4f7f\u56fe\u50cf\u88c5\u9970\uff1a",
|
||||
"ID attribute must be unique": "ID \u5c5e\u6027\u5fc5\u987b\u662f\u552f\u4e00\u7684",
|
||||
"Make ID unique": "\u4f7f ID \u72ec\u4e00\u65e0\u4e8c",
|
||||
"Keep this ID and remove all others": "\u4fdd\u7559\u6b64 ID \u5e76\u5220\u9664\u6240\u6709\u5176\u4ed6",
|
||||
"Remove this ID": "\u5220\u9664\u6b64 ID",
|
||||
"Remove all IDs": "\u6e05\u9664\u5168\u90e8IDs",
|
||||
"Checklist": "\u6e05\u5355",
|
||||
"Anchor": "\u951a\u70b9",
|
||||
"Special character": "\u7279\u6b8a\u7b26\u53f7",
|
||||
"Code sample": "\u4ee3\u7801\u793a\u4f8b",
|
||||
"Color": "\u989c\u8272",
|
||||
"Document properties": "\u6587\u6863\u5c5e\u6027",
|
||||
"Image description": "\u56fe\u7247\u63cf\u8ff0",
|
||||
"Image": "\u56fe\u7247",
|
||||
"Insert link": "\u63d2\u5165\u94fe\u63a5",
|
||||
"Target": "\u6253\u5f00\u65b9\u5f0f",
|
||||
"Link": "\u94fe\u63a5",
|
||||
"Poster": "\u5c01\u9762",
|
||||
"Media": "\u5a92\u4f53",
|
||||
"Print": "\u6253\u5370",
|
||||
"Prev": "\u4e0a\u4e00\u4e2a",
|
||||
"Find and replace": "\u67e5\u627e\u548c\u66ff\u6362",
|
||||
"Whole words": "\u5168\u5b57\u5339\u914d",
|
||||
"Insert template": "\u63d2\u5165\u6a21\u677f"
|
||||
});
|
||||
|
|
@ -0,0 +1,419 @@
|
|||
tinymce.addI18n('zh_TW',{
|
||||
"Redo": "\u91cd\u505a",
|
||||
"Undo": "\u64a4\u92b7",
|
||||
"Cut": "\u526a\u4e0b",
|
||||
"Copy": "\u8907\u88fd",
|
||||
"Paste": "\u8cbc\u4e0a",
|
||||
"Select all": "\u5168\u9078",
|
||||
"New document": "\u65b0\u6587\u4ef6",
|
||||
"Ok": "\u78ba\u5b9a",
|
||||
"Cancel": "\u53d6\u6d88",
|
||||
"Visual aids": "\u5c0f\u5e6b\u624b",
|
||||
"Bold": "\u7c97\u9ad4",
|
||||
"Italic": "\u659c\u9ad4",
|
||||
"Underline": "\u4e0b\u5283\u7dda",
|
||||
"Strikethrough": "\u522a\u9664\u7dda",
|
||||
"Superscript": "\u4e0a\u6a19",
|
||||
"Subscript": "\u4e0b\u6a19",
|
||||
"Clear formatting": "\u6e05\u9664\u683c\u5f0f",
|
||||
"Align left": "\u5de6\u908a\u5c0d\u9f4a",
|
||||
"Align center": "\u4e2d\u9593\u5c0d\u9f4a",
|
||||
"Align right": "\u53f3\u908a\u5c0d\u9f4a",
|
||||
"Justify": "\u5de6\u53f3\u5c0d\u9f4a",
|
||||
"Bullet list": "\u9805\u76ee\u6e05\u55ae",
|
||||
"Numbered list": "\u6578\u5b57\u6e05\u55ae",
|
||||
"Decrease indent": "\u6e1b\u5c11\u7e2e\u6392",
|
||||
"Increase indent": "\u589e\u52a0\u7e2e\u6392",
|
||||
"Close": "\u95dc\u9589",
|
||||
"Formats": "\u683c\u5f0f",
|
||||
"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u60a8\u7684\u700f\u89bd\u5668\u4e0d\u652f\u63f4\u5b58\u53d6\u526a\u8cbc\u7c3f\uff0c\u53ef\u4ee5\u4f7f\u7528\u5feb\u901f\u9375 Ctrl + X\/C\/V \u4ee3\u66ff\u526a\u4e0b\u3001\u8907\u88fd\u8207\u8cbc\u4e0a\u3002",
|
||||
"Headers": "\u6a19\u984c",
|
||||
"Header 1": "\u6a19\u984c 1",
|
||||
"Header 2": "\u6a19\u984c 2",
|
||||
"Header 3": "\u6a19\u984c 3",
|
||||
"Header 4": "\u6a19\u984c 4",
|
||||
"Header 5": "\u6a19\u984c 5",
|
||||
"Header 6": "\u6a19\u984c 6",
|
||||
"Headings": "\u6a19\u984c",
|
||||
"Heading 1": "\u6a19\u984c1",
|
||||
"Heading 2": "\u6a19\u984c2",
|
||||
"Heading 3": "\u6a19\u984c3",
|
||||
"Heading 4": "\u6a19\u984c4",
|
||||
"Heading 5": "\u6a19\u984c5",
|
||||
"Heading 6": "\u6a19\u984c6",
|
||||
"Preformatted": "\u9810\u5148\u683c\u5f0f\u5316\u7684",
|
||||
"Div": "Div",
|
||||
"Pre": "Pre",
|
||||
"Code": "\u4ee3\u78bc",
|
||||
"Paragraph": "\u6bb5\u843d",
|
||||
"Blockquote": "\u5f15\u6587\u5340\u584a",
|
||||
"Inline": "\u5167\u806f",
|
||||
"Blocks": "\u57fa\u584a",
|
||||
"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u76ee\u524d\u5c07\u4ee5\u7d14\u6587\u5b57\u7684\u6a21\u5f0f\u8cbc\u4e0a\uff0c\u60a8\u53ef\u4ee5\u518d\u9ede\u9078\u4e00\u6b21\u53d6\u6d88\u3002",
|
||||
"Fonts": "\u5b57\u578b",
|
||||
"Font Sizes": "\u5b57\u578b\u5927\u5c0f",
|
||||
"Class": "\u985e\u578b",
|
||||
"Browse for an image": "\u5f9e\u5716\u7247\u4e2d\u700f\u89bd",
|
||||
"OR": "\u6216",
|
||||
"Drop an image here": "\u62d6\u66f3\u5716\u7247\u81f3\u6b64",
|
||||
"Upload": "\u4e0a\u50b3",
|
||||
"Block": "\u5340\u584a",
|
||||
"Align": "\u5c0d\u9f4a",
|
||||
"Default": "\u9810\u8a2d",
|
||||
"Circle": "\u7a7a\u5fc3\u5713",
|
||||
"Disc": "\u5be6\u5fc3\u5713",
|
||||
"Square": "\u6b63\u65b9\u5f62",
|
||||
"Lower Alpha": "\u5c0f\u5beb\u82f1\u6587\u5b57\u6bcd",
|
||||
"Lower Greek": "\u5e0c\u81d8\u5b57\u6bcd",
|
||||
"Lower Roman": "\u5c0f\u5beb\u7f85\u99ac\u6578\u5b57",
|
||||
"Upper Alpha": "\u5927\u5beb\u82f1\u6587\u5b57\u6bcd",
|
||||
"Upper Roman": "\u5927\u5beb\u7f85\u99ac\u6578\u5b57",
|
||||
"Anchor...": "\u9328\u9ede...",
|
||||
"Name": "\u540d\u7a31",
|
||||
"Id": "Id",
|
||||
"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id\u61c9\u4ee5\u5b57\u6bcd\u958b\u982d\uff0c\u5f8c\u9762\u63a5\u8457\u5b57\u6bcd\uff0c\u6578\u5b57\uff0c\u7834\u6298\u865f\uff0c\u9ede\u6578\uff0c\u5192\u865f\u6216\u4e0b\u5283\u7dda\u3002",
|
||||
"You have unsaved changes are you sure you want to navigate away?": "\u7de8\u8f2f\u5c1a\u672a\u88ab\u5132\u5b58\uff0c\u4f60\u78ba\u5b9a\u8981\u96e2\u958b\uff1f",
|
||||
"Restore last draft": "\u8f09\u5165\u4e0a\u4e00\u6b21\u7de8\u8f2f\u7684\u8349\u7a3f",
|
||||
"Special character...": "\u7279\u6b8a\u5b57\u5143......",
|
||||
"Source code": "\u539f\u59cb\u78bc",
|
||||
"Insert\/Edit code sample": "\u63d2\u5165\/\u7de8\u8f2f \u7a0b\u5f0f\u78bc\u7bc4\u4f8b",
|
||||
"Language": "\u8a9e\u8a00",
|
||||
"Code sample...": "\u7a0b\u5f0f\u78bc\u7bc4\u4f8b...",
|
||||
"Color Picker": "\u9078\u8272\u5668",
|
||||
"R": "\u7d05",
|
||||
"G": "\u7da0",
|
||||
"B": "\u85cd",
|
||||
"Left to right": "\u5f9e\u5de6\u5230\u53f3",
|
||||
"Right to left": "\u5f9e\u53f3\u5230\u5de6",
|
||||
"Emoticons...": "\u8868\u60c5\u7b26\u865f\u2026",
|
||||
"Metadata and Document Properties": "\u5f8c\u8a2d\u8cc7\u6599\u8207\u6587\u4ef6\u5c6c\u6027",
|
||||
"Title": "\u6a19\u984c",
|
||||
"Keywords": "\u95dc\u9375\u5b57",
|
||||
"Description": "\u63cf\u8ff0",
|
||||
"Robots": "\u6a5f\u5668\u4eba",
|
||||
"Author": "\u4f5c\u8005",
|
||||
"Encoding": "\u7de8\u78bc",
|
||||
"Fullscreen": "\u5168\u87a2\u5e55",
|
||||
"Action": "\u52d5\u4f5c",
|
||||
"Shortcut": "\u5feb\u901f\u9375",
|
||||
"Help": "\u5e6b\u52a9",
|
||||
"Address": "\u5730\u5740",
|
||||
"Focus to menubar": "\u8df3\u81f3\u9078\u55ae\u5217",
|
||||
"Focus to toolbar": "\u8df3\u81f3\u5de5\u5177\u5217",
|
||||
"Focus to element path": "\u8df3\u81f3HTML\u5143\u7d20\u5217",
|
||||
"Focus to contextual toolbar": "\u8df3\u81f3\u5feb\u6377\u9078\u55ae",
|
||||
"Insert link (if link plugin activated)": "\u65b0\u589e\u6377\u5f91 (\u6377\u5f91\u5916\u639b\u555f\u7528\u6642)",
|
||||
"Save (if save plugin activated)": "\u5132\u5b58 (\u5132\u5b58\u5916\u639b\u555f\u7528\u6642)",
|
||||
"Find (if searchreplace plugin activated)": "\u5c0b\u627e (\u5c0b\u627e\u53d6\u4ee3\u5916\u639b\u555f\u7528\u6642)",
|
||||
"Plugins installed ({0}):": "({0}) \u500b\u5916\u639b\u5df2\u5b89\u88dd\uff1a",
|
||||
"Premium plugins:": "\u52a0\u503c\u5916\u639b\uff1a",
|
||||
"Learn more...": "\u4e86\u89e3\u66f4\u591a...",
|
||||
"You are using {0}": "\u60a8\u6b63\u5728\u4f7f\u7528 {0}",
|
||||
"Plugins": "\u5916\u639b",
|
||||
"Handy Shortcuts": "\u5feb\u901f\u9375",
|
||||
"Horizontal line": "\u6c34\u5e73\u7dda",
|
||||
"Insert\/edit image": "\u63d2\u5165\/\u7de8\u8f2f \u5716\u7247",
|
||||
"Image description": "\u5716\u7247\u63cf\u8ff0",
|
||||
"Source": "\u5716\u7247\u7db2\u5740",
|
||||
"Dimensions": "\u5c3a\u5bf8",
|
||||
"Constrain proportions": "\u7b49\u6bd4\u4f8b\u7e2e\u653e",
|
||||
"General": "\u4e00\u822c",
|
||||
"Advanced": "\u9032\u968e",
|
||||
"Style": "\u6a23\u5f0f",
|
||||
"Vertical space": "\u9ad8\u5ea6",
|
||||
"Horizontal space": "\u5bec\u5ea6",
|
||||
"Border": "\u908a\u6846",
|
||||
"Insert image": "\u63d2\u5165\u5716\u7247",
|
||||
"Image...": "\u5716\u7247......",
|
||||
"Image list": "\u5716\u7247\u6e05\u55ae",
|
||||
"Rotate counterclockwise": "\u9006\u6642\u91dd\u65cb\u8f49",
|
||||
"Rotate clockwise": "\u9806\u6642\u91dd\u65cb\u8f49",
|
||||
"Flip vertically": "\u5782\u76f4\u7ffb\u8f49",
|
||||
"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f49",
|
||||
"Edit image": "\u7de8\u8f2f\u5716\u7247",
|
||||
"Image options": "\u5716\u7247\u9078\u9805",
|
||||
"Zoom in": "\u653e\u5927",
|
||||
"Zoom out": "\u7e2e\u5c0f",
|
||||
"Crop": "\u88c1\u526a",
|
||||
"Resize": "\u8abf\u6574\u5927\u5c0f",
|
||||
"Orientation": "\u65b9\u5411",
|
||||
"Brightness": "\u4eae\u5ea6",
|
||||
"Sharpen": "\u92b3\u5316",
|
||||
"Contrast": "\u5c0d\u6bd4",
|
||||
"Color levels": "\u984f\u8272\u5c64\u6b21",
|
||||
"Gamma": "\u4f3d\u99ac\u503c",
|
||||
"Invert": "\u53cd\u8f49",
|
||||
"Apply": "\u61c9\u7528",
|
||||
"Back": "\u5f8c\u9000",
|
||||
"Insert date\/time": "\u63d2\u5165 \u65e5\u671f\/\u6642\u9593",
|
||||
"Date\/time": "\u65e5\u671f\/\u6642\u9593",
|
||||
"Insert\/Edit Link": "\u63d2\u5165\/\u7de8\u8f2f\u9023\u7d50",
|
||||
"Insert\/edit link": "\u63d2\u5165\/\u7de8\u8f2f\u9023\u7d50",
|
||||
"Text to display": "\u986f\u793a\u6587\u5b57",
|
||||
"Url": "\u7db2\u5740",
|
||||
"Open link in...": "\u958b\u555f\u9023\u7d50\u65bc...",
|
||||
"Current window": "\u76ee\u524d\u8996\u7a97",
|
||||
"None": "\u7121",
|
||||
"New window": "\u53e6\u958b\u8996\u7a97",
|
||||
"Remove link": "\u79fb\u9664\u9023\u7d50",
|
||||
"Anchors": "\u52a0\u5165\u9328\u9ede",
|
||||
"Link...": "\u9023\u7d50...",
|
||||
"Paste or type a link": "\u8cbc\u4e0a\u6216\u8f38\u5165\u9023\u7d50",
|
||||
"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5beb\u7684URL\u70ba\u96fb\u5b50\u90f5\u4ef6\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7db4\u55ce\uff1f",
|
||||
"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5beb\u7684URL\u5c6c\u65bc\u5916\u90e8\u93c8\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7db4\u55ce\uff1f",
|
||||
"Link list": "\u9023\u7d50\u6e05\u55ae",
|
||||
"Insert video": "\u63d2\u5165\u5f71\u97f3",
|
||||
"Insert\/edit video": "\u63d2\u4ef6\/\u7de8\u8f2f \u5f71\u97f3",
|
||||
"Insert\/edit media": "\u63d2\u5165\/\u7de8\u8f2f \u5a92\u9ad4",
|
||||
"Alternative source": "\u66ff\u4ee3\u5f71\u97f3",
|
||||
"Alternative source URL": "\u66ff\u4ee3\u4f86\u6e90URL",
|
||||
"Media poster (Image URL)": "\u5a92\u9ad4\u6d77\u5831\uff08\u5f71\u50cfImage URL\uff09",
|
||||
"Paste your embed code below:": "\u8acb\u5c07\u60a8\u7684\u5d4c\u5165\u5f0f\u7a0b\u5f0f\u78bc\u8cbc\u5728\u4e0b\u9762:",
|
||||
"Embed": "\u5d4c\u5165\u78bc",
|
||||
"Media...": "\u5a92\u9ad4...",
|
||||
"Nonbreaking space": "\u4e0d\u5206\u884c\u7684\u7a7a\u683c",
|
||||
"Page break": "\u5206\u9801",
|
||||
"Paste as text": "\u4ee5\u7d14\u6587\u5b57\u8cbc\u4e0a",
|
||||
"Preview": "\u9810\u89bd",
|
||||
"Print...": "\u5217\u5370...",
|
||||
"Save": "\u5132\u5b58",
|
||||
"Find": "\u641c\u5c0b",
|
||||
"Replace with": "\u66f4\u63db",
|
||||
"Replace": "\u66ff\u63db",
|
||||
"Replace all": "\u66ff\u63db\u5168\u90e8",
|
||||
"Previous": "\u4e0a\u4e00\u500b",
|
||||
"Next": "\u4e0b\u4e00\u500b",
|
||||
"Find and replace...": "\u5c0b\u627e\u53ca\u53d6\u4ee3...",
|
||||
"Could not find the specified string.": "\u7121\u6cd5\u67e5\u8a62\u5230\u6b64\u7279\u5b9a\u5b57\u4e32",
|
||||
"Match case": "\u76f8\u5339\u914d\u6848\u4ef6",
|
||||
"Find whole words only": "\u50c5\u627e\u51fa\u5b8c\u6574\u5b57\u532f",
|
||||
"Spell check": "\u62fc\u5beb\u6aa2\u67e5",
|
||||
"Ignore": "\u5ffd\u7565",
|
||||
"Ignore all": "\u5ffd\u7565\u6240\u6709",
|
||||
"Finish": "\u5b8c\u6210",
|
||||
"Add to Dictionary": "\u52a0\u5165\u5b57\u5178\u4e2d",
|
||||
"Insert table": "\u63d2\u5165\u8868\u683c",
|
||||
"Table properties": "\u8868\u683c\u5c6c\u6027",
|
||||
"Delete table": "\u522a\u9664\u8868\u683c",
|
||||
"Cell": "\u5132\u5b58\u683c",
|
||||
"Row": "\u5217",
|
||||
"Column": "\u884c",
|
||||
"Cell properties": "\u5132\u5b58\u683c\u5c6c\u6027",
|
||||
"Merge cells": "\u5408\u4f75\u5132\u5b58\u683c",
|
||||
"Split cell": "\u5206\u5272\u5132\u5b58\u683c",
|
||||
"Insert row before": "\u63d2\u5165\u5217\u5728...\u4e4b\u524d",
|
||||
"Insert row after": "\u63d2\u5165\u5217\u5728...\u4e4b\u5f8c",
|
||||
"Delete row": "\u522a\u9664\u5217",
|
||||
"Row properties": "\u5217\u5c6c\u6027",
|
||||
"Cut row": "\u526a\u4e0b\u5217",
|
||||
"Copy row": "\u8907\u88fd\u5217",
|
||||
"Paste row before": "\u8cbc\u4e0a\u5217\u5728...\u4e4b\u524d",
|
||||
"Paste row after": "\u8cbc\u4e0a\u5217\u5728...\u4e4b\u5f8c",
|
||||
"Insert column before": "\u63d2\u5165\u6b04\u4f4d\u5728...\u4e4b\u524d",
|
||||
"Insert column after": "\u63d2\u5165\u6b04\u4f4d\u5728...\u4e4b\u5f8c",
|
||||
"Delete column": "\u522a\u9664\u884c",
|
||||
"Cols": "\u6b04\u4f4d\u6bb5",
|
||||
"Rows": "\u5217",
|
||||
"Width": "\u5bec\u5ea6",
|
||||
"Height": "\u9ad8\u5ea6",
|
||||
"Cell spacing": "\u5132\u5b58\u683c\u5f97\u9593\u8ddd",
|
||||
"Cell padding": "\u5132\u5b58\u683c\u7684\u908a\u8ddd",
|
||||
"Show caption": "\u986f\u793a\u6a19\u984c",
|
||||
"Left": "\u5de6\u908a",
|
||||
"Center": "\u4e2d\u9593",
|
||||
"Right": "\u53f3\u908a",
|
||||
"Cell type": "\u5132\u5b58\u683c\u7684\u985e\u578b",
|
||||
"Scope": "\u7bc4\u570d",
|
||||
"Alignment": "\u5c0d\u9f4a",
|
||||
"H Align": "\u6c34\u5e73\u4f4d\u7f6e",
|
||||
"V Align": "\u5782\u76f4\u4f4d\u7f6e",
|
||||
"Top": "\u7f6e\u9802",
|
||||
"Middle": "\u7f6e\u4e2d",
|
||||
"Bottom": "\u7f6e\u5e95",
|
||||
"Header cell": "\u6a19\u982d\u5132\u5b58\u683c",
|
||||
"Row group": "\u5217\u7fa4\u7d44",
|
||||
"Column group": "\u6b04\u4f4d\u7fa4\u7d44",
|
||||
"Row type": "\u884c\u7684\u985e\u578b",
|
||||
"Header": "\u6a19\u982d",
|
||||
"Body": "\u4e3b\u9ad4",
|
||||
"Footer": "\u9801\u5c3e",
|
||||
"Border color": "\u908a\u6846\u984f\u8272",
|
||||
"Insert template...": "\u63d2\u5165\u6a23\u7248...",
|
||||
"Templates": "\u6a23\u7248",
|
||||
"Template": "\u6a23\u677f",
|
||||
"Text color": "\u6587\u5b57\u984f\u8272",
|
||||
"Background color": "\u80cc\u666f\u984f\u8272",
|
||||
"Custom...": "\u81ea\u8a02",
|
||||
"Custom color": "\u81ea\u8a02\u984f\u8272",
|
||||
"No color": "No color",
|
||||
"Remove color": "\u79fb\u9664\u984f\u8272",
|
||||
"Table of Contents": "\u76ee\u9304",
|
||||
"Show blocks": "\u986f\u793a\u5340\u584a\u8cc7\u8a0a",
|
||||
"Show invisible characters": "\u986f\u793a\u96b1\u85cf\u5b57\u5143",
|
||||
"Word count": "\u8a08\u7b97\u5b57\u6578",
|
||||
"Count": "\u8a08\u7b97",
|
||||
"Document": "\u6587\u4ef6",
|
||||
"Selection": "\u9078\u9805",
|
||||
"Words": "\u5b57\u6578",
|
||||
"Words: {0}": "\u5b57\u6578\uff1a{0}",
|
||||
"{0} words": "{0} \u5b57\u5143",
|
||||
"File": "\u6a94\u6848",
|
||||
"Edit": "\u7de8\u8f2f",
|
||||
"Insert": "\u63d2\u5165",
|
||||
"View": "\u6aa2\u8996",
|
||||
"Format": "\u683c\u5f0f",
|
||||
"Table": "\u8868\u683c",
|
||||
"Tools": "\u5de5\u5177",
|
||||
"Powered by {0}": "\u7531 {0} \u63d0\u4f9b",
|
||||
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u8c50\u5bcc\u7684\u6587\u672c\u5340\u57df\u3002\u6309ALT-F9\u524d\u5f80\u4e3b\u9078\u55ae\u3002\u6309ALT-F10\u547c\u53eb\u5de5\u5177\u6b04\u3002\u6309ALT-0\u5c0b\u6c42\u5e6b\u52a9",
|
||||
"Image title": "\u5716\u7247\u6a19\u984c",
|
||||
"Border width": "\u6846\u7dda\u5bec\u5ea6",
|
||||
"Border style": "\u6846\u7dda\u6a23\u5f0f",
|
||||
"Error": "\u932f\u8aa4",
|
||||
"Warn": "\u8b66\u544a",
|
||||
"Valid": "\u6709\u6548",
|
||||
"To open the popup, press Shift+Enter": "\u8981\u958b\u555f\u5f48\u51fa\u8996\u7a97\uff0c\u8acb\u6309Shift+Enter",
|
||||
"Rich Text Area. Press ALT-0 for help.": "\u5bcc\u6587\u672c\u5340\u57df\u3002\u8acb\u6309ALT-0\u5c0b\u6c42\u5354\u52a9\u3002",
|
||||
"System Font": "\u7cfb\u7d71\u5b57\u578b",
|
||||
"Failed to upload image: {0}": "\u7121\u6cd5\u4e0a\u50b3\u5f71\u50cf\uff1a{0}",
|
||||
"Failed to load plugin: {0} from url {1}": "\u7121\u6cd5\u4e0a\u50b3\u63d2\u4ef6\uff1a{0}\u81eaurl{1}",
|
||||
"Failed to load plugin url: {0}": "\u7121\u6cd5\u4e0a\u50b3\u63d2\u4ef6\uff1a{0}",
|
||||
"Failed to initialize plugin: {0}": "\u7121\u6cd5\u555f\u52d5\u63d2\u4ef6\uff1a{0}",
|
||||
"example": "\u7bc4\u4f8b",
|
||||
"Search": "\u641c\u7d22",
|
||||
"All": "\u5168\u90e8",
|
||||
"Currency": "\u8ca8\u5e63",
|
||||
"Text": "\u6587\u672c",
|
||||
"Quotations": "\u5f15\u7528",
|
||||
"Mathematical": "\u6578\u5b78",
|
||||
"Extended Latin": "\u62c9\u4e01\u5b57\u6bcd\u64f4\u5145",
|
||||
"Symbols": "\u7b26\u865f",
|
||||
"Arrows": "\u7bad\u982d",
|
||||
"User Defined": "\u4f7f\u7528\u8005\u5df2\u5b9a\u7fa9",
|
||||
"dollar sign": "\u7f8e\u5143\u7b26\u865f",
|
||||
"currency sign": "\u8ca8\u5e63\u7b26\u865f",
|
||||
"euro-currency sign": "\u6b50\u5143\u7b26\u865f",
|
||||
"colon sign": "\u79d1\u6717\u7b26\u865f",
|
||||
"cruzeiro sign": "\u514b\u9b6f\u8cfd\u7f85\u7b26\u865f",
|
||||
"french franc sign": "\u6cd5\u6717\u7b26\u865f",
|
||||
"lira sign": "\u91cc\u62c9\u7b26\u865f",
|
||||
"mill sign": "\u6587\u7b26\u865f",
|
||||
"naira sign": "\u5948\u62c9\u7b26\u865f",
|
||||
"peseta sign": "\u6bd4\u585e\u5854\u7b26\u865f",
|
||||
"rupee sign": "\u76e7\u6bd4\u7b26\u865f",
|
||||
"won sign": "\u97d3\u571c\u7b26\u865f",
|
||||
"new sheqel sign": "\u65b0\u8b1d\u514b\u723e\u7b26\u865f",
|
||||
"dong sign": "\u8d8a\u5357\u76fe\u7b26\u865f",
|
||||
"kip sign": "\u8001\u64be\u5e63\u7b26\u865f",
|
||||
"tugrik sign": "\u8499\u53e4\u5e63\u7b26\u865f",
|
||||
"drachma sign": "\u5fb7\u514b\u62c9\u99ac\u7b26\u865f",
|
||||
"german penny symbol": "\u5fb7\u570b\u5206\u7b26\u865f",
|
||||
"peso sign": "\u62ab\u7d22\u7b26\u865f",
|
||||
"guarani sign": "\u5df4\u62c9\u572d\u5e63\u7b26\u865f",
|
||||
"austral sign": "\u963f\u6839\u5ef7\u5e63\u7b26\u865f",
|
||||
"hryvnia sign": "\u70cf\u514b\u862d\u5e63\u7b26\u865f",
|
||||
"cedi sign": "\u8fe6\u7d0d\u5e63\u7b26\u865f",
|
||||
"livre tournois sign": "\u91cc\u5f17\u723e\u7b26\u865f",
|
||||
"spesmilo sign": "\u570b\u969b\u5e63\u7b26\u865f",
|
||||
"tenge sign": "\u54c8\u85a9\u514b\u5e63\u7b26\u865f",
|
||||
"indian rupee sign": "\u5370\u5ea6\u76e7\u6bd4\u7b26\u865f",
|
||||
"turkish lira sign": "\u571f\u8033\u5176\u91cc\u62c9\u7b26\u865f",
|
||||
"nordic mark sign": "\u5317\u6b50\u99ac\u514b\u7b26\u865f",
|
||||
"manat sign": "\u4e9e\u585e\u62dc\u7136\u5e63\u7b26\u865f",
|
||||
"ruble sign": "\u76e7\u5e03\u7b26\u865f",
|
||||
"yen character": "\u65e5\u5713\u7b26\u865f",
|
||||
"yuan character": "\u4eba\u6c11\u5e63\u7b26\u865f",
|
||||
"yuan character, in hong kong and taiwan": "\u6e2f\u5143\u8207\u53f0\u5e63\u7b26\u865f",
|
||||
"yen\/yuan character variant one": "\u65e5\u5713\/\u4eba\u6c11\u5e63\u7b26\u865f\u8b8a\u5316\u578b",
|
||||
"Loading emoticons...": "\u8f09\u5165\u8868\u60c5\u7b26\u865f\u2026",
|
||||
"Could not load emoticons": "\u7121\u6cd5\u8f09\u5165\u8868\u60c5\u7b26\u865f",
|
||||
"People": "\u4eba",
|
||||
"Animals and Nature": "\u52d5\u7269\u8207\u81ea\u7136",
|
||||
"Food and Drink": "\u98f2\u98df",
|
||||
"Activity": "\u6d3b\u52d5",
|
||||
"Travel and Places": "\u65c5\u884c\u8207\u5730\u9ede",
|
||||
"Objects": "\u7269\u4ef6",
|
||||
"Flags": "\u65d7\u6a19",
|
||||
"Characters": "\u5b57\u5143",
|
||||
"Characters (no spaces)": "\u5b57\u5143\uff08\u7121\u7a7a\u683c\uff09",
|
||||
"{0} characters": "{0}\u5b57\u5143",
|
||||
"Error: Form submit field collision.": "\u932f\u8aa4\uff1a\u8868\u683c\u905e\u4ea4\u6b04\u4f4d\u885d\u7a81\u3002",
|
||||
"Error: No form element found.": "\u932f\u8aa4\uff1a\u627e\u4e0d\u5230\u8868\u683c\u5143\u7d20\u3002",
|
||||
"Update": "\u66f4\u65b0",
|
||||
"Color swatch": "\u8272\u5f69\u6a23\u672c",
|
||||
"Turquoise": "\u571f\u8033\u5176\u85cd",
|
||||
"Green": "\u7da0\u8272",
|
||||
"Blue": "\u85cd\u8272",
|
||||
"Purple": "\u7d2b\u8272",
|
||||
"Navy Blue": "\u6df1\u85cd\u8272",
|
||||
"Dark Turquoise": "\u6df1\u571f\u8033\u5176\u85cd",
|
||||
"Dark Green": "\u6df1\u7da0\u8272",
|
||||
"Medium Blue": "\u4e2d\u85cd\u8272",
|
||||
"Medium Purple": "\u4e2d\u7d2b\u8272",
|
||||
"Midnight Blue": "\u9ed1\u85cd\u8272",
|
||||
"Yellow": "\u9ec3\u8272",
|
||||
"Orange": "\u6a59\u8272",
|
||||
"Red": "\u7d05\u8272",
|
||||
"Light Gray": "\u6dfa\u7070\u8272",
|
||||
"Gray": "\u7070\u8272",
|
||||
"Dark Yellow": "\u6df1\u9ec3\u8272",
|
||||
"Dark Orange": "\u6df1\u6a59\u8272",
|
||||
"Dark Red": "\u6697\u7d05\u8272",
|
||||
"Medium Gray": "\u4e2d\u7070\u8272",
|
||||
"Dark Gray": "\u6df1\u7070\u8272",
|
||||
"Light Green": "\u6de1\u7da0\u8272",
|
||||
"Light Yellow": "\u6dfa\u9ec3\u8272",
|
||||
"Light Red": "\u6dfa\u7d05\u8272",
|
||||
"Light Purple": "\u6dfa\u7d2b\u8272",
|
||||
"Light Blue": "\u6dfa\u85cd\u8272",
|
||||
"Dark Purple": "\u6df1\u7d2b\u8272",
|
||||
"Dark Blue": "\u6df1\u85cd\u8272",
|
||||
"Black": "\u9ed1\u8272",
|
||||
"White": "\u767d\u8272",
|
||||
"Switch to or from fullscreen mode": "\u8f49\u63db\u81ea\/\u81f3\u5168\u87a2\u5e55\u6a21\u5f0f",
|
||||
"Open help dialog": "\u958b\u555f\u5354\u52a9\u5c0d\u8a71",
|
||||
"history": "\u6b77\u53f2",
|
||||
"styles": "\u6a23\u5f0f",
|
||||
"formatting": "\u683c\u5f0f",
|
||||
"alignment": "\u5c0d\u9f4a",
|
||||
"indentation": "\u7e2e\u6392",
|
||||
"permanent pen": "\u6c38\u4e45\u6027\u7b46",
|
||||
"comments": "\u8a3b\u89e3",
|
||||
"Format Painter": "\u8907\u88fd\u683c\u5f0f",
|
||||
"Insert\/edit iframe": "\u63d2\u5165\/\u7de8\u8f2fiframe",
|
||||
"Capitalization": "\u5927\u5beb",
|
||||
"lowercase": "\u5c0f\u5beb",
|
||||
"UPPERCASE": "\u5927\u5beb",
|
||||
"Title Case": "\u5b57\u9996\u5927\u5beb",
|
||||
"Permanent Pen Properties": "\u6c38\u4e45\u6a19\u8a18\u5c6c\u6027",
|
||||
"Permanent pen properties...": "\u6c38\u4e45\u6a19\u8a18\u5c6c\u6027......",
|
||||
"Font": "\u5b57\u578b",
|
||||
"Size": "\u5b57\u5f62\u5927\u5c0f",
|
||||
"More...": "\u66f4\u591a\u8cc7\u8a0a......",
|
||||
"Spellcheck Language": "\u62fc\u5beb\u8a9e\u8a00",
|
||||
"Select...": "\u9078\u64c7......",
|
||||
"Preferences": "\u9996\u9078\u9805",
|
||||
"Yes": "\u662f",
|
||||
"No": "\u5426",
|
||||
"Keyboard Navigation": "\u9375\u76e4\u5c0e\u822a",
|
||||
"Version": "\u7248\u672c",
|
||||
"Anchor": "\u52a0\u5165\u9328\u9ede",
|
||||
"Special character": "\u7279\u6b8a\u5b57\u5143",
|
||||
"Code sample": "\u7a0b\u5f0f\u78bc\u7bc4\u4f8b",
|
||||
"Color": "\u984f\u8272",
|
||||
"Emoticons": "\u8868\u60c5",
|
||||
"Document properties": "\u6587\u4ef6\u7684\u5c6c\u6027",
|
||||
"Image": "\u5716\u7247",
|
||||
"Insert link": "\u63d2\u5165\u9023\u7d50",
|
||||
"Target": "\u958b\u555f\u65b9\u5f0f",
|
||||
"Link": "\u9023\u7d50",
|
||||
"Poster": "\u9810\u89bd\u5716\u7247",
|
||||
"Media": "\u5a92\u9ad4",
|
||||
"Print": "\u5217\u5370",
|
||||
"Prev": "\u4e0a\u4e00\u500b",
|
||||
"Find and replace": "\u5c0b\u627e\u53ca\u53d6\u4ee3",
|
||||
"Whole words": "\u6574\u500b\u55ae\u5b57",
|
||||
"Spellcheck": "\u62fc\u5b57\u6aa2\u67e5",
|
||||
"Caption": "\u8868\u683c\u6a19\u984c",
|
||||
"Insert template": "\u63d2\u5165\u6a23\u7248"
|
||||
});
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
body{background-color:#2f3742;color:#dfe0e4;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
@media screen{html{background:#f4f4f4;min-height:100%}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}@media screen{body{background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.15);box-sizing:border-box;margin:1rem auto 0;max-width:820px;min-height:calc(100vh - 1rem);padding:4rem 6rem 6rem 6rem}}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure figcaption{color:#999;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem auto;max-width:900px}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse}
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;left:0;margin:0;overflow:hidden;-ms-scroll-chaining:none;overscroll-behavior:none;padding:0;position:fixed;top:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox.tox-tinymce.tox-fullscreen{background-color:transparent;z-index:1200}.tox-shadowhost.tox-fullscreen{z-index:1200}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse}
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
|
||||
* Licensed under the LGPL or a commercial license.
|
||||
* For LGPL see License.txt in the project root for license information.
|
||||
* For commercial licenses see https://www.tiny.cloud/
|
||||
*/
|
||||
body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;left:0;margin:0;overflow:hidden;-ms-scroll-chaining:none;overscroll-behavior:none;padding:0;position:fixed;top:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox.tox-tinymce.tox-fullscreen{background-color:transparent;z-index:1200}.tox-shadowhost.tox-fullscreen{z-index:1200}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { ElConfigProvider } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { getElementPlusLocale } from '@/i18n';
|
||||
|
||||
const { locale } = useI18n({ useScope: 'global' });
|
||||
const lang = computed(() => getElementPlusLocale(locale.value as string));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- tinymce 对话框的层级太低,必须调低 ElementPlus 的 对话框层级(默认为2000) -->
|
||||
<el-config-provider :locale="lang" :z-index="500">
|
||||
<router-view />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
import axios from '@/utils/request';
|
||||
|
||||
export const imageUploadUrl = `${import.meta.env.VITE_BASE_API}/backend/image-upload`;
|
||||
export const avatarUploadUrl = `${import.meta.env.VITE_BASE_API}/backend/avatar-upload`;
|
||||
export const videoUploadUrl = `${import.meta.env.VITE_BASE_API}/backend/video-upload`;
|
||||
export const audioUploadUrl = `${import.meta.env.VITE_BASE_API}/backend/audio-upload`;
|
||||
export const mediaUploadUrl = `${import.meta.env.VITE_BASE_API}/backend/media-upload`;
|
||||
export const docUploadUrl = `${import.meta.env.VITE_BASE_API}/backend/doc-upload`;
|
||||
export const fileUploadUrl = `${import.meta.env.VITE_BASE_API}/backend/file-upload`;
|
||||
|
||||
export const cropImage = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/image-crop', data)).data;
|
||||
export const cropAvatar = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/avatar-crop', data)).data;
|
||||
export const fetchImage = async (url: string): Promise<any> => (await axios.post('/backend/image-fetch', url, { headers: { 'Content-Type': 'text/plain' } })).data;
|
||||
|
||||
export const queryConfigModel = async (): Promise<any> => (await axios.get('/backend/core/config/model')).data;
|
||||
export const queryConfig = async (): Promise<any> => (await axios.get('/backend/core/config')).data;
|
||||
export const queryConfigGrey = async (): Promise<any> => (await axios.get('/backend/core/config/grey')).data;
|
||||
export const queryConfigSms = async (): Promise<any> => (await axios.get('/backend/core/config/sms')).data;
|
||||
export const queryConfigEmail = async (): Promise<any> => (await axios.get('/backend/core/config/email')).data;
|
||||
export const queryUploadStorage = async (): Promise<any> => (await axios.get('/backend/core/config/upload-storage')).data;
|
||||
export const queryHtmlStorage = async (): Promise<any> => (await axios.get('/backend/core/config/html-storage')).data;
|
||||
export const queryTemplateStorage = async (): Promise<any> => (await axios.get('/backend/core/config/template-storage')).data;
|
||||
export const updateConfigBase = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/config/base?_method=put', data)).data;
|
||||
export const updateConfigCustoms = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/config/customs?_method=put', data)).data;
|
||||
export const updateConfigUpload = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/config/upload?_method=put', data)).data;
|
||||
export const updateConfigGrey = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/config/grey?_method=put', data)).data;
|
||||
export const updateConfigRegister = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/config/register?_method=put', data)).data;
|
||||
export const updateConfigSecurity = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/config/security?_method=put', data)).data;
|
||||
export const updateConfigSms = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/config/sms?_method=put', data)).data;
|
||||
export const sendTestSms = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/config/sms/send', data)).data;
|
||||
export const updateConfigEmail = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/config/email?_method=put', data)).data;
|
||||
export const sendTestEmail = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/config/email/send', data)).data;
|
||||
export const updateUploadStorage = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/config/upload-storage?_method=put', data)).data;
|
||||
export const updateHtmlStorage = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/config/html-storage?_method=put', data)).data;
|
||||
export const updateTemplateStorage = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/config/template-storage?_method=put', data)).data;
|
||||
export const storagePathAllowed = async (path: string): Promise<any> => (await axios.get('/backend/core/config/storage-path-allowed', { params: { path } })).data;
|
||||
|
||||
export const querySiteSettings = async (): Promise<any> => (await axios.get('/backend/core/site-settings')).data;
|
||||
export const updateSiteBaseSettings = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/site-settings/base?_method=put', data)).data;
|
||||
export const updateSiteCustomsSettings = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/site-settings/customs?_method=put', data)).data;
|
||||
export const updateSiteWatermarkSettings = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/site-settings/watermark?_method=put', data)).data;
|
||||
export const updateSiteEditorSettings = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/site-settings/editor?_method=put', data)).data;
|
||||
export const updateSiteMessageBoardSettings = async (data: Record<string, any>): Promise<any> =>
|
||||
(await axios.post('/backend/core/site-settings/message-board?_method=put', data)).data;
|
||||
export const querySiteHtmlSettings = async (): Promise<any> => (await axios.get('/backend/core/site-settings/html')).data;
|
||||
export const updateSiteHtmlSettings = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/site-settings/html?_method=put', data)).data;
|
||||
export const queryCurrentSiteThemeList = async (): Promise<any> => (await axios.get('/backend/core/site/theme')).data;
|
||||
|
||||
export const queryModelList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/model', { params })).data;
|
||||
export const queryModel = async (id: string): Promise<any> => (await axios.get(`/backend/core/model/${id}`)).data;
|
||||
export const createModel = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/model', data)).data;
|
||||
export const updateModel = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/model?_method=put', data)).data;
|
||||
export const updateModelOrder = async (data: string[]): Promise<any> => (await axios.post('/backend/core/model/order?_method=put', data)).data;
|
||||
export const deleteModel = async (data: string[]): Promise<any> => (await axios.post('/backend/core/model?_method=delete', data)).data;
|
||||
|
||||
export const queryDictTypeList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/dict-type', { params })).data;
|
||||
export const queryDictType = async (id: string): Promise<any> => (await axios.get(`/backend/core/dict-type/${id}`)).data;
|
||||
export const createDictType = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/dict-type', data)).data;
|
||||
export const updateDictType = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/dict-type?_method=put', data)).data;
|
||||
export const updateDictTypeOrder = async (data: string[]): Promise<any> => (await axios.post('/backend/core/dict-type/order?_method=put', data)).data;
|
||||
export const deleteDictType = async (data: string[]): Promise<any> => (await axios.post('/backend/core/dict-type?_method=delete', data)).data;
|
||||
export const dictTypeAliasExist = async (alias: string, scope: number): Promise<any> => (await axios.get('/backend/core/dict-type/alias-exist', { params: { alias, scope } })).data;
|
||||
|
||||
export const queryBlockList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/block', { params })).data;
|
||||
export const queryBlock = async (id: string): Promise<any> => (await axios.get(`/backend/core/block/${id}`)).data;
|
||||
export const createBlock = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/block', data)).data;
|
||||
export const updateBlock = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/block?_method=put', data)).data;
|
||||
export const updateBlockOrder = async (data: string[]): Promise<any> => (await axios.post('/backend/core/block/order?_method=put', data)).data;
|
||||
export const deleteBlock = async (data: string[]): Promise<any> => (await axios.post('/backend/core/block?_method=delete', data)).data;
|
||||
export const blockAliasExist = async (alias: string, scope: number): Promise<any> => (await axios.get('/backend/core/block/alias-exist', { params: { alias, scope } })).data;
|
||||
export const blockScopeNotAllowed = async (scope: number, blockId: string): Promise<any> =>
|
||||
(await axios.get('/backend/core/block/scope-not-allowed', { params: { scope, blockId } })).data;
|
||||
|
||||
export const queryMessageBoardTypeList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/message-board-type', { params })).data;
|
||||
export const queryMessageBoardType = async (id: string): Promise<any> => (await axios.get(`/backend/ext/message-board-type/${id}`)).data;
|
||||
export const createMessageBoardType = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/message-board-type', data)).data;
|
||||
export const updateMessageBoardType = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/message-board-type?_method=put', data)).data;
|
||||
export const updateMessageBoardTypeOrder = async (fromId: string, toId: string): Promise<any> =>
|
||||
(await axios.post('/backend/ext/message-board-type/update-order', { fromId, toId })).data;
|
||||
export const deleteMessageBoardType = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/message-board-type?_method=delete', data)).data;
|
||||
|
||||
export const queryPerformanceTypeList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/performance-type', { params })).data;
|
||||
export const queryPerformanceType = async (id: string): Promise<any> => (await axios.get(`/backend/core/performance-type/${id}`)).data;
|
||||
export const createPerformanceType = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/performance-type', data)).data;
|
||||
export const updatePerformanceType = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/performance-type?_method=put', data)).data;
|
||||
export const updatePerformanceTypeOrder = async (fromId: string, toId: string): Promise<any> =>
|
||||
(await axios.post('/backend/core/performance-type/update-order', { fromId, toId })).data;
|
||||
export const deletePerformanceType = async (data: string[]): Promise<any> => (await axios.post('/backend/core/performance-type?_method=delete', data)).data;
|
||||
|
||||
export const queryFormTypeList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/form-type', { params })).data;
|
||||
export const queryFormType = async (id: string): Promise<any> => (await axios.get(`/backend/ext/form-type/${id}`)).data;
|
||||
export const createFormType = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/form-type', data)).data;
|
||||
export const updateFormType = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/form-type?_method=put', data)).data;
|
||||
export const updateFormTypeOrder = async (fromId: string, toId: string): Promise<any> => (await axios.post('/backend/ext/form-type/update-order', { fromId, toId })).data;
|
||||
export const deleteFormType = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/form-type?_method=delete', data)).data;
|
||||
export const queryFormListTemplates = async (): Promise<any> => (await axios.get('/backend/ext/form-type/list-templates')).data;
|
||||
export const queryFormItemTemplates = async (): Promise<any> => (await axios.get('/backend/ext/form-type/item-templates')).data;
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
import axios from '@/utils/request';
|
||||
|
||||
export const jodConvertDocUrl = `${import.meta.env.VITE_BASE_API}/backend/core/jod-convert/doc`;
|
||||
export const jodConvertLibraryUrl = `${import.meta.env.VITE_BASE_API}/backend/core/jod-convert/library`;
|
||||
export const queryJodConvertEnabled = async (): Promise<boolean> => (await axios.get('/backend/core/jod-convert/enabled')).data;
|
||||
|
||||
export const queryChannelList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/channel', { params })).data;
|
||||
export const queryChannel = async (id: string): Promise<any> => (await axios.get(`/backend/core/channel/${id}`)).data;
|
||||
export const createChannel = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/channel', data)).data;
|
||||
export const updateChannel = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/channel?_method=put', data)).data;
|
||||
export const updateChannelNav = async (id: string, nav: boolean): Promise<any> => (await axios.post('/backend/core/channel/nav?_method=put', { id, nav })).data;
|
||||
export const updateChannelReal = async (id: string, real: boolean): Promise<any> => (await axios.post('/backend/core/channel/real?_method=put', { id, real })).data;
|
||||
export const moveChannel = async (fromId: string, toId: string, type: 'inner' | 'before' | 'after'): Promise<any> =>
|
||||
(await axios.post('/backend/core/channel/move?_method=put', { fromId, toId, type })).data;
|
||||
export const batchMoveChannel = async (fromIds: string[], toId: string, type: 'inner' | 'before' | 'after'): Promise<any> =>
|
||||
(await axios.post('/backend/core/channel/batch-move?_method=put', { fromIds, toId, type })).data;
|
||||
export const batchMergeChannel = async (fromIds: string[], toId: string): Promise<any> =>
|
||||
(await axios.post('/backend/core/channel/batch-merge?_method=put', { fromIds, toId })).data;
|
||||
export const tidyTreeChannel = async (): Promise<any> => (await axios.post('/backend/core/channel/tidy-tree?_method=put')).data;
|
||||
export const deleteChannel = async (data: string[]): Promise<any> => (await axios.post('/backend/core/channel?_method=delete', data)).data;
|
||||
export const queryChannelPermissions = async (): Promise<any> => (await axios.get('/backend/core/channel/channel-permissions')).data;
|
||||
export const queryChannelTemplates = async (): Promise<any> => (await axios.get('/backend/core/channel/channel-templates')).data;
|
||||
export const queryArticleTemplates = async (): Promise<any> => (await axios.get('/backend/core/channel/article-templates')).data;
|
||||
export const channelAliasExist = async (alias?: string): Promise<any> => (await axios.get('/backend/core/channel/alias-exist', { params: { alias } })).data;
|
||||
|
||||
export const queryArticlePage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/article', { params })).data;
|
||||
export const queryArticleRejectCount = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/article/reject-count', { params })).data;
|
||||
export const queryArticle = async (id: string): Promise<any> => (await axios.get(`/backend/core/article/${id}`)).data;
|
||||
export const queryArticleTitleSimilarity = async (similarity: number, title: string, excludeId?: string): Promise<any> =>
|
||||
(await axios.get('/backend/core/article/title-similarity', { params: { similarity, title, excludeId } })).data;
|
||||
export const createArticle = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/article', data)).data;
|
||||
export const updateArticle = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/article?_method=put', data)).data;
|
||||
export const updateArticleOrder = async (fromId: string, toId: string): Promise<any> => (await axios.post('/backend/core/article/update-order', { fromId, toId })).data;
|
||||
export const internalPushArticle = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/article/internal-push', data)).data;
|
||||
export const externalPushArticle = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/article/external-push', data)).data;
|
||||
export const stickyArticle = async (ids: string[], sticky: number, stickyDate?: Date): Promise<any> =>
|
||||
(await axios.post('/backend/core/article/sticky?_method=put', { ids, sticky, stickyDate })).data;
|
||||
export const deleteArticle = async (data: string[]): Promise<any> => (await axios.post('/backend/core/article/delete?_method=put', data)).data;
|
||||
export const submitArticle = async (data: string[]): Promise<any> => (await axios.post('/backend/core/article/submit?_method=put', data)).data;
|
||||
export const archiveArticle = async (data: string[]): Promise<any> => (await axios.post('/backend/core/article/archive?_method=put', data)).data;
|
||||
export const offlineArticle = async (data: string[]): Promise<any> => (await axios.post('/backend/core/article/offline?_method=put', data)).data;
|
||||
export const completelyDeleteArticle = async (data: string[]): Promise<any> => (await axios.post('/backend/core/article?_method=delete', data)).data;
|
||||
|
||||
export const queryArticleReviewPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/article-review', { params })).data;
|
||||
export const queryArticlePendingCount = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/article-review/pending-count', { params })).data;
|
||||
export const queryArticleReview = async (id: string): Promise<any> => (await axios.get(`/backend/core/article-review/${id}`)).data;
|
||||
export const passArticles = async (data: string[]): Promise<any> => (await axios.post('/backend/core/article-review/pass?_method=put', data)).data;
|
||||
export const passArticle = async (taskId: string, properties: Record<string, string>, comment: string): Promise<any> =>
|
||||
(await axios.post(`/backend/core/article-review/pass/${taskId}?_method=put`, { properties, comment })).data;
|
||||
export const delegateArticle = async (taskId: string, toUserId: string, comment: string): Promise<any> =>
|
||||
(await axios.post(`/backend/core/article-review/delegate?_method=put`, { taskId, toUserId, comment })).data;
|
||||
export const transferArticle = async (taskId: string, toUserId: string, comment: string): Promise<any> =>
|
||||
(await axios.post(`/backend/core/article-review/transfer?_method=put`, { taskId, toUserId, comment })).data;
|
||||
export const backArticle = async (taskId: string, activityId: string, comment: string): Promise<any> =>
|
||||
(await axios.post(`/backend/core/article-review/back?_method=put`, { taskId, activityId, comment })).data;
|
||||
export const rejectArticle = async (taskIds: string[], reason: string): Promise<any> =>
|
||||
(await axios.post('/backend/core/article-review/reject?_method=put', { taskIds, reason })).data;
|
||||
|
||||
export const queryBlockItemList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/block-item', { params })).data;
|
||||
export const queryBlockItem = async (id: string): Promise<any> => (await axios.get(`/backend/core/block-item/${id}`)).data;
|
||||
export const createBlockItem = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/block-item', data)).data;
|
||||
export const updateBlockItem = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/block-item?_method=put', data)).data;
|
||||
export const updateBlockItemOrder = async (fromId: string, toId: string): Promise<any> => (await axios.post('/backend/core/block-item/update-order', { fromId, toId })).data;
|
||||
export const deleteBlockItem = async (data: string[]): Promise<any> => (await axios.post('/backend/core/block-item?_method=delete', data)).data;
|
||||
|
||||
export const queryDictList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/dict', { params })).data;
|
||||
export const queryDictListByAlias = async (alias: string, name?: string): Promise<any> => (await axios.get('/backend/core/dict/list-by-alias', { params: { alias, name } })).data;
|
||||
export const queryDict = async (id: string): Promise<any> => (await axios.get(`/backend/core/dict/${id}`)).data;
|
||||
export const createDict = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/dict', data)).data;
|
||||
export const updateDict = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/dict?_method=put', data)).data;
|
||||
export const updateDictOrder = async (data: string[]): Promise<any> => (await axios.post('/backend/core/dict/order?_method=put', data)).data;
|
||||
export const deleteDict = async (data: string[]): Promise<any> => (await axios.post('/backend/core/dict?_method=delete', data)).data;
|
||||
|
||||
export const fulltextReindexAll = async (): Promise<any> => (await axios.post('/backend/core/generator/fulltext-reindex-all')).data;
|
||||
export const fulltextReindexSite = async (): Promise<any> => (await axios.post('/backend/core/generator/fulltext-reindex-site')).data;
|
||||
export const htmlAll = async (): Promise<any> => (await axios.post('/backend/core/generator/html-all')).data;
|
||||
export const htmlAllHome = async (): Promise<any> => (await axios.post('/backend/core/generator/html-all-home')).data;
|
||||
export const htmlHome = async (): Promise<any> => (await axios.post('/backend/core/generator/html-home')).data;
|
||||
export const htmlChannel = async (): Promise<any> => (await axios.post('/backend/core/generator/html-channel')).data;
|
||||
export const htmlArticle = async (): Promise<any> => (await axios.post('/backend/core/generator/html-article')).data;
|
||||
|
||||
export const queryTagPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/tag', { params })).data;
|
||||
export const queryTagList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/tag/list', { params })).data;
|
||||
export const queryTag = async (id: string): Promise<any> => (await axios.get(`/backend/core/tag/${id}`)).data;
|
||||
export const createTag = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/tag', data)).data;
|
||||
export const updateTag = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/tag?_method=put', data)).data;
|
||||
export const deleteTag = async (data: string[]): Promise<any> => (await axios.post('/backend/core/tag?_method=delete', data)).data;
|
||||
|
||||
export const queryFormPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/form', { params })).data;
|
||||
export const queryForm = async (id: string): Promise<any> => (await axios.get(`/backend/ext/form/${id}`)).data;
|
||||
export const queryFormRejectCount = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/form/reject-count', { params })).data;
|
||||
export const createForm = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/form', data)).data;
|
||||
export const updateForm = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/form?_method=put', data)).data;
|
||||
export const updateFormOrder = async (fromId: string, toId: string): Promise<any> => (await axios.post('/backend/ext/form/update-order', { fromId, toId })).data;
|
||||
export const deleteForm = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/form/delete?_method=put', data)).data;
|
||||
export const submitForm = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/form/submit?_method=put', data)).data;
|
||||
export const completelyDeleteForm = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/form?_method=delete', data)).data;
|
||||
|
||||
export const queryFormReviewPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/form-review', { params })).data;
|
||||
export const queryFormReviewTypeList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/form-review/type-list', { params })).data;
|
||||
export const queryFormPendingCount = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/form-review/pending-count', { params })).data;
|
||||
export const queryFormReview = async (id: string): Promise<any> => (await axios.get(`/backend/ext/form-review/${id}`)).data;
|
||||
export const passForms = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/form-review/pass?_method=put', data)).data;
|
||||
export const passForm = async (taskId: string, properties: Record<string, string>, comment: string): Promise<any> =>
|
||||
(await axios.post(`/backend/ext/form-review/pass/${taskId}?_method=put`, { properties, comment })).data;
|
||||
export const delegateForm = async (taskId: string, toUserId: string, comment: string): Promise<any> =>
|
||||
(await axios.post(`/backend/ext/form-review/delegate?_method=put`, { taskId, toUserId, comment })).data;
|
||||
export const transferForm = async (taskId: string, toUserId: string, comment: string): Promise<any> =>
|
||||
(await axios.post(`/backend/ext/form-review/transfer?_method=put`, { taskId, toUserId, comment })).data;
|
||||
export const backForm = async (taskId: string, activityId: string, comment: string): Promise<any> =>
|
||||
(await axios.post(`/backend/ext/form-review/back?_method=put`, { taskId, activityId, comment })).data;
|
||||
export const rejectForm = async (taskIds: string[], reason: string): Promise<any> => (await axios.post('/backend/ext/form-review/reject?_method=put', { taskIds, reason })).data;
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import axios from '@/utils/request';
|
||||
|
||||
export const uploadWebFileTemplateUrl = `${import.meta.env.VITE_BASE_API}/backend/ext/web-file-template/upload`;
|
||||
export const uploadZipWebFileTemplateUrl = `${import.meta.env.VITE_BASE_API}/backend/ext/web-file-template/upload-zip`;
|
||||
export const queryWebFileTemplateList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/web-file-template', { params })).data;
|
||||
export const queryWebFileTemplate = async (id: string): Promise<any> => (await axios.get('/backend/ext/web-file-template/show', { params: { id } })).data;
|
||||
export const downloadZipWebFileTemplate = async (dir: string, names: string[]): Promise<any> =>
|
||||
(await axios.post('/backend/ext/web-file-template/download-zip', { dir, names }, { responseType: 'blob' })).data;
|
||||
export const createWebFileTemplate = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/web-file-template', data)).data;
|
||||
export const mkdirWebFileTemplate = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/web-file-template/mkdir', data)).data;
|
||||
export const updateWebFileTemplate = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/web-file-template?_method=put', data)).data;
|
||||
export const renameWebFileTemplate = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/web-file-template/rename?_method=put', data)).data;
|
||||
export const copyWebFileTemplate = async (dir: string, names: string[], destDir: string): Promise<any> =>
|
||||
(await axios.post('/backend/ext/web-file-template/copy', { dir, names, destDir })).data;
|
||||
export const moveWebFileTemplate = async (dir: string, names: string[], destDir: string): Promise<any> =>
|
||||
(await axios.post('/backend/ext/web-file-template/move', { dir, names, destDir })).data;
|
||||
export const deleteWebFileTemplate = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/web-file-template?_method=delete', data)).data;
|
||||
|
||||
export const uploadWebFileUploadUrl = `${import.meta.env.VITE_BASE_API}/backend/ext/web-file-upload/upload`;
|
||||
export const uploadZipWebFileUploadUrl = `${import.meta.env.VITE_BASE_API}/backend/ext/web-file-upload/upload-zip`;
|
||||
export const queryWebFileUploadList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/web-file-upload', { params })).data;
|
||||
export const queryWebFileUpload = async (id: string): Promise<any> => (await axios.get('/backend/ext/web-file-upload/show', { params: { id } })).data;
|
||||
export const downloadZipWebFileUpload = async (dir: string, names: string[]): Promise<any> =>
|
||||
(await axios.post('/backend/ext/web-file-upload/download-zip', { dir, names }, { responseType: 'blob' })).data;
|
||||
export const createWebFileUpload = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/web-file-upload', data)).data;
|
||||
export const mkdirWebFileUpload = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/web-file-upload/mkdir', data)).data;
|
||||
export const updateWebFileUpload = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/web-file-upload?_method=put', data)).data;
|
||||
export const renameWebFileUpload = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/web-file-upload/rename?_method=put', data)).data;
|
||||
export const copyWebFileUpload = async (dir: string, names: string[], destDir: string): Promise<any> =>
|
||||
(await axios.post('/backend/ext/web-file-upload/copy', { dir, names, destDir })).data;
|
||||
export const moveWebFileUpload = async (dir: string, names: string[], destDir: string): Promise<any> =>
|
||||
(await axios.post('/backend/ext/web-file-upload/move', { dir, names, destDir })).data;
|
||||
export const deleteWebFileUpload = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/web-file-upload?_method=delete', data)).data;
|
||||
|
||||
export const uploadWebFileHtmlUrl = `${import.meta.env.VITE_BASE_API}/backend/ext/web-file-html/upload`;
|
||||
export const uploadZipWebFileHtmlUrl = `${import.meta.env.VITE_BASE_API}/backend/ext/web-file-html/upload-zip`;
|
||||
export const queryWebFileHtmlList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/web-file-html', { params })).data;
|
||||
export const queryWebFileHtml = async (id: string): Promise<any> => (await axios.get('/backend/ext/web-file-html/show', { params: { id } })).data;
|
||||
export const downloadZipWebFileHtml = async (dir: string, names: string[]): Promise<any> =>
|
||||
(await axios.post('/backend/ext/web-file-html/download-zip', { dir, names }, { responseType: 'blob' })).data;
|
||||
export const createWebFileHtml = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/web-file-html', data)).data;
|
||||
export const mkdirWebFileHtml = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/web-file-html/mkdir', data)).data;
|
||||
export const updateWebFileHtml = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/web-file-html?_method=put', data)).data;
|
||||
export const renameWebFileHtml = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/web-file-html/rename?_method=put', data)).data;
|
||||
export const copyWebFileHtml = async (dir: string, names: string[], destDir: string): Promise<any> =>
|
||||
(await axios.post('/backend/ext/web-file-html/copy', { dir, names, destDir })).data;
|
||||
export const moveWebFileHtml = async (dir: string, names: string[], destDir: string): Promise<any> =>
|
||||
(await axios.post('/backend/ext/web-file-html/move', { dir, names, destDir })).data;
|
||||
export const deleteWebFileHtml = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/web-file-html?_method=delete', data)).data;
|
||||
|
||||
export const queryBackupDatabaseList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/backup-database', { params })).data;
|
||||
export const queryBackupDatabase = async (id: string): Promise<any> => (await axios.get(`/backend/ext/backup-database/${id}`)).data;
|
||||
export const backupBackupDatabase = async (): Promise<any> => (await axios.post('/backend/ext/backup-database')).data;
|
||||
export const restoreBackupDatabase = async (name: string): Promise<any> => (await axios.post('/backend/ext/backup-database?_method=put', { name })).data;
|
||||
export const deleteBackupDatabase = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/backup-database?_method=delete', data)).data;
|
||||
|
||||
export const queryBackupTemplatesList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/backup-templates', { params })).data;
|
||||
export const queryBackupTemplates = async (id: string): Promise<any> => (await axios.get(`/backend/ext/backup-templates/${id}`)).data;
|
||||
export const backupBackupTemplates = async (): Promise<any> => (await axios.post('/backend/ext/backup-templates')).data;
|
||||
export const restoreBackupTemplates = async (name: string): Promise<any> => (await axios.post('/backend/ext/backup-templates?_method=put', { name })).data;
|
||||
export const deleteBackupTemplates = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/backup-templates?_method=delete', data)).data;
|
||||
|
||||
export const queryBackupUploadsList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/backup-uploads', { params })).data;
|
||||
export const queryBackupUploads = async (id: string): Promise<any> => (await axios.get(`/backend/ext/backup-uploads/${id}`)).data;
|
||||
export const backupBackupUploads = async (): Promise<any> => (await axios.post('/backend/ext/backup-uploads')).data;
|
||||
export const restoreBackupUploads = async (name: string): Promise<any> => (await axios.post('/backend/ext/backup-uploads?_method=put', { name })).data;
|
||||
export const deleteBackupUploads = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/backup-uploads?_method=delete', data)).data;
|
||||
|
||||
export const queryIncrementalUploadsList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/incremental-uploads', { params })).data;
|
||||
export const queryIncrementalUploads = async (id: string): Promise<any> => (await axios.get(`/backend/ext/incremental-uploads/${id}`)).data;
|
||||
export const backupIncrementalUploads = async (): Promise<any> => (await axios.post('/backend/ext/incremental-uploads')).data;
|
||||
export const restoreIncrementalUploads = async (name: string): Promise<any> => (await axios.post('/backend/ext/incremental-uploads?_method=put', { name })).data;
|
||||
export const mergeIncrementalUploads = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/incremental-uploads/merge', data)).data;
|
||||
export const deleteIncrementalUploads = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/incremental-uploads?_method=delete', data)).data;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import axios from '@/utils/request';
|
||||
|
||||
export const queryMessageBoardPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/message-board', { params })).data;
|
||||
export const queryMessageBoardUnreviewedCount = async (params?: Record<string, any>): Promise<any> =>
|
||||
(await axios.get('/backend/ext/message-board/unreviewed-count', { params })).data;
|
||||
export const queryMessageBoard = async (id: string): Promise<any> => (await axios.get(`/backend/ext/message-board/${id}`)).data;
|
||||
export const createMessageBoard = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/message-board', data)).data;
|
||||
export const updateMessageBoard = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/message-board?_method=put', data)).data;
|
||||
export const updateMessageBoardStatus = async (ids: string[], status: number): Promise<any> =>
|
||||
(await axios.post('/backend/ext/message-board/status?_method=put', { ids, status })).data;
|
||||
export const deleteMessageBoard = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/message-board?_method=delete', data)).data;
|
||||
|
||||
export const queryVotePage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/vote', { params })).data;
|
||||
export const queryVote = async (id: string): Promise<any> => (await axios.get(`/backend/ext/vote/${id}`)).data;
|
||||
export const createVote = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/vote', data)).data;
|
||||
export const updateVote = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/vote?_method=put', data)).data;
|
||||
export const updateVoteOrder = async (fromId: string, toId: string): Promise<any> => (await axios.post('/backend/ext/vote/update-order', { fromId, toId })).data;
|
||||
export const deleteVote = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/vote?_method=delete', data)).data;
|
||||
|
||||
export const querySurveyPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/survey', { params })).data;
|
||||
export const querySurvey = async (id: string): Promise<any> => (await axios.get(`/backend/ext/survey/${id}`)).data;
|
||||
export const createSurvey = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/survey', data)).data;
|
||||
export const updateSurvey = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/survey?_method=put', data)).data;
|
||||
export const updateSurveyOrder = async (fromId: string, toId: string): Promise<any> => (await axios.post('/backend/ext/survey/update-order', { fromId, toId })).data;
|
||||
export const deleteSurvey = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/survey?_method=delete', data)).data;
|
||||
export const querySurveyOptionFeedbackPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/survey/option-feedback', { params })).data;
|
||||
export const querySurveyItemFeedbackPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/survey/item-feedback', { params })).data;
|
||||
export const updateSurveyItemFeedback = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/survey/item-feedback?_method=put', data)).data;
|
||||
|
||||
export const queryExamplePage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/example', { params })).data;
|
||||
export const queryExample = async (id: string): Promise<any> => (await axios.get(`/backend/ext/example/${id}`)).data;
|
||||
export const createExample = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/example', data)).data;
|
||||
export const updateExample = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/example?_method=put', data)).data;
|
||||
export const deleteExample = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/example?_method=delete', data)).data;
|
||||
|
||||
export const queryCollectionPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/collection', { params })).data;
|
||||
export const queryCollection = async (id: string): Promise<any> => (await axios.get(`/backend/ext/collection/${id}`)).data;
|
||||
export const createCollection = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/collection', data)).data;
|
||||
export const updateCollection = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/collection?_method=put', data)).data;
|
||||
export const updateCollectionOrder = async (fromId: string, toId: string): Promise<any> => (await axios.post('/backend/ext/collection/update-order', { fromId, toId })).data;
|
||||
export const deleteCollection = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/collection?_method=delete', data)).data;
|
||||
export const startCollection = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/collection/start?_method=put', data)).data;
|
||||
export const pauseCollection = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/collection/pause?_method=put', data)).data;
|
||||
export const stopCollection = async (data: string[]): Promise<any> => (await axios.post('/backend/ext/collection/stop?_method=put', data)).data;
|
||||
export const collectionSetupListUrls = async (listUrls: string, pageBegin: number, pageEnd: number, listDesc: boolean): Promise<any> =>
|
||||
(await axios.get(`/backend/ext/collection/setup/list-urls`, { params: { listUrls, pageBegin, pageEnd, listDesc } })).data;
|
||||
export const collectionSetupDetailUrls = async (
|
||||
listUrls: string,
|
||||
pageBegin: number,
|
||||
pageEnd: number,
|
||||
userAgent: string,
|
||||
charset: string,
|
||||
listAreaPattern: string,
|
||||
itemUrlPattern: string,
|
||||
itemUrlReg: boolean,
|
||||
itemUrlJs: boolean,
|
||||
): Promise<any> =>
|
||||
(
|
||||
await axios.get(`/backend/ext/collection/setup/detail-urls`, {
|
||||
params: { listUrls, pageBegin, pageEnd, userAgent, charset, listAreaPattern, itemUrlPattern, itemUrlReg, itemUrlJs },
|
||||
})
|
||||
).data;
|
||||
export const collectionSetupFetchContent = async (url: string, userAgent: string, charset: string): Promise<any> =>
|
||||
(await axios.get(`/backend/ext/collection/setup/fetch-content`, { params: { url, userAgent, charset } })).data;
|
||||
export const collectionSetupMatch = async (text: string, texts: string[] | undefined, pattern: string, multi?: boolean, reg?: boolean, js?: boolean): Promise<any> =>
|
||||
(await axios.post(`/backend/ext/collection/setup/match?_method=put`, { text, texts, pattern, multi, reg, js })).data;
|
||||
export const collectionSetupFilter = async (text: string, texts: string[] | undefined, filter: string, multi?: boolean): Promise<any> =>
|
||||
(await axios.post(`/backend/ext/collection/setup/filter?_method=put`, { text, texts, filter, multi })).data;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import axios from '@/utils/request';
|
||||
|
||||
export const queryShortMessagePage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/short-message', { params })).data;
|
||||
export const queryShortMessage = async (id: string): Promise<any> => (await axios.get(`/backend/core/short-message/${id}`)).data;
|
||||
export const createShortMessage = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/short-message', data)).data;
|
||||
export const updateShortMessage = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/short-message?_method=put', data)).data;
|
||||
export const deleteShortMessage = async (data: string[]): Promise<any> => (await axios.post('/backend/core/short-message?_method=delete', data)).data;
|
||||
|
||||
export const queryLoginLogPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/login-log', { params })).data;
|
||||
export const queryLoginLog = async (id: string): Promise<any> => (await axios.get(`/backend/core/login-log/${id}`)).data;
|
||||
export const createLoginLog = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/login-log', data)).data;
|
||||
export const updateLoginLog = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/login-log?_method=put', data)).data;
|
||||
export const deleteLoginLog = async (data: string[]): Promise<any> => (await axios.post('/backend/core/login-log?_method=delete', data)).data;
|
||||
|
||||
export const queryOperationLogPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/operation-log', { params })).data;
|
||||
export const queryOperationLog = async (id: string): Promise<any> => (await axios.get(`/backend/core/operation-log/${id}`)).data;
|
||||
export const createOperationLog = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/operation-log', data)).data;
|
||||
export const updateOperationLog = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/operation-log?_method=put', data)).data;
|
||||
export const deleteOperationLog = async (data: string[]): Promise<any> => (await axios.post('/backend/core/operation-log?_method=delete', data)).data;
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import axios from '@/utils/request';
|
||||
|
||||
export interface LoginParam {
|
||||
username: string;
|
||||
password: string;
|
||||
browser?: boolean;
|
||||
}
|
||||
|
||||
export interface RefreshTokenParam {
|
||||
refreshToken: string;
|
||||
browser?: boolean;
|
||||
}
|
||||
|
||||
export const accountLogin = async (data: LoginParam): Promise<any> => (await axios.post('/auth/jwt/login', data)).data;
|
||||
export const accountLogout = async (refreshToken: string): Promise<any> => (await axios.post('/auth/jwt/logout', { refreshToken })).data;
|
||||
export const accountRefreshToken = async (data: RefreshTokenParam): Promise<any> => (await axios.post('/auth/jwt/refresh-token', data)).data;
|
||||
export const queryCurrentUser = async (): Promise<any> => (await axios.get('/env/current-user')).data;
|
||||
export const queryCurrentSiteList = async (): Promise<any> => (await axios.get('/env/current-site-list')).data;
|
||||
export const queryClientPublicKey = async (): Promise<any> => (await axios.get('/env/client-public-key')).data;
|
||||
export const queryConfig = async (): Promise<any> => (await axios.get('/env/config')).data;
|
||||
export const queryCaptcha = async (): Promise<any> => (await axios.get('/captcha')).data;
|
||||
export const queryIsDisplayCaptcha = async (): Promise<any> => (await axios.get('/captcha/is-display')).data;
|
||||
export const sendMobileMessage = async (captchaToken: string, captcha: string, mobile: string, usage: number): Promise<any> =>
|
||||
(await axios.post('/sms/mobile', { captchaToken, captcha, receiver: mobile, usage })).data;
|
||||
export const queryIsMfaLogin = async (): Promise<any> => (await axios.get('/env/is-mfa-login')).data;
|
||||
export const tryCaptcha = async (token: string, captcha: string): Promise<any> => (await axios.get('/captcha/try', { params: { token, captcha } })).data;
|
||||
export const mobileNotExist = async (mobile: string): Promise<any> => (await axios.get('/user/mobile-not-exist', { params: { mobile } })).data;
|
||||
export const updatePassword = async (data: Record<string, any>): Promise<any> => (await axios.post('/update-password?_method=put', data)).data;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import axios from '@/utils/request';
|
||||
|
||||
export const updatePersonalPassword = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/personal/password?_method=put', data)).data;
|
||||
|
||||
export const queryMachineCode = async (): Promise<any> => (await axios.get('/backend/core/machine/code')).data;
|
||||
export const queryMachineLicense = async (): Promise<any> => (await axios.get('/backend/core/machine/license')).data;
|
||||
|
||||
export const querySystemInfo = async (): Promise<any> => (await axios.get('/backend/core/homepage/system-info')).data;
|
||||
export const querySystemMonitor = async (): Promise<any> => (await axios.get('/backend/core/homepage/system-monitor')).data;
|
||||
export const querySystemLoad = async (): Promise<any> => (await axios.get('/backend/core/homepage/system-load')).data;
|
||||
export const queryGeneratedKey = async (): Promise<any> => (await axios.get('/backend/core/homepage/generated-key')).data;
|
||||
export const queryContentStat = async (): Promise<any> => (await axios.get('/backend/core/homepage/content-stat')).data;
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import axios from '@/utils/request';
|
||||
|
||||
export const queryTrendStat = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/visit/trend-stat', { params })).data;
|
||||
export const queryVisitedPageStat = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/visit/visited-page-stat', { params })).data;
|
||||
export const queryEntryPageStat = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/visit/entry-page-stat', { params })).data;
|
||||
export const queryVisitorStat = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/visit/visitor-stat', { params })).data;
|
||||
export const querySourceStat = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/visit/source-stat', { params })).data;
|
||||
export const queryCountryStat = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/visit/country-stat', { params })).data;
|
||||
export const queryProvinceStat = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/visit/province-stat', { params })).data;
|
||||
export const queryDeviceStat = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/visit/device-stat', { params })).data;
|
||||
export const queryOsStat = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/visit/os-stat', { params })).data;
|
||||
export const queryBrowserStat = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/visit/browser-stat', { params })).data;
|
||||
export const querySourceTypeStat = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/visit/source-type-stat', { params })).data;
|
||||
|
||||
export const queryArticleStatByUser = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/article-stat/by-user', { params })).data;
|
||||
export const queryArticleStatByOrg = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/article-stat/by-org', { params })).data;
|
||||
export const queryArticleStatByChannel = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/article-stat/by-channel', { params })).data;
|
||||
|
||||
export const queryPerformanceStatByUser = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/performance-stat/by-user', { params })).data;
|
||||
export const queryPerformanceStatByOrg = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/ext/performance-stat/by-org', { params })).data;
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import axios from '@/utils/request';
|
||||
|
||||
export const queryCurrentSite = async (): Promise<any> => (await axios.get('/backend/core/site/current')).data;
|
||||
|
||||
export const querySiteList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/site', { params })).data;
|
||||
export const querySite = async (id: string): Promise<any> => (await axios.get(`/backend/core/site/${id}`)).data;
|
||||
export const createSite = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/site', data)).data;
|
||||
export const updateSite = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/site?_method=put', data)).data;
|
||||
export const openSite = async (data: string[]): Promise<any> => (await axios.post('/backend/core/site/open?_method=put', data)).data;
|
||||
export const closeSite = async (data: string[]): Promise<any> => (await axios.post('/backend/core/site/close?_method=put', data)).data;
|
||||
export const moveSite = async (fromId: string, toId: string, type: 'inner' | 'before' | 'after'): Promise<any> =>
|
||||
(await axios.post('/backend/core/site/move?_method=put', { fromId, toId, type })).data;
|
||||
export const tidyTreeSite = async (): Promise<any> => (await axios.post('/backend/core/site/tidy-tree?_method=put')).data;
|
||||
export const deleteSite = async (data: string[]): Promise<any> => (await axios.post('/backend/core/site?_method=delete', data)).data;
|
||||
export const querySiteThemeList = async (id: string): Promise<any> => (await axios.get(`/backend/core/site/${id}/theme`)).data;
|
||||
|
||||
export const queryAttachmentPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/attachment', { params })).data;
|
||||
export const queryAttachment = async (id: string): Promise<any> => (await axios.get(`/backend/core/attachment/${id}`)).data;
|
||||
export const createAttachment = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/attachment', data)).data;
|
||||
export const updateAttachment = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/attachment?_method=put', data)).data;
|
||||
export const deleteAttachment = async (data: string[]): Promise<any> => (await axios.post('/backend/core/attachment?_method=delete', data)).data;
|
||||
|
||||
export const queryTaskPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/task', { params })).data;
|
||||
export const queryTask = async (id: string): Promise<any> => (await axios.get(`/backend/core/task/${id}`)).data;
|
||||
export const createTask = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/task', data)).data;
|
||||
export const updateTask = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/task?_method=put', data)).data;
|
||||
export const deleteTask = async (data: string[]): Promise<any> => (await axios.post('/backend/core/task?_method=delete', data)).data;
|
||||
|
||||
export const queryProcessModelList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/process-model', { params })).data;
|
||||
export const queryProcessModel = async (id: string): Promise<any> => (await axios.get(`/backend/core/process-model/${id}`)).data;
|
||||
export const queryProcessModelXml = async (id: string): Promise<any> => (await axios.get(`/backend/core/process-model/xml/${id}`)).data;
|
||||
export const createProcessModel = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/process-model', data)).data;
|
||||
export const updateProcessModel = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/process-model?_method=put', data)).data;
|
||||
export const updateProcessModelXml = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/process-model/xml?_method=put', data)).data;
|
||||
export const validateProcessModelXml = async (xml: string): Promise<any> =>
|
||||
(await axios.post('/backend/core/process-model/xml/validate', xml, { headers: { 'Content-Type': 'text/plain' } })).data;
|
||||
export const deployProcessModel = async (id: string): Promise<any> => (await axios.post(`/backend/core/process-model/deploy/${id}`)).data;
|
||||
export const deleteProcessModel = async (data: string[]): Promise<any> => (await axios.post('/backend/core/process-model?_method=delete', data)).data;
|
||||
|
||||
export const queryProcessDefinitionList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/process-definition', { params })).data;
|
||||
export const queryProcessDefinitionXml = async (id: string): Promise<any> => (await axios.get(`/backend/core/process-definition/xml/${id}`)).data;
|
||||
export const deleteProcessDefinition = async (data: string[]): Promise<any> => (await axios.post('/backend/core/process-definition?_method=delete', data)).data;
|
||||
|
||||
export const queryProcessInstanceList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/process-instance', { params })).data;
|
||||
export const queryProcessTaskList = async (instanceId: string): Promise<any> => (await axios.get(`/backend/core/process-instance/task/${instanceId}`)).data;
|
||||
export const queryProcessBackActivityList = async (taskId: string): Promise<any> => (await axios.get(`/backend/core/process-instance/back-activity/${taskId}`)).data;
|
||||
export const deleteProcessInstance = async (data: string[]): Promise<any> => (await axios.post('/backend/core/process-instance?_method=delete', data)).data;
|
||||
|
||||
export const queryProcessHistoryList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/process-history', { params })).data;
|
||||
export const deleteProcessHistory = async (data: string[]): Promise<any> => (await axios.post('/backend/core/process-history?_method=delete', data)).data;
|
||||
|
||||
export const queryTaskFormExists = async (taskIds: string[]): Promise<any> => (await axios.post('/backend/core/process-form/task-form-exists', taskIds)).data;
|
||||
export const queryTaskFormProperties = async (taskId: string): Promise<any> => (await axios.get('/backend/core/process-form/task-form-properties', { params: { taskId } })).data;
|
||||
export const queryStartFormProperties = async (processKey: string): Promise<any> =>
|
||||
(await axios.get('/backend/core/process-form/start-form-properties', { params: { processKey } })).data;
|
||||
|
||||
export const querySensitiveWordPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/sensitive-word', { params })).data;
|
||||
export const querySensitiveWord = async (id: string): Promise<any> => (await axios.get(`/backend/core/sensitive-word/${id}`)).data;
|
||||
export const createSensitiveWord = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/sensitive-word', data)).data;
|
||||
export const updateSensitiveWord = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/sensitive-word?_method=put', data)).data;
|
||||
export const deleteSensitiveWord = async (data: string[]): Promise<any> => (await axios.post('/backend/core/sensitive-word?_method=delete', data)).data;
|
||||
export const validateSensitiveWord = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/sensitive-word/validate-sensitive-word', data)).data;
|
||||
|
||||
export const queryErrorWordPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/error-word', { params })).data;
|
||||
export const queryErrorWord = async (id: string): Promise<any> => (await axios.get(`/backend/core/error-word/${id}`)).data;
|
||||
export const createErrorWord = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/error-word', data)).data;
|
||||
export const updateErrorWord = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/error-word?_method=put', data)).data;
|
||||
export const deleteErrorWord = async (data: string[]): Promise<any> => (await axios.post('/backend/core/error-word?_method=delete', data)).data;
|
||||
export const validateErrorWord = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/error-word/validate-error-word', data)).data;
|
||||
|
||||
export const importDataTestConnection = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/import-data/test-connection', data)).data;
|
||||
export const importDataChannel = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/import-data/import-channel', data)).data;
|
||||
export const importDataArticle = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/ext/import-data/import-article', data)).data;
|
||||
export const importDataDeleteCorrespond = async (): Promise<any> => (await axios.post('/backend/ext/import-data/delete-correspond?_method=delete')).data;
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import axios from '@/utils/request';
|
||||
|
||||
export const queryOrgList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/org', { params })).data;
|
||||
export const queryOrg = async (id: string): Promise<any> => (await axios.get(`/backend/core/org/${id}`)).data;
|
||||
export const createOrg = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/org', data)).data;
|
||||
export const updateOrg = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/org?_method=put', data)).data;
|
||||
export const updateOrgPermission = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/org/permission?_method=put', data)).data;
|
||||
export const moveOrg = async (fromId: string, toId: string, type: 'inner' | 'before' | 'after'): Promise<any> =>
|
||||
(await axios.post('/backend/core/org/move?_method=put', { fromId, toId, type })).data;
|
||||
export const tidyTreeOrg = async (): Promise<any> => (await axios.post('/backend/core/org/tidy-tree?_method=put')).data;
|
||||
export const deleteOrg = async (data: string[]): Promise<any> => (await axios.post('/backend/core/org?_method=delete', data)).data;
|
||||
export const queryOrgPermissions = async (): Promise<any> => (await axios.get('/backend/core/org/permissions')).data;
|
||||
export const queryOrgArticlePermissions = async (orgId: string, siteId?: string): Promise<any> =>
|
||||
(await axios.get('/backend/core/org/article-permissions', { params: { orgId, siteId } })).data;
|
||||
export const queryOrgChannelPermissions = async (orgId: string, siteId?: string): Promise<any> =>
|
||||
(await axios.get('/backend/core/org/channel-permissions', { params: { orgId, siteId } })).data;
|
||||
export const queryOrgPermPermissions = async (orgId: string, global: boolean): Promise<any> =>
|
||||
(await axios.get('/backend/core/org/org-permissions', { params: { orgId, global } })).data;
|
||||
|
||||
export const queryRoleList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/role', { params })).data;
|
||||
export const queryRole = async (id: string): Promise<any> => (await axios.get(`/backend/core/role/${id}`)).data;
|
||||
export const createRole = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/role', data)).data;
|
||||
export const updateRole = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/role?_method=put', data)).data;
|
||||
export const updateRolePermission = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/role/permission?_method=put', data)).data;
|
||||
export const updateRoleOrder = async (data: string[]): Promise<any> => (await axios.post('/backend/core/role/order?_method=put', data)).data;
|
||||
export const deleteRole = async (data: string[]): Promise<any> => (await axios.post('/backend/core/role?_method=delete', data)).data;
|
||||
export const queryRoleArticlePermissions = async (roleId: string, siteId?: string): Promise<any> =>
|
||||
(await axios.get('/backend/core/role/article-permissions', { params: { roleId, siteId } })).data;
|
||||
export const queryRoleChannelPermissions = async (roleId: string, siteId?: string): Promise<any> =>
|
||||
(await axios.get('/backend/core/role/channel-permissions', { params: { roleId, siteId } })).data;
|
||||
export const queryRoleOrgPermissions = async (roleId: string, siteId?: string): Promise<any> =>
|
||||
(await axios.get('/backend/core/role/org-permissions', { params: { roleId, siteId } })).data;
|
||||
export const roleScopeNotAllowed = async (scope: number, roleId: string): Promise<any> =>
|
||||
(await axios.get('/backend/core/role/scope-not-allowed', { params: { scope, roleId } })).data;
|
||||
|
||||
export const queryGroupList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/group', { params })).data;
|
||||
export const queryGroup = async (id: string): Promise<any> => (await axios.get(`/backend/core/group/${id}`)).data;
|
||||
export const createGroup = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/group', data)).data;
|
||||
export const updateGroup = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/group?_method=put', data)).data;
|
||||
export const updateGroupPermission = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/group/permission?_method=put', data)).data;
|
||||
export const updateGroupOrder = async (data: string[]): Promise<any> => (await axios.post('/backend/core/group/order?_method=put', data)).data;
|
||||
export const deleteGroup = async (data: string[]): Promise<any> => (await axios.post('/backend/core/group?_method=delete', data)).data;
|
||||
export const groupAccessPermissions = async (groupId: string, siteId?: string): Promise<any> =>
|
||||
(await axios.get('/backend/core/group/access-permissions', { params: { groupId, siteId } })).data;
|
||||
|
||||
export const queryUserPage = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/user', { params })).data;
|
||||
export const queryUserList = async (params?: Record<string, any>): Promise<any> => (await axios.get('/backend/core/user/list', { params })).data;
|
||||
export const queryUser = async (id: string): Promise<any> => (await axios.get(`/backend/core/user/${id}`)).data;
|
||||
export const createUser = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/user', data)).data;
|
||||
export const updateUser = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/user?_method=put', data)).data;
|
||||
export const updateUserPermission = async (data: Record<string, any>): Promise<any> => (await axios.post('/backend/core/user/permission?_method=put', data)).data;
|
||||
export const updateUserPassword = async (id: string, password: string): Promise<any> => (await axios.post('/backend/core/user/password?_method=put', { id, password })).data;
|
||||
export const updateUserStatus = async (ids: string[], status: number): Promise<any> => (await axios.post('/backend/core/user/status?_method=put', { ids, status })).data;
|
||||
export const deleteUser = async (data: string[]): Promise<any> => (await axios.post('/backend/core/user?_method=delete', data)).data;
|
||||
export const usernameExist = async (username?: string): Promise<any> => (await axios.get('/backend/core/user/username-exist', { params: { username } })).data;
|
||||
export const emailExist = async (email?: string): Promise<any> => (await axios.get('/backend/core/user/email-exist', { params: { email } })).data;
|
||||
export const mobileExist = async (mobile?: string): Promise<any> => (await axios.get('/backend/core/user/mobile-exist', { params: { mobile } })).data;
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
|
|
@ -0,0 +1,36 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { compile } from 'path-to-regexp';
|
||||
|
||||
defineOptions({
|
||||
name: 'BreadCrumb',
|
||||
});
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const itemList = ref<any[]>([]);
|
||||
|
||||
const pathCompile = (path: string) => {
|
||||
const { params } = route;
|
||||
const toPath = compile(path);
|
||||
return toPath(params);
|
||||
};
|
||||
const handleLink = (item: any) => {
|
||||
const { redirect, path } = item;
|
||||
router.push(redirect || pathCompile(path));
|
||||
};
|
||||
watchEffect(() => {
|
||||
itemList.value = route.matched.filter((item) => item.meta?.title);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-breadcrumb separator="/">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(item, index) in itemList" :key="item.path">
|
||||
<span v-if="index === itemList.length - 1" class="text-gray-400">{{ $t(item.meta.title) }}</span>
|
||||
<a v-else @click.prevent="() => handleLink(item)">{{ $t(item.meta.title) }}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,287 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, onMounted, PropType, ref, toRefs, watch } from 'vue';
|
||||
import { Plus, Delete } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import _ from 'lodash';
|
||||
import { perm } from '@/stores/useCurrentUser';
|
||||
|
||||
const CONTINUOUS_SETTINGS = 'ujcms_continuous_settings';
|
||||
function fetchContinuous(): Record<string, boolean> {
|
||||
const settings = localStorage.getItem(CONTINUOUS_SETTINGS);
|
||||
return settings ? JSON.parse(settings) : {};
|
||||
}
|
||||
function storeContinuous(settings: Record<string, boolean>) {
|
||||
localStorage.setItem(CONTINUOUS_SETTINGS, JSON.stringify(settings));
|
||||
}
|
||||
function getContinuous(name: string) {
|
||||
const settings = fetchContinuous();
|
||||
return settings[name] ?? false;
|
||||
}
|
||||
function setContinuous(name: string, continuous: boolean) {
|
||||
const settings = fetchContinuous();
|
||||
settings[name] = continuous;
|
||||
storeContinuous(settings);
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Boolean, required: true },
|
||||
values: { type: Object, required: true },
|
||||
name: { type: String, required: true },
|
||||
beanId: { type: [Number, String], default: null },
|
||||
beanIds: { type: Array as PropType<string[] | number[]>, required: true },
|
||||
initValues: { type: Function as PropType<(bean?: any, isEditor?: boolean) => any>, required: true },
|
||||
toValues: { type: Function as PropType<(bean: any) => any>, required: true },
|
||||
queryBean: { type: Function as PropType<(id: any) => Promise<any>>, required: true },
|
||||
createBean: { type: Function as PropType<(bean: any) => Promise<any>>, required: true },
|
||||
updateBean: { type: Function as PropType<(bean: any) => Promise<any>>, required: true },
|
||||
deleteBean: { type: Function as PropType<(ids: any[]) => Promise<any>>, required: true },
|
||||
disableDelete: { type: Function as PropType<(bean: any) => boolean>, default: null },
|
||||
disableEdit: { type: Function as PropType<(bean: any) => boolean>, default: null },
|
||||
beforeValidate: { type: Function as PropType<(values: any) => Promise<boolean> | void>, default: null },
|
||||
preventSubmit: { type: Function as PropType<(values: any) => Promise<boolean>>, default: null },
|
||||
addable: { type: Boolean, default: true },
|
||||
action: { type: String as PropType<'add' | 'copy' | 'edit'>, default: 'edit' },
|
||||
showId: { type: Boolean, default: true },
|
||||
perms: { type: String, default: null },
|
||||
focus: { type: Object, default: null },
|
||||
large: { type: Boolean, default: false },
|
||||
labelPosition: { type: String as PropType<'top' | 'right' | 'left'>, default: 'right' },
|
||||
labelWidth: { type: String, default: '150px' },
|
||||
});
|
||||
const emit = defineEmits({
|
||||
'update:modelValue': null,
|
||||
'update:values': null,
|
||||
finished: null,
|
||||
beanChange: null,
|
||||
beforeSubmit: null,
|
||||
});
|
||||
|
||||
const { name, beanId, beanIds, focus, values, action, modelValue: visible } = toRefs(props);
|
||||
const { t } = useI18n();
|
||||
const loading = ref<boolean>(false);
|
||||
const buttonLoading = ref<boolean>(false);
|
||||
const continuous = ref<boolean>(getContinuous(name.value));
|
||||
const form = ref<any>();
|
||||
const bean = ref<any>(props.initValues());
|
||||
const origValues = ref<any>();
|
||||
const id = ref<any>();
|
||||
const ids = ref<Array<any>>([]);
|
||||
const isEdit = computed(() => id.value != null && action.value === 'edit');
|
||||
const unsaved = computed(() => {
|
||||
// 调试 未保存
|
||||
// if (!_.isEqual(origValues.value, values.value)) {
|
||||
// console.log(JSON.stringify(origValues.value));
|
||||
// console.log(JSON.stringify(values.value));
|
||||
// }
|
||||
return !loading.value && !_.isEqual(origValues.value, values.value);
|
||||
});
|
||||
const disabled = computed(() => props.disableEdit?.(bean.value) ?? false);
|
||||
const title = computed(() => `${name.value} - ${isEdit.value ? `${t(disabled.value ? 'detail' : 'edit')} (ID: ${id.value})` : `${t('add')}`}`);
|
||||
const loadBean = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
bean.value = id.value != null ? await props.queryBean(id.value) : props.initValues(values.value, isEdit.value);
|
||||
origValues.value = id.value != null ? props.toValues(bean.value) : bean.value;
|
||||
emit('update:values', _.cloneDeep(origValues.value));
|
||||
emit('beanChange', bean.value);
|
||||
form.value?.resetFields();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
onMounted(() => emit('update:values', props.initValues()));
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
ids.value = beanIds.value;
|
||||
if (id.value !== beanId.value) {
|
||||
id.value = beanId.value;
|
||||
} else {
|
||||
loadBean();
|
||||
}
|
||||
}
|
||||
});
|
||||
watch(id, () => {
|
||||
loadBean();
|
||||
});
|
||||
watch(continuous, () => setContinuous(name.value, continuous.value));
|
||||
const index = computed(() => ids.value.indexOf(id.value));
|
||||
const hasPrev = computed(() => index.value > 0);
|
||||
const hasNext = computed(() => index.value < ids.value.length - 1);
|
||||
const handlePrev = () => {
|
||||
if (hasPrev.value) {
|
||||
id.value = ids.value[index.value - 1];
|
||||
}
|
||||
};
|
||||
const handleNext = () => {
|
||||
if (hasNext.value) {
|
||||
id.value = ids.value[index.value + 1];
|
||||
}
|
||||
};
|
||||
const handleAdd = () => {
|
||||
focus.value?.focus?.();
|
||||
id.value = undefined;
|
||||
};
|
||||
const handleCancel = () => {
|
||||
emit('update:modelValue', false);
|
||||
};
|
||||
const handleDelete = async () => {
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
await props.deleteBean([id.value]);
|
||||
if (!continuous.value) emit('update:modelValue', false);
|
||||
if (hasNext.value) {
|
||||
handleNext();
|
||||
ids.value.splice(index.value - 1, 1);
|
||||
} else if (hasPrev.value) {
|
||||
handlePrev();
|
||||
ids.value.splice(index.value + 1, 1);
|
||||
} else {
|
||||
emit('update:modelValue', false);
|
||||
}
|
||||
ElMessage.success(t('success'));
|
||||
emit('finished');
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
};
|
||||
const resetOrigValues = () => {
|
||||
origValues.value = props.toValues(values.value);
|
||||
};
|
||||
const handleSubmit = async (stay = false) => {
|
||||
await props.beforeValidate?.(values.value);
|
||||
form.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return;
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
if ((await props.preventSubmit?.(values.value)) ?? false) {
|
||||
return;
|
||||
}
|
||||
emit('beforeSubmit', values.value);
|
||||
if (isEdit.value) {
|
||||
await props.updateBean(values.value);
|
||||
resetOrigValues();
|
||||
} else {
|
||||
await props.createBean(values.value);
|
||||
focus.value?.focus?.();
|
||||
emit('update:values', props.initValues(values.value, isEdit.value));
|
||||
form.value.resetFields();
|
||||
}
|
||||
ElMessage.success(t('success'));
|
||||
if (!continuous.value && !stay) emit('update:modelValue', false);
|
||||
emit('finished', bean.value);
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
const submit = (
|
||||
executor: (
|
||||
values: any,
|
||||
payload: { isEdit: boolean; continuous: boolean; form: any; props: any; focus: any; loadBean: () => Promise<any>; resetOrigValues: () => void; emit: any },
|
||||
) => Promise<boolean | undefined>,
|
||||
) => {
|
||||
form.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return;
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
if ((await props.preventSubmit?.(values.value)) ?? false) {
|
||||
return;
|
||||
}
|
||||
emit('beforeSubmit', values.value);
|
||||
|
||||
const stay = await executor(values.value, {
|
||||
isEdit: isEdit.value,
|
||||
continuous: continuous.value,
|
||||
form: form.value,
|
||||
props,
|
||||
focus: focus.value,
|
||||
loadBean,
|
||||
resetOrigValues,
|
||||
emit,
|
||||
});
|
||||
|
||||
if (!continuous.value && !stay) emit('update:modelValue', false);
|
||||
emit('finished', bean.value);
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
const remove = async (
|
||||
executor: (values: any, payload: { isEdit: boolean; continuous: boolean; form: any; props: any; focus: any; loadBean: () => Promise<any>; emit: any }) => Promise<any>,
|
||||
) => {
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
await executor(values.value, { isEdit: isEdit.value, continuous: continuous.value, form: form.value, props, focus: focus.value, loadBean, emit });
|
||||
if (!continuous.value) {
|
||||
emit('update:modelValue', false);
|
||||
}
|
||||
if (hasNext.value) {
|
||||
handleNext();
|
||||
ids.value.splice(index.value - 1, 1);
|
||||
} else if (hasPrev.value) {
|
||||
handlePrev();
|
||||
ids.value.splice(index.value + 1, 1);
|
||||
} else {
|
||||
emit('update:modelValue', false);
|
||||
}
|
||||
ElMessage.success(t('success'));
|
||||
emit('finished');
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
};
|
||||
defineExpose({ form, submit, remove, defaultSubmit: handleSubmit });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="title"
|
||||
:close-on-click-modal="!unsaved"
|
||||
:model-value="modelValue"
|
||||
:close-on-press-escape="!unsaved"
|
||||
:width="large ? '98%' : '768px'"
|
||||
:top="large ? '16px' : '8vh'"
|
||||
@update:model-value="(event) => $emit('update:modelValue', event)"
|
||||
@opened="() => !isEdit && focus?.focus()"
|
||||
>
|
||||
<template #header>
|
||||
{{ name }} -
|
||||
<span v-if="isEdit">
|
||||
{{ $t(disabled ? 'detail' : 'edit') }}
|
||||
<span v-if="showId">(ID: {{ id }})</span>
|
||||
</span>
|
||||
<span v-else>{{ $t('add') }}</span>
|
||||
</template>
|
||||
<div v-loading="loading || buttonLoading" class="space-x-2">
|
||||
<el-button v-if="isEdit && addable" :disabled="perm(`${perms}:create`)" type="primary" :icon="Plus" @click="handleAdd">{{ $t('add') }}</el-button>
|
||||
<slot name="header-action" :bean="bean" :is-edit="isEdit" :disabled="disabled" :unsaved="unsaved" :disable-delete="disableDelete" :handle-delete="handleDelete">
|
||||
<el-popconfirm v-if="isEdit" :title="$t('confirmDelete')" @confirm="() => handleDelete()">
|
||||
<template #reference>
|
||||
<el-button :disabled="disableDelete?.(bean) || perm(`${perms}:delete`)" :icon="Delete">{{ $t('delete') }}</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</slot>
|
||||
<el-button-group v-if="isEdit">
|
||||
<el-button :disabled="!hasPrev" @click="handlePrev">{{ $t('form.prev') }}</el-button>
|
||||
<el-button :disabled="!hasNext" @click="handleNext">{{ $t('form.next') }}</el-button>
|
||||
</el-button-group>
|
||||
<el-button type="primary" @click="handleCancel">{{ $t('back') }}</el-button>
|
||||
<el-tooltip :content="$t('form.continuous')" placement="top">
|
||||
<el-switch v-model="continuous" size="small"></el-switch>
|
||||
</el-tooltip>
|
||||
<el-tag v-if="unsaved" type="danger">{{ $t('form.unsaved') }}</el-tag>
|
||||
<slot name="header-status" :bean="bean" :is-edit="isEdit" :disabled="disabled"></slot>
|
||||
</div>
|
||||
<el-form ref="form" :class="['mt-5', 'pr-5']" :model="values" :disabled="disabled" :label-width="labelWidth" :label-position="labelPosition" scroll-to-error>
|
||||
<slot :bean="bean" :is-edit="isEdit" :disabled="disabled"></slot>
|
||||
<div v-if="!disabled" v-loading="buttonLoading">
|
||||
<slot name="footer-action" :bean="bean" :is-edit="isEdit" :disabled="disabled" :handle-submit="handleSubmit">
|
||||
<el-button :disabled="perm(isEdit ? `${perms}:update` : `${perms}:create`)" type="primary" native-type="submit" @click.prevent="() => handleSubmit()">
|
||||
{{ $t('save') }}
|
||||
</el-button>
|
||||
</slot>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import { QuestionFilled } from '@element-plus/icons-vue';
|
||||
|
||||
defineProps({
|
||||
label: { type: String, default: null },
|
||||
tooltip: { type: String, default: null },
|
||||
help: { type: Boolean, default: false },
|
||||
message: { type: String, default: '' },
|
||||
fixWidth: { type: Boolean, default: true },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="inline-flex items-center">
|
||||
<div class="overflow-hidden text-clip whitespace-nowrap" :title="label ?? $t(message)">{{ label ?? $t(message) }}</div>
|
||||
<el-tooltip v-if="help" :content="tooltip ?? $t(message + '.tooltip')" placement="top">
|
||||
<el-icon class="text-base align-text-top"><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import { Top, Bottom, ArrowUp, ArrowDown } from '@element-plus/icons-vue';
|
||||
|
||||
defineProps({
|
||||
disabled: { type: Boolean, required: true },
|
||||
});
|
||||
defineEmits({
|
||||
move: null,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-button-group>
|
||||
<el-button :disabled="disabled" :icon="Top" @click="() => $emit('move', 'top')">{{ $t('moveTop') }}</el-button>
|
||||
<el-button :disabled="disabled" :icon="ArrowUp" @click="() => $emit('move', 'up')">{{ $t('moveUp') }}</el-button>
|
||||
<el-button :disabled="disabled" :icon="ArrowDown" @click="() => $emit('move', 'down')">{{ $t('moveDown') }}</el-button>
|
||||
<el-button :disabled="disabled" :icon="Bottom" @click="() => $emit('move', 'bottom')">{{ $t('moveBottom') }}</el-button>
|
||||
</el-button-group>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
<script setup lang="ts">
|
||||
import { useSlots, watch, provide, computed, ref, toRefs, Slots } from 'vue';
|
||||
import { Plus, Minus, Search, Refresh } from '@element-plus/icons-vue';
|
||||
import QueryInput from './QueryInput.vue';
|
||||
|
||||
const props = defineProps({ params: { type: Object, required: true } });
|
||||
const { params } = toRefs(props);
|
||||
const slots: Readonly<Slots> = useSlots();
|
||||
provide('params', params);
|
||||
defineEmits({
|
||||
search: null,
|
||||
reset: null,
|
||||
});
|
||||
const inputs = computed<any[]>(
|
||||
() =>
|
||||
slots
|
||||
.default?.()
|
||||
.flatMap((item: any) => (item.children?.length > 0 ? item.children : item))
|
||||
.flatMap((item: any) => (item.children?.length > 0 ? item.children : item))
|
||||
.filter((item: any) => item.props?.name != null) ?? [],
|
||||
);
|
||||
const data = computed<any[]>(() => inputs.value.map((item) => ({ label: item.props?.label, name: item.props?.name })));
|
||||
const names = ref<string[]>([]);
|
||||
const remains = computed(() => data.value.filter((it) => !names.value.includes(it.name)));
|
||||
|
||||
const clearParams = () => {
|
||||
Object.keys(params.value).forEach((key) => {
|
||||
if (!names.value.includes(key) && names.value.findIndex((item) => item.split(',').includes(key)) === -1) {
|
||||
delete params.value[key];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
data,
|
||||
() => {
|
||||
const [first] = data.value;
|
||||
if (names.value.length > 0) {
|
||||
const sourceNames = data.value.map((item: any) => item.name);
|
||||
names.value.filter((name: string) => sourceNames.includes(name));
|
||||
Object.keys(params.value).forEach((key) => {
|
||||
if (!sourceNames.includes(key) && sourceNames.findIndex((item) => item.split(',').includes(key)) === -1) {
|
||||
delete params.value[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
if (names.value.length === 0) {
|
||||
names.value = [first.name];
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
const handelRow = (index: number) => {
|
||||
if (index === 0) {
|
||||
const [item] = remains.value;
|
||||
names.value[names.value.length] = item.name;
|
||||
} else {
|
||||
names.value.splice(index, 1);
|
||||
clearParams();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="flex">
|
||||
<div class="space-y-1">
|
||||
<div v-for="(name, index) in names" :key="name" class="flex">
|
||||
<el-button :icon="index == 0 ? Plus : Minus" :disabled="index <= 0 && remains.length <= 0" circle @click="() => handelRow(index)"></el-button>
|
||||
<el-select v-model="names[index]" class="w-44" @change="() => clearParams()">
|
||||
<el-option v-for="item in data.filter((it) => it.name === names[index] || remains.includes(it))" :key="item.name" :label="item.label" :value="item.name"></el-option>
|
||||
</el-select>
|
||||
<query-input :inputs="inputs" :name="names[index]"></query-input>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<el-button-group class="ml-2">
|
||||
<el-button native-type="submit" :icon="Search" @click.prevent="() => $emit('search')">{{ $t('search') }}</el-button>
|
||||
<el-button :icon="Refresh" @click="() => $emit('reset')">{{ $t('reset') }}</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, toRefs } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'QueryInput',
|
||||
props: { inputs: { type: Array, required: true }, name: { type: String, required: true } },
|
||||
setup(props) {
|
||||
const { inputs, name } = toRefs(props);
|
||||
return () => inputs.value.find((item: any) => item.props.name === name.value);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<script setup lang="ts">
|
||||
import { inject, PropType, ref, toRefs } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'QueryItem',
|
||||
});
|
||||
const props = defineProps({
|
||||
label: { type: String, required: true },
|
||||
name: { type: String, required: true },
|
||||
// 'string' | 'date' | 'datetime' | 'number'
|
||||
type: { type: String, default: null },
|
||||
options: { type: Object as PropType<Array<{ label: string; value: string | number }>>, default: null },
|
||||
multiple: { type: Boolean, default: true },
|
||||
});
|
||||
const params = inject<any>('params');
|
||||
const { name } = toRefs(props);
|
||||
const [firstName, secondName] = name.value.split(',');
|
||||
const first = ref<string>(firstName);
|
||||
const second = ref<string>(secondName);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot>
|
||||
<div v-if="type === 'number'" class="inline-block">
|
||||
<el-input-number v-model="params[first]" :placeholder="$t('begin.number')" class="w-48"></el-input-number>
|
||||
<el-input-number v-model="params[second]" :placeholder="$t('end.number')" class="w-48"></el-input-number>
|
||||
</div>
|
||||
<el-date-picker
|
||||
v-else-if="type === 'date'"
|
||||
v-model="params[name]"
|
||||
type="daterange"
|
||||
:start-placeholder="$t('begin.date')"
|
||||
:end-placeholder="$t('end.date')"
|
||||
:editable="false"
|
||||
class="w-96"
|
||||
></el-date-picker>
|
||||
<el-date-picker
|
||||
v-else-if="type === 'datetime'"
|
||||
v-model="params[name]"
|
||||
type="datetimerange"
|
||||
:start-placeholder="$t('begin.date')"
|
||||
:end-placeholder="$t('end.date')"
|
||||
:editable="false"
|
||||
class="w-96"
|
||||
>
|
||||
</el-date-picker>
|
||||
<!--
|
||||
<div v-else-if="type === 'date'" class="inline-block">
|
||||
<el-date-picker v-model="params[first]" type="date" :placeholder="$t('begin.date')" class="w-48"></el-date-picker>
|
||||
<el-date-picker v-model="params[second]" type="date" :placeholder="$t('end.date')" class="w-48"></el-date-picker>
|
||||
</div>
|
||||
<div v-else-if="type === 'datetime'" class="inline-block">
|
||||
<el-date-picker v-model="params[first]" type="datetime" class="w-48"></el-date-picker>
|
||||
<el-date-picker v-model="params[second]" type="datetime" class="w-48"></el-date-picker>
|
||||
</div>
|
||||
-->
|
||||
<el-select v-else-if="options" v-model="params[name]" :multiple="multiple" class="w-96">
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
<el-input v-else v-model="params[name]" class="w-96"></el-input>
|
||||
</slot>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { default as QueryForm } from './QueryForm.vue';
|
||||
export { default as QueryItem } from './QueryItem.vue';
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<script lang="ts">
|
||||
import { computed, watch, defineComponent, toRefs } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useColumnSettingsStore, ColumnState } from '@/stores/columnSettingsStore';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ColumnList',
|
||||
props: { name: { type: String, required: true } },
|
||||
setup(props, { slots }) {
|
||||
const { name } = toRefs(props);
|
||||
const { t } = useI18n();
|
||||
const slotColumns = computed<any[]>(() => slots.default?.().flatMap((item: any) => (item.children?.length > 0 ? item.children : item)) ?? []);
|
||||
// 获取栏目名称
|
||||
const getColumnTitle = (columnProps: any) => {
|
||||
// 如果是checkbox列,则名称为“选择框”
|
||||
if (columnProps?.type === 'selection') return t('table.selection');
|
||||
return columnProps?.label;
|
||||
};
|
||||
const settingsStore = useColumnSettingsStore();
|
||||
// 获取el-table-column的名称、是否显示
|
||||
const origins = computed<ColumnState[]>(() => slotColumns.value.map((column) => ({ title: getColumnTitle(column.props), display: column.props?.display !== 'none' })));
|
||||
watch(
|
||||
[name, origins],
|
||||
() => {
|
||||
settingsStore.setOriginSettings(name.value, origins.value);
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
const settings = computed<ColumnState[]>(() => settingsStore.getCurrentSettings(name.value));
|
||||
const columns = computed(() =>
|
||||
slotColumns.value
|
||||
.filter((column) => {
|
||||
const matched = settings.value.find((item) => getColumnTitle(column.props) === item.title);
|
||||
return matched?.display ?? column.props?.display !== 'none';
|
||||
})
|
||||
.map((column) => ({ ...column, key: getColumnTitle(column.props) }))
|
||||
.sort((a, b) => {
|
||||
let indexA = settings.value.findIndex((item) => item.title === getColumnTitle(a));
|
||||
if (indexA < 0) indexA = slotColumns.value.findIndex((item) => getColumnTitle(item) === getColumnTitle(a));
|
||||
let indexB = settings.value.findIndex((item) => item.title === getColumnTitle(b));
|
||||
if (indexB < 0) indexB = slotColumns.value.findIndex((item) => getColumnTitle(item) === getColumnTitle(b));
|
||||
return indexA - indexB;
|
||||
}),
|
||||
);
|
||||
return { columns };
|
||||
},
|
||||
render() {
|
||||
return this.columns;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<script setup lang="ts">
|
||||
import { toRefs, computed, ref } from 'vue';
|
||||
import { Setting } from '@element-plus/icons-vue';
|
||||
import { useColumnSettingsStore, mergeColumns, ColumnState } from '@/stores/columnSettingsStore';
|
||||
|
||||
const props = defineProps({ name: { type: String, required: true } });
|
||||
const { name } = toRefs(props);
|
||||
const settingsStore = useColumnSettingsStore();
|
||||
const settings = computed<ColumnState[]>(() => settingsStore.getCurrentSettings(name.value));
|
||||
const origins = computed<ColumnState[]>(() => settingsStore.getOriginSettings(name.value));
|
||||
const merges = computed<ColumnState[]>(() => mergeColumns(settings.value, origins.value));
|
||||
const visible = ref<boolean>(false);
|
||||
const resetColumns = () => {
|
||||
settingsStore.setCurrentSettings(name.value, mergeColumns([], origins.value));
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="inline-flex align-middle">
|
||||
<el-tooltip :content="$t('table.columnsSetting')" placement="top">
|
||||
<el-icon class="text-lg cursor-pointer text-gray-regular" @click="() => (visible = true)"><Setting /></el-icon>
|
||||
</el-tooltip>
|
||||
<el-drawer v-model="visible" :title="$t('table.columnsSetting')" :size="375">
|
||||
<el-button class="mb-1" round @click="resetColumns">{{ $t('reset') }}</el-button>
|
||||
<ul>
|
||||
<li v-for="(column, index) in merges" :key="column.title" :divided="index === 0">
|
||||
<el-checkbox v-model="column.display">{{ column.title }}</el-checkbox>
|
||||
</li>
|
||||
</ul>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export { default as ColumnSetting } from './ColumnSetting.vue';
|
||||
export { default as ColumnList } from './ColumnList.vue';
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import { reactive, toRef } from 'vue';
|
||||
|
||||
export interface ColumnState {
|
||||
title: string;
|
||||
display: boolean;
|
||||
}
|
||||
|
||||
const COLUMN_SETTINGS = 'ujcms_column_settings';
|
||||
|
||||
function fetchColumnSettings(): Record<string, ColumnState[]> {
|
||||
const settings = localStorage.getItem(COLUMN_SETTINGS);
|
||||
return settings ? JSON.parse(settings) : {};
|
||||
}
|
||||
|
||||
const originStore: Record<string, ColumnState[]> = reactive({});
|
||||
const settingStore: Record<string, ColumnState[]> = reactive(fetchColumnSettings());
|
||||
|
||||
export function storeColumnSettings() {
|
||||
localStorage.setItem(COLUMN_SETTINGS, JSON.stringify(settingStore));
|
||||
}
|
||||
export const getColumnOrigins = (name: string) => {
|
||||
if (!originStore[name]) originStore[name] = [];
|
||||
return toRef(originStore, name);
|
||||
};
|
||||
export const mergeColumns = (settings: ColumnState[], origins: ColumnState[]) => {
|
||||
// 去除不存在的列
|
||||
for (let i = 0, len = settings.length; i < len; ) {
|
||||
if (origins.findIndex((column) => column.title === settings[i].title) === -1) {
|
||||
settings.splice(i, 1);
|
||||
len -= 1;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
// 增加未记录的列
|
||||
origins.forEach((column) => {
|
||||
if (settings.findIndex((item) => item.title === column.title) === -1) {
|
||||
settings.push({ ...column });
|
||||
}
|
||||
});
|
||||
return settings;
|
||||
};
|
||||
export const setColumnOrigins = (name: string, origins: ColumnState[]) => {
|
||||
originStore[name] = origins;
|
||||
if (!settingStore[name]) settingStore[name] = [];
|
||||
const settings = settingStore[name];
|
||||
mergeColumns(settings, origins);
|
||||
};
|
||||
export const getColumnSettings = (name: string) => {
|
||||
if (!settingStore[name]) settingStore[name] = [];
|
||||
return toRef(settingStore, name);
|
||||
};
|
||||
// export const setColumnSettings = (name: string, settings: ColumnState[]) => {
|
||||
// settingStore[name] = settings;
|
||||
// };
|
||||
|
|
@ -0,0 +1,392 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, ref, toRefs, watch, onMounted, onBeforeUnmount, onActivated, onDeactivated, PropType } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useFormItem } from 'element-plus';
|
||||
import { getAuthHeaders } from '@/utils/auth';
|
||||
import { getSiteHeaders } from '@/utils/common';
|
||||
import { currentUser } from '@/stores/useCurrentUser';
|
||||
import { useSysConfigStore } from '@/stores/sysConfigStore';
|
||||
import { imageUploadUrl, fileUploadUrl, mediaUploadUrl, fetchImage } from '@/api/config';
|
||||
|
||||
// 参考:https://www.tiny.cloud/docs/advanced/usage-with-module-loaders/webpack/webpack_es6_npm/
|
||||
// 参考:https://github.com/tinymce/tinymce-vue/blob/main/src/main/ts/components/Editor.ts
|
||||
// Import TinyMCE
|
||||
import tinymce from 'tinymce';
|
||||
// Default icons are required for TinyMCE 5.3 or above
|
||||
import 'tinymce/icons/default';
|
||||
// A theme is also required
|
||||
import 'tinymce/themes/silver';
|
||||
// Any plugins you want to use has to be imported
|
||||
import 'tinymce/plugins/advlist';
|
||||
// import 'tinymce/plugins/anchor';
|
||||
// import 'tinymce/plugins/autolink';
|
||||
import 'tinymce/plugins/autoresize';
|
||||
import 'tinymce/plugins/autosave';
|
||||
import 'tinymce/plugins/charmap';
|
||||
import 'tinymce/plugins/code';
|
||||
import 'tinymce/plugins/codesample';
|
||||
import 'tinymce/plugins/directionality';
|
||||
import 'tinymce/plugins/fullscreen';
|
||||
import 'tinymce/plugins/hr';
|
||||
// import 'tinymce/plugins/insertdatetime';
|
||||
import 'tinymce/plugins/image';
|
||||
import 'tinymce/plugins/imagetools';
|
||||
import 'tinymce/plugins/link';
|
||||
import 'tinymce/plugins/lists';
|
||||
import 'tinymce/plugins/media';
|
||||
// import 'tinymce/plugins/nonbreaking';
|
||||
// import 'tinymce/plugins/noneditable';
|
||||
import 'tinymce/plugins/pagebreak';
|
||||
import 'tinymce/plugins/paste';
|
||||
import 'tinymce/plugins/preview';
|
||||
// import 'tinymce/plugins/print';
|
||||
import 'tinymce/plugins/quickbars';
|
||||
// import 'tinymce/plugins/save';
|
||||
import 'tinymce/plugins/searchreplace';
|
||||
// import 'tinymce/plugins/spellchecker';
|
||||
// import 'tinymce/plugins/tabfocus';
|
||||
import 'tinymce/plugins/table';
|
||||
// import 'tinymce/plugins/template';
|
||||
// import 'tinymce/plugins/textpattern';
|
||||
// import 'tinymce/plugins/toc';
|
||||
import 'tinymce/plugins/visualblocks';
|
||||
import 'tinymce/plugins/visualchars';
|
||||
// import 'tinymce/plugins/wordcount';
|
||||
|
||||
import './plugins/indent2em';
|
||||
import './plugins/typesetting';
|
||||
|
||||
import { isTextarea, uuid, initEditor } from './utils';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TinymceEditor',
|
||||
props: {
|
||||
id: { type: String, default: null },
|
||||
modelValue: { type: String, default: '' },
|
||||
disabled: { type: Boolean, default: false },
|
||||
inline: { type: Boolean },
|
||||
init: { type: Object, default: null },
|
||||
modelEvents: { type: [String, Array], default: null },
|
||||
plugins: { type: [String, Array] as PropType<string | string[]>, default: null },
|
||||
toolbar: { type: [String, Array], default: null },
|
||||
outputFormat: {
|
||||
type: String as PropType<'html' | 'text'>,
|
||||
default: 'html',
|
||||
validator: (prop: string) => prop === 'html' || prop === 'text',
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'input', 'change', 'blur', 'keydown'],
|
||||
setup(props, ctx) {
|
||||
const { disabled, modelValue } = toRefs(props);
|
||||
const { t } = useI18n();
|
||||
const sysConfig = useSysConfigStore();
|
||||
const element = ref<any>();
|
||||
const vueEditor = ref<any>();
|
||||
const elementId: string = props.id || uuid('tiny-vue');
|
||||
const inlineEditor: boolean = (props.init && props.init.inline) || props.inline;
|
||||
let mounting = true;
|
||||
const { formItem } = useFormItem();
|
||||
|
||||
const initWrapper = (): void => {
|
||||
let publicPath = import.meta.env.VITE_PUBLIC_PATH;
|
||||
if (publicPath.endsWith('/')) {
|
||||
publicPath = publicPath.substring(0, publicPath.length - 1);
|
||||
}
|
||||
const ep2 = currentUser.epRank >= 2;
|
||||
const finalInit = {
|
||||
base_url: '/tinymce',
|
||||
language_url: `${publicPath}/tinymce/langs/zh_CN.js`,
|
||||
language: 'zh_CN',
|
||||
skin: 'oxide',
|
||||
skin_url: `${publicPath}/tinymce/skins/ui/oxide`,
|
||||
// 必须添加 '/tinymce/skins/content/default/content.min.css'。否则 fontselect 默认不显示“系统字体”。
|
||||
content_css: [`${publicPath}/tinymce/skins/ui/oxide/content.min.css`, `${publicPath}/tinymce/skins/content/default/content.min.css`],
|
||||
// 设置编辑器默认字体
|
||||
content_style: 'body { font-size: 14px; }',
|
||||
menubar: false,
|
||||
// 工具栏模式,默认 'floating',超出一行的工具栏会隐藏,点击 '...' 按钮展开。设置为 'warp' 则全部显示。
|
||||
// toolbar_mode: 'wrap',
|
||||
plugins:
|
||||
'advlist autoresize autosave charmap code codesample directionality fullscreen hr image imagetools lists link media pagebreak paste preview quickbars ' +
|
||||
`searchreplace table visualblocks visualchars indent2em ${ep2 ? 'typesetting' : ''}`,
|
||||
toolbar:
|
||||
`fullscreen code ${
|
||||
ep2 ? '| typesetting' : ''
|
||||
} | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | selectall removeformat pastetext | ` +
|
||||
'quickimage media | blockquote codesample table | bullist numlist | indent2em outdent indent lineheight | forecolor backcolor | fontselect fontsizeselect formatselect | ' +
|
||||
'superscript subscript charmap | hr | ltr rtl | visualblocks visualchars | restoredraft undo redo | preview searchreplace',
|
||||
font_formats:
|
||||
'宋体=SimSun; 微软雅黑=Microsoft YaHei; 楷体=SimKai,KaiTi; 黑体=SimHei; 隶书=SimLi,LiSu; Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;' +
|
||||
'Arial Black=arial black,avant garde;Comic Sans MS=comic sans ms,sans-serif;Helvetica=helvetica;Impact=impact,chicago;Times New Roman=times new roman,times',
|
||||
fontsize_formats: '8px 10px 12px 14px 16px 18px 24px 36px 48px 64px 72px 96px',
|
||||
quickbars_selection_toolbar: 'bold italic | h2 h3 blockquote | link',
|
||||
quickbars_insert_toolbar: false,
|
||||
paste_data_images: true,
|
||||
image_uploadtab: false,
|
||||
image_advtab: true,
|
||||
image_caption: true,
|
||||
images_file_types: sysConfig.upload.imageTypes,
|
||||
min_height: 300,
|
||||
max_height: 500,
|
||||
convert_urls: false,
|
||||
autosave_ask_before_unload: false,
|
||||
...props.init,
|
||||
images_upload_handler(blobInfo: any, success: any, failure: any, progress: any) {
|
||||
const fileSizeLimitByte = sysConfig.upload.imageLimitByte;
|
||||
if (fileSizeLimitByte > 0 && blobInfo.blob().size > fileSizeLimitByte) {
|
||||
failure(t('error.fileMaxSize', { size: `${fileSizeLimitByte / 1024 / 1024}MB` }), { remove: true });
|
||||
return;
|
||||
}
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', imageUploadUrl);
|
||||
|
||||
xhr.upload.onprogress = (e) => {
|
||||
progress((e.loaded / e.total) * 100);
|
||||
};
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 403) {
|
||||
failure(`HTTP Error: ${xhr.status}`, { remove: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (xhr.status < 200 || xhr.status >= 300) {
|
||||
failure(`HTTP Error: ${xhr.status}`, { remove: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const json = JSON.parse(xhr.responseText);
|
||||
|
||||
if (!json || typeof json.url !== 'string') {
|
||||
failure(`Invalid JSON: ${xhr.responseText}`, { remove: true });
|
||||
return;
|
||||
}
|
||||
success(json.url);
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
failure(`Image upload failed due to a XHR Transport error. Code: ${xhr.status}`, { remove: true });
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', blobInfo.blob(), blobInfo.filename());
|
||||
// 需要水印
|
||||
formData.append('isWatermark', 'true');
|
||||
|
||||
Object.entries(getAuthHeaders()).forEach(([key, value]: any) => xhr.setRequestHeader(key, value));
|
||||
Object.entries(getSiteHeaders()).forEach(([key, value]: any) => xhr.setRequestHeader(key, value));
|
||||
xhr.send(formData);
|
||||
},
|
||||
|
||||
file_picker_callback(callback: any, val: any, meta: any) {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('type', 'file');
|
||||
|
||||
let fileSizeLimtByte = 0;
|
||||
let uploadUrl: string;
|
||||
if (meta.filetype === 'image') {
|
||||
fileSizeLimtByte = sysConfig.upload.imageLimitByte;
|
||||
input.setAttribute('accept', sysConfig.upload.imageInputAccept);
|
||||
uploadUrl = imageUploadUrl;
|
||||
// input.setAttribute('accept', 'image/*');
|
||||
} else if (meta.filetype === 'media') {
|
||||
fileSizeLimtByte = sysConfig.upload.mediaLimitByte;
|
||||
input.setAttribute('accept', sysConfig.upload.mediaInputAccept);
|
||||
uploadUrl = mediaUploadUrl;
|
||||
// input.setAttribute('accept', 'video/*');
|
||||
} else {
|
||||
fileSizeLimtByte = sysConfig.upload.fileLimitByte;
|
||||
input.setAttribute('accept', sysConfig.upload.fileInputAccept);
|
||||
uploadUrl = fileUploadUrl;
|
||||
}
|
||||
|
||||
/*
|
||||
Note: In modern browsers input[type="file"] is functional without
|
||||
even adding it to the DOM, but that might not be the case in some older
|
||||
or quirky browsers like IE, so you might want to add it to the DOM
|
||||
just in case, and visually hide it. And do not forget do remove it
|
||||
once you do not need it anymore.
|
||||
*/
|
||||
|
||||
input.onchange = (event: Event) => {
|
||||
const { files } = event.target as HTMLInputElement;
|
||||
const file = files?.item(0);
|
||||
if (!file) return;
|
||||
if (fileSizeLimtByte > 0 && file.size > fileSizeLimtByte) {
|
||||
tinymce.activeEditor.windowManager.alert(t('error.fileMaxSize', { size: `${fileSizeLimtByte / 1024 / 1024}MB` }));
|
||||
return;
|
||||
}
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', uploadUrl);
|
||||
|
||||
// xhr.upload.onprogress = (e) => {
|
||||
// progress((e.loaded / e.total) * 100);
|
||||
// };
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 403) {
|
||||
tinymce.activeEditor.windowManager.alert(`HTTP Error: ${xhr.status}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (xhr.status < 200 || xhr.status >= 300) {
|
||||
tinymce.activeEditor.windowManager.alert(`HTTP Error: ${xhr.status}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const json = JSON.parse(xhr.responseText);
|
||||
|
||||
if (!json || typeof json.url !== 'string') {
|
||||
tinymce.activeEditor.windowManager.alert(`Invalid JSON: ${xhr.responseText}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (meta.filetype === 'image') {
|
||||
callback(json.url, { alt: '' });
|
||||
} else if (meta.filetype === 'media') {
|
||||
callback(json.url);
|
||||
// callback('movie.mp4', { source2: 'alt.ogg', poster: 'image.jpg' });
|
||||
} else {
|
||||
callback(json.url, { text: json.name });
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
tinymce.activeEditor.windowManager.alert(`Image upload failed due to a XHR Transport error. Code: ${xhr.status}`);
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file, file.name);
|
||||
|
||||
Object.entries(getAuthHeaders()).forEach(([key, value]: any) => xhr.setRequestHeader(key, value));
|
||||
xhr.send(formData);
|
||||
};
|
||||
|
||||
input.click();
|
||||
},
|
||||
|
||||
readonly: props.disabled,
|
||||
selector: `#${elementId}`,
|
||||
// plugins: mergePlugins(props.init && props.init.plugins, props.plugins),
|
||||
// toolbar: props.toolbar || (props.init && props.init.toolbar),
|
||||
inline: inlineEditor,
|
||||
setup: (editor: any) => {
|
||||
vueEditor.value = editor;
|
||||
editor.on('init', (event: Event) => initEditor(event, props, ctx, editor, modelValue, formItem));
|
||||
if (props.init && typeof props.init.setup === 'function') {
|
||||
props.init.setup(editor);
|
||||
}
|
||||
|
||||
const replaceString = (content: string, search: string, replace: string): string => {
|
||||
let index = 0;
|
||||
do {
|
||||
index = content.indexOf(search, index);
|
||||
if (index !== -1) {
|
||||
content = content.substring(0, index) + replace + content.substring(index + search.length);
|
||||
index += replace.length - search.length + 1;
|
||||
}
|
||||
} while (index !== -1);
|
||||
return content;
|
||||
};
|
||||
const transparentSrc = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||
const replaceImageUrl = (content: string, targetUrl: string, replacementUrl: string): string => {
|
||||
const replacementString = `src="${replacementUrl}"${replacementUrl === transparentSrc ? ' data-mce-placeholder="1"' : ''}`;
|
||||
content = replaceString(content, `src="${targetUrl}"`, replacementString);
|
||||
content = replaceString(content, 'data-mce-src="' + targetUrl + '"', 'data-mce-src="' + replacementUrl + '"');
|
||||
return content;
|
||||
};
|
||||
const replaceUrlInUndoStack = (targetUrl: string, replacementUrl: string) => {
|
||||
editor.undoManager.data.forEach((level: any) => {
|
||||
if (level.type === 'fragmented') {
|
||||
level.fragments = level.fragments.map((fragment: any) => replaceImageUrl(fragment, targetUrl, replacementUrl));
|
||||
} else {
|
||||
level.content = replaceImageUrl(level.content, targetUrl, replacementUrl);
|
||||
}
|
||||
});
|
||||
};
|
||||
editor.on('SetContent', ({ content, format, paste }: { content: string; format?: string; paste?: boolean; selection?: boolean }) => {
|
||||
if (format === 'html' && paste && content.includes('src="')) {
|
||||
const images = Array.from(editor.getBody().getElementsByTagName('img')).filter((img: any) => {
|
||||
const src = img.src;
|
||||
if (src.startsWith(sysConfig.base.uploadUrlPrefix)) {
|
||||
return false;
|
||||
}
|
||||
if (img.hasAttribute('data-mce-bogus')) {
|
||||
return false;
|
||||
}
|
||||
if (img.hasAttribute('data-mce-placeholder')) {
|
||||
return false;
|
||||
}
|
||||
if (!src || src === transparentSrc) {
|
||||
return false;
|
||||
}
|
||||
if (src.indexOf('blob:') === 0) {
|
||||
return false;
|
||||
}
|
||||
if (src.indexOf('data:') === 0) {
|
||||
return false;
|
||||
}
|
||||
const host = new URL(src).host;
|
||||
for (let domain of sysConfig.security.ssrfList) {
|
||||
if (domain === '*' || host === domain || host.endsWith('.' + domain)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
images.forEach(async (image: any) => {
|
||||
const data = await fetchImage(image.src);
|
||||
if (data.status === -1) {
|
||||
console.warn(data.message);
|
||||
return;
|
||||
}
|
||||
const resultUri = data.result.url;
|
||||
const src = editor.convertURL(resultUri, 'src');
|
||||
replaceUrlInUndoStack(image.src, resultUri);
|
||||
editor.$(image).attr({
|
||||
src: resultUri,
|
||||
'data-mce-src': src,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
branding: false,
|
||||
};
|
||||
if (isTextarea(element.value)) {
|
||||
element.value.style.visibility = '';
|
||||
}
|
||||
tinymce.init({ toolbar_mode: 'sliding', ...finalInit });
|
||||
mounting = false;
|
||||
};
|
||||
watch(disabled, () => {
|
||||
if (vueEditor.value != null) {
|
||||
vueEditor.value.setMode(disabled.value ? 'readonly' : 'design');
|
||||
}
|
||||
});
|
||||
onMounted(async () => {
|
||||
initWrapper();
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
tinymce.remove(vueEditor.value);
|
||||
});
|
||||
if (!inlineEditor) {
|
||||
onActivated(() => {
|
||||
if (!mounting) {
|
||||
initWrapper();
|
||||
}
|
||||
});
|
||||
onDeactivated(() => {
|
||||
tinymce.remove(vueEditor.value);
|
||||
});
|
||||
}
|
||||
return { element, elementId, editor: vueEditor };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<textarea :id="elementId" ref="element"></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import Tinymce from './Tinymce.vue';
|
||||
export default Tinymce;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { Editor } from 'tinymce';
|
||||
import { doAction } from '../core/actions';
|
||||
|
||||
const register = (editor: Editor, defaultOptions: any): void => {
|
||||
editor.addCommand(defaultOptions.id, () => {
|
||||
doAction(editor, defaultOptions);
|
||||
});
|
||||
};
|
||||
|
||||
export { register };
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { Editor } from 'tinymce';
|
||||
|
||||
const doAction = (editor: Editor, defaultOptions: any): void => {
|
||||
editor.formatter.toggle(defaultOptions.id);
|
||||
editor.nodeChanged();
|
||||
// const { dom, selection } = editor;
|
||||
// const blocks = selection.getSelectedBlocks();
|
||||
// const styleName = 'text-indent';
|
||||
// let textIndentExists: boolean;
|
||||
// tinymce.each(blocks, (block: Element) => {
|
||||
// const parents = dom.getParents(block, undefined, dom.getRoot());
|
||||
// const parent = parents[parents.length - 1];
|
||||
// if (!['p', 'div'].includes(parent.nodeName.toLowerCase())) {
|
||||
// return;
|
||||
// }
|
||||
// if (textIndentExists === undefined) {
|
||||
// // 使用 parseInt 可以将 0em 或 0px 转换成 0
|
||||
// textIndentExists = parseInt(dom.getStyle(parent, styleName)) > 0;
|
||||
// }
|
||||
// dom.setStyle(parent, styleName, textIndentExists ? '' : '2em');
|
||||
// });
|
||||
};
|
||||
|
||||
export { doAction };
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import Plugin from './plugin';
|
||||
|
||||
const defaultOptions = {
|
||||
id: 'indent2em',
|
||||
name: '首行缩进',
|
||||
tooltip: '首行缩进',
|
||||
icon: '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M170.666667 563.2v-102.4H887.466667v102.4zM170.666667 836.266667v-102.4H887.466667v102.4zM512 290.133333v-102.4H887.466667v102.4zM238.933333 341.333333V136.533333l204.8 102.4z" p-id="5210"></path></svg>',
|
||||
};
|
||||
|
||||
Plugin(defaultOptions);
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import tinymce from 'tinymce';
|
||||
import * as commands from './api/commands';
|
||||
import * as buttons from './ui/buttons';
|
||||
|
||||
export default (defaultOptions): void => {
|
||||
tinymce.PluginManager.add('indent2em', function (editor) {
|
||||
commands.register(editor, defaultOptions);
|
||||
buttons.register(editor, defaultOptions);
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import { Editor, Ui } from 'tinymce';
|
||||
|
||||
const register = (editor: Editor, defaultOptions: any): void => {
|
||||
const onAction = () => editor.execCommand(defaultOptions.id);
|
||||
|
||||
// const onSetup = (buttonApi: Ui.Toolbar.ToolbarToggleButtonInstanceApi) => {
|
||||
// const indentSelector = '*[style*="text-indent"], *[data-mce-style*="text-indent"]';
|
||||
// const containerSelector = 'p,div';
|
||||
// const unbindActiveSelectorChange = editor.selection.selectorChangedWithUnbind(indentSelector, (active: boolean, args: { node: Node; parents: Element[] }) => {
|
||||
// const parent = editor.dom.getParent(args.node, containerSelector);
|
||||
// // 使用 parseInt 可以将 0em 或 0px 转换成 0
|
||||
// buttonApi.setActive(parent != null && parseInt(editor.dom.getStyle(parent, 'text-indent')) > 0 && active);
|
||||
// }).unbind;
|
||||
// const unbindDesabledSelectorChange = editor.selection.selectorChangedWithUnbind(containerSelector, (active: boolean) => {
|
||||
// buttonApi.setDisabled(!active);
|
||||
// }).unbind;
|
||||
// return () => {
|
||||
// unbindActiveSelectorChange();
|
||||
// unbindDesabledSelectorChange();
|
||||
// };
|
||||
// };
|
||||
|
||||
// const onSetup = (api: Ui.Toolbar.ToolbarToggleButtonInstanceApi) => {
|
||||
// const { dom } = editor;
|
||||
// const nodeChangeHandler = (e: EditorEvent<Events.NodeChangeEvent>) => {
|
||||
// const { parents } = e;
|
||||
// const parent = parents[parents.length - 1];
|
||||
// const enabled = ['p', 'div'].includes(parent?.nodeName.toLowerCase());
|
||||
// api.setDisabled(!enabled);
|
||||
// // 使用 parseInt 可以将 0em 或 0px 转换成 0
|
||||
// api.setActive(enabled && parseInt(dom.getStyle(parent, 'text-indent')) > 0);
|
||||
// };
|
||||
// editor.on('NodeChange', nodeChangeHandler);
|
||||
// return () => editor.off('NodeChange', nodeChangeHandler);
|
||||
// };
|
||||
|
||||
const onSetup = (api: Ui.Toolbar.ToolbarToggleButtonInstanceApi) => {
|
||||
const indent2em = [
|
||||
{
|
||||
selector: 'p,div',
|
||||
styles: {
|
||||
textIndent: '2em',
|
||||
},
|
||||
inherit: false,
|
||||
},
|
||||
];
|
||||
editor.formatter.register(defaultOptions.id, indent2em);
|
||||
const nodeChangeHandler = () => {
|
||||
api.setActive(editor.formatter.match(defaultOptions.id));
|
||||
};
|
||||
editor.on('NodeChange', nodeChangeHandler);
|
||||
return () => editor.off('NodeChange', nodeChangeHandler);
|
||||
};
|
||||
|
||||
if (!editor.ui.registry.getAll().icons[defaultOptions.id]) {
|
||||
editor.ui.registry.addIcon(defaultOptions.id, defaultOptions.icon);
|
||||
}
|
||||
|
||||
editor.ui.registry.addToggleButton(defaultOptions.id, {
|
||||
icon: defaultOptions.id,
|
||||
tooltip: defaultOptions.tooltip,
|
||||
onAction,
|
||||
onSetup,
|
||||
});
|
||||
|
||||
editor.ui.registry.addToggleMenuItem(defaultOptions.id, {
|
||||
icon: defaultOptions.id,
|
||||
text: defaultOptions.tooltip,
|
||||
onAction,
|
||||
});
|
||||
};
|
||||
|
||||
export { register };
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
import { Ref, watch, SetupContext } from 'vue';
|
||||
|
||||
const validEvents = [
|
||||
'onActivate',
|
||||
'onAddUndo',
|
||||
'onBeforeAddUndo',
|
||||
'onBeforeExecCommand',
|
||||
'onBeforeGetContent',
|
||||
'onBeforeRenderUI',
|
||||
'onBeforeSetContent',
|
||||
'onBeforePaste',
|
||||
'onBlur',
|
||||
'onChange',
|
||||
'onClearUndos',
|
||||
'onClick',
|
||||
'onContextMenu',
|
||||
'onCopy',
|
||||
'onCut',
|
||||
'onDblclick',
|
||||
'onDeactivate',
|
||||
'onDirty',
|
||||
'onDrag',
|
||||
'onDragDrop',
|
||||
'onDragEnd',
|
||||
'onDragGesture',
|
||||
'onDragOver',
|
||||
'onDrop',
|
||||
'onExecCommand',
|
||||
'onFocus',
|
||||
'onFocusIn',
|
||||
'onFocusOut',
|
||||
'onGetContent',
|
||||
'onHide',
|
||||
'onInit',
|
||||
'onKeyDown',
|
||||
'onKeyPress',
|
||||
'onKeyUp',
|
||||
'onLoadContent',
|
||||
'onMouseDown',
|
||||
'onMouseEnter',
|
||||
'onMouseLeave',
|
||||
'onMouseMove',
|
||||
'onMouseOut',
|
||||
'onMouseOver',
|
||||
'onMouseUp',
|
||||
'onNodeChange',
|
||||
'onObjectResizeStart',
|
||||
'onObjectResized',
|
||||
'onObjectSelected',
|
||||
'onPaste',
|
||||
'onPostProcess',
|
||||
'onPostRender',
|
||||
'onPreProcess',
|
||||
'onProgressState',
|
||||
'onRedo',
|
||||
'onRemove',
|
||||
'onReset',
|
||||
'onSaveContent',
|
||||
'onSelectionChange',
|
||||
'onSetAttrib',
|
||||
'onSetContent',
|
||||
'onShow',
|
||||
'onSubmit',
|
||||
'onUndo',
|
||||
'onVisualAid',
|
||||
];
|
||||
|
||||
const isValidKey = (key: string): boolean => validEvents.map((event) => event.toLowerCase()).indexOf(key.toLowerCase()) !== -1;
|
||||
|
||||
const bindHandlers = (initEvent: Event, listeners: any, editor: any): void => {
|
||||
Object.keys(listeners)
|
||||
.filter(isValidKey)
|
||||
.forEach((key: string) => {
|
||||
const handler = listeners[key];
|
||||
if (typeof handler === 'function') {
|
||||
if (key === 'onInit') {
|
||||
handler(initEvent, editor);
|
||||
} else {
|
||||
editor.on(key.substring(2), (e: any) => handler(e, editor));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const bindModelHandlers = (props: any, ctx: SetupContext, editor: any, modelValue: Ref<string>, formItem: any): void => {
|
||||
const modelEvents = props.modelEvents ? props.modelEvents : null;
|
||||
const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents;
|
||||
|
||||
watch(modelValue, (val: string, prevVal: string) => {
|
||||
if (editor && typeof val === 'string' && val !== prevVal && val !== editor.getContent({ format: props.outputFormat })) {
|
||||
editor.setContent(val);
|
||||
}
|
||||
});
|
||||
|
||||
// 要加上 paste 事件,否则首次粘贴时内容会为空。使用 'change input paste undo redo' 在快速剪切、粘贴的情况下,有可能还是会出现 '必填字段' 的错误提示。
|
||||
editor.on(normalizedEvents ?? 'change keyup undo redo', () => {
|
||||
const content = editor.getContent({ format: props.outputFormat });
|
||||
ctx.emit('update:modelValue', content);
|
||||
ctx.emit('input', content);
|
||||
ctx.emit('change', content);
|
||||
formItem?.validate?.('change').catch((err: any) => {
|
||||
if (import.meta.env.MODE !== 'production') {
|
||||
console.warn(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
editor.on('blur', (event: any) => {
|
||||
ctx.emit('blur', event);
|
||||
});
|
||||
editor.on('keydown', (event: any) => {
|
||||
ctx.emit('keydown', event);
|
||||
});
|
||||
};
|
||||
|
||||
const initEditor = (initEvent: Event, props: any, ctx: any, editor: any, modelValue: Ref<string>, formItem: any): void => {
|
||||
editor.setContent(modelValue.value ?? '');
|
||||
bindModelHandlers(props, ctx, editor, modelValue, formItem);
|
||||
bindHandlers(initEvent, ctx.attrs, editor);
|
||||
};
|
||||
|
||||
let unique = 0;
|
||||
const uuid = (prefix: string): string => {
|
||||
const time = Date.now();
|
||||
const random = Math.floor(Math.random() * 1000000000);
|
||||
unique += 1;
|
||||
return `${prefix}_${random + unique}${String(time)}`;
|
||||
};
|
||||
|
||||
const isTextarea = (element: Element | null): element is HTMLTextAreaElement => element !== null && element.tagName.toLowerCase() === 'textarea';
|
||||
|
||||
const normalizePluginArray = (plugins?: string | string[]): string[] => {
|
||||
if (typeof plugins === 'undefined' || plugins === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.isArray(plugins) ? plugins : plugins.split(' ');
|
||||
};
|
||||
|
||||
const mergePlugins = (initPlugins: string | string[], inputPlugins?: string | string[]): string[] => normalizePluginArray(initPlugins).concat(normalizePluginArray(inputPlugins));
|
||||
|
||||
export { bindHandlers, bindModelHandlers, initEditor, isValidKey, uuid, isTextarea, mergePlugins };
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
<script lang="ts">
|
||||
const editorEvents = ['load', 'change', 'caretChange', 'focus', 'blur', 'keydown', 'keyup', 'beforePreviewRender', 'beforeConvertWysiwygToMarkdown'];
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, toRefs, watch, PropType, onUnmounted, nextTick } from 'vue';
|
||||
import { useFormItem } from 'element-plus';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import { decodeHTML } from 'entities';
|
||||
import Editor, { EditorType, PreviewStyle, EditorOptions } from '@toast-ui/editor';
|
||||
import chart from '@toast-ui/editor-plugin-chart';
|
||||
import codeSyntaxHighlight from '@toast-ui/editor-plugin-code-syntax-highlight';
|
||||
import tableMergedCell from '@toast-ui/editor-plugin-table-merged-cell';
|
||||
import uml from '@toast-ui/editor-plugin-uml';
|
||||
import Prism from 'prismjs';
|
||||
import { addImageBlobHook, toggleFullScreen, clickOutside } from './utils';
|
||||
import '@toast-ui/editor/dist/i18n/zh-cn';
|
||||
import '@toast-ui/editor/dist/i18n/zh-tw';
|
||||
import '@toast-ui/editor/dist/toastui-editor.css';
|
||||
import '@toast-ui/chart/dist/toastui-chart.css';
|
||||
import 'prismjs/themes/prism.css';
|
||||
import 'prismjs/components/prism-clojure.js';
|
||||
import '@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css';
|
||||
import '@toast-ui/editor-plugin-table-merged-cell/dist/toastui-editor-plugin-table-merged-cell.css';
|
||||
|
||||
defineOptions({
|
||||
name: 'TuiEditor',
|
||||
});
|
||||
const props = defineProps({
|
||||
modelValue: { type: String, default: '' },
|
||||
html: { type: String, default: '' },
|
||||
initialEditType: { type: String as PropType<EditorType>, default: 'markdown' },
|
||||
height: { type: String, default: '300px' },
|
||||
previewStyle: { type: String as PropType<PreviewStyle>, default: 'tab' },
|
||||
language: { type: String, default: 'en' },
|
||||
options: { type: Object, default: null },
|
||||
});
|
||||
const emit = defineEmits([...editorEvents, 'update:modelValue', 'update:html', 'different']);
|
||||
|
||||
const { modelValue, html, initialEditType, height, previewStyle, language, options } = toRefs(props);
|
||||
const toastuiEditor = ref<any>();
|
||||
let editor: Editor;
|
||||
const { formItem } = useFormItem();
|
||||
|
||||
watch(previewStyle, () => {
|
||||
editor.changePreviewStyle(previewStyle.value);
|
||||
});
|
||||
watch(height, () => {
|
||||
editor.setHeight(height.value);
|
||||
});
|
||||
watch([modelValue, html], () => {
|
||||
updateEditorValue();
|
||||
});
|
||||
|
||||
const createFullscreenButton = () => {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'toastui-editor-toolbar-icons text-xl';
|
||||
button.style.backgroundImage = 'none';
|
||||
button.style.margin = '0';
|
||||
button.innerHTML = 'F';
|
||||
button.addEventListener('click', () => {
|
||||
toggleFullScreen(editor, toastuiEditor.value, height.value);
|
||||
});
|
||||
return button;
|
||||
};
|
||||
|
||||
const updateEditorValue = () => {
|
||||
if (modelValue.value && modelValue.value !== editor.getMarkdown()) {
|
||||
editor.setMarkdown(modelValue.value);
|
||||
} else if (!modelValue.value && html.value) {
|
||||
// markdown无值,html有值,则用设置html
|
||||
editor.setHTML(html.value);
|
||||
// 防止在切换编辑器时,因清空markdown值导致事件无效
|
||||
nextTick().then(() => {
|
||||
emit('update:modelValue', editor.getMarkdown());
|
||||
});
|
||||
return;
|
||||
} else if (!modelValue.value && !html.value) {
|
||||
// markdown无值,html无值,则清空编辑器
|
||||
editor.setMarkdown('');
|
||||
}
|
||||
// 检查markdown生成的HTML和原HTML是否匹配。但是经过后台处理的HTML肯定和编辑器返回的不一样
|
||||
const currHtml = getHTML();
|
||||
if (modelValue.value && decodeHTML(html.value) !== currHtml) {
|
||||
// 触发不匹配事件
|
||||
emit('different', html.value, currHtml);
|
||||
emit('update:html', currHtml);
|
||||
}
|
||||
};
|
||||
|
||||
// 内容为空时,默认生成以下HTML,应作为空串处理
|
||||
const emptyHtml = '<p><br class="ProseMirror-trailingBreak"></p>';
|
||||
const eventOptions: any = {};
|
||||
|
||||
editorEvents.forEach((eventName: string) => {
|
||||
eventOptions[eventName] = (...args: any[]) => {
|
||||
if (eventName === 'change') {
|
||||
const newHtml = getHTML();
|
||||
if (newHtml !== html.value) {
|
||||
emit('update:html', newHtml !== emptyHtml ? newHtml : '');
|
||||
}
|
||||
const newMarkdown = editor.getMarkdown();
|
||||
if (newMarkdown !== modelValue.value) {
|
||||
emit('update:modelValue', newMarkdown);
|
||||
}
|
||||
formItem?.validate?.('change').catch((err: any) => {
|
||||
if (import.meta.env.MODE !== 'production') {
|
||||
console.warn(err);
|
||||
}
|
||||
});
|
||||
} else if (eventName === 'keydown') {
|
||||
// 第一个参数为 editorType,第二个参数为事件对象
|
||||
const event = args[1];
|
||||
if (event.ctrlKey && !event.shiftKey && event.key.toLowerCase() === 'z') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
editor.exec('undo');
|
||||
} else if ((event.ctrlKey && event.key.toLowerCase() === 'y') || (event.ctrlKey && event.shiftKey && event.key.toLowerCase() === 'z')) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
editor.exec('redo');
|
||||
}
|
||||
}
|
||||
emit(eventName, ...args);
|
||||
};
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const chartOptions = {
|
||||
maxWidth: 800,
|
||||
maxHeight: 400,
|
||||
};
|
||||
const computedOptions: EditorOptions = {
|
||||
...options?.value,
|
||||
|
||||
initialValue: modelValue.value ?? '',
|
||||
initialEditType: initialEditType.value,
|
||||
height: height.value,
|
||||
previewStyle: previewStyle.value,
|
||||
language: language.value,
|
||||
autofocus: false,
|
||||
usageStatistics: false,
|
||||
useCommandShortcut: false,
|
||||
el: toastuiEditor.value,
|
||||
events: eventOptions,
|
||||
hooks: { addImageBlobHook },
|
||||
plugins: [[chart, chartOptions], [codeSyntaxHighlight, { highlighter: Prism }], tableMergedCell, uml],
|
||||
toolbarItems: [
|
||||
[
|
||||
{
|
||||
name: 'fullscreen',
|
||||
el: createFullscreenButton(),
|
||||
tooltip: 'Fullscreen',
|
||||
},
|
||||
],
|
||||
['heading', 'bold', 'italic', 'strike'],
|
||||
['hr', 'quote'],
|
||||
['ul', 'ol', 'task', 'indent', 'outdent'],
|
||||
['table', 'image', 'link'],
|
||||
['code', 'codeblock'],
|
||||
['scrollSync'],
|
||||
],
|
||||
};
|
||||
editor = new Editor(computedOptions);
|
||||
updateEditorValue();
|
||||
});
|
||||
onUnmounted(() => {
|
||||
editorEvents.forEach((event) => {
|
||||
editor.off(event);
|
||||
});
|
||||
});
|
||||
const getHTML = () => {
|
||||
const html = editor.getHTML();
|
||||
if (html != null) {
|
||||
return html.replaceAll(/<p><br[ /]*><\/p>/gi, '');
|
||||
}
|
||||
return html;
|
||||
};
|
||||
const setHTML = (html: string): void => editor.setHTML(html);
|
||||
const getMarkdown = () => editor.getMarkdown();
|
||||
const setMarkdown = (markdown: string): void => editor.setMarkdown(markdown);
|
||||
const getRootElement = () => toastuiEditor.value;
|
||||
defineExpose({ getRootElement, getHTML, getMarkdown, setHTML, setMarkdown });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 在ElementPlus的对话框中,“更多”工具条按钮点击后,点击其它地方不会关闭工具条 -->
|
||||
<div ref="toastuiEditor" v-on-click-outside="clickOutside"></div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.ProseMirror),
|
||||
:deep(.toastui-editor-contents) {
|
||||
font-family:
|
||||
ui-sans-serif,
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
'Helvetica Neue',
|
||||
Arial,
|
||||
'Noto Sans',
|
||||
'PingFang SC',
|
||||
'Hiragino Sans GB',
|
||||
'Microsoft YaHei',
|
||||
'WenQuanYi Micro Hei',
|
||||
sans-serif,
|
||||
'Apple Color Emoji',
|
||||
'Segoe UI Emoji',
|
||||
'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default as TuiEditor } from './TuiEditor.vue';
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
import { imageUploadUrl } from '@/api/config';
|
||||
import { getAuthHeaders } from '@/utils/auth';
|
||||
import { getSiteHeaders } from '@/utils/common';
|
||||
import Editor from '@toast-ui/editor';
|
||||
|
||||
/**
|
||||
* 在对话框中使用编辑器时,点击更多工具按钮后,再点击页面其它地方,弹出的工具不会消失。需要认为的抛出一个点击事件。
|
||||
*/
|
||||
export const clickOutside = (event: Event) => {
|
||||
if (event.bubbles || !event.cancelable || event.composed) {
|
||||
const myEvent = new Event('click', { bubbles: false, cancelable: true, composed: false });
|
||||
document.dispatchEvent(myEvent);
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleFullScreen = (editor: Editor, element: HTMLElement, height: string): void => {
|
||||
const style = element.style;
|
||||
if (style.height !== '100vh') {
|
||||
style.height = '100vh';
|
||||
style.width = '100vw';
|
||||
style.position = 'fixed';
|
||||
style.zIndex = '10000000000';
|
||||
style.top = '0px';
|
||||
style.left = '0px';
|
||||
style.backgroundColor = 'white';
|
||||
editor.changePreviewStyle('vertical');
|
||||
} else {
|
||||
style.height = height;
|
||||
style.width = '';
|
||||
style.position = '';
|
||||
style.zIndex = '';
|
||||
style.top = '';
|
||||
style.left = '';
|
||||
style.backgroundColor = '';
|
||||
editor.changePreviewStyle('tab');
|
||||
}
|
||||
};
|
||||
|
||||
export const addImageBlobHook = (blob: Blob | File, callback: any): void => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', imageUploadUrl);
|
||||
Object.entries(getSiteHeaders()).forEach(([key, value]: any) => xhr.setRequestHeader(key, value));
|
||||
|
||||
// xhr.upload.onprogress = (e) => {
|
||||
// (e.loaded / e.total) * 100;
|
||||
// };
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 403) {
|
||||
ElMessageBox.alert(`HTTP Error: ${xhr.status}`, { type: 'warning' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (xhr.status < 200 || xhr.status >= 300) {
|
||||
ElMessageBox.alert(`HTTP Error: ${xhr.status}`, { type: 'warning' });
|
||||
return;
|
||||
}
|
||||
|
||||
const json = JSON.parse(xhr.responseText);
|
||||
|
||||
if (!json || typeof json.url !== 'string') {
|
||||
ElMessageBox.alert(`Invalid JSON: ${xhr.responseText}`, { type: 'warning' });
|
||||
return;
|
||||
}
|
||||
callback(json.url);
|
||||
};
|
||||
|
||||
xhr.onerror = () => {
|
||||
ElMessageBox.alert(`Image upload failed due to a XHR Transport error. Code: ${xhr.status}`, { type: 'warning' });
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', blob);
|
||||
|
||||
Object.entries(getAuthHeaders()).forEach(([key, value]: any) => xhr.setRequestHeader(key, value));
|
||||
xhr.send(formData);
|
||||
};
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, toRefs, computed, PropType } from 'vue';
|
||||
import { UploadFile, UploadFiles } from 'element-plus';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { handleError } from '@/utils/request';
|
||||
import { getAuthHeaders } from '@/utils/auth';
|
||||
import { getSiteHeaders } from '@/utils/common';
|
||||
import { useSysConfigStore } from '@/stores/sysConfigStore';
|
||||
import { imageUploadUrl, videoUploadUrl, audioUploadUrl, docUploadUrl, fileUploadUrl } from '@/api/config';
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String as PropType<'image' | 'video' | 'audio' | 'library' | 'doc' | 'file' | 'any'>,
|
||||
default: 'file',
|
||||
validator: (value: string) => ['image', 'video', 'audio', 'library', 'doc', 'file', 'any'].includes(value),
|
||||
},
|
||||
button: { type: String, default: null },
|
||||
data: { type: Object as PropType<Record<string, any>>, default: null },
|
||||
uploadAction: { type: String, default: null },
|
||||
fileAccept: { type: String, default: null },
|
||||
fileMaxSize: { type: Number, default: null },
|
||||
multiple: { type: Boolean },
|
||||
disabled: { type: Boolean, default: false },
|
||||
onSuccess: { type: Function as PropType<((response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) => void) | undefined>, default: null },
|
||||
});
|
||||
|
||||
const { type, uploadAction, fileAccept, fileMaxSize } = toRefs(props);
|
||||
const { t } = useI18n();
|
||||
const sysConfig = useSysConfigStore();
|
||||
const progressFile = ref<any>({});
|
||||
const action = computed(() => {
|
||||
if (uploadAction?.value != null) {
|
||||
return uploadAction.value;
|
||||
}
|
||||
switch (type.value) {
|
||||
case 'image':
|
||||
return imageUploadUrl;
|
||||
case 'video':
|
||||
return videoUploadUrl;
|
||||
case 'audio':
|
||||
return audioUploadUrl;
|
||||
case 'library':
|
||||
return docUploadUrl;
|
||||
case 'doc':
|
||||
return docUploadUrl;
|
||||
case 'file':
|
||||
return fileUploadUrl;
|
||||
default:
|
||||
throw new Error(`Type not support: ${type.value}`);
|
||||
}
|
||||
});
|
||||
const accept = computed(() => {
|
||||
if (fileAccept?.value != null) {
|
||||
return fileAccept.value;
|
||||
}
|
||||
switch (type.value) {
|
||||
case 'image':
|
||||
return sysConfig.upload.imageInputAccept;
|
||||
case 'video':
|
||||
return sysConfig.upload.videoInputAccept;
|
||||
case 'audio':
|
||||
return sysConfig.upload.audioInputAccept;
|
||||
case 'library':
|
||||
return sysConfig.upload.libraryInputAccept;
|
||||
case 'doc':
|
||||
return sysConfig.upload.docInputAccept;
|
||||
case 'file':
|
||||
return sysConfig.upload.fileInputAccept;
|
||||
case 'any':
|
||||
return undefined;
|
||||
default:
|
||||
throw new Error(`Type not support: ${type.value}`);
|
||||
}
|
||||
});
|
||||
const maxSize = computed(() => {
|
||||
if (fileMaxSize?.value != null) {
|
||||
return fileMaxSize.value;
|
||||
}
|
||||
switch (type.value) {
|
||||
case 'image':
|
||||
return sysConfig.upload.imageLimitByte;
|
||||
case 'video':
|
||||
return sysConfig.upload.videoLimitByte;
|
||||
case 'audio':
|
||||
return sysConfig.upload.audioLimitByte;
|
||||
case 'library':
|
||||
return sysConfig.upload.libraryLimitByte;
|
||||
case 'doc':
|
||||
return sysConfig.upload.docLimitByte;
|
||||
case 'file':
|
||||
return sysConfig.upload.fileLimitByte;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
const beforeUpload = (file: any) => {
|
||||
if (maxSize.value > 0 && file.size > maxSize.value) {
|
||||
ElMessage.error(t('error.fileMaxSize', { size: `${maxSize.value / 1024 / 1024} MB` }));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const onError = (error: Error) => {
|
||||
handleError(JSON.parse(error.message));
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-upload
|
||||
:action="action"
|
||||
:headers="{ ...getAuthHeaders(), ...getSiteHeaders() }"
|
||||
:data="data"
|
||||
:accept="accept"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="onSuccess"
|
||||
:on-progress="(event, file) => (progressFile = file)"
|
||||
:on-error="onError"
|
||||
:show-file-list="false"
|
||||
:disabled="disabled"
|
||||
:multiple="multiple"
|
||||
drag
|
||||
>
|
||||
<!--
|
||||
// 用于测试上传进度条
|
||||
action="https://jsonplaceholder.typicode.com/posts/"
|
||||
-->
|
||||
<slot>
|
||||
<span>{{ button ?? $t('clickOrDragToUpload') }}</span>
|
||||
<!-- <el-button type="primary" :disabled="disabled">{{ button ?? $t('clickOrDragToUpload') }}</el-button> -->
|
||||
</slot>
|
||||
</el-upload>
|
||||
<el-progress v-if="progressFile.status === 'uploading'" :percentage="parseInt(progressFile.percentage, 10)"></el-progress>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-upload-dragger) {
|
||||
padding: 0 20px;
|
||||
@apply bg-primary-lighter text-primary;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, toRefs, computed, watch } from 'vue';
|
||||
import { useFormItem } from 'element-plus';
|
||||
import { Close, Document, CircleCheck } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { handleError } from '@/utils/request';
|
||||
import { getAuthHeaders } from '@/utils/auth';
|
||||
import { getSiteHeaders } from '@/utils/common';
|
||||
import { useSysConfigStore } from '@/stores/sysConfigStore';
|
||||
import { fileUploadUrl } from '@/api/config';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Array, default: () => [] },
|
||||
fileAccept: { type: String, default: null },
|
||||
fileMaxSize: { type: Number, default: null },
|
||||
disabled: { type: Boolean, default: false },
|
||||
});
|
||||
const emit = defineEmits({ 'update:modelValue': null });
|
||||
|
||||
const { fileAccept, fileMaxSize, modelValue } = toRefs(props);
|
||||
const { t } = useI18n();
|
||||
const sysConfig = useSysConfigStore();
|
||||
const progressFile = ref<any>({});
|
||||
const fileList = computed({
|
||||
get: (): any[] => modelValue.value,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
});
|
||||
const { formItem } = useFormItem();
|
||||
watch(
|
||||
fileList,
|
||||
() => {
|
||||
formItem?.validate?.('change').catch((err: any) => {
|
||||
if (import.meta.env.MODE !== 'production') {
|
||||
console.warn(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
const previewVisible = ref<boolean>(false);
|
||||
const previewFile = ref<any>({});
|
||||
const form = ref<any>();
|
||||
|
||||
const handlePreview = (file: any) => {
|
||||
previewFile.value = file;
|
||||
previewVisible.value = true;
|
||||
};
|
||||
const handleSubmit = () => {
|
||||
form.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return;
|
||||
previewVisible.value = false;
|
||||
});
|
||||
};
|
||||
const accept = computed(() => fileAccept?.value ?? sysConfig.upload.fileInputAccept);
|
||||
const maxSize = computed(() => fileMaxSize?.value ?? sysConfig.upload.fileLimitByte);
|
||||
const beforeUpload = (file: any) => {
|
||||
if (maxSize.value > 0 && file.size > maxSize.value) {
|
||||
ElMessage.error(t('error.fileMaxSize', { size: `${maxSize.value / 1024 / 1024} MB` }));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const onError = (error: Error) => {
|
||||
handleError(JSON.parse(error.message));
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<el-upload
|
||||
:action="fileUploadUrl"
|
||||
:headers="{ ...getAuthHeaders(), ...getSiteHeaders() }"
|
||||
:accept="accept"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="(res: any) => fileList.push({ name: res.name, url: res.url, length: res.size })"
|
||||
:on-progress="(event: any, file: any) => (progressFile = file)"
|
||||
:on-error="onError"
|
||||
:show-file-list="false"
|
||||
:disabled="disabled"
|
||||
multiple
|
||||
drag
|
||||
>
|
||||
<!--
|
||||
// 用于测试上传进度条
|
||||
action="https://jsonplaceholder.typicode.com/posts/"
|
||||
-->
|
||||
{{ $t('clickOrDragToUpload') }}
|
||||
<!-- <el-button type="primary">{{ $t('clickToUpload') }}</el-button> -->
|
||||
</el-upload>
|
||||
<el-progress v-if="progressFile.status === 'uploading'" :percentage="parseInt(progressFile.percentage, 10)"></el-progress>
|
||||
<transition-group tag="ul" :class="['el-upload-list', 'el-upload-list--text', { 'is-disabled': disabled }]" name="el-list">
|
||||
<li v-for="file in fileList" :key="file.url" class="el-upload-list__item is-success">
|
||||
<a class="el-upload-list__item-name" @click="() => handlePreview(file)">
|
||||
<el-icon class="el-icon--document"><Document /></el-icon>{{ file.name }}
|
||||
</a>
|
||||
<label class="el-upload-list__item-status-label">
|
||||
<el-icon class="el-icon--upload-success el-icon--circle-check"><CircleCheck /></el-icon>
|
||||
</label>
|
||||
<el-icon v-if="!disabled" class="el-icon--close" @click="() => fileList.splice(fileList.indexOf(file), 1)"><Close /></el-icon>
|
||||
</li>
|
||||
</transition-group>
|
||||
<el-dialog v-model="previewVisible" :title="$t('article.fileList.attribute')" top="5vh" :width="768" append-to-body>
|
||||
<el-form ref="form" :model="previewFile" label-width="150px">
|
||||
<el-form-item prop="name" :label="$t('name')" :rules="{ required: true, message: () => $t('v.required') }">
|
||||
<el-input v-model="previewFile.name" maxlength="100"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="length" :label="$t('size')" :rules="{ required: true, message: () => $t('v.required') }">
|
||||
<el-input v-model="previewFile.length" maxlength="19">
|
||||
<template #append>Byte</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="url" label="URL" :rules="{ required: true, message: () => $t('v.required') }">
|
||||
<el-input v-model="previewFile.url" maxlength="255"></el-input>
|
||||
</el-form-item>
|
||||
<el-button type="primary" native-type="submit" @click.prevent="() => handleSubmit()">{{ $t('save') }}</el-button>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-upload-dragger) {
|
||||
padding: 0 20px;
|
||||
@apply bg-primary-lighter text-primary;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, toRefs, PropType } from 'vue';
|
||||
import Cropper from 'cropperjs';
|
||||
import 'cropperjs/dist/cropper.css';
|
||||
import { cropImage, cropAvatar } from '@/api/config';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Boolean, required: true },
|
||||
/**
|
||||
* image: 图片上传, avatar: 头像上传
|
||||
*/
|
||||
type: { type: String as PropType<'image' | 'avatar'>, default: 'image' },
|
||||
src: { type: String, default: null },
|
||||
width: { type: Number, default: null },
|
||||
height: { type: Number, default: null },
|
||||
thumbnailWidth: { type: Number, default: null },
|
||||
thumbnailHeight: { type: Number, default: null },
|
||||
});
|
||||
const emit = defineEmits({ 'update:modelValue': null, success: null });
|
||||
|
||||
const { modelValue, type, src, width, height, thumbnailWidth, thumbnailHeight } = toRefs(props);
|
||||
const visible = computed({
|
||||
get: () => modelValue.value,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
});
|
||||
|
||||
const imgRef = ref<any>();
|
||||
const cropper = ref<any>();
|
||||
const cropParam = ref<any>({});
|
||||
|
||||
const initCropper = () => {
|
||||
if (imgRef.value) {
|
||||
cropper.value = new Cropper(imgRef.value, {
|
||||
aspectRatio: width?.value && height?.value ? width.value / height.value : NaN,
|
||||
autoCropArea: width?.value && height?.value ? 1 : 0.8,
|
||||
viewMode: 1,
|
||||
minCropBoxWidth: width?.value ?? 16,
|
||||
minCropBoxHeight: height?.value ?? 16,
|
||||
zoomable: false,
|
||||
crop(event) {
|
||||
cropParam.value.url = src.value;
|
||||
cropParam.value.x = Math.floor(event.detail.x);
|
||||
cropParam.value.y = Math.floor(event.detail.y);
|
||||
cropParam.value.width = Math.floor(event.detail.width);
|
||||
cropParam.value.height = Math.floor(event.detail.height);
|
||||
cropParam.value.maxWidth = width?.value;
|
||||
cropParam.value.maxHeight = height?.value;
|
||||
cropParam.value.thumbnailWidth = thumbnailWidth?.value;
|
||||
cropParam.value.thumbnailHeight = thumbnailHeight?.value;
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
const destroyCropper = () => {
|
||||
if (cropper.value) {
|
||||
cropper.value.destroy();
|
||||
}
|
||||
};
|
||||
const handleSubmit = async () => {
|
||||
visible.value = false;
|
||||
if (type.value === 'avatar') {
|
||||
emit('success', (await cropAvatar(cropParam.value)).url);
|
||||
} else {
|
||||
emit('success', (await cropImage(cropParam.value)).url);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="$t('imageCrop')" top="5vh" :width="768" destroy-on-close append-to-body @closed="() => destroyCropper()">
|
||||
<div class="text-center">
|
||||
<img ref="imgRef" :src="src" alt="" class="inline" style="max-height: 410px" @load="() => initCropper()" />
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<el-button type="primary" native-type="submit" class="mt-4" @click.prevent="() => handleSubmit()">{{ $t('save') }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* Ensure the size of the image fit the container perfectly */
|
||||
:deep(img) {
|
||||
display: block;
|
||||
/* This rule is very important, please don't ignore this */
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, toRefs, computed, watch, PropType } from 'vue';
|
||||
import { useFormItem } from 'element-plus';
|
||||
import { Plus, Crop, View, Delete } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import draggable from 'vuedraggable';
|
||||
import { handleError } from '@/utils/request';
|
||||
import { getAuthHeaders } from '@/utils/auth';
|
||||
import { getSiteHeaders } from '@/utils/common';
|
||||
import { useSysConfigStore } from '@/stores/sysConfigStore';
|
||||
import { imageUploadUrl } from '@/api/config';
|
||||
import ImageCropper from './ImageCropper.vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Array, default: () => [] },
|
||||
fileAccept: { type: String, default: null },
|
||||
fileMaxSize: { type: Number, default: null },
|
||||
maxWidth: { type: Number, default: null },
|
||||
maxHeight: { type: Number, default: null },
|
||||
listType: { type: String as PropType<'pictureCard' | 'picture'>, default: 'pictureCard' },
|
||||
disabled: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const emit = defineEmits({ 'update:modelValue': null });
|
||||
|
||||
const { modelValue, maxWidth, maxHeight, fileAccept, fileMaxSize } = toRefs(props);
|
||||
const { t } = useI18n();
|
||||
const sysConfig = useSysConfigStore();
|
||||
const dragging = ref<boolean>(false);
|
||||
const progressFile = ref<any>({});
|
||||
const currentFile = ref<any>({});
|
||||
const previewVisible = ref<boolean>(false);
|
||||
const cropperVisible = ref<boolean>(false);
|
||||
const previewFile = ref<any>({ src: 'data:;base64,=' });
|
||||
const fileList = computed({
|
||||
get: (): any[] => modelValue.value,
|
||||
set: (val: any) => emit('update:modelValue', val),
|
||||
});
|
||||
const { formItem } = useFormItem();
|
||||
watch(
|
||||
fileList,
|
||||
() => {
|
||||
formItem?.validate?.('change').catch((err: any) => {
|
||||
if (import.meta.env.MODE !== 'production') {
|
||||
console.warn(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
const handlePreview = (file: any) => {
|
||||
previewFile.value = file;
|
||||
previewVisible.value = true;
|
||||
};
|
||||
const thumbnailWidth = 300;
|
||||
const thumbnailHeight = 300;
|
||||
const getData = () => {
|
||||
const data: any = { isWatermark: true, thumbnailWidth, thumbnailHeight };
|
||||
if (maxWidth?.value != null) {
|
||||
data.maxWidth = maxWidth.value;
|
||||
}
|
||||
if (maxHeight?.value != null) {
|
||||
data.maxHeight = maxHeight.value;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
const accept = computed(() => fileAccept?.value ?? sysConfig.upload.imageInputAccept);
|
||||
const maxSize = computed(() => fileMaxSize?.value ?? sysConfig.upload.imageLimitByte);
|
||||
const beforeUpload = (file: any) => {
|
||||
if (maxSize.value > 0 && file.size > maxSize.value) {
|
||||
ElMessage.error(t('error.fileMaxSize', { size: `${maxSize.value / 1024 / 1024} MB` }));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const onError = (error: Error) => {
|
||||
handleError(JSON.parse(error.message));
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- <transition-group tag="ul" :class="['el-upload-list', 'el-upload-list--picture-card', { 'is-disabled': disabled }]" name="el-list"> -->
|
||||
<draggable
|
||||
:list="fileList"
|
||||
tag="ul"
|
||||
item-key="url"
|
||||
:animation="250"
|
||||
class="el-upload-list"
|
||||
:class="[listType === 'picture' ? 'el-upload-list--picture' : 'el-upload-list--picture-card', { 'is-disabled': disabled }]"
|
||||
@start="() => (dragging = true)"
|
||||
@end="() => (dragging = false)"
|
||||
>
|
||||
<template #item="{ element: file }">
|
||||
<li class="el-upload-list__item is-success">
|
||||
<div :class="listType === 'picture' ? ['w-32', 'h-32'] : ['w-full', 'h-full']" class="relative flex items-center justify-center bg-gray-50">
|
||||
<img class="block max-w-full max-h-full" :src="file.url" alt="" />
|
||||
<div
|
||||
class="absolute space-x-4 bg-black bg-opacity-50 rounded-md opacity-0 cursor-move full-flex-center"
|
||||
:class="dragging ? undefined : 'hover:opacity-100'"
|
||||
@click.stop
|
||||
>
|
||||
<el-icon class="image-action" :title="$t('cropImage')" @click="() => ((cropperVisible = true), (currentFile = file))"><Crop /></el-icon>
|
||||
<el-icon class="image-action" :title="$t('previewImage')" @click="() => handlePreview(file)"><View /></el-icon>
|
||||
<el-icon class="image-action" :title="$t('deleteImage')" @click="() => fileList.splice(fileList.indexOf(file), 1)"><Delete /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="listType === 'picture'" class="ml-2">
|
||||
<el-input v-model="file.url" placeholder="URL" maxlength="255">
|
||||
<template #prepend>URL</template>
|
||||
</el-input>
|
||||
<el-input v-model="file.name" :placeholder="$t('article.imageList.name')" class="mt-1">
|
||||
<template #prepend>{{ $t('article.imageList.name') }}</template>
|
||||
</el-input>
|
||||
<el-input v-model="file.description" type="textarea" :rows="2" :placeholder="$t('article.imageList.description')" class="mt-1"></el-input>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-upload
|
||||
:action="imageUploadUrl"
|
||||
:headers="{ ...getAuthHeaders(), ...getSiteHeaders() }"
|
||||
:data="getData()"
|
||||
:accept="accept"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="(res: any, file: any) => fileList.push({ name: res.name, url: res.url })"
|
||||
:on-progress="(event: any, file: any) => (progressFile = file)"
|
||||
:on-error="onError"
|
||||
:show-file-list="false"
|
||||
:disabled="disabled"
|
||||
multiple
|
||||
drag
|
||||
>
|
||||
<el-progress v-if="progressFile.status === 'uploading'" type="circle" :percentage="parseInt(progressFile.percentage, 10)" />
|
||||
<div v-else class="el-upload--picture-card">
|
||||
<el-icon><Plus /></el-icon>
|
||||
</div>
|
||||
</el-upload>
|
||||
</template>
|
||||
</draggable>
|
||||
<!-- </transition-group> -->
|
||||
<div>
|
||||
<el-dialog v-model="previewVisible" top="5vh" :width="768">
|
||||
<el-input v-model="previewFile.url" maxlength="255">
|
||||
<template #prepend>URL</template>
|
||||
</el-input>
|
||||
<el-input v-if="listType !== 'picture'" v-model="previewFile.name" :placeholder="$t('article.imageList.name')" class="mt-1">
|
||||
<template #prepend>{{ $t('article.imageList.name') }}</template>
|
||||
</el-input>
|
||||
<el-input
|
||||
v-if="listType !== 'picture'"
|
||||
v-model="previewFile.description"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:placeholder="$t('article.imageList.description')"
|
||||
class="mt-1"
|
||||
></el-input>
|
||||
<img :src="previewFile.url" alt="" class="mt-1 border border-gray-300" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
<image-cropper
|
||||
v-model="cropperVisible"
|
||||
:src="currentFile.url"
|
||||
:thumbnail-width="thumbnailWidth"
|
||||
:thumbnail-height="thumbnailHeight"
|
||||
@success="(url) => (currentFile.url = url)"
|
||||
></image-cropper>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-dialog__headerbtn) {
|
||||
top: 4px;
|
||||
}
|
||||
.full-flex-center {
|
||||
@apply w-full h-full flex justify-center items-center;
|
||||
}
|
||||
.image-action {
|
||||
@apply cursor-pointer text-xl text-white;
|
||||
}
|
||||
:deep(.el-upload-dragger) {
|
||||
padding: 0;
|
||||
}
|
||||
:deep(.el-upload--picture-card) {
|
||||
border: 0;
|
||||
}
|
||||
// 修复进度圈位置不正确
|
||||
:deep(.el-upload-list--picture-card .el-progress) {
|
||||
left: 0;
|
||||
transform: none;
|
||||
}
|
||||
// 修复拖动图集无动画效果
|
||||
:deep(.el-upload-list__item) {
|
||||
transition: none;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, toRefs, PropType } from 'vue';
|
||||
import { useFormItem } from 'element-plus';
|
||||
import { Plus, Crop, View, Delete } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { getAuthHeaders } from '@/utils/auth';
|
||||
import { getSiteHeaders } from '@/utils/common';
|
||||
import { handleError } from '@/utils/request';
|
||||
import { useSysConfigStore } from '@/stores/sysConfigStore';
|
||||
import { imageUploadUrl, avatarUploadUrl } from '@/api/config';
|
||||
import ImageCropper from './ImageCropper.vue';
|
||||
|
||||
// 'image/jpg,image/jpeg,image/png,image/gif'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: String, default: null },
|
||||
fileAccept: { type: String, default: null },
|
||||
fileMaxSize: { type: Number, default: null },
|
||||
width: { type: Number, default: null },
|
||||
height: { type: Number, default: null },
|
||||
/**
|
||||
* none: 原图上传, cut: 自动裁剪, resize: 自动压缩, manual: 手动裁剪
|
||||
*/
|
||||
mode: { type: String as PropType<'none' | 'cut' | 'resize' | 'manual'>, default: 'none' },
|
||||
/**
|
||||
* image: 图片上传, avatar: 头像上传
|
||||
*/
|
||||
type: { type: String as PropType<'image' | 'avatar'>, default: 'image' },
|
||||
disabled: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const emit = defineEmits({ 'update:modelValue': null, cropSuccess: null });
|
||||
|
||||
const { modelValue, type, width, height, mode, fileAccept, fileMaxSize } = toRefs(props);
|
||||
const { t } = useI18n();
|
||||
const sysConfig = useSysConfigStore();
|
||||
const progressFile = ref<any>({});
|
||||
const previewVisible = ref<boolean>(false);
|
||||
const cropperVisible = ref<boolean>(false);
|
||||
const { formItem } = useFormItem();
|
||||
const src = computed({
|
||||
get: (): string | undefined => modelValue.value,
|
||||
set: (val?: string) => {
|
||||
emit('update:modelValue', val);
|
||||
formItem?.validate?.('change').catch((err: any) => {
|
||||
if (import.meta.env.MODE !== 'production') {
|
||||
console.warn(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
const resizable = computed(() => ['cut', 'resize'].includes(mode.value));
|
||||
const data = computed(() => {
|
||||
const params: any = { resizeMode: mode.value === 'cut' ? 'cut' : 'normal' };
|
||||
if (width.value != null) {
|
||||
// 为0不限制,为空则依然受全局图片宽高限制
|
||||
params.maxWidth = resizable.value ? width.value : 0;
|
||||
}
|
||||
if (height.value != null) {
|
||||
// 为0不限制,为空则依然受全局图片宽高限制
|
||||
params.maxHeight = resizable.value ? height.value : 0;
|
||||
}
|
||||
return params;
|
||||
});
|
||||
const accept = computed(() => fileAccept.value ?? sysConfig.upload.imageInputAccept);
|
||||
const maxSize = computed(() => fileMaxSize.value ?? sysConfig.upload.imageLimitByte);
|
||||
const beforeUpload = (file: any) => {
|
||||
if (maxSize.value > 0 && file.size > maxSize.value) {
|
||||
ElMessage.error(t('error.fileMaxSize', { size: `${maxSize.value / 1024 / 1024} MB` }));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const onError = (error: Error) => {
|
||||
handleError(JSON.parse(error.message));
|
||||
};
|
||||
const onCropSuccess = (url: string) => {
|
||||
src.value = url;
|
||||
emit('cropSuccess', url);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-upload
|
||||
:action="type === 'avatar' ? avatarUploadUrl : imageUploadUrl"
|
||||
:headers="{ ...getAuthHeaders(), ...getSiteHeaders() }"
|
||||
:accept="accept"
|
||||
:before-upload="beforeUpload"
|
||||
:data="data"
|
||||
:show-file-list="false"
|
||||
:on-success="(res: any) => ((src = res.url), (cropperVisible = mode === 'manual'))"
|
||||
:on-error="onError"
|
||||
:on-progress="(event: any, file: any) => (progressFile = file)"
|
||||
:disabled="disabled"
|
||||
:drag="!src"
|
||||
>
|
||||
<!--
|
||||
// 用于测试上传进度条
|
||||
action="https://jsonplaceholder.typicode.com/posts/"
|
||||
-->
|
||||
<div v-if="src" class="relative full-flex-center rounded-border hover:border-opacity-0">
|
||||
<img :src="src" class="block max-w-full max-h-full" />
|
||||
<div class="absolute space-x-4 bg-black bg-opacity-50 rounded-md opacity-0 cursor-default full-flex-center hover:opacity-100" @click.stop>
|
||||
<el-icon class="image-action" :title="$t('cropImage')" @click="() => (cropperVisible = true)"><Crop /></el-icon>
|
||||
<el-icon class="image-action" :title="$t('previewImage')" @click="() => (previewVisible = true)"><View /></el-icon>
|
||||
<el-icon class="image-action" :title="$t('deleteImage')" @click="() => (src = undefined)"><Delete /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<el-progress v-else-if="progressFile.status === 'uploading'" type="circle" :percentage="parseInt(progressFile.percentage, 10)" />
|
||||
<div v-else class="el-upload--picture-card">
|
||||
<el-icon><Plus /></el-icon>
|
||||
</div>
|
||||
</el-upload>
|
||||
<div>
|
||||
<el-dialog v-model="previewVisible" top="5vh" :width="768" append-to-body destroy-on-close>
|
||||
<el-input v-model="src">
|
||||
<template #prepend>URL</template>
|
||||
</el-input>
|
||||
<img :src="src" alt="" class="mt-1 border border-gray-300" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
<image-cropper v-model="cropperVisible" :type="type" :src="src" :width="width" :height="height" @success="onCropSuccess" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-dialog__headerbtn) {
|
||||
top: 4px;
|
||||
}
|
||||
:deep(.el-upload) {
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
}
|
||||
.full-flex-center {
|
||||
@apply w-full h-full flex justify-center items-center;
|
||||
}
|
||||
.rounded-border {
|
||||
border: 1px solid #c0ccda;
|
||||
@apply rounded-md bg-gray-50;
|
||||
}
|
||||
.image-action {
|
||||
@apply cursor-pointer text-xl text-white;
|
||||
}
|
||||
:deep(.el-upload-dragger) {
|
||||
padding: 0;
|
||||
}
|
||||
:deep(.el-upload--picture-card) {
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export { default as ImageUpload } from './ImageUpload.vue';
|
||||
export { default as ImageListUpload } from './ImageListUpload.vue';
|
||||
export { default as FileListUpload } from './FileListUpload.vue';
|
||||
export { default as BaseUpload } from './BaseUpload.vue';
|
||||
|
|
@ -0,0 +1,523 @@
|
|||
import { assign, forEach, isArray, every } from 'min-dash';
|
||||
import { is } from 'bpmn-js/lib/util/ModelUtil';
|
||||
import { isExpanded, isHorizontal, isEventSubProcess } from 'bpmn-js/lib/util/DiUtil';
|
||||
import { isAny } from 'bpmn-js/lib/features/modeling/util/ModelingUtil';
|
||||
import { getChildLanes } from 'bpmn-js/lib/features/modeling/util/LaneUtil';
|
||||
import { hasPrimaryModifier } from 'diagram-js/lib/util/Mouse';
|
||||
|
||||
/**
|
||||
* A provider for BPMN 2.0 elements context pad
|
||||
* 将任务(Task)改为用户任务(UserTask)
|
||||
* https://github.com/bpmn-io/bpmn-js/blob/develop/lib/features/context-pad/ContextPadProvider.js
|
||||
*/
|
||||
/**
|
||||
* @typedef {import('didi').Injector} Injector
|
||||
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
|
||||
* @typedef {import('diagram-js/lib/features/context-pad/ContextPad').default} ContextPad
|
||||
* @typedef {import('../modeling/Modeling').default} Modeling
|
||||
* @typedef {import('../modeling/ElementFactory').default} ElementFactory
|
||||
* @typedef {import('diagram-js/lib/features/connect/Connect').default} Connect
|
||||
* @typedef {import('diagram-js/lib/features/create/Create').default} Create
|
||||
* @typedef {import('diagram-js/lib/features/popup-menu/PopupMenu').default} PopupMenu
|
||||
* @typedef {import('diagram-js/lib/features/canvas/Canvas').default} Canvas
|
||||
* @typedef {import('diagram-js/lib/features/rules/Rules').default} Rules
|
||||
* @typedef {import('diagram-js/lib/i18n/translate/translate').default} Translate
|
||||
*
|
||||
* @typedef {import('../../model/Types').Element} Element
|
||||
* @typedef {import('../../model/Types').ModdleElement} ModdleElement
|
||||
*
|
||||
* @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').default<Element>} BaseContextPadProvider
|
||||
* @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').ContextPadEntries} ContextPadEntries
|
||||
* @typedef {import('diagram-js/lib/features/context-pad/ContextPadProvider').ContextPadEntry} ContextPadEntry
|
||||
*
|
||||
* @typedef { { autoPlace?: boolean; } } ContextPadConfig
|
||||
*/
|
||||
|
||||
/**
|
||||
* BPMN-specific context pad provider.
|
||||
*
|
||||
* @implements {BaseContextPadProvider}
|
||||
*
|
||||
* @param {ContextPadConfig} config
|
||||
* @param {Injector} injector
|
||||
* @param {EventBus} eventBus
|
||||
* @param {ContextPad} contextPad
|
||||
* @param {Modeling} modeling
|
||||
* @param {ElementFactory} elementFactory
|
||||
* @param {Connect} connect
|
||||
* @param {Create} create
|
||||
* @param {PopupMenu} popupMenu
|
||||
* @param {Canvas} canvas
|
||||
* @param {Rules} rules
|
||||
* @param {Translate} translate
|
||||
*/
|
||||
export default function FlowableContextPadProvider(
|
||||
config,
|
||||
injector,
|
||||
eventBus,
|
||||
contextPad,
|
||||
modeling,
|
||||
elementFactory,
|
||||
connect,
|
||||
create,
|
||||
popupMenu,
|
||||
canvas,
|
||||
rules,
|
||||
translate,
|
||||
appendPreview,
|
||||
) {
|
||||
config = config || {};
|
||||
|
||||
contextPad.registerProvider(this);
|
||||
|
||||
this._contextPad = contextPad;
|
||||
|
||||
this._modeling = modeling;
|
||||
|
||||
this._elementFactory = elementFactory;
|
||||
this._connect = connect;
|
||||
this._create = create;
|
||||
this._popupMenu = popupMenu;
|
||||
this._canvas = canvas;
|
||||
this._rules = rules;
|
||||
this._translate = translate;
|
||||
this._eventBus = eventBus;
|
||||
this._appendPreview = appendPreview;
|
||||
|
||||
if (config.autoPlace !== false) {
|
||||
this._autoPlace = injector.get('autoPlace', false);
|
||||
}
|
||||
|
||||
eventBus.on('create.end', 250, function (event) {
|
||||
var context = event.context,
|
||||
shape = context.shape;
|
||||
|
||||
if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var entries = contextPad.getEntries(shape);
|
||||
|
||||
if (entries.replace) {
|
||||
entries.replace.action.click(event, shape);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FlowableContextPadProvider.$inject = [
|
||||
'config.contextPad',
|
||||
'injector',
|
||||
'eventBus',
|
||||
'contextPad',
|
||||
'modeling',
|
||||
'elementFactory',
|
||||
'connect',
|
||||
'create',
|
||||
'popupMenu',
|
||||
'canvas',
|
||||
'rules',
|
||||
'translate',
|
||||
'appendPreview',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {Element[]} elements
|
||||
*
|
||||
* @return {ContextPadEntries}
|
||||
*/
|
||||
FlowableContextPadProvider.prototype.getMultiElementContextPadEntries = function (elements) {
|
||||
var modeling = this._modeling;
|
||||
|
||||
var actions = {};
|
||||
|
||||
if (this._isDeleteAllowed(elements)) {
|
||||
assign(actions, {
|
||||
delete: {
|
||||
group: 'edit',
|
||||
className: 'bpmn-icon-trash',
|
||||
title: this._translate('Delete'),
|
||||
action: {
|
||||
click: function (event, elements) {
|
||||
modeling.removeElements(elements.slice());
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Element[]} elements
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
FlowableContextPadProvider.prototype._isDeleteAllowed = function (elements) {
|
||||
var baseAllowed = this._rules.allowed('elements.delete', {
|
||||
elements: elements,
|
||||
});
|
||||
|
||||
if (isArray(baseAllowed)) {
|
||||
return every(baseAllowed, function (element) {
|
||||
return includes(baseAllowed, element);
|
||||
});
|
||||
}
|
||||
|
||||
return baseAllowed;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Element} element
|
||||
*
|
||||
* @return {ContextPadEntries}
|
||||
*/
|
||||
FlowableContextPadProvider.prototype.getContextPadEntries = function (element) {
|
||||
var contextPad = this._contextPad,
|
||||
modeling = this._modeling,
|
||||
elementFactory = this._elementFactory,
|
||||
connect = this._connect,
|
||||
create = this._create,
|
||||
popupMenu = this._popupMenu,
|
||||
rules = this._rules,
|
||||
autoPlace = this._autoPlace,
|
||||
translate = this._translate,
|
||||
appendPreview = this._appendPreview;
|
||||
|
||||
var actions = {};
|
||||
|
||||
if (element.type === 'label') {
|
||||
return actions;
|
||||
}
|
||||
|
||||
var businessObject = element.businessObject;
|
||||
|
||||
function startConnect(event, element) {
|
||||
connect.start(event, element);
|
||||
}
|
||||
|
||||
function removeElement(e, element) {
|
||||
modeling.removeElements([element]);
|
||||
}
|
||||
|
||||
function getReplaceMenuPosition(element) {
|
||||
var Y_OFFSET = 5;
|
||||
|
||||
var pad = contextPad.getPad(element).html;
|
||||
|
||||
var padRect = pad.getBoundingClientRect();
|
||||
|
||||
var pos = {
|
||||
x: padRect.left,
|
||||
y: padRect.bottom + Y_OFFSET,
|
||||
};
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an append action.
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {string} className
|
||||
* @param {string} title
|
||||
* @param {Object} [options]
|
||||
*
|
||||
* @return {ContextPadEntry}
|
||||
*/
|
||||
function appendAction(type, className, title, options) {
|
||||
function appendStart(event, element) {
|
||||
var shape = elementFactory.createShape(assign({ type: type }, options));
|
||||
|
||||
create.start(event, shape, {
|
||||
source: element,
|
||||
});
|
||||
|
||||
appendPreview.cleanUp();
|
||||
}
|
||||
|
||||
var append = autoPlace
|
||||
? function (_, element) {
|
||||
var shape = elementFactory.createShape(assign({ type: type }, options));
|
||||
|
||||
autoPlace.append(element, shape);
|
||||
|
||||
appendPreview.cleanUp();
|
||||
}
|
||||
: appendStart;
|
||||
|
||||
var previewAppend = autoPlace
|
||||
? function (_, element) {
|
||||
// mouseover
|
||||
appendPreview.create(element, type, options);
|
||||
|
||||
return () => {
|
||||
// mouseout
|
||||
appendPreview.cleanUp();
|
||||
};
|
||||
}
|
||||
: null;
|
||||
|
||||
return {
|
||||
group: 'model',
|
||||
className: className,
|
||||
title: title,
|
||||
action: {
|
||||
dragstart: appendStart,
|
||||
click: append,
|
||||
hover: previewAppend,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function splitLaneHandler(count) {
|
||||
return function (_, element) {
|
||||
// actual split
|
||||
modeling.splitLane(element, count);
|
||||
|
||||
// refresh context pad after split to
|
||||
// get rid of split icons
|
||||
contextPad.open(element, true);
|
||||
};
|
||||
}
|
||||
|
||||
if (isAny(businessObject, ['bpmn:Lane', 'bpmn:Participant']) && isExpanded(element)) {
|
||||
var childLanes = getChildLanes(element);
|
||||
|
||||
assign(actions, {
|
||||
'lane-insert-above': {
|
||||
group: 'lane-insert-above',
|
||||
className: 'bpmn-icon-lane-insert-above',
|
||||
title: translate('Add lane above'),
|
||||
action: {
|
||||
click: function (event, element) {
|
||||
modeling.addLane(element, 'top');
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (childLanes.length < 2) {
|
||||
if (isHorizontal(element) ? element.height >= 120 : element.width >= 120) {
|
||||
assign(actions, {
|
||||
'lane-divide-two': {
|
||||
group: 'lane-divide',
|
||||
className: 'bpmn-icon-lane-divide-two',
|
||||
title: translate('Divide into two lanes'),
|
||||
action: {
|
||||
click: splitLaneHandler(2),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (isHorizontal(element) ? element.height >= 180 : element.width >= 180) {
|
||||
assign(actions, {
|
||||
'lane-divide-three': {
|
||||
group: 'lane-divide',
|
||||
className: 'bpmn-icon-lane-divide-three',
|
||||
title: translate('Divide into three lanes'),
|
||||
action: {
|
||||
click: splitLaneHandler(3),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
assign(actions, {
|
||||
'lane-insert-below': {
|
||||
group: 'lane-insert-below',
|
||||
className: 'bpmn-icon-lane-insert-below',
|
||||
title: translate('Add lane below'),
|
||||
action: {
|
||||
click: function (event, element) {
|
||||
modeling.addLane(element, 'bottom');
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (is(businessObject, 'bpmn:FlowNode')) {
|
||||
if (is(businessObject, 'bpmn:EventBasedGateway')) {
|
||||
assign(actions, {
|
||||
'append.receive-task': appendAction('bpmn:ReceiveTask', 'bpmn-icon-receive-task', translate('Append receive task')),
|
||||
'append.message-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-message',
|
||||
translate('Append message intermediate catch event'),
|
||||
{ eventDefinitionType: 'bpmn:MessageEventDefinition' },
|
||||
),
|
||||
'append.timer-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-timer',
|
||||
translate('Append timer intermediate catch event'),
|
||||
{ eventDefinitionType: 'bpmn:TimerEventDefinition' },
|
||||
),
|
||||
'append.condition-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-condition',
|
||||
translate('Append conditional intermediate catch event'),
|
||||
{ eventDefinitionType: 'bpmn:ConditionalEventDefinition' },
|
||||
),
|
||||
'append.signal-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-signal',
|
||||
translate('Append signal intermediate catch event'),
|
||||
{ eventDefinitionType: 'bpmn:SignalEventDefinition' },
|
||||
),
|
||||
});
|
||||
} else if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {
|
||||
assign(actions, {
|
||||
// 'append.compensation-activity': appendAction('bpmn:Task', 'bpmn-icon-task', translate('Append compensation activity'), {
|
||||
'append.compensation-activity': appendAction('bpmn:UserTask', 'bpmn-icon-user-task', translate('Append compensation activity'), {
|
||||
isForCompensation: true,
|
||||
}),
|
||||
});
|
||||
} else if (
|
||||
!is(businessObject, 'bpmn:EndEvent') &&
|
||||
!businessObject.isForCompensation &&
|
||||
!isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
|
||||
!isEventSubProcess(businessObject)
|
||||
) {
|
||||
assign(actions, {
|
||||
'append.end-event': appendAction('bpmn:EndEvent', 'bpmn-icon-end-event-none', translate('Append end event')),
|
||||
'append.gateway': appendAction('bpmn:ExclusiveGateway', 'bpmn-icon-gateway-none', translate('Append gateway')),
|
||||
// 'append.append-task': appendAction('bpmn:Task', 'bpmn-icon-task', translate('Append task')),
|
||||
'append.append-user-task': appendAction('bpmn:UserTask', 'bpmn-icon-user-task', translate('Append user task')),
|
||||
'append.intermediate-event': appendAction('bpmn:IntermediateThrowEvent', 'bpmn-icon-intermediate-event-none', translate('Append intermediate/boundary event')),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
|
||||
// Replace menu entry
|
||||
assign(actions, {
|
||||
replace: {
|
||||
group: 'edit',
|
||||
className: 'bpmn-icon-screw-wrench',
|
||||
title: translate('Change element'),
|
||||
action: {
|
||||
click: function (event, element) {
|
||||
var position = assign(getReplaceMenuPosition(element), {
|
||||
cursor: { x: event.x, y: event.y },
|
||||
});
|
||||
|
||||
popupMenu.open(element, 'bpmn-replace', position, {
|
||||
title: translate('Change element'),
|
||||
width: 300,
|
||||
search: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (is(businessObject, 'bpmn:SequenceFlow')) {
|
||||
assign(actions, {
|
||||
'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation', translate('Add text annotation')),
|
||||
});
|
||||
}
|
||||
|
||||
if (isAny(businessObject, ['bpmn:FlowNode', 'bpmn:InteractionNode', 'bpmn:DataObjectReference', 'bpmn:DataStoreReference'])) {
|
||||
assign(actions, {
|
||||
'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation', translate('Add text annotation')),
|
||||
|
||||
connect: {
|
||||
group: 'connect',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate('Connect to other element'),
|
||||
action: {
|
||||
click: startConnect,
|
||||
dragstart: startConnect,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (is(businessObject, 'bpmn:TextAnnotation')) {
|
||||
assign(actions, {
|
||||
connect: {
|
||||
group: 'connect',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate('Connect using association'),
|
||||
action: {
|
||||
click: startConnect,
|
||||
dragstart: startConnect,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (isAny(businessObject, ['bpmn:DataObjectReference', 'bpmn:DataStoreReference'])) {
|
||||
assign(actions, {
|
||||
connect: {
|
||||
group: 'connect',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate('Connect using data input association'),
|
||||
action: {
|
||||
click: startConnect,
|
||||
dragstart: startConnect,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (is(businessObject, 'bpmn:Group')) {
|
||||
assign(actions, {
|
||||
'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation', translate('Add text annotation')),
|
||||
});
|
||||
}
|
||||
|
||||
// delete element entry, only show if allowed by rules
|
||||
var deleteAllowed = rules.allowed('elements.delete', { elements: [element] });
|
||||
|
||||
if (isArray(deleteAllowed)) {
|
||||
// was the element returned as a deletion candidate?
|
||||
deleteAllowed = deleteAllowed[0] === element;
|
||||
}
|
||||
|
||||
if (deleteAllowed) {
|
||||
assign(actions, {
|
||||
delete: {
|
||||
group: 'edit',
|
||||
className: 'bpmn-icon-trash',
|
||||
title: translate('Delete'),
|
||||
action: {
|
||||
click: removeElement,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
// helpers /////////
|
||||
|
||||
/**
|
||||
* @param {ModdleElement} businessObject
|
||||
* @param {string} type
|
||||
* @param {string} eventDefinitionType
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
function isEventType(businessObject, type, eventDefinitionType) {
|
||||
var isType = businessObject.$instanceOf(type);
|
||||
var isDefinition = false;
|
||||
|
||||
var definitions = businessObject.eventDefinitions || [];
|
||||
forEach(definitions, function (def) {
|
||||
if (def.$type === eventDefinitionType) {
|
||||
isDefinition = true;
|
||||
}
|
||||
});
|
||||
|
||||
return isType && isDefinition;
|
||||
}
|
||||
|
||||
function includes(array, item) {
|
||||
return array.indexOf(item) !== -1;
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import DirectEditingModule from 'diagram-js-direct-editing';
|
||||
import ContextPadModule from 'diagram-js/lib/features/context-pad';
|
||||
import SelectionModule from 'diagram-js/lib/features/selection';
|
||||
import ConnectModule from 'diagram-js/lib/features/connect';
|
||||
import CreateModule from 'diagram-js/lib/features/create';
|
||||
import AppendPreviewModule from 'bpmn-js/lib/features/append-preview';
|
||||
import PopupMenuModule from 'bpmn-js/lib/features/popup-menu';
|
||||
import FlowableContextPadProvider from './FlowableContextPadProvider';
|
||||
|
||||
/**
|
||||
* https://github.com/bpmn-io/bpmn-js/blob/develop/lib/features/context-pad/index.js
|
||||
*/
|
||||
export default {
|
||||
__depends__: [AppendPreviewModule, DirectEditingModule, ContextPadModule, SelectionModule, ConnectModule, CreateModule, PopupMenuModule],
|
||||
// 覆盖自带的 contextPadProvider 全部自己定义
|
||||
__init__: ['contextPadProvider'],
|
||||
contextPadProvider: ['type', FlowableContextPadProvider],
|
||||
};
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import translations from './translations';
|
||||
/**
|
||||
* https://github.com/bpmn-io/bpmn-js-examples/blob/main/i18n/src/customTranslate/customTranslate.js
|
||||
*/
|
||||
export default function customTranslate(template, replacements) {
|
||||
replacements = replacements || {};
|
||||
|
||||
// Translate
|
||||
template = translations[template] || template;
|
||||
|
||||
// Replace
|
||||
return template.replace(/{([^}]+)}/g, function (_, key) {
|
||||
return replacements[key] || '{' + key + '}';
|
||||
});
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue