jeecguniapp1.0

This commit is contained in:
yangzhq68909 2025-04-29 16:37:17 +08:00
parent bd1d2cd6d2
commit cfe8db7730
435 changed files with 196363 additions and 1 deletions

106
.commitlintrc.cjs Normal file
View File

@ -0,0 +1,106 @@
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
const scopes = fs
.readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name.replace(/s$/, ''))
// precomputed scope
const scopeComplete = execSync('git status --porcelain || true')
.toString()
.trim()
.split('\n')
.find((r) => ~r.indexOf('M src'))
?.replace(/(\/)/g, '%%')
?.match(/src%%((\w|-)*)/)?.[1]
?.replace(/s$/, '')
module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
rules: {
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'subject-case': [0],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'perf',
'style',
'docs',
'test',
'refactor',
'build',
'ci',
'chore',
'revert',
'wip',
'workflow',
'types',
'release',
],
],
},
prompt: {
/** @use `pnpm commit :f` */
alias: {
f: 'docs: fix typos',
r: 'docs: update README',
s: 'style: update code format',
b: 'build: bump dependencies',
c: 'chore: update config',
},
customScopesAlign: !scopeComplete ? 'top' : 'bottom',
defaultScope: scopeComplete,
scopes: [...scopes, 'mock'],
allowEmptyIssuePrefixs: false,
allowCustomIssuePrefixs: false,
// English
typesAppend: [
{ value: 'wip', name: 'wip: work in process' },
{ value: 'workflow', name: 'workflow: workflow improvements' },
{ value: 'types', name: 'types: type definition file changes' },
],
// 中英文对照版
// messages: {
// type: '选择你要提交的类型 :',
// scope: '选择一个提交范围 (可选):',
// customScope: '请输入自定义的提交范围 :',
// subject: '填写简短精炼的变更描述 :\n',
// body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n',
// breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n',
// footerPrefixsSelect: '选择关联issue前缀 (可选):',
// customFooterPrefixs: '输入自定义issue前缀 :',
// footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
// confirmCommit: '是否提交或修改commit ?',
// },
// types: [
// { value: 'feat', name: 'feat: 新增功能' },
// { value: 'fix', name: 'fix: 修复缺陷' },
// { value: 'docs', name: 'docs: 文档变更' },
// { value: 'style', name: 'style: 代码格式' },
// { value: 'refactor', name: 'refactor: 代码重构' },
// { value: 'perf', name: 'perf: 性能优化' },
// { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' },
// { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' },
// { value: 'ci', name: 'ci: 修改 CI 配置、脚本' },
// { value: 'revert', name: 'revert: 回滚 commit' },
// { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' },
// { value: 'wip', name: 'wip: 正在开发中' },
// { value: 'workflow', name: 'workflow: 工作流程改进' },
// { value: 'types', name: 'types: 类型定义文件修改' },
// ],
// emptyScopesAlias: 'empty: 不填写',
// customScopesAlias: 'custom: 自定义',
},
}

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
root = true
[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格tab | space
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off # 关闭最大行长度限制
trim_trailing_whitespace = false # 关闭末尾空格修剪

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
src/uni_modules/

100
.eslintrc-auto-import.json Normal file
View File

@ -0,0 +1,100 @@
{
"globals": {
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"DirectiveBinding": true,
"EffectScope": true,
"ExtractDefaultPropTypes": true,
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"InjectionKey": true,
"MaybeRef": true,
"MaybeRefOrGetter": true,
"PropType": true,
"Ref": true,
"VNode": true,
"WritableComputedRef": true,
"computed": true,
"createApp": true,
"customRef": true,
"defineAsyncComponent": true,
"defineComponent": true,
"effectScope": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"inject": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onAddToFavorites": true,
"onBackPress": true,
"onBeforeMount": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onDeactivated": true,
"onError": true,
"onErrorCaptured": true,
"onHide": true,
"onLaunch": true,
"onLoad": true,
"onMounted": true,
"onNavigationBarButtonTap": true,
"onNavigationBarSearchInputChanged": true,
"onNavigationBarSearchInputClicked": true,
"onNavigationBarSearchInputConfirmed": true,
"onNavigationBarSearchInputFocusChanged": true,
"onPageNotFound": true,
"onPageScroll": true,
"onPullDownRefresh": true,
"onReachBottom": true,
"onReady": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onResize": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onShareAppMessage": true,
"onShareTimeline": true,
"onShow": true,
"onTabItemTap": true,
"onThemeChange": true,
"onUnhandledRejection": true,
"onUnload": true,
"onUnmounted": true,
"onUpdated": true,
"onWatcherCleanup": true,
"provide": true,
"reactive": true,
"readonly": true,
"ref": true,
"resolveComponent": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"toRaw": true,
"toRef": true,
"toRefs": true,
"toValue": true,
"triggerRef": true,
"unref": true,
"useAttrs": true,
"useCssModule": true,
"useCssVars": true,
"useId": true,
"useModel": true,
"useRequest": true,
"useSlots": true,
"useTemplateRef": true,
"useUpload": true,
"watch": true,
"watchEffect": true,
"watchPostEffect": true,
"watchSyncEffect": true
}
}

97
.eslintrc.cjs Normal file
View File

@ -0,0 +1,97 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:vue/vue3-essential',
// eslint-plugin-import 插件, @see https://www.npmjs.com/package/eslint-plugin-import
'plugin:import/recommended',
// eslint-config-airbnb-base 插件 已经改用 eslint-config-standard 插件
'standard',
// 1. 接入 prettier 的规则
'prettier',
'plugin:prettier/recommended',
'./.eslintrc-auto-import.json',
],
overrides: [
{
env: {
node: true,
},
files: ['.eslintrc.{js,cjs}'],
parserOptions: {
sourceType: 'script',
},
},
],
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: [
'@typescript-eslint',
'vue',
// 2. 加入 prettier 的 eslint 插件
'prettier',
// eslint-import-resolver-typescript 插件,@see https://www.npmjs.com/package/eslint-import-resolver-typescript
'import',
],
rules: {
// 3. 注意要加上这一句,开启 prettier 自动修复的功能
'prettier/prettier': 'error',
// turn on errors for missing imports
'import/no-unresolved': 'off',
// 对后缀的检测,否则 import 一个ts文件也会报错需要手动添加'.ts', 增加了下面的配置后就不用了
'import/extensions': [
'error',
'ignorePackages',
{ js: 'never', jsx: 'never', ts: 'never', tsx: 'never' },
],
// 只允许1个默认导出关闭否则不能随意export xxx
'import/prefer-default-export': ['off'],
'no-console': ['off'],
// 'no-unused-vars': ['off'],
// '@typescript-eslint/no-unused-vars': ['off'],
// 解决vite.config.ts报错问题
'import/no-extraneous-dependencies': 'off',
'no-plusplus': 'off',
'no-shadow': 'off',
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'no-underscore-dangle': 'off',
'no-use-before-define': 'off',
'no-undef': 'off',
'no-unused-vars': 'off',
'no-param-reassign': 'off',
'@typescript-eslint/no-unused-vars': 'off',
// 避免 `eslint` 对于 `typescript` 函数重载的误报
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': 'error',
},
// eslint-import-resolver-typescript 插件,@see https://www.npmjs.com/package/eslint-import-resolver-typescript
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {},
},
},
globals: {
$t: true,
uni: true,
UniApp: true,
wx: true,
WechatMiniprogram: true,
getCurrentPages: true,
UniHelper: true,
Page: true,
App: true,
NodeJS: true,
},
}

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
*.js linguist-language=vue
*.css linguist-language=vue
*.ts linguist-language=vue

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
node_modules
dist
.npmrc
.cache
## ide
**/.idea
*.iml
## backend
**/target
**/logs
**/dist
## front
/yarn-error.log

12
.prettierignore Normal file
View File

@ -0,0 +1,12 @@
# unplugin-auto-import 生成的类型文件,每次提交都改变,所以加入这里吧,与 .gitignore 配合使用
auto-import.d.ts
# vite-plugin-uni-pages 生成的类型文件,每次切换分支都一堆不同的,所以直接 .gitignore
uni-pages.d.ts
# 插件生成的文件
src/pages.json
src/manifest.json
# 忽略自动生成文件
src/service/app/**

19
.prettierrc.cjs Normal file
View File

@ -0,0 +1,19 @@
// @see https://prettier.io/docs/en/options
module.exports = {
singleQuote: true,
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: false,
trailingComma: 'all',
endOfLine: 'auto',
htmlWhitespaceSensitivity: 'ignore',
overrides: [
{
files: '*.json',
options: {
trailingComma: 'none',
},
},
],
}

1
.stylelintignore Normal file
View File

@ -0,0 +1 @@
src/uni_modules/

58
.stylelintrc.cjs Normal file
View File

@ -0,0 +1,58 @@
// .stylelintrc.cjs
module.exports = {
root: true,
extends: [
// stylelint-config-standard 替换成了更宽松的 stylelint-config-recommended
'stylelint-config-recommended',
// stylelint-config-standard-scss 替换成了更宽松的 stylelint-config-recommended-scss
'stylelint-config-recommended-scss',
'stylelint-config-recommended-vue/scss',
'stylelint-config-html/vue',
'stylelint-config-recess-order',
],
plugins: ['stylelint-prettier'],
overrides: [
// 扫描 .vue/html 文件中的<style>标签内的样式
{
files: ['**/*.{vue,html}'],
customSyntax: 'postcss-html',
},
{
files: ['**/*.{css,scss}'],
customSyntax: 'postcss-scss',
},
],
// 自定义规则
rules: {
'prettier/prettier': true,
// 允许 global 、export 、v-deep等伪类
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['global', 'export', 'v-deep', 'deep'],
},
],
'unit-no-unknown': [
true,
{
ignoreUnits: ['rpx'],
},
],
// 处理小程序page标签不认识的问题
'selector-type-no-unknown': [
true,
{
ignoreTypes: ['page'],
},
],
'comment-empty-line-before': 'never', // never|always|always-multi-line|never-multi-line
'custom-property-empty-line-before': 'never',
'no-empty-source': null,
'comment-no-empty': null,
'no-duplicate-selectors': null,
'scss/comment-no-empty': null,
'selector-class-pattern': null,
'font-family-no-missing-generic-family-keyword': null,
},
}

View File

@ -0,0 +1,3 @@
{
"type": "module"
}

214
LICENSE Normal file
View File

@ -0,0 +1,214 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (c) 2019 <a href="http://www.jeecg.com">Jeecg Boot</a> All rights reserved.
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.
In any case, you must not make any such use of this software as to develop software which may be considered competitive with this software.
JeecgUniapp 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京地址中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱jeecgos@163.com
本软件受适用的国家软件著作权法(包括国际条约)和开源协议 双重保护许可。
开源协议中文释意如下:
1.JeecgUniapp开源版本无任何限制在遵循本开源协议条款下允许商用使用不会造成侵权行为。
2.允许基于本平台软件开展业务系统开发。
3.在任何情况下,您不得使用本软件开发可能被认为与本软件竞争的软件。
最终解释权归http://www.jeecg.com

188
README.md
View File

@ -1,2 +1,188 @@
# JeecgUniapp
# 项目介绍
JeecgUniapp 是`JeecgBoot低代码平台`的配套`APP移动框架`,项目采用 Uniapp、Vue3.0、Vite、 Wot-design-uni、TypeScript 等最新技术栈,包括二次封装组件、路由拦截、请求拦截等功能。实现了与 `JeecgBoot` 完美对接:
目前已经实现登录、用户信息、通讯录、公告、移动首页、九宫格、聊天、Online表单、仪表盘等功能提供了丰富的组件!
当前最新版本: 3.0.0发布日期2025-03-10
[![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
[![](https://img.shields.io/badge/Author-北京国炬软件-orange.svg)](http://www.jeecg.com)
[![](https://img.shields.io/badge/version-3.0.0-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot)
[![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot)
### 视频介绍
[![](https://upload.jeecg.com/jeecg/qiaoqiaoyunsite/jeecguniappvideo.png)](https://www.bilibili.com/video/BV15QQeYoEfQ)
### 新版特点
- 一份代码多终端适配小程序、H5、安卓、ios、鸿蒙Next。
- 学习成本低、组件丰富、兼容性好、支持iframe嵌入。
- 新版APP具备低代码能力包括表单设计、仪表盘设计等。
- 新版最大亮点是架构升级到 Vue3适配鸿蒙 Next。
- 支持使用 VSCode 和 IntelliJ IDEA 开发,不再必须 HBuilderX这显著提升了开发体验和效率。
- 支持低代码能力例如Online表单可以在APP端展示和进行数据的添加与修改仪表盘和大屏也支持移动端展示。
### 前端技术栈
- 前端 IDE 建议Vscode、HBuilderX、Intellij IDEA
- 最新技术栈Uniapp + Vue3.0 + Vite + TypeScript + Wot-design-uni + pinia + unocss
- 依赖管理node、pnpm
### 环境要求
- 本地环境安装 node18+、pnpm 7.3+
### 后台源码
- https://github.com/jeecgboot/jeecg-boot
### 技术交流
- 产品官网: [http://jeecg.com/appIndex](http://jeecg.com/appIndex)
- 开发文档: [https://help.jeecg.com/uniapp3](https://help.jeecg.com/uniapp3)
- 官方支持: 遇到BUG可以在github上 [发Issue](https://github.com/jeecgboot/jeecg-uniapp/issues/new)
- QQ交流群 716488839
### 快速启动
#### 配置接口地址
> 配置文件:`env/.env.development`
请把 http://localhost:8080/jeecg-boot 替换成自己地址,其他不用改。
```javascript
// 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development';
// 是否去除console 和 debugger
VITE_DELETE_CONSOLE = false;
// 是否开启sourcemap
VITE_SHOW_SOURCEMAP = true;
// 后台接口全路径地址(必填)
VITE_SERVER_BASEURL = 'http://localhost:8080/jeecg-boot';
```
#### 启动项目
执行命令安装依赖
```
pnpm i
```
运行启动命令,运行 `H5`
```
pnpm run dev
```
### 新旧版本对比
| 特性 | 旧版 | 新版 |
| -------------------- | -------------- |------------------------------------------------|
| **技术栈** | Vue2技术陈旧 | Vue3现代化开发体验 |
| **UI 框架** | 未集成 | 集成`wot-design-uni` |
| **编辑器** | 仅限 HbuilderX | 支持 VSCode、Intellij IDEA 等主流编辑器 |
| **鸿蒙** | 不支持 | 支持鸿蒙系统 |
| **构建工具** | Webpack | Vite构建更快 |
| **Unocss 原子化** | 不支持 | 支持 Unocss 原子化 |
| **TypeScript 支持** | 不支持 | 支持 TS提供类型提示 |
| **基础组件封装** | 较少 | 封装丰富组件用户、部门、分类字典树、自定义树、popup、popupDict、导航组件等 |
| **代码片段快捷创建** | 无 | 支持 v3 快速创建页面片段 |
### 入门必备
本项目需要一定前端基础知识,请确保掌握 Vue 的基础知识,以便能处理一些常见的问题。 建议在开发前先学一下以下内容,提前了解和学习这些知识,会对项目理解非常有帮助:
- [Vue3 文档](https://cn.vuejs.org)
- [Uniapp](https://uniapp.dcloud.net.cn/)
- [Wot-design-uni](https://wot-design-uni.cn)
- [Unibest 文档](https://www.unibest.tech)
- [TypeScript](https://www.typescriptlang.org)
- [Es6](https://es6.ruanyifeng.com/)
- [Vitejs](https://vitejs.dev)
- [Pinia(vuex 替代方案)](https://pinia.esm.dev/introduction.html)
- [Vue-RFCS](https://github.com/vuejs/rfcs)
- [UnoCSS](https://unocss.dev)
### 效果预览
- 基础功能
![](https://oscimg.oschina.net/oscnet/up-9fb74025440e6066651599d78b4bc78f2cd.png)
![](https://oscimg.oschina.net/oscnet/up-7605e213638a559bba64279b6db93af3ed0.png)
![](https://oscimg.oschina.net/oscnet/up-43ddd52486509ab06a920c3f99f42b8b432.png)
![](https://oscimg.oschina.net/oscnet/up-02d83a8fe3fab4c0153862a9084f8a94cbb.png)
![](https://oscimg.oschina.net/oscnet/up-937a63d5e13869c40e6f1437452171d8235.png)
- 聊天功能
![](https://oscimg.oschina.net/oscnet/up-64bea8f521888f1c2b192c140ad3ba47024.png)
![](https://oscimg.oschina.net/oscnet/up-58d50a5ca1e3c15e628ede2dc59e87bca97.png)
![](https://oscimg.oschina.net/oscnet/up-800a03ecb4238a788ed32ecceefa809895b.png)
![](https://oscimg.oschina.net/oscnet/up-e840f754aa5927584a8e482662758b45218.png)
![](https://oscimg.oschina.net/oscnet/up-941cbcc10b4b6fc02d1f62b446764a465c3.png)
- Online表单
![](https://oscimg.oschina.net/oscnet/up-051105a465e568b6a8935100a5a1af6de69.png)
![](https://oscimg.oschina.net/oscnet/up-942a0398a8cae684ee2bf8203c3ec96b709.png)
![](https://oscimg.oschina.net/oscnet/up-dbc6687d6c809f2682c5b2fb013ee6475e5.png)
- 仪表盘
![](https://oscimg.oschina.net/oscnet/up-c31fb30bce7784318b7c3487e44cd3d928b.png)
![](https://oscimg.oschina.net/oscnet/up-b945e4d0fed55091cc30b63044cbd6cdee4.png)
- 动画展示
![](https://oscimg.oschina.net/oscnet/up-49e27699eb278c7c6b6748bfeaeb6c13b72.gif)
### 功能模块
```
├─框架实现
│ ├─APP开发框架搭建
│ ├─登录对接
│ ├─TOKEN接口机制
│ ├─热更新\覆盖更新
├─基础功能
│ ├─菜单栏目
│ ├─登录页面
│ ├─移动首页
│ ├─个人信息设置
├─消息中心
│ ├─通讯录
│ ├─系统公告
│ ├─消息推送
│ ├─在线聊天
├─低代码功能
│ ├─Online表单列表+表单渲染)
│ ├─仪表盘(移动展示)
├─示例代码
│ ├─调用摄像头扫码(扫码)
│ ├─获取地理位置(定位)
├─新增组件
│ ├─页面滚动
│ ├─日历
│ ├─时间选择
│ ├─下拉选择
│ ├─图片上传
├─。。。
```

29
env/.env vendored Normal file
View File

@ -0,0 +1,29 @@
VITE_APP_TITLE = 'JeecgBoot-uniapp'
VITE_APP_PORT = 9000
VITE_UNI_APPID = '__UNI__9F097F0'
VITE_WX_APPID = 'wx8e287639924edb51'
# h5部署网站的base配置到 manifest.config.ts 里的 h5.router.base
VITE_APP_PUBLIC_BASE=/
VITE_SERVER_BASEURL = ''
# 上传接口
VITE_UPLOAD_BASEURL = ''
# 有些同学可能需要在微信小程序里面根据 develop、trial、release 分别设置上传地址,参考代码如下。
# 下面的变量如果没有设置,会默认使用 VITE_SERVER_BASEURL or VITE_UPLOAD_BASEURL
VITE_SERVER_BASEURL__WEIXIN_DEVELOP = ''
VITE_SERVER_BASEURL__WEIXIN_TRIAL = ''
VITE_SERVER_BASEURL__WEIXIN_RELEASE = ''
VITE_UPLOAD_BASEURL__WEIXIN_DEVELOP = ''
VITE_UPLOAD_BASEURL__WEIXIN_TRIAL = ''
VITE_UPLOAD_BASEURL__WEIXIN_RELEASE = ''
# h5是否需要配置代理
VITE_APP_PROXY= false
VITE_APP_PROXY_PREFIX = '/api'
# 是否启用mock (1.仅支持h5 2.启用必须要开启代理,否则生效。)
VITE_USE_MOCK = true

9
env/.env.development vendored Normal file
View File

@ -0,0 +1,9 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
# 是否去除console 和 debugger
VITE_DELETE_CONSOLE = false
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = true
#VITE_SERVER_BASEURL = 'http://127.0.0.1:8080/jeecg-boot'
VITE_SERVER_BASEURL = 'https://36.112.48.190/jeecg-boot'

8
env/.env.production vendored Normal file
View File

@ -0,0 +1,8 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
# 是否去除console 和 debugger
VITE_DELETE_CONSOLE = true
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = false
VITE_SERVER_BASEURL = 'https://api3.boot.jeecg.com'

4
env/.env.test vendored Normal file
View File

@ -0,0 +1,4 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'development'
# 是否去除console 和 debugger
VITE_DELETE_CONSOLE = false

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

26
index.html Normal file
View File

@ -0,0 +1,26 @@
<!doctype html>
<html build-time="%BUILD_TIME%">
<head>
<meta charset="UTF-8" />
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
<script>
var coverSupport =
'CSS' in window &&
typeof CSS.supports === 'function' &&
(CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') +
'" />',
)
</script>
<title>JeecgBoot-uniapp3</title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

162
manifest.config.ts Normal file
View File

@ -0,0 +1,162 @@
// manifest.config.ts
import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
import path from 'node:path'
import { loadEnv } from 'vite'
//
const env = loadEnv(process.env.NODE_ENV!, path.resolve(process.cwd(), 'env'))
const {
VITE_APP_TITLE,
VITE_UNI_APPID,
VITE_WX_APPID,
VITE_APP_PUBLIC_BASE,
VITE_FALLBACK_LOCALE,
} = env
export default defineManifestConfig({
name: VITE_APP_TITLE,
appid: VITE_UNI_APPID,
description: '',
versionName: '1.0.0',
versionCode: '100',
transformPx: false,
locale: VITE_FALLBACK_LOCALE, // 'zh-Hans'
/* 5+App特有相关 */
'app-plus': {
usingComponents: true,
nvueStyleCompiler: 'uni-app',
compilerVersion: 3,
compatible: {
ignoreVersion: true,
},
splashscreen: {
alwaysShowBeforeRender: true,
waiting: true,
autoclose: true,
delay: 0,
},
/* 模块配置 */
modules: {
Maps: {},
Messaging: {},
Contacts: {},
Camera: {},
},
/* 应用发布信息 */
distribute: {
/* android打包配置 */
android: {
minSdkVersion: 30,
targetSdkVersion: 30,
abiFilters: ['armeabi-v7a', 'arm64-v8a'],
permissions: [
'<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>',
'<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>',
'<uses-permission android:name="android.permission.VIBRATE"/>',
'<uses-permission android:name="android.permission.READ_LOGS"/>',
'<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>',
'<uses-feature android:name="android.hardware.camera.autofocus"/>',
'<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>',
'<uses-permission android:name="android.permission.CAMERA"/>',
'<uses-permission android:name="android.permission.GET_ACCOUNTS"/>',
'<uses-permission android:name="android.permission.READ_PHONE_STATE"/>',
'<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>',
'<uses-permission android:name="android.permission.WAKE_LOCK"/>',
'<uses-permission android:name="android.permission.FLASHLIGHT"/>',
'<uses-feature android:name="android.hardware.camera"/>',
'<uses-permission android:name="android.permission.WRITE_SETTINGS"/>',
],
},
/* ios打包配置 */
ios: {},
/* SDK配置 */
sdkConfigs: {
maps: {
amap: {
name: 'amap_15931993294Bqxlq8EgG',
appkey_ios: 'c913e46ffdf548ebc56ac1cf4d883e7e',
appkey_android: 'c913e46ffdf548ebc56ac1cf4d883e7e',
},
},
},
/* 图标配置 */
icons: {
android: {
hdpi: 'src/static/app/icons/72x72.png',
xhdpi: 'src/static/app/icons/96x96.png',
xxhdpi: 'src/static/app/icons/144x144.png',
xxxhdpi: 'src/static/app/icons/192x192.png',
},
ios: {
appstore: 'src/static/app/icons/1024x1024.png',
ipad: {
app: 'src/static/app/icons/76x76.png',
'app@2x': 'src/static/app/icons/152x152.png',
notification: 'src/static/app/icons/20x20.png',
'notification@2x': 'src/static/app/icons/40x40.png',
'proapp@2x': 'src/static/app/icons/167x167.png',
settings: 'src/static/app/icons/29x29.png',
'settings@2x': 'src/static/app/icons/58x58.png',
spotlight: 'src/static/app/icons/40x40.png',
'spotlight@2x': 'src/static/app/icons/80x80.png',
},
iphone: {
'app@2x': 'src/static/app/icons/120x120.png',
'app@3x': 'src/static/app/icons/180x180.png',
'notification@2x': 'src/static/app/icons/40x40.png',
'notification@3x': 'src/static/app/icons/60x60.png',
'settings@2x': 'src/static/app/icons/58x58.png',
'settings@3x': 'src/static/app/icons/87x87.png',
'spotlight@2x': 'src/static/app/icons/80x80.png',
'spotlight@3x': 'src/static/app/icons/120x120.png',
},
},
},
},
},
/* 快应用特有相关 */
quickapp: {},
/* 小程序特有相关 */
'mp-weixin': {
appid: VITE_WX_APPID,
setting: {
urlCheck: false,
minified: true
},
usingComponents: true,
// __usePrivacyCheck__: true,
},
'mp-alipay': {
usingComponents: true,
styleIsolation: 'shared',
},
'mp-baidu': {
usingComponents: true,
},
'mp-toutiao': {
usingComponents: true,
},
h5: {
router: {
base: VITE_APP_PUBLIC_BASE,
},
sdkConfigs: {
maps: {
amap: {
key: '21f194a0d33197f874f7bbdd198419be',
securityJsCode: 'a46b425f31a4de445b2966d998fba851',
serviceHost: '',
},
},
},
},
'app-harmony': {
distribute: {
bundleName: 'uniapp.demo.test',
},
},
uniStatistics: {
enable: false,
},
vueVersion: '3',
})

109
mock/getChatList.ts Normal file
View File

@ -0,0 +1,109 @@
// mock/getChatLists.js
import Mock from 'mockjs'
export default [
{
url: '/api/eoa/im/newApi/getChatList',
method: 'get', // post
response: () => {
return Mock.mock({
code: 200,
success: true,
result: {
'logVoList|8-10': [
{
'id|+1': 1,
fromUserName: '@cname',
sendTime: Mock.mock('@date("yyyy-MM-dd")'),
fromAvatar: () => {
const sentences = [
'https://picsum.photos/100/100',
'https://random.imagecdn.app/100/100',
'https://q1.qlogo.cn/g?b=qq&nk=190848757&s=100',
'https://dummyimage.com/100x100/000/fff&text=%E6%9D%8E%E5%9B%9B',
'https://dummyimage.com/100x100/f37b1d/fff&text=%E7%8E%8B%E4%BA%94',
'https://dummyimage.com/100x100/59c7b8/fff&text=%E5%85%AD%E5%AD%90',
]
return sentences[Math.floor(Math.random() * sentences.length)]
},
'type|1': ['friend', 'discussion', 'group'],
'izTop|1': [1, 0],
'status|1': ['offline', 'online'],
'msgFrom|+12': 4000,
'msgTo|+34': 100,
},
],
},
})
},
},
{
url: '/api/eoa/im/newApi/creatFriendSession',
method: 'post', // post
response: () => {
return Mock.mock({
code: 200,
success: true,
result: {
'accountId|+100': 4000,
avatar: 'https://q1.qlogo.cn/g?b=qq&nk=190848757&s=100',
email: '@email',
'id|12': 112,
'msgTo|+34': 100,
'phone|9': 123,
},
})
},
},
{
url: '/api/eoa/im/newApi/records',
method: 'get', // post
response: () => {
return Mock.mock({
code: 200,
success: true,
result: {
'records|8-10': [
{
'id|+1': 1,
fromUserName: '@cname',
sendTime: Mock.mock('@date("yyyy-MM-dd")'),
fromAvatar: 'https://dummyimage.com/100x100/000/fff&text=%E6%9D%8E%E5%9B%9B',
'type|1': ['friend', 'discussion', 'group'],
'izTop|1': [1, 0],
'status|1': ['offline', 'online'],
'msgFrom|+12': 4000,
'msgTo|+34': 100,
// msgData: Mock.mock('@cparagraph()'),
msgData: () => Mock.mock('@cparagraph()'),
userId: '1678948772039729154',
msgType: 'text',
},
],
},
})
},
},
{
url: '/api/eoa/im/newApi/creatFriendSession',
method: 'post', // post
response: () => {
return Mock.mock({
code: 200,
success: true,
result: 'success',
})
},
},
{
url: '/api/eoa/im/newApi/sendMessage',
method: 'post', // post
response: () => {
return Mock.mock({
code: 200,
success: true,
result: 'success',
})
},
},
]

View File

@ -0,0 +1,13 @@
import type { GenerateServiceProps } from 'openapi-ts-request'
export default [
{
schemaPath: 'http://petstore.swagger.io/v2/swagger.json',
serversPath: './src/service/app',
requestLibPath: `import request from '@/utils/request';\n import { CustomRequestOptions } from '@/interceptors/request';`,
requestOptionsType: 'CustomRequestOptions',
isGenReactQuery: true,
reactQueryMode: 'vue',
isGenJavaScript: false,
},
] as GenerateServiceProps[]

156
package.json Normal file
View File

@ -0,0 +1,156 @@
{
"name": "JeecgUniapp3",
"type": "commonjs",
"version": "3.0.0",
"description": "JeecgBoot配套APP移动框架一份代码多终端适配支持小程序、H5、APP、ios、安卓、鸿蒙",
"engines": {
"node": ">=18",
"pnpm": ">=7.30"
},
"scripts": {
"preinstall": "npx only-allow pnpm",
"uvm": "npx @dcloudio/uvm@latest",
"uvm-rm": "node ./scripts/postupgrade.js",
"postuvm": "echo upgrade uni-app success!",
"dev:app": "uni -p app",
"dev:app-android": "uni -p app-android",
"dev:app-ios": "uni -p app-ios",
"dev:custom": "uni -p",
"dev": "uni",
"dev:h5": "uni",
"dev:h5:ssr": "uni --ssr",
"dev:mp": "uni -p mp-weixin",
"dev:mp-alipay": "uni -p mp-alipay",
"dev:mp-baidu": "uni -p mp-baidu",
"dev:mp-jd": "uni -p mp-jd",
"dev:mp-kuaishou": "uni -p mp-kuaishou",
"dev:mp-lark": "uni -p mp-lark",
"dev:mp-qq": "uni -p mp-qq",
"dev:mp-toutiao": "uni -p mp-toutiao",
"dev:mp-weixin": "uni -p mp-weixin",
"dev:mp-xhs": "uni -p mp-xhs",
"dev:quickapp-webview": "uni -p quickapp-webview",
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build:app": "uni build -p app",
"build:app-android": "uni build -p app-android",
"build:app-ios": "uni build -p app-ios",
"build:custom": "uni build -p",
"build:h5": "uni build",
"build": "uni build",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp": "uni build -p mp-weixin",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-jd": "uni build -p mp-jd",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
"build:mp-lark": "uni build -p mp-lark",
"build:mp-qq": "uni build -p mp-qq",
"build:mp-toutiao": "uni build -p mp-toutiao",
"build:mp-weixin": "uni build -p mp-weixin",
"build:mp-xhs": "uni build -p mp-xhs",
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
"type-check": "vue-tsc --noEmit",
"release": "standard-version",
"cz": "czg",
"openapi-ts-request": "openapi-ts"
},
"resolutions": {
"bin-wrapper": "npm:bin-wrapper-china"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-4030620241128001",
"@dcloudio/uni-app-harmony": "3.0.0-4030620241128001",
"@dcloudio/uni-app-plus": "3.0.0-4030620241128001",
"@dcloudio/uni-components": "3.0.0-4030620241128001",
"@dcloudio/uni-h5": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-alipay": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-baidu": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-jd": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-lark": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-qq": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-toutiao": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-weixin": "3.0.0-4030620241128001",
"@dcloudio/uni-mp-xhs": "3.0.0-4030620241128001",
"@dcloudio/uni-quickapp-webview": "3.0.0-4030620241128001",
"@tanstack/vue-query": "^5.62.16",
"@vant/area-data": "^2.0.0",
"abortcontroller-polyfill": "^1.7.8",
"base-64": "^1.0.0",
"dayjs": "1.11.10",
"echarts": "^5.6.0",
"md5": "^2.3.0",
"pinia": "2.0.36",
"pinia-plugin-persistedstate": "3.2.1",
"qs": "6.5.3",
"uni-parse-pages": "^0.0.1",
"vue": "3.4.21",
"vue-i18n": "9.1.9",
"wot-design-uni": "^1.4.0",
"z-paging": "^2.8.4"
},
"devDependencies": {
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.3",
"@dcloudio/types": "^3.4.14",
"@dcloudio/uni-automator": "3.0.0-4030620241128001",
"@dcloudio/uni-cli-shared": "3.0.0-4030620241128001",
"@dcloudio/uni-stacktracey": "3.0.0-4030620241128001",
"@dcloudio/uni-uts-v1": "3.0.0-4030620241128001",
"@dcloudio/vite-plugin-uni": "3.0.0-4030620241128001",
"@esbuild/darwin-arm64": "0.20.2",
"@esbuild/darwin-x64": "0.20.2",
"@iconify-json/carbon": "^1.2.4",
"@rollup/rollup-darwin-x64": "^4.28.0",
"@types/node": "^20.17.9",
"@types/wechat-miniprogram": "^3.4.8",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@uni-helper/uni-types": "1.0.0-alpha.3",
"@uni-helper/vite-plugin-uni-layouts": "^0.1.10",
"@uni-helper/vite-plugin-uni-manifest": "^0.2.7",
"@uni-helper/vite-plugin-uni-pages": "0.2.20",
"@uni-helper/vite-plugin-uni-platform": "^0.0.4",
"@unocss/preset-legacy-compat": "^0.59.4",
"@vue/runtime-core": "^3.4.21",
"@vue/tsconfig": "^0.1.3",
"autoprefixer": "^10.4.20",
"commitlint": "^18.6.1",
"czg": "^1.9.4",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "^9.32.0",
"mockjs": "^1.1.0",
"openapi-ts-request": "^1.1.2",
"postcss": "^8.4.49",
"postcss-html": "^1.7.0",
"postcss-scss": "^4.0.9",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "1.77.6",
"standard-version": "^9.5.0",
"stylelint": "^16.11.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recess-order": "^4.6.0",
"stylelint-config-recommended": "^14.0.1",
"stylelint-config-recommended-scss": "^14.1.0",
"stylelint-config-recommended-vue": "^1.5.0",
"stylelint-prettier": "^5.0.2",
"terser": "^5.36.0",
"typescript": "^5.7.2",
"unocss": "^0.58.9",
"unocss-applet": "^0.7.8",
"unplugin-auto-import": "^0.17.8",
"vite": "5.2.8",
"vite-plugin-mock": "^3.0.2",
"vite-plugin-restart": "^0.4.2",
"vitepress": "^1.5.0",
"vue-tsc": "^1.0.24"
}
}

55
pages.config.ts Normal file
View File

@ -0,0 +1,55 @@
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
export default defineUniPages({
globalStyle: {
navigationStyle: 'default',
navigationBarTitleText: 'uniapp',
navigationBarBackgroundColor: '#f8f8f8',
navigationBarTextStyle: 'black',
backgroundColor: '#FFFFFF',
},
easycom: {
autoscan: true,
custom: {
'^wd-(.*)': 'wot-design-uni/components/wd-$1/wd-$1.vue',
'^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)':
'z-paging/components/z-paging$1/z-paging$1.vue',
},
},
tabBar: {
color: '#aaa',
selectedColor: '#39b54a',
backgroundColor: '#F8F8F8',
borderStyle: 'black',
height: '50px',
fontSize: '11px',
iconWidth: '24px',
spacing: '3px',
list: [
{
iconPath: 'static/tabbar/tabbar-message-2.png',
selectedIconPath: 'static/tabbar/tabbar-message.png',
pagePath: 'pages/message/message',
text: '消息',
},
{
iconPath: 'static/tabbar/tabbar-home-2.png',
selectedIconPath: 'static/tabbar/tabbar-home.png',
pagePath: 'pages/index/index',
text: '协作',
},
{
iconPath: 'static/tabbar/tabbar-workHome-2.png',
selectedIconPath: 'static/tabbar/tabbar-workHome.png',
pagePath: 'pages/workHome/index',
text: '工作台',
},
{
iconPath: 'static/tabbar/tabbar-user-2.png',
selectedIconPath: 'static/tabbar/tabbar-user.png',
pagePath: 'pages/user/people',
text: '个人',
},
],
},
})

15207
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

36
scripts/postupgrade.js Normal file
View File

@ -0,0 +1,36 @@
// # `pnpm upgrade` `uniapp`
// #
// #
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { exec } = require('child_process')
//
const dependencies = [
'@dcloudio/uni-app-harmony',
// TODO:
'@dcloudio/uni-mp-alipay',
'@dcloudio/uni-mp-baidu',
'@dcloudio/uni-mp-jd',
'@dcloudio/uni-mp-kuaishou',
'@dcloudio/uni-mp-lark',
'@dcloudio/uni-mp-qq',
'@dcloudio/uni-mp-toutiao',
'@dcloudio/uni-mp-xhs',
'@dcloudio/uni-quickapp-webview',
// i18n
'vue-i18n',
]
// 使exec
exec(`pnpm un ${dependencies.join(' ')}`, (error, stdout, stderr) => {
if (error) {
//
console.error(`执行出错: ${error}`)
return
}
//
console.log(`stdout: ${stdout}`)
//
console.error(`stderr: ${stderr}`)
})

118
src/App.vue Normal file
View File

@ -0,0 +1,118 @@
<script lang="ts">
import { onLaunch, onShow, onHide, onLoad, onReady } from '@dcloudio/uni-app'
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
import { beforEach } from '@/router/index'
import { jurisdictionApi } from '@/api/system/login';
import { useAppStore } from '@/store'
export default {
onLaunch: function (options) {
console.log('App Launch')
console.log('应用启动路径:', options.path)
},
onShow: function (options) {
console.log('App Show')
console.log('应用启动路径:', options.path)
//
setTimeout(() => {
const currentPage = options.path
beforEach({ path: '/' }, { path: currentPage, fullPath: currentPage }, (data) => {
if (data?.path) {
uni.redirectTo({ url: data.path })
}
})
}, 100)
jurisdictionApi("1827997127165677570").then((res : any) => {
// 0 1
if (res.success) {
const appStore = useAppStore()
appStore.setIsGray(res.result.value) //
}
})
},
onHide: function () {
console.log('App Hide')
},
//
globalData: {
isLocalConfig: true,
systemInfo: uni.getSystemInfoSync(),
navHeight: 44,
},
}
</script>
<style lang="scss">
.gray {
filter: grayscale(1);
}
body {
font-size: 14px;
color: #333333;
font-family:
Helvetica Neue,
Helvetica,
sans-serif;
}
uni-page-body {
height: 100%;
&>uni-view {
height: 100%;
}
}
.shadow-warp {
position: relative;
box-shadow: 0 0 5px rgba(168, 92, 92, 0.1);
}
/* stylelint-disable selector-type-no-unknown */
button::after {
border: none;
}
swiper,
scroll-view {
flex: 1;
height: 100%;
overflow: hidden;
}
image {
width: 100%;
height: 100%;
vertical-align: middle;
}
// 使 unocss: text-ellipsis
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
//
.ellipsis-2 {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-all;
}
//
.ellipsis-3 {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
</style>

37
src/api/system/login.ts Normal file
View File

@ -0,0 +1,37 @@
import { http } from '@/utils/http'; // @/ ./src/
// ts HTTP TypeScript HTTP "POST""post"
interface LoginParams {
username: string;
password: string;
captcha?: string; //
}
/**
* 统一的登录方法
* @param config 登录参数
* @returns 登录请求的 Promise
*/
export function loginApi(config : LoginParams) {
// captcha/sys/login,/sys/sinopecLogin
const url = config.captcha ? '/sys/login' : '/sys/sinopecLogin';
return http({
url,
method: 'POST',
data: config,
});
}
/**
* 获取是否灰化
* @param id 登录参数
* @returns 0正常 1灰化
*/
export function jurisdictionApi(id : string) { //
return http({
url: '/CxcJurisdiction/cxcJurisdiction/queryById',
method: 'GET',
data: {
id
}
})
}

117
src/common/areaData/Area.ts Normal file
View File

@ -0,0 +1,117 @@
import {pcaa as REGION_DATA} from "./pcaUtils";
/**
* Area 属性all的类型
*/
interface PlainPca {
id: string;
text: string;
pid: string;
index: Number;
}
/**
* 省市区工具类 -解决列表省市区组件的翻译问题
*/
class Area {
all: PlainPca[];
/**
* 构造器
* @param pcaa
*/
constructor(pcaa?) {
if (!pcaa) {
pcaa = REGION_DATA;
}
let arr: PlainPca[] = [];
const province = pcaa['86'];
Object.keys(province).map((key) => {
arr.push({ id: key, text: province[key], pid: '86', index: 1 });
const city = pcaa[key];
Object.keys(city).map((key2) => {
arr.push({ id: key2, text: city[key2], pid: key, index: 2 });
const qu = pcaa[key2];
if (qu) {
Object.keys(qu).map((key3) => {
arr.push({ id: key3, text: qu[key3], pid: key2, index: 3 });
});
}
});
});
this.all = arr;
}
get pca() {
return this.all;
}
getCode(text) {
if (!text || text.length == 0) {
return '';
}
for (let item of this.all) {
if (item.text === text) {
return item.id;
}
}
}
//update-begin-author:liusq---date:20230404--for: [issue/382]JAreaLinkage---
getText(code,index=3) {
if (!code || code.length == 0) {
return '';
}
let arr = [];
this.getAreaBycode(code, arr, index);
return arr.join('/');
}
//update-end-author:liusq---date:20230404--for: [issue/382]JAreaLinkage---
getRealCode(code) {
let arr = [];
this.getPcode(code, arr, 3);
return arr;
}
getPcode(id, arr, index) {
for (let item of this.all) {
if (item.id === id && item.index == index) {
arr.unshift(id);
if (item.pid != '86') {
this.getPcode(item.pid, arr, --index);
}
}
}
}
getAreaBycode(code, arr, index) {
for (let item of this.all) {
if (item.id === code && item.index == index) {
arr.unshift(item.text);
if (item.pid != '86') {
this.getAreaBycode(item.pid, arr, --index);
}
}
}
}
}
const jeecgAreaData = new Area();
// code
const getAreaTextByCode = function (code) {
let index = 3;
//update-begin-author:liusq---date:20220531--for: codecodecode ---
if (code && code.includes(',')) {
index = code.split(",").length;
code = code.substr(code.lastIndexOf(',') + 1);
}
//update-end-author:liusq---date:20220531--for: codecodecode ---
return jeecgAreaData.getText(code,index);
};
// code
const getAreaArrByCode = function (code) {
return jeecgAreaData.getRealCode(code);
};
export { getAreaTextByCode,getAreaArrByCode };

View File

@ -0,0 +1,57 @@
import {areaList} from '@vant/area-data'
//
export const pcaa = freezeDeep(usePlatPcaaData())
/**
* 获取扁平化的省市区数据
*/
function usePlatPcaaData() {
const {city_list: city, county_list: county, province_list: province} = areaList;
const dataMap = new Map<string, Recordable>()
const flatData: Recordable = {'86': province}
//
Object.keys(province).forEach((code) => {
flatData[code] = {}
dataMap.set(code.slice(0, 2), flatData[code])
})
//
Object.keys(city).forEach((code) => {
flatData[code] = {}
dataMap.set(code.slice(0, 4), flatData[code])
//
const getProvince = dataMap.get(code.slice(0, 2))
if (getProvince) {
getProvince[code] = city[code]
}
});
//
Object.keys(county).forEach((code) => {
//
const getCity = dataMap.get(code.slice(0, 4))
if (getCity) {
getCity[code] = county[code]
}
});
return flatData
}
/**
*
* 深度冻结对象
* @param obj Object or Array
*/
export function freezeDeep(obj: Recordable | Recordable[]) {
if (obj != null) {
if (Array.isArray(obj)) {
obj.forEach(item => freezeDeep(item))
} else if (typeof obj === 'object') {
Object.values(obj).forEach(value => {
freezeDeep(value)
})
}
Object.freeze(obj)
}
return obj
}

135
src/common/constants.ts Normal file
View File

@ -0,0 +1,135 @@
export const ACCESS_TOKEN = 'Access-Token'
export const USER_NAME = 'login_username'
export const USER_INFO = 'login_user_info'
export const NAV_BAR_COLOR = 'bg-gradual-blue'
export const APP_ROUTE = 'app_route_list'
export const APP_CONFIG = 'app_config'
export const X_TENANT_ID = 'X-Tenant-Id'
export const X_Low_App_ID = 'X-Low-App-ID'
export const TENANT_LIST = 'tenant_list'
export const ROUTE_PARAMS = "cacheRouteParams"
export const HOME_PAGE = "/pages/message/message"
//10
export const HOME_CONFIG_EXPIRED_TIME = 10*60
export const phone = '---'
export const email = '---'
export const company = '---'
const STORAGE_OPTIONS = {
namespace: 'pro__', // key prefix
name: 'ls', // name variable Vue.[ls] or this.[$ls],
storage: 'local', // storage name session, local, memory
}
export default STORAGE_OPTIONS;
//
export const conditionObj = {
input:[{label:"包含",value:"like"},{label:"以...开始",value:"right_like"},{label:"以...结尾",value:"left_like"},{label:"在...中",value:"in"}],
number:[{label:"大于",value:"gt"},{label:"大于等于",value:"ge"},{label:"小于",value:"lt"},{label:"小于等于",value:"le"}],
date:[{label:"大于",value:"gt"},{label:"大于等于",value:"ge"},{label:"小于",value:"lt"},{label:"小于等于",value:"le"}],
select:[],
checkbox:[{label:"多词匹配",value:"elemMatch"}],
}
/**
* 颜色板
* classic经典
* technology科技
* business商务
* botany植物
* natural自然
* colour彩色
*/
export const colorPanel = {
classic:["#64b5f6","#4db6ac","#ffb74d","#e57373","#9575cd","#a1887f","#90a4ae","#4dd0e1","#81c784","#ff8a65"],
technology:["#3a5b84","#4d6e98","#7594b9","#bfd7f2","#18619f","#408aca","#5ea8e9","#81c3fc","#71a5cb","#a1cae4"],
business:["#ccedf7","#b9dcf0","#12a0e7","#0663a4","#458890","#97d9cd","#4bb8bf","#20899c","#f44336 ","#a2c7d9"],
botany:["#34b392","#4ac2a6","#8ed1c0","#ccdec6","#61bdb5","#7993a1","#93a889","#5e8d83","#115040","#bcc5b4"],
natural:["#85cacd","#a7d676","#fee159","#fbc78e","#ef918b","#a9b5ff","#e7daca","#fc803a","#fea1ac","#c2a3cd"],
colour:["#fddb9c","#f9ae91","#f59193","#d47f97","#bd86a6","#f595a1","#624772","#fe7156","#ffbda3","#877fa8"]
};
//
export const allCondition = [
{label:"包含",value:"like"},
{label:"以...开始",value:"right_like"},
{label:"以...结尾",value:"left_like"},
{label:"在...中",value:"in"},
{label:"大于",value:"gt"},
{label:"大于等于",value:"ge"},
{label:"小于",value:"lt"},
{label:"小于等于",value:"le"},
{label:"多词匹配",value:"elemMatch"},
{label:"等于",value:"eq"},
{label:"不等于",value:"ne"},
{label:"为空",value:"empty"},
{label:"不为空",value:"not_empty"}
]
//
export const compList = [
"JBar",
"JStackBar",
"JMultipleBar",
"JNegativeBar",
"JLine",
"JMultipleLine",
"DoubleLineBar",
"JPie",
"JRing",
"JFunnel",
"JPyramidFunnel",
"JRadar",
"JCircleRadar",
"JGauge",
"JColorGauge",
"JScatter",
"JBubble",
"JDragEditor",
"JCarousel",
"JIframe",
"JNumber",
"JCustomButton",
"JPivotTable",
"JBubbleMap",
"JBarMap",
"JHeatMap",
];
//
export const noActionList = [
"JCustomButton",
"JIframe",
"JCarousel",
"JDragEditor",
];
//
export const systemFields = [{
dataIndex:"create_time",
key:"create_time",
title:"创建时间",
type:'date',
width:200
},{
dataIndex:"create_by",
key:"create_by",
title:"创建人",
width:150
},{
dataIndex:"update_time",
key:"update_time",
title:"修改时间",
type:'date',
width:200
},{
dataIndex:"update_by",
key:"update_by",
title:"修改人",
width:150
}]

108
src/common/is.ts Normal file
View File

@ -0,0 +1,108 @@
const toString = Object.prototype.toString
export function is(val: unknown, type: string) {
return toString.call(val) === `[object ${type}]`
}
export function isDef<T = unknown>(val?: T): val is T {
return typeof val !== 'undefined'
}
export function isUnDef<T = unknown>(val?: T): val is T {
return !isDef(val)
}
export function isObject(val: any): val is Record<any, any> {
return val !== null && is(val, 'Object')
}
export function isEmpty<T = unknown>(val: T): val is T {
if (isArray(val) || isString(val)) {
return val.length === 0
}
if (val instanceof Map || val instanceof Set) {
return val.size === 0
}
if (isObject(val)) {
return Object.keys(val).length === 0
}
return false
}
export function isDate(val: unknown): val is Date {
return is(val, 'Date')
}
export function isNull(val: unknown): val is null {
return val === null
}
export function isNullAndUnDef(val: unknown): val is null | undefined {
return isUnDef(val) && isNull(val)
}
export function isNullOrUnDef(val: unknown): val is null | undefined {
return isUnDef(val) || isNull(val)
}
export function isNumber(val: unknown): val is number {
return is(val, 'Number')
}
export function isPromise<T = any>(val: any): val is Promise<T> {
// update-begin--author:sunjianlei---date:20211022---for: Promise Object --------
return is(val, 'Promise') && isFunction(val.then) && isFunction(val.catch)
// update-end--author:sunjianlei---date:20211022---for: Promise Object --------
}
export function isString(val: unknown): val is string {
return is(val, 'String')
}
export function isJsonObjectString(val: string): val is string {
if (!val) {
return false
}
return val.startsWith('{') && val.endsWith('}')
}
export function isFunction(val: unknown): val is Function {
return typeof val === 'function'
}
export function isBoolean(val: unknown): val is boolean {
return is(val, 'Boolean')
}
export function isRegExp(val: unknown): val is RegExp {
return is(val, 'RegExp')
}
export function isArray(val: any): val is Array<any> {
return val && Array.isArray(val)
}
export function isWindow(val: any): val is Window {
return typeof window !== 'undefined' && is(val, 'Window')
}
export function isElement(val: unknown): val is Element {
return isObject(val) && !!val.tagName
}
export function isMap(val: unknown): val is Map<any, any> {
return is(val, 'Map')
}
export const isServer = typeof window === 'undefined'
export const isClient = !isServer
export function isUrl(path: string): boolean {
const reg =
/(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/
return reg.test(path)
}

140
src/common/socket.ts Normal file
View File

@ -0,0 +1,140 @@
// @ts-nocheck
import { randomString } from './uitls'
import { useUserStore } from '@/store/user'
const baseUrl = import.meta.env.VITE_SERVER_BASEURL
class socket {
constructor() {
this.socketUrl = baseUrl
this.socketStart = false
this.socketType = ''
this.monitorSocketError()
this.monitorSocketClose()
this.socketReceive()
}
init(socket_type, callback?) {
const userStore = useUserStore()
const _this = this
if (baseUrl) {
if (this.socketStart) {
console.log('webSocket已经启动了')
} else {
_this.socketType = socket_type
let url =
this.socketUrl.replace('https://', 'wss://').replace('http://', 'ws://') +
'/' +
socket_type +
'/' +
userStore.userInfo.userid +
'_app'
if (socket_type == 'eoaNewChatSocket') {
let randomMessageId = randomString(6)
url =
this.socketUrl.replace('https://', 'wss://').replace('http://', 'ws://') +
'/eoaNewChatSocket/' +
userStore.userInfo.userid +
'/' +
randomMessageId
}
console.log('启动this.socketUrl连接地址', url)
// update-begin-author:taoyan date:20220422 for:v2.4.6 websocket #3278
let token = userStore.userInfo.token
uni.connectSocket({
url: url,
method: 'GET',
protocols: [token],
})
// update-end-author:taoyan date:20220422 for: v2.4.6 websocket #3278
uni.onSocketOpen((res) => {
this.socketStart = true
callback && callback()
console.log('WebSocket连接已打开')
})
/* setTimeout(() => {
_this.getHeartbeat();
}, 5000); */
}
} else {
console.log('config/baseUrl socketUrl为空')
}
}
// Socket
send(data, callback) {
const userStore = useUserStore()
const _this = this
if (userStore.userInfo.userid) {
data.userUid = userStore.userInfo.userid
}
console.log(data)
uni.sendSocketMessage({
data: JSON.stringify(data),
success: () => {
callback && callback(true)
},
fail: () => {
callback && callback(false)
},
})
}
// Socket
socketReceive() {
const _this = this
uni.onSocketMessage(function (res) {
console.log('APP:--》收到服务器内容:')
let data = JSON.parse(res.data)
// console.log('', data);
_this.acceptMessage && _this.acceptMessage(data)
})
}
// Socket
closeSocket() {
const _this = this
uni.closeSocket()
_this.socketStart = false
}
// Socket
monitorSocketClose() {
const _this = this
uni.onSocketClose(function (res) {
console.log('WebSocket 已关闭!')
_this.socketStart = false
setTimeout(function () {
_this.init(_this.socketType)
}, 3000)
})
}
// Socket
monitorSocketError() {
const _this = this
uni.onSocketError(function (res) {
_this.socketStart = false
console.log('WebSocket连接打开失败请检查')
})
}
//
getHeartbeat() {
const userStore = useUserStore()
const _this = this
this.send(
{
type: '心跳',
userUid: userStore.userInfo.userid,
},
(val) => {
setTimeout(() => {
if (val) {
// _this.getHeartbeat();
} else {
if (!_this.socketStart) {
// _this.init();
}
}
}, 10000)
},
)
}
}
const mySocket = new socket()
export default mySocket

331
src/common/uitls.ts Normal file
View File

@ -0,0 +1,331 @@
import pagesJson from '../pages.json'
// uni-parse-pages
import pagesJsonToRoutes from 'uni-parse-pages'
import { colorPanel } from './constants'
/**
* 缓存,默认有效期2小时
* @param key 缓存key
* @param value 缓存值
* @param seconds 缓存时间
* @returns {*}
*/
export function cache(key, value = null, seconds = 2 * 3600) {
var timestamp = +new Date() / 1000
if (key && value === null) {
//
var val = uni.getStorageSync(key)
if (val && val.length > 0) {
var tmp = val.split('|')
if (!tmp[2] || timestamp >= tmp[2]) {
console.log('key已失效')
//
uni.removeStorageSync(key)
return ''
} else {
console.log('key未失效')
if (tmp[1] == 'json') {
return JSON.parse(tmp[0])
}
return tmp[0]
}
}
} else if (key && value) {
//
var expire = timestamp + seconds
console.log('typeof value', typeof value)
if (typeof value == 'object') {
value = JSON.stringify(value) + '|json|' + expire
} else {
value = value + '|string|' + expire
}
uni.setStorageSync(key, value)
} else {
console.log('key不能空')
}
}
//
export const getStaticDomainURL = () => {
return import.meta.env.VITE_SERVER_BASEURL + '/sys/common/static'
}
export const getFileAccessHttpUrl = function (avatar, subStr?) {
if (!avatar) return ''
if (!subStr) subStr = 'http'
if (avatar) {
avatar = avatar.replace(/user_imgs\\/, 'user_imgs/')
}
if (avatar && avatar.startsWith(subStr)) {
return avatar
} else {
return getStaticDomainURL() + '/' + avatar
}
}
interface hasRouteType {
name?: string
path?: string
routeList?: any
}
//
export const hasRoute = ({ name, path, routeList }: hasRouteType) => {
routeList = routeList ?? pagesJsonToRoutes(pagesJson)
if (path) {
return !!routeList.find((item) => item.path === path)
}
if (name) {
return !!routeList.find((item) => item.path.split('/').pop() === name)
}
}
/**
* 人性化显示时间
*
* @param {Object} datetime
*/
export function beautifyTime(datetime = '') {
if (datetime == null) {
return ''
}
datetime = datetime.toString().replace(/-/g, '/')
let time = new Date()
let outTime = new Date(datetime)
if (/^[1-9]\d*$/.test(datetime)) {
outTime = new Date(parseInt(datetime))
}
if (time.getTime() < outTime.getTime()) {
return parseTime(outTime, '{y}/{m}/{d}')
}
if (time.getFullYear() != outTime.getFullYear()) {
return parseTime(outTime, '{y}/{m}/{d}')
}
if (time.getMonth() != outTime.getMonth()) {
return parseTime(outTime, '{m}/{d}')
}
if (time.getDate() != outTime.getDate()) {
let day = outTime.getDate() - time.getDate()
if (day == -1) {
return parseTime(outTime, '昨天 {h}:{i}')
}
if (day == -2) {
return parseTime(outTime, '前天 {h}:{i}')
}
return parseTime(outTime, '{m}-{d}')
}
if (time.getHours() != outTime.getHours()) {
return parseTime(outTime, '{h}:{i}')
}
let minutes = outTime.getMinutes() - time.getMinutes()
if (minutes == 0) {
return '刚刚'
}
minutes = Math.abs(minutes)
return `${minutes}分钟前`
}
/**
* 格式化时间
* @param {Object} time
* @param {Object} cFormat
*/
export function parseTime(time, cFormat) {
if (arguments.length === 0) {
return null
}
let date
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
if (typeof time === 'object') {
date = time
} else {
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
time = parseInt(time)
} else {
time = new Date(time)
}
date = new Date(time.toString().replace(/-/g, '/'))
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay(),
}
const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
const value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value]
}
return value.toString().padStart(2, '0')
})
return time_str
}
/**
* 随机生成字符串
* @param length 字符串的长度
* @param chats 可选字符串区间只会生成传入的字符串中的字符
* @return string 生成的字符串
*/
export function randomString(length, chats) {
if (!length) length = 1
if (!chats) chats = '0123456789qwertyuioplkjhgfdsazxcvbnm'
let str = ''
for (let i = 0; i < length; i++) {
//@ts-ignore
let num = randomNumber(0, chats.length - 1)
str += chats[num]
}
return str
}
/**
* 随机生成数字
*
* 示例生成长度为 12 的随机数randomNumber(12)
* 示例生成 3~23 之间的随机数randomNumber(3, 23)
*
* @param1 最小值 | 长度
* @param2 最大值
* @return int 生成后的数字
*/
export function randomNumber() {
//
const random = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min)
}
if (arguments.length === 1) {
//@ts-ignore
let [length] = arguments
// 0
//@ts-ignore
let nums = [...Array(length).keys()].map((i) => (i > 0 ? random(0, 9) : random(1, 9)))
return parseInt(nums.join(''))
} else if (arguments.length >= 2) {
//@ts-ignore
let [min, max] = arguments
return random(min, max)
} else {
return Number.NaN
}
}
/**
* 时间格式化
* @param value
* @param fmt
* @returns {*}
*/
export function formatDate(value, fmt) {
var regPos = /^\d+(\.\d+)?$/
if (regPos.test(value)) {
//
let getDate = new Date(value)
let o = {
'M+': getDate.getMonth() + 1,
'd+': getDate.getDate(),
'h+': getDate.getHours(),
'H+': getDate.getHours(),
'm+': getDate.getMinutes(),
's+': getDate.getSeconds(),
'q+': Math.floor((getDate.getMonth() + 3) / 3),
S: getDate.getMilliseconds(),
}
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (getDate.getFullYear() + '').substr(4 - RegExp.$1.length))
}
for (let k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length),
)
}
}
return fmt
} else {
//TODO
if (value && value.length > 0) {
value = value.trim()
return value.substr(0, fmt.length)
}
return value
}
}
// antd
export function getWeekMonthQuarterYear(date) {
// ISO
const getISOWeek = (date) => {
const jan4 = new Date(date.getFullYear(), 0, 4)
const oneDay = 86400000 //
return Math.ceil(((date - jan4.getTime()) / oneDay + jan4.getDay() + 1) / 7)
}
//
const dateObj = new Date(date)
//
const week = getISOWeek(dateObj)
//
const month = dateObj.getMonth() + 1 // 01
//
const quarter = Math.floor(dateObj.getMonth() / 3) + 1
//
const year = dateObj.getFullYear()
return {
year: `${year}`,
month: `${year}-${month.toString().padStart(2, '0')}`,
week: `${year}-${week}`,
quarter: `${year}-Q${quarter}`,
}
}
// 1 10
export function getRandomIntBetweenOneAndTen() {
return Math.floor(Math.random() * 10) + 1;
}
/**
* 获取随机颜色
* @param {any} color
* 颜色板
* classic经典
* technology科技
* business商务
* botany植物
* natural自然
* colour彩色
* @return
*/
export function getRandomColor() {
let colorType = ['classic','technology','business','botany','natural','colour'];
// 0 1
let randomIndex = Math.floor(Math.random() * colorType.length);
//
let randomColorType = colorType[randomIndex];
return colorPanel['natural'][getRandomIntBetweenOneAndTen()] || '#00bcd4';
}
//
export const getPlaceholder = (attrs: any = {}) => {
let label = attrs.label
if (label.endsWith('') || label.endsWith(':')) {
label = label.substr(0, label.length - 1)
}
return `请选择${label}`
}

135
src/common/work.ts Normal file
View File

@ -0,0 +1,135 @@
/**
* 常用服务
* useful server
*/
const icon_prefix = '/static/index/128/'
/*
*/
export const us = {
data: [
{
title: 'online',
icon: icon_prefix + 'qingjia1.png',
description: '请假申请',
useCount: 10000,
routeIndex: 'online',
enabled: true,
},
{
title: '组件示例',
icon: icon_prefix + 'chuchai.png',
description: '出差申请',
useCount: 10000,
routeIndex: 'demo',
enabled: true,
},
{
title: '公文发文',
icon: icon_prefix + 'gongwen.png',
description: '公文发文',
useCount: 10000,
routeIndex: 'docSend',
},
{
title: '通知公告',
icon: icon_prefix + 'tongzhi.png',
description: '查看企业对员工下发的通知公告',
useCount: 10000,
routeIndex: 'annotationList',
enabled: true,
},
{
title: '日程',
icon: icon_prefix + 'richeng.png',
description: '建立和查看个人工作安排',
useCount: 10000,
routeIndex: 'plan',
},
{
title: '考勤',
icon: icon_prefix + 'kaoqin.png',
description: '工作考勤',
routeIndex: 'attendance',
useCount: 10000,
},
{
title: '内部邮件',
icon: icon_prefix + 'youjian.png',
description: '查看内部消息',
useCount: 10000,
dot: false,
routeIndex: 'mailHome',
},
{
title: '通讯录',
icon: icon_prefix + 'tongxun.png',
description: '查看组员',
useCount: 10000,
//routeIndex:'addressBook',
routeIndex: 'levelAddressBook',
},
{
title: '日报',
icon: icon_prefix + 'richang.png',
description: '记录每天的工作经验和心得',
useCount: 1000,
},
{
title: '周报',
icon: icon_prefix + 'zhoubao.png',
description: '总结每周的工作情况和下周计划',
useCount: 10000,
},
],
}
/**
* other server 其他服务
*/
export const os = {
data: [
{
title: '新闻中心',
icon: icon_prefix + 'xinwen.png',
description: '新闻中心',
routeIndex: 'columnList',
useCount: 10000,
},
{
title: '文档中心',
icon: icon_prefix + 'wendang.png',
description: '文档中心',
routeIndex: 'fileHome',
useCount: 10000,
},
{
title: '会议',
icon: icon_prefix + 'huiyi.png',
description: '会议',
useCount: 10000,
routeIndex: 'meeting',
},
{
title: '任务中心',
icon: icon_prefix + 'renwu.png',
description: '任务中心',
useCount: 10000,
},
{
title: '合同',
icon: icon_prefix + 'hetong.png',
description: '合同',
useCount: 10000,
},
// #ifndef MP-WEIXIN
{
title: '聊天',
icon: icon_prefix + 'kehu.png',
description: '聊天',
routeIndex: 'chathome',
},
// #endif
],
}

View File

@ -0,0 +1,59 @@
<template>
<wd-popup v-model="show" position="bottom" @close="handleClose">
<view class="contetn">
<wd-text v-if="title" :text="title"></wd-text>
<wd-cell-group border>
<wd-cell
v-for="(item, index) in options"
:icon="item.icon"
:label="item.label"
:custom-class="item.color"
clickable
@click="handleClick(item)"
></wd-cell>
</wd-cell-group>
</view>
</wd-popup>
</template>
<script setup lang="ts">
import { ref } from 'vue'
defineOptions({
name: 'BottomOperate',
options: {
styleIsolation: 'shared',
},
})
const eimt = defineEmits(['change', 'close'])
const show = ref(true)
const props = defineProps(['title', 'data', 'options'])
const handleClose = () => {
show.value = false
setTimeout(() => {
eimt('close')
}, 300)
}
const handleClick = (item) => {
eimt('change', { option: item, data: props.data })
handleClose()
}
</script>
<style lang="scss" scoped>
.contetn {
padding: 10px;
.wd-text.is-default {
font-size: 14px;
color: #666;
}
:deep(.wd-cell) {
padding-left: 0;
--wot-cell-label-color: #444;
--wot-cell-label-fs: 14px;
&.red {
color: red;
--wot-cell-label-color: red;
}
}
}
</style>

View File

@ -0,0 +1,258 @@
<template>
<view class="CategorySelect">
<view @click="handleClick">
<wd-input
:placeholder="`请选择${$attrs.label}`"
v-bind="$attrs"
readonly
v-model="showText"
></wd-input>
</view>
<wd-popup position="bottom" v-model="popupShow">
<view class="content">
<view class="operation">
<view class="cancel text-gray-5" @click.stop="cancel">取消</view>
<view class="confrim" @click.stop="confirm">确定</view>
</view>
<scroll-view class="flex-1" scroll-y>
<DaTree
:data="treeData"
labelField="title"
valueField="key"
loadMode
:showCheckbox="multiple"
:showRadioIcon="false"
:checkStrictly="true"
:loadApi="asyncLoadTreeData"
@change="handleTreeChange"
></DaTree>
</scroll-view>
</view>
</wd-popup>
</view>
</template>
<script setup lang="ts">
import { ref, watch, useAttrs } from 'vue'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { http } from '@/utils/http'
import DaTree from '@/uni_modules/da-tree/index.vue'
import { isArray } from '@/utils/is'
defineOptions({
name: 'CategorySelect',
})
const props = defineProps({
modelValue: {
type: [Array, String],
},
placeholder: {
type: String,
default: '请选择',
required: false,
},
condition: {
type: String,
default: '',
required: false,
},
//
multiple: {
type: Boolean,
default: false,
},
pid: {
type: String,
default: '',
required: false,
},
pcode: {
type: String,
default: '',
required: false,
},
})
const emit = defineEmits(['change', 'update:modelValue'])
const toast = useToast()
const api = {
loadDictItem: '/sys/category/loadDictItem/',
loadTreeData: '/sys/category/loadTreeData',
}
const showText = ref('')
const popupShow = ref(false)
const treeData = ref<any[]>([])
const treeValue = ref([])
const handleClick = () => {
popupShow.value = true
}
const cancel = () => {
popupShow.value = false
}
const confirm = () => {
const titles = treeValue.value.map((item) => item.title)
const keys = treeValue.value.map((item) => item.key).join(',')
showText.value = titles.join(',')
popupShow.value = false
emit('update:modelValue', keys)
emit('change', keys)
}
const handleTreeChange = (value, record) => {
const { originItem, checkedStatus } = record
const { key, title } = originItem
if (checkedStatus) {
//
if (props.multiple) {
treeValue.value.push({ key, title })
} else {
treeValue.value = [{ key, title }]
}
} else {
//
if (props.multiple) {
const findIndex = treeValue.value.findIndex((item) => item.key == key)
if (findIndex != -1) {
treeValue.value.splice(findIndex, 1)
}
} else {
treeValue.value = []
}
}
}
const transformField = (result) => {
for (let i of result) {
i.value = i.key
if (i.leaf == false) {
i.isLeaf = false
} else if (i.leaf == true) {
i.isLeaf = true
}
}
}
//
const asyncLoadTreeData = ({ originItem }) => {
return new Promise<void>((resolve) => {
let param = {
pid: originItem.key,
condition: props.condition,
}
http
.get(api.loadTreeData, param)
.then((res: any) => {
if (res.success) {
const { result } = res
transformField(result)
resolve(result)
} else {
resolve(null)
}
})
.catch((err) => resolve(null))
})
}
//
function loadRoot() {
let param = {
pid: props.pid,
pcode: !props.pcode ? '0' : props.pcode,
condition: props.condition,
}
http
.get(api.loadTreeData, param)
.then((res: any) => {
if (res.success) {
const { result } = res
if (result && result.length > 0) {
transformField(result)
treeData.value = result
}
} else {
toast.warning('分类字典书组件根节点数据加载失败~')
}
})
.catch((err) => {
toast.warning('分类字典书组件根节点数据加载失败~')
})
}
// input
function loadItemByCode() {
let value = props.modelValue
if (isArray(props.modelValue)) {
// @ts-ignore
value = value.join()
}
if (value === treeData.value.map((item) => item.key).join(',')) {
//
return
}
http
.get(api.loadDictItem, { ids: value })
.then((res: any) => {
if (res.success) {
const { result = [] } = res
showText.value = result.join(',')
} else {
}
})
.catch((err) => {})
}
watch(
() => props.modelValue,
() => {
loadItemByCode()
},
{ deep: true, immediate: true },
)
watch(
() => props.pcode,
() => {
loadRoot()
},
{ deep: true, immediate: true },
)
</script>
<style lang="scss" scoped>
:deep(.wd-popup-wrapper) {
.wd-popup {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
}
.content {
height: 50vh;
width: 100vw;
display: flex;
flex-direction: column;
.operation {
display: flex;
justify-content: space-between;
line-height: 40px;
padding: 0 5px;
position: relative;
&::before {
content: ' ';
position: absolute;
bottom: 0;
left: 8px;
right: 8px;
height: 1px;
background-color: #e5e5e5;
}
.cancel,
.confrim {
font-size: 15px;
height: 40px;
min-width: 40px;
text-align: center;
}
.confrim {
color: var(--wot-color-theme);
}
}
:deep(.da-tree) {
.da-tree-item__checkbox {
// display: none;
}
}
}
</style>

View File

@ -0,0 +1,100 @@
<template>
<view
class="wrap"
:style="{
'--borderColor': borderColor,
'--imgWidth': imgWidth,
'--imgHeight': imgHeight,
}"
>
<wd-grid :clickable="clickable" :column="column">
<template v-for="(item, index) in modelValue" :key="item[itemKey]">
<wd-grid-item
:custom-class="getClass(index)"
use-icon-slot
:text="item.text"
@itemclick="handleClik(item, index)"
>
<template #icon>
<wd-img :width="imgWidth" :height="imgHeight" :src="item.img"></wd-img>
</template>
</wd-grid-item>
</template>
</wd-grid>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
defineOptions({
name: 'Grid',
options: {
styleIsolation: 'shared',
},
})
const props = defineProps({
modelValue: {
type: Array,
default: () => [],
},
column: {
type: Number,
default: 4,
},
itemKey: {
type: String,
default: 'id',
},
imgWidth: {
type: String,
default: '28px',
},
imgHeight: {
type: String,
default: '28px',
},
clickable: {
type: Boolean,
default: true,
},
borderColor: {
type: String,
default: 'rgba(165, 165, 165, 0.1)',
},
})
const emit = defineEmits(['itemClik'])
const getClass = (index) => {
let className = ''
if (index < props.column) {
className = 'first-row'
}
if ((index + 1) % props.column == 0) {
className += ` lastCol`
}
return className
}
const handleClik = (item, index) => {
emit('itemClik', item, index)
}
</script>
<style lang="scss" scoped>
:deep(.wd-grid-item) {
box-sizing: border-box;
border-right: 1px solid var(--borderColor, rgba(165, 165, 165, 0.1));
border-bottom: 1px solid var(--borderColor, rgba(165, 165, 165, 0.1));
&.first-row {
border-top: 1px solid var(--borderColor, rgba(165, 165, 165, 0.1));
}
&.lastCol {
border-right: none;
}
.wd-grid-item__text {
margin-top: 10px;
}
.wd-grid-item__wrapper {
width: var(--imgWidth, 28px) !important;
height: var(--imgHeight, 28px) !important;
}
}
</style>

View File

@ -0,0 +1,144 @@
<template>
<view class="previewImage" @tap="close">
<view class="page" v-if="urls.length > 0">
<text class="text">{{ current + 1 }} / {{ urls.length }}</text>
</view>
<swiper
class="swiper"
:current="current"
@change="swiperChange"
@touchstart="handleTouchStart"
@touchend="handleTouchEnd"
>
<swiper-item class="swiperItem" v-for="(item, index) in urls" :key="index">
<movable-area class="movable-area" scale-area>
<movable-view
class="movable-view"
direction="all"
:inertia="true"
damping="100"
scale="true"
scale-min="1"
scale-max="4"
:scale-value="scale"
>
<scroll-view scroll-y="true" class="uni-scroll-view">
<view class="scroll-view">
<image
:key="index"
class="image"
:src="item"
mode="widthFix"
@longpress="onLongpress(item)"
/>
</view>
</scroll-view>
</movable-view>
</movable-area>
</swiper-item>
</swiper>
</view>
</template>
<script>
export default {
props: {
urls: {
type: Array,
required: true,
default: () => {
return []
},
},
},
data() {
return {
show: false,
current: 0, //
scale: 1,
isZooming: false, //
}
},
methods: {
open(current) {
this.current = this.urls.findIndex((item) => item === current)
},
//
close() {
if (!this.isZooming) {
this.show = false
this.current = 0
this.$emit('close')
}
},
//
swiperChange(e) {
this.current = e.detail.current
},
//
onLongpress(e) {
this.$emit('onLongpress', e)
},
handleTouchStart() {
this.isZooming = true
},
handleTouchEnd() {
this.isZooming = false
},
},
}
</script>
<style lang="scss" scoped>
.previewImage {
z-index: 9999;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #000000;
.swiper {
width: 100%;
height: 100vh;
.swiperItem {
.movable-area {
height: 100%;
width: 100%;
.movable-view {
width: 100%;
min-height: 100%;
.uni-scroll-view {
height: 100vh;
}
.scroll-view {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
.image {
width: 100%;
height: auto;
}
}
}
}
}
}
.page {
position: absolute;
z-index: 9999;
width: 100%;
top: 60rpx;
text-align: center;
.text {
color: #fff;
font-size: 32rpx;
background-color: rgba(0, 0, 0, 0.5);
padding: 3rpx 16rpx;
border-radius: 20rpx;
user-select: none;
}
}
}
</style>

View File

@ -0,0 +1,193 @@
<template>
<view class="pageLayout">
<view
v-if="navbarShow"
:class="{ pageNav: true, transparent: navBgTransparent, fixed: navFixed }"
:style="{ height: `${statusBarHeight + navHeight}px` }"
>
<view class="statusBar" :style="{ height: `${statusBarHeight}px` }"></view>
<wd-navbar
:bordered="!navBgTransparent"
:title="navTitle"
:leftText="navLeftText"
:leftArrow="navLeftArrow"
:rightText="navRightText"
@clickLeft="handleClickLeft"
@clickRight="handleClickRight"
custom-class="nav"
>
<template v-if="$slots.navRight" #right>
<slot name="navRight"></slot>
</template>
</wd-navbar>
</view>
<view class="pageContent">
<slot></slot>
</view>
<view class="tabbar"></view>
<wd-toast></wd-toast>
<wd-message-box></wd-message-box>
<wd-notify></wd-notify>
</view>
</template>
<script setup lang="ts">
import { useSlots } from 'vue'
import { useRouter } from '@/plugin/uni-mini-router'
import { useParamsStore } from '@/store/page-params'
defineOptions({
name: 'pageLayout',
options: {
// apply-shared.()
// shared.()
styleIsolation: 'shared',
},
})
const paramsStore = useParamsStore()
const router = useRouter()
const props = defineProps({
backRouteName: {
type: String,
default: '',
},
backRoutePath: {
type: String,
default: '',
},
routeParams: {
type: Object,
default: () => {},
},
routeQuery: {
type: Object,
default: () => {},
},
routeMethod: {
type: String,
default: 'replace',
},
navbarShow: {
type: Boolean,
default: true,
},
navBgTransparent: {
type: Boolean,
default: false,
},
navFixed: {
type: Boolean,
default: false,
},
type: {
type: String,
default: 'page', //'page','popup'
},
navTitle: {
type: String,
default: '',
},
navLeftText: {
type: String,
default: '返回',
},
navLeftArrow: {
typeof: Boolean,
default: true,
},
navRightText: {
typeof: String,
default: '',
},
})
const slot = useSlots()
const globalData = getApp().globalData
const { systemInfo, navHeight } = globalData
const { statusBarHeight } = systemInfo
const emit = defineEmits(['navBack', 'navRight'])
const handleClickLeft = () => {
emit('navBack')
//
if (props.type === 'page') {
const pages = getCurrentPages()
if (props.backRouteName || props.backRoutePath) {
const prevPage = pages[pages.length - 2]
if (prevPage) {
const route = prevPage.route
const name = route.split('/').pop()
if (route === props.backRoutePath || props.backRouteName === name) {
router.back()
clearPageParamsCache()
return
}
}
if (props.backRouteName) {
router[props.routeMethod]({ name: props.backRouteName, params: props.routeParams })
clearPageParamsCache()
} else {
router[props.routeMethod]({ name: props.backRoutePath, query: props.routeQuery })
clearPageParamsCache()
}
} else {
router.back()
clearPageParamsCache()
}
}
}
const clearPageParamsCache = () => {
//
const pages = getCurrentPages()
const curPage = pages[pages.length - 1]
const curRoute = curPage.route
const name = curRoute.split('/').pop()
paramsStore.clearPageParams(name)
}
const handleClickRight = () => {
emit('navRight')
}
console.log('props:', props)
</script>
<style lang="scss" scoped>
.pageLayout {
display: flex;
flex-direction: column;
height: 100vh;
width: 100vw;
.pageNav {
background-image: linear-gradient(45deg, #0081ff, #1cbbb4);
&.transparent {
background-image: none;
}
&.fixed {
position: fixed;
top: 0;
left: 0;
width: 100%;
}
.statusBar {
width: 100%;
height: 0;
}
:deep(.wd-navbar) {
background-color: transparent;
--wot-navbar-title-font-weight: 400;
--wot-navbar-arrow-size: 18px;
--wot-navbar-desc-font-size: 14px;
--wot-navbar-title-font-size: 16px;
}
}
.pageContent {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
background-color: #f1f1f1;
}
.tabbar {
/* #ifdef H5 */
height: var(--window-bottom);
/* #endif */
}
}
</style>

View File

@ -0,0 +1,118 @@
<template>
<view class="Popup">
<view @click="handleClick">
<wd-input
:placeholder="`请选择${$attrs.label}`"
type="text"
readonly
v-model="showText"
clearable
v-bind="$attrs"
/>
</view>
<popupReportModal
v-if="reportModal.show"
:code="code"
:showFiled="reportModal.showFiled"
:multi="multi"
@close="handleClose"
@change="handleChange"
></popupReportModal>
</view>
</template>
<script setup lang="ts">
import { ref, watch, useAttrs } from 'vue'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { http } from '@/utils/http'
import popupReportModal from './components/popupReportModal.vue'
defineOptions({
name: 'Popup',
options: {
styleIsolation: 'shared'
}
})
const props = defineProps({
code: {
type: String,
required: true,
default: '',
},
fieldConfig: {
type: Array,
required: true,
default: () => [],
},
setFieldsValue: {
type: Function,
required: true,
default: () => {},
},
modelValue: {
type: String,
default: '',
},
multi: {
type: Boolean,
default: false,
},
spliter: {
type: String,
default: ',',
},
})
const emit = defineEmits(['change', 'update:modelValue'])
const toast = useToast()
const showText = ref('')
const attrs: any = useAttrs()
const reportModal = reactive({
show: false,
showFiled: props.fieldConfig.map((item) => item['target']).join(','),
})
if (!props.code || props.fieldConfig.length == 0) {
toast.error('popup参数未正确配置!')
}
/**
* 监听value数值
*/
watch(
() => props.modelValue,
(val) => {
showText.value = val && val.length > 0 ? val.split(props.spliter).join(',') : ''
},
{ immediate: true },
)
function callBack(rows) {
let fieldConfig: any = props.fieldConfig
//popup
let values = {}
let labels = []
for (let item of fieldConfig) {
let val = rows.map((row) => row[item.source])
val = val.length == 1 ? val[0] : val.join(',')
item.target.split(',').forEach((target) => {
values[target] = val
})
}
showText.value = labels.join(',')
props.setFieldsValue(values)
emit('change', values)
// emit('update:modelValue', values)
}
const handleClick = () => {
if (!attrs.disabled) {
reportModal.show = true
}
}
const handleClose = () => {
reportModal.show = false
}
const handleChange = (data) => {
console.log('选中的值:', data)
callBack(data)
}
</script>
<style scoped></style>

View File

@ -0,0 +1,290 @@
<template>
<wd-popup position="bottom" v-model="show">
<PageLayout
:navTitle="navTitle"
type="popup"
navRightText="确定"
@navRight="handleConfirm"
@navBack="handleCancel"
>
<view class="wrap">
<z-paging
ref="paging"
:fixed="false"
v-model="dataList"
@query="queryList"
:default-page-size="15"
>
<template #top>
<wd-search
hide-cancel
:placeholder="search.placeholder"
v-model="search.keyword"
@search="handleSearch"
@clear="handleClear"
/>
</template>
<template v-if="multi">
<wd-checkbox-group shape="square" v-model="checkedValue">
<template v-for="(item, index) in dataList" :key="index">
<view class="list" @click="hanldeCheck(index)">
<view class="left text-gray-5">
<template v-for="(cItem, cIndex) in columns" :key="cIndex">
<view class="row">
<text class="label">{{ cItem.title }}</text>
<text class="value">{{ item[cItem.dataIndex] }}</text>
</view>
</template>
</view>
<view class="right" @click.stop>
<wd-checkbox ref="checkboxRef" :modelValue="index"></wd-checkbox>
</view>
</view>
</template>
</wd-checkbox-group>
</template>
<template v-else>
<wd-radio-group shape="dot" v-model="checkedValue">
<template v-for="(item, index) in dataList" :key="index">
<wd-cell>
<view class="list" @click="hanldeCheck(index)">
<view class="left text-gray-5">
<template v-for="(cItem, cIndex) in columns" :key="cIndex">
<view class="row">
<text class="label">{{ cItem.title }}</text>
<text class="value">{{ item[cItem.dataIndex] }}</text>
</view>
</template>
</view>
<view class="right" @click.stop>
<wd-radio :value="index"></wd-radio>
</view>
</view>
</wd-cell>
</template>
</wd-radio-group>
</template>
</z-paging>
</view>
</PageLayout>
</wd-popup>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { http } from '@/utils/http'
import { isArray } from '@/utils/is'
defineOptions({
name: 'popupReportModal',
options: {
styleIsolation: 'shared',
},
})
const props = defineProps({
code: {
type: String,
default: '',
required: true,
},
showFiled: {
type: String,
default: '',
required: true,
},
multi: {
type: Boolean,
default: true,
},
})
const emit = defineEmits(['change', 'close'])
const toast = useToast()
const show = ref(true)
const api = {
getColumns: '/online/cgreport/api/getRpColumns',
getData: '/online/cgreport/api/getData',
getQueryInfo: '/online/cgreport/api/getQueryInfo',
}
console.log('props:::', props)
const navTitle = ref('')
const paging = ref(null)
const dataList = ref([])
// id
let rpConfigId = null
let loadedColumns = false
const dictOptions = ref([])
const columns = ref([])
const checkedValue: any = ref(props.multi ? [] : '')
const checkboxRef = ref(null)
const search = reactive({
keyword: '',
placeholder: '',
field: '',
})
const handleClose = () => {
setTimeout(() => {
emit('close')
}, 400)
}
const handleConfirm = () => {
if (checkedValue.value.length == 0) {
toast.warning('还没选择~')
return
}
const result = []
let value = checkedValue.value
if (!Array.isArray(checkedValue.value)) {
value = [checkedValue.value]
}
value.forEach((index) => {
result.push(dataList.value[index])
})
show.value = false
emit('change', result)
handleClose()
}
const handleCancel = () => {
show.value = false
handleClose()
console.log('取消了~')
}
//
function handleSearch() {
paging.value.reload()
}
//
function handleClear() {
search.keyword = ''
handleSearch()
}
const hanldeCheck = (index) => {
if (props.multi) {
if (Array.isArray(checkboxRef.value)) {
checkboxRef.value[index].toggle()
}
} else {
checkedValue.value = index
}
}
const getQueryInfo = () => {
const analysis = (data = []) => {
if (data.length) {
search.placeholder = `请输入${data[0].label}`
search.field = data[0].field
} else {
const item = columns[0] ?? {}
search.placeholder = `请输入${item.title}`
search.field = item.dataIndex
}
}
http
.get(`${api.getQueryInfo}/${rpConfigId}`)
.then((res: any) => {
if (res.success) {
analysis(res.result)
} else {
analysis()
}
})
.catch((err) => {
analysis()
})
}
const getRpColumns = () => {
return new Promise<void>((resolve, reject) => {
if (loadedColumns) {
resolve()
} else {
http
.get(`${api.getColumns}/${props.code}`)
.then((res: any) => {
if (res.success) {
loadedColumns = true
const { result } = res
navTitle.value = result.cgRpConfigName
dictOptions.value = result.dictOptions
rpConfigId = result.cgRpConfigId
const fileds = props.showFiled.split(',')
result.columns?.forEach((item) => {
if (fileds.includes(item.dataIndex)) {
columns.value.push(item)
}
})
getQueryInfo()
resolve()
} else {
reject()
}
})
.catch((err) => {
reject()
})
}
})
}
const queryList = (pageNo, pageSize) => {
const pararms = { pageNo, pageSize }
if (search.keyword) {
pararms[search.field] = `*${search.keyword}*`
}
getRpColumns()
.then(() => {
http
.get(`${api.getData}/${rpConfigId}`, pararms)
.then((res: any) => {
if (res.success && res.result.records) {
paging.value.complete(res.result.records ?? [])
} else {
paging.value.complete(false)
}
})
.catch((err) => {})
})
.catch((err) => {})
}
</script>
<style lang="scss" scoped>
:deep(.wd-cell) {
--wot-color-white: tranparent;
--wot-cell-padding: 0;
.wd-cell__wrapper {
--wot-cell-wrapper-padding: 0;
}
.wd-cell__left {
display: none;
}
}
:deep(.wd-checkbox-group) {
--wot-checkbox-bg: tranparent;
}
:deep(.wd-radio-group) {
--wot-radio-bg: tranparent;
}
.list {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
padding: 16px;
margin-top: 16px;
.left {
display: flex;
justify-content: center;
flex-direction: column;
.row {
display: flex;
}
}
.right {
:deep(.wd-checkbox) {
margin-bottom: 0;
}
}
}
.wrap {
height: 100%;
}
</style>

View File

@ -0,0 +1,186 @@
<template>
<view class="PopupDict">
<view @click.stop="handleClick">
<wd-select-picker
v-model="showText"
:columns="options"
readonly
:type="multi ? 'checkbox' : 'radio'"
@click="() => (reportModal.show = true)"
v-bind="$attrs"
></wd-select-picker>
</view>
<popupReportModal
v-if="reportModal.show"
:code="code"
:showFiled="labelFiled"
:multi="multi"
@close="handleClose"
@change="handleChange"
></popupReportModal>
</view>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { http } from '@/utils/http'
import popupReportModal from '@/components/Popup/components/popupReportModal.vue'
defineOptions({
name: 'PopupDict',
options: {
styleIsolation: 'shared'
}
})
const props = defineProps({
dictCode: {
type: String,
required: true,
default: '',
},
modelValue: {
type: String,
default: '',
},
multi: {
type: Boolean,
default: false,
},
spliter: {
type: String,
default: ',',
},
})
const emit = defineEmits(['change', 'update:modelValue'])
const toast = useToast()
const showText = ref<any>(props.multi ? [] : '')
const options = ref<any>([])
const cgRpConfigId = ref('')
const code = ref(props.dictCode.split(',')[0])
const labelFiled = ref(props.dictCode.split(',')[1])
const valueFiled = ref(props.dictCode.split(',')[2])
const reportModal = reactive({
show: false,
})
//url
const configUrl = reactive({
getColumns: '/online/cgreport/api/getRpColumns/',
getData: '/online/cgreport/api/getData/',
})
if (!code.value || !valueFiled.value || !labelFiled.value) {
toast.error('popupDict参数未正确配置!')
}
/**
* 监听value数值
*/
watch(
() => props.modelValue,
(val) => {
const callBack = () => {
if (props.multi) {
showText.value = val && val.length > 0 ? val.split(props.spliter) : []
} else {
showText.value = val ?? ''
}
}
if (props.modelValue) {
if (cgRpConfigId.value) {
loadData({ callBack })
} else {
loadColumnsInfo({ callBack })
}
} else {
callBack()
}
},
{ immediate: true },
)
watch(
() => showText.value,
(val) => {
let result
if (props.multi) {
result = val.join(',')
} else {
result = val
}
nextTick(() => {
emit('change', result)
emit('update:modelValue', result)
})
},
)
/**
* 加载列信息
*/
function loadColumnsInfo({ callBack }) {
let url = `${configUrl.getColumns}${code.value}`
http
.get(url)
.then((res: any) => {
if (res.success) {
cgRpConfigId.value = res.result.cgRpConfigId
loadData({ callBack })
}
})
.catch((err) => {
callBack?.()
})
}
function loadData({ callBack }) {
let url = `${configUrl.getData}${unref(cgRpConfigId)}`
http
.get(url, { ['force_' + valueFiled.value]: props.modelValue })
.then((res: any) => {
let data = res.result
if (data.records?.length) {
options.value = data.records.map((item) => {
return { value: item[valueFiled.value], label: item[labelFiled.value] }
})
}
})
.finally(() => {
callBack?.()
})
}
/**
* 传值回调
*/
function callBack(rows) {
const dataOptions: any = []
const dataValue: any = []
let result
rows.forEach((item) => {
dataOptions.push({ value: item[valueFiled.value], label: item[labelFiled.value] })
dataValue.push(item[valueFiled.value])
})
options.value = dataOptions
if (props.multi) {
showText.value = dataValue
result = dataValue.join(props.spliter)
} else {
showText.value = dataValue[0]
result = dataValue[0]
}
nextTick(() => {
emit('change', result)
emit('update:modelValue', result)
})
}
const handleClick = () => {
reportModal.show = true
}
const handleClose = () => {
reportModal.show = false
}
const handleChange = (data) => {
console.log('选中的值:', data)
callBack(data)
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,84 @@
<template>
<view class="ProgressMap">
<view class="title">{{ title }}</view>
<view
:class="{
stepBox: true,
active: item.activeStep,
'u-iconfont': true,
'u-icon-clock': !item.activeStep,
'u-icon-success': item.activeStep,
}"
v-for="(item, index) in dataSource"
:key="index"
>
<view :class="{ stepContent: true, active: item.activeStep }">
<view class="item" v-for="(inItem, inIndex) in item.data" :key="inIndex">
{{ inItem.label }}
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
defineProps({
title: {
type: String,
default: '',
},
dataSource: {
type: Array,
default: () => [],
},
})
</script>
<style lang="scss" scoped>
.title {
padding: 20upx;
font-weight: bold;
}
.stepBox {
position: relative;
padding: 32upx 32upx 32upx 120upx;
color: #aaaaaa;
&.active {
color: #39b54a;
}
&::before {
position: absolute;
left: 40upx;
background-color: #fff;
z-index: 9;
top: 40upx;
border-radius: 50%;
margin: 8upx;
}
&::after {
content: '';
display: block;
position: absolute;
width: 0.5px;
background-color: #ddd;
left: 60upx;
height: 100%;
top: 0;
z-index: 8;
}
.stepContent {
padding: 10upx 50upx 10upx 30upx;
border-radius: 8upx;
background-color: #f0f0f0;
color: #333333;
font-size: 24upx;
&.active {
background-color: #39b54a;
color: #fff;
box-shadow: 0 0 5px #39b54a;
}
}
}
</style>

View File

@ -0,0 +1,68 @@
<template>
<wd-popup v-model="show" position="right" @close="handleClose">
<view class="content">
<wd-text v-if="title" :text="title"></wd-text>
<wd-cell-group border>
<wd-radio-group v-model="checked">
<template v-for="(item, index) in options">
<wd-cell :title="item.title" clickable @click="handleSelected(item)">
<wd-radio :value="item.key"></wd-radio>
</wd-cell>
</template>
</wd-radio-group>
</wd-cell-group>
</view>
</wd-popup>
</template>
<script setup lang="ts">
import { hasRoute, cache } from '@/common/uitls'
import { ref } from 'vue'
const eimt = defineEmits(['change', 'close'])
const show = ref(true)
const props = defineProps(['title', 'data', 'options', 'checked'])
const checked = ref(props.checked)
const handleClose = () => {
show.value = false
setTimeout(() => {
eimt('close')
}, 300)
}
const handleSelected = (item) => {
checked.value = item.key
eimt('change', { option: item, data: props.data })
handleClose()
}
</script>
<style lang="scss" scoped>
.content {
max-width: 200px;
padding: 10px;
.wd-text.is-default {
font-size: 14px;
color: #666;
}
.wd-cell {
padding-left: 0;
--wot-cell-label-color: #444;
--wot-cell-label-fs: 14px;
&.red {
color: red;
--wot-cell-label-color: red;
}
}
.wd-cell-group {
:deep(.wd-cell__wrapper) {
align-items: center;
.wd-cell__right {
flex: none;
width: 24px;
}
.wd-radio {
margin-top: 0;
}
}
}
}
</style>

View File

@ -0,0 +1,251 @@
<template>
<view class="CategorySelect">
<view @click="handleClick">
<wd-input
:placeholder="getPlaceholder($attrs)"
v-bind="$attrs"
v-model="showText"
clearable
readonly
></wd-input>
</view>
<wd-popup position="bottom" v-model="popupShow">
<view class="content">
<view class="operation">
<view class="cancel text-gray-5" @click.stop="cancel">取消</view>
<view class="confrim" @click.stop="confirm">确定</view>
</view>
<scroll-view class="flex-1" scroll-y>
<DaTree
:data="treeData"
:labelField="labelKey"
:valueField="rowKey"
loadMode
:showCheckbox="multiple"
:showRadioIcon="false"
:checkStrictly="true"
:loadApi="asyncLoadTreeData"
@change="handleTreeChange"
></DaTree>
</scroll-view>
</view>
</wd-popup>
</view>
</template>
<script setup lang="ts">
import { ref, watch, useAttrs } from 'vue'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { http } from '@/utils/http'
import DaTree from '@/uni_modules/da-tree/index.vue'
import { isArray } from '@/utils/is'
import { getPlaceholder } from '@/common/uitls'
defineOptions({
name: 'SelectDept',
})
const props = defineProps({
modelValue: {
type: [Array, String],
},
//
multiple: {
type: Boolean,
default: true,
},
rowKey: {
type: String,
default: 'key',
},
labelKey: {
type: String,
default: 'title',
},
})
const emit = defineEmits(['change', 'update:modelValue'])
const toast = useToast()
const api = {
loadDictItem: '/sys/category/loadDictItem/',
queryDepartTreeSync: '/sys/sysDepart/queryDepartTreeSync',
}
const showText = ref('')
const popupShow = ref(false)
const treeData = ref<any[]>([])
const treeValue = ref([])
const handleClick = () => {
popupShow.value = true
}
const cancel = () => {
popupShow.value = false
}
const confirm = () => {
const titles = treeValue.value.map((item) => item.title)
const keys = treeValue.value.map((item) => item.key).join(',')
showText.value = titles.join(',')
popupShow.value = false
emit('update:modelValue', keys)
emit('change', keys)
}
const handleClear = (params) => {
console.log('params:::', params)
}
const handleTreeChange = (value, record) => {
const { originItem, checkedStatus } = record
if (checkedStatus) {
//
if (props.multiple) {
treeValue.value.push({ key: originItem[props.rowKey], title: originItem[props.labelKey] })
} else {
treeValue.value = [{ key: originItem[props.rowKey], title: originItem[props.labelKey] }]
}
} else {
//
if (props.multiple) {
const findIndex = treeValue.value.findIndex(
(item) => item[props.rowKey] == originItem[props.rowKey],
)
if (findIndex != -1) {
treeValue.value.splice(findIndex, 1)
}
} else {
treeValue.value = []
}
}
}
const transformField = (result) => {
for (let i of result) {
i.value = i.key
if (i.isLeaf == false) {
i.leaf = false
} else if (i.isLeaf == true) {
i.leaf = true
}
}
}
//
const asyncLoadTreeData = ({ originItem }) => {
return new Promise<void>((resolve) => {
let param = {
pid: originItem.key,
primaryKey: props.rowKey,
}
http
.get(api.queryDepartTreeSync, param)
.then((res: any) => {
if (res.success) {
const { result } = res
transformField(result)
resolve(result)
} else {
resolve(null)
}
})
.catch((err) => resolve(null))
})
}
//
function loadRoot() {
let param = {
primaryKey: props.rowKey,
}
http
.get(api.queryDepartTreeSync, param)
.then((res: any) => {
if (res.success) {
const { result } = res
if (result && result.length > 0) {
transformField(result)
treeData.value = result
}
} else {
console.error('部门组件加载根节点数据失败~')
}
})
.catch((err) => {
console.error('部门组件加载根节点数据失败~')
})
}
// input
function loadItemByCode() {
let value = props.modelValue
console.log('部门组件翻译props.modelValue', props.modelValue)
if (isArray(props.modelValue)) {
// @ts-ignore
value = value.join(',')
}
if (value === treeData.value.map((item) => item.key).join(',')) {
//
return
}
http
.get(api.queryDepartTreeSync, { ids: value })
.then((res: any) => {
if (res.success) {
const { result = [] } = res
showText.value = result.map((item) => item[props.labelKey]).join(',')
} else {
}
})
.catch((err) => {})
}
watch(
() => props.modelValue,
() => {
loadItemByCode()
},
{ deep: true, immediate: true },
)
watch(
() => props.pcode,
() => {
loadRoot()
},
{ deep: true, immediate: true },
)
</script>
<style lang="scss" scoped>
:deep(.wd-popup-wrapper) {
.wd-popup {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
}
.content {
height: 50vh;
width: 100vw;
display: flex;
flex-direction: column;
.operation {
display: flex;
justify-content: space-between;
line-height: 40px;
padding: 0 5px;
position: relative;
&::before {
content: ' ';
position: absolute;
bottom: 0;
left: 8px;
right: 8px;
height: 1px;
background-color: #e5e5e5;
}
.cancel,
.confrim {
font-size: 15px;
height: 40px;
min-width: 40px;
text-align: center;
}
.confrim {
color: var(--wot-color-theme);
}
}
:deep(.da-tree) {
.da-tree-item__checkbox {
// display: none;
}
}
}
</style>

View File

@ -0,0 +1,117 @@
<template>
<view class="SelectUser">
<view @click.stop="handleClick">
<wd-input
:placeholder="getPlaceholder($attrs)"
v-bind="$attrs"
readonly
v-model="showText"
></wd-input>
</view>
<SelectUserModal
v-if="modalShow"
:selected="modelValue"
:modalTitle="modalTitle"
:maxSelectCount="maxSelectCount"
:multi="!isRadioSelection"
@change="handleChange"
@close="() => (modalShow = false)"
></SelectUserModal>
</view>
</template>
<script setup lang="ts">
import { ref, watch, useAttrs } from 'vue'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { http } from '@/utils/http'
import DaTree from '@/uni_modules/da-tree/index.vue'
import { isArray, isString } from '@/utils/is'
import SelectUserModal from './components/SelectUserModal.vue'
import { getPlaceholder } from '@/common/uitls'
defineOptions({
name: 'SelectUser',
options: {
styleIsolation: 'shared',
},
})
const props = defineProps({
modelValue: {
type: [Array, String],
default: '',
},
labelKey: {
type: String,
default: 'realname',
},
rowKey: {
type: String,
default: 'username',
},
isRadioSelection: {
type: Boolean,
default: false,
},
modalTitle: {
type: String,
default: '选择用户',
},
maxSelectCount: {
type: Number,
},
})
const emit = defineEmits(['update:modelValue', 'change'])
const api = {
list: '/sys/user/list',
}
const showText = ref('')
const modalShow = ref(false)
//
const transform = () => {
let value = props.modelValue
let len
if (isArray(value) || isString(value)) {
if (isArray(value)) {
len = value.length
value = value.join(',')
} else {
len = value.split(',').length
}
value = value.trim()
if (value) {
const params = { isMultiTranslate: true, pageSize: len, [props.rowKey]: value }
http.get(api.list, params).then((res: any) => {
if (res.success) {
const records = res.result?.records ?? []
showText.value = records.map((item) => item[props.labelKey]).join(',')
} else {
console.log('翻译失败~')
}
})
}
} else {
showText.value = ''
}
}
// popup
const handleClick = () => {
modalShow.value = true
}
const handleChange = (data) => {
const rowkey = data.map((item) => item[props.rowKey]).join(',')
const labelKey = data.map((item) => item[props.labelKey]).join(',')
showText.value = labelKey
emit('update:modelValue', rowkey)
emit('change', rowkey)
}
watch(
() => props.modelValue,
() => {
transform()
},
{ immediate: true },
)
</script>
<style scoped></style>

View File

@ -0,0 +1,282 @@
<template>
<wd-popup position="bottom" v-model="show">
<PageLayout
:navTitle="modalTitle"
type="popup"
navRightText="确定"
@navRight="handleConfirm"
@navBack="handleCancel"
>
<view class="wrap">
<z-paging
ref="paging"
:fixed="false"
v-model="dataList"
@query="queryList"
:default-page-size="15"
>
<template #top>
<wd-search
hide-cancel
:placeholder="search.placeholder"
v-model="search.keyword"
@search="handleSearch"
@clear="handleClear"
/>
</template>
<template v-if="multi">
<wd-checkbox-group shape="square" v-model="checkedValue">
<template v-for="(item, index) in dataList" :key="index">
<view class="list" @click="hanldeCheck(index, item.username)">
<view class="left text-gray-5">
<wd-img
custom-class="avatar"
radius="50%"
height="40"
width="40"
:src="getAvatar(item.avatar)"
></wd-img>
<view class="subContent">
<text>账号{{ item.username }}</text>
<text>姓名{{ item.realname }}</text>
</view>
</view>
<view class="right" @click.stop>
<wd-checkbox ref="checkboxRef" :modelValue="item.username"></wd-checkbox>
</view>
</view>
</template>
</wd-checkbox-group>
</template>
<template v-else>
<wd-radio-group shape="dot" v-model="checkedValue">
<template v-for="(item, index) in dataList" :key="index">
<wd-cell>
<view class="list" @click="hanldeCheck(index, item.username)">
<view class="left text-gray-5">
<wd-img
custom-class="avatar"
radius="50%"
height="40"
width="40"
:src="getAvatar(item.avatar)"
></wd-img>
<view class="subContent">
<text>账号{{ item.username }}</text>
<text>姓名{{ item.realname }}</text>
</view>
</view>
<view class="right" @click.stop>
<wd-radio :value="item.username"></wd-radio>
</view>
</view>
</wd-cell>
</template>
</wd-radio-group>
</template>
</z-paging>
</view>
</PageLayout>
</wd-popup>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { http } from '@/utils/http'
import { isArray, isString } from '@/utils/is'
import { cache, getFileAccessHttpUrl } from '@/common/uitls'
import defaultAvatar from '@/static/default-avatar.png'
defineOptions({
name: 'SelectUserModal',
options: {
styleIsolation: 'shared',
},
})
const props = defineProps({
multi: {
type: Boolean,
default: true,
},
modalTitle: {
type: String,
default: '选择用户',
},
maxSelectCount: {
type: Number,
},
selected: {
type: [Array, String],
default: '',
},
})
const emit = defineEmits(['change', 'close'])
const toast = useToast()
const show = ref(true)
const api = {
selectUserList: '/sys/user/selectUserList',
userlist: '/sys/user/list',
}
const paging = ref(null)
const dataList = ref([])
const checkedValue: any = ref(props.multi ? [] : '')
const checkboxRef = ref(null)
const search = reactive({
keyword: '',
placeholder: '输入姓名可搜索',
field: 'realname',
})
const handleClose = () => {
setTimeout(() => {
emit('close')
}, 400)
}
const handleConfirm = () => {
if (checkedValue.value.length == 0) {
toast.warning('还没选择~')
return
}
const result = []
let value = checkedValue.value
if (!Array.isArray(checkedValue.value)) {
value = [checkedValue.value]
}
value.forEach((username, index) => {
const findIndex = dataList.value.findIndex((item) => item['username'] === username)
result.push(dataList.value[findIndex])
})
show.value = false
emit('change', result)
handleClose()
}
const handleCancel = () => {
show.value = false
handleClose()
console.log('取消了~')
}
//
function handleSearch() {
paging.value.reload()
}
//
function handleClear() {
search.keyword = ''
handleSearch()
}
const hanldeCheck = (index, username) => {
if (props.multi) {
if (Array.isArray(checkboxRef.value)) {
checkboxRef.value[index].toggle()
nextTick(() => {
if (props.maxSelectCount) {
if (checkedValue.value.length > props.maxSelectCount) {
toast.warning(`最多可选择${props.maxSelectCount}个用户`)
//
checkboxRef.value[index].toggle()
}
}
})
}
} else {
checkedValue.value = username
}
}
const getAvatar = (url) => {
let result = getFileAccessHttpUrl(url)
if (result.length) {
return result
} else {
return defaultAvatar
}
}
const queryList = (pageNo, pageSize) => {
const pararms = { pageNo, pageSize, column: 'createTime', order: 'desc' }
if (search.keyword) {
pararms[search.field] = `*${search.keyword}*`
}
http
.get(`${api.userlist}`, pararms)
.then((res: any) => {
if (res.success && res.result.records) {
paging.value.complete(res.result.records ?? [])
} else {
paging.value.complete(false)
}
})
.catch((err) => {})
}
const init = () => {
if (props.selected.length) {
if (props.multi) {
if (isArray(props.selected)) {
checkedValue.value = props.selected
} else if (isString(props.selected)) {
checkedValue.value = props.selected.split(',')
}
} else {
if (isString(props.selected)) {
checkedValue.value = props.selected
} else if (isArray(props.selected)) {
checkedValue.value = props.selected.join(',')
}
}
}
}
init()
</script>
<style lang="scss" scoped>
:deep(.wd-cell) {
--wot-color-white: tranparent;
--wot-cell-padding: 0;
.wd-cell__wrapper {
--wot-cell-wrapper-padding: 0;
}
.wd-cell__left {
display: none;
}
}
:deep(.wd-checkbox-group) {
--wot-checkbox-bg: tranparent;
}
:deep(.wd-radio-group) {
--wot-radio-bg: tranparent;
}
.list {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
padding: 16px;
margin-top: 16px;
.left {
display: flex;
align-items: center;
text-align: left;
.avatar {
margin-right: 8px;
background-color: #e9e9e9;
}
.subContent {
display: flex;
flex-direction: column;
}
}
.right {
:deep(.wd-checkbox) {
margin-bottom: 0;
}
}
}
.wrap {
height: 100%;
}
:deep(.wd-popup-wrapper) {
.wd-popup {
top: 100px;
}
}
</style>

View File

@ -0,0 +1,346 @@
<template>
<view class="TreeSelect">
<view @click="handleClick">
<wd-input
:placeholder="`请选择${$attrs.label}`"
v-bind="$attrs"
readonly
v-model="showText"
></wd-input>
</view>
<wd-popup position="bottom" v-model="popupShow">
<view class="content">
<view class="operation">
<view class="cancel text-gray-5" @click.stop="cancel">取消</view>
<view class="confrim" @click.stop="confirm">确定</view>
</view>
<scroll-view class="flex-1" scroll-y>
<DaTree
:data="treeData"
labelField="title"
valueField="key"
loadMode
:showCheckbox="multiple"
:showRadioIcon="false"
:checkStrictly="true"
:loadApi="asyncLoadTreeData"
@change="handleTreeChange"
></DaTree>
</scroll-view>
</view>
</wd-popup>
</view>
</template>
<script setup lang="ts">
import { ref, watch, useAttrs } from 'vue'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { http } from '@/utils/http'
import DaTree from '@/uni_modules/da-tree/index.vue'
import { isArray } from '@/utils/is'
defineOptions({
name: 'TreeSelect',
options: {
styleIsolation: 'shared',
},
})
const props = defineProps({
modelValue: {
type: [Array, String],
},
dict: {
type: String,
default: 'id',
},
pidValue: {
type: String,
default: '',
},
pidField: {
type: String,
default: 'pid',
},
hasChildField: {
type: String,
default: '',
},
condition: {
type: String,
default: '',
required: false,
},
converIsLeafVal: {
type: Number,
default: 1,
},
//
multiple: {
type: Boolean,
default: true,
},
hiddenNodeKey: {
type: String,
default: '',
},
// url: {
// type: String,
// default: '',
// },
// params: {
// type: Object,
// default: () => ({}),
// },
})
const emit = defineEmits(['change', 'update:modelValue'])
const toast = useToast()
const api = {
loadTreeData: '/sys/dict/loadTreeData',
view: '/sys/dict/loadDictItem/',
}
const showText = ref('')
const popupShow = ref(false)
const treeData = ref<any[]>([])
const treeValue = ref([])
const tableName = ref<any>('')
const text = ref<any>('')
const code = ref<any>('')
const handleClick = () => {
popupShow.value = true
}
const cancel = () => {
popupShow.value = false
}
const confirm = () => {
const titles = treeValue.value.map((item) => item.title)
const keys = treeValue.value.map((item) => item.key).join(',')
showText.value = titles.join(',')
popupShow.value = false
emit('update:modelValue', keys)
emit('change', keys)
}
const handleTreeChange = (value, record) => {
const { originItem, checkedStatus } = record
const { key, title } = originItem
if (checkedStatus) {
//
if (props.multiple) {
treeValue.value.push({ key, title })
} else {
treeValue.value = [{ key, title }]
}
} else {
//
if (props.multiple) {
const findIndex = treeValue.value.findIndex((item) => item.key == key)
if (findIndex != -1) {
treeValue.value.splice(findIndex, 1)
}
} else {
treeValue.value = []
}
}
}
const transformField = (result) => {
for (let i of result) {
i.value = i.key
if (i.leaf == false) {
i.isLeaf = false
} else if (i.leaf == true) {
i.isLeaf = true
}
}
}
//
const asyncLoadTreeData = ({ originItem }) => {
return new Promise<void>((resolve) => {
let param = {
pid: originItem.key,
pidField: props.pidField,
hasChildField: props.hasChildField,
converIsLeafVal: props.converIsLeafVal,
condition: props.condition,
tableName: unref(tableName),
text: unref(text),
code: unref(code),
}
http
.get(api.loadTreeData, param)
.then((res: any) => {
if (res.success) {
const { result } = res
transformField(result)
resolve(result)
} else {
resolve(null)
}
})
.catch((err) => resolve(null))
})
}
//
function loadRoot() {
let param = {
pid: props.pidValue,
pidField: props.pidField,
hasChildField: props.hasChildField,
condition: props.condition,
converIsLeafVal: props.converIsLeafVal,
tableName: unref(tableName),
text: unref(text),
code: unref(code),
}
http
.get(api.loadTreeData, param)
.then((res: any) => {
if (res.success) {
const { result } = res
if (result && result.length > 0) {
transformField(result)
handleHiddenNode(result)
treeData.value = result
}
} else {
toast.warning('自定义树组件根节点数据加载失败~')
}
})
.catch((err) => {
toast.warning('自定义树组件根节点数据加载失败~')
})
}
// input
function loadItemByCode() {
let value = props.modelValue
if (isArray(props.modelValue)) {
// @ts-ignore
value = value.join()
}
if (value === treeData.value.map((item) => item.key).join(',')) {
//
return
}
http
.get(`${api.view}${props.dict}`, { key: value })
.then((res: any) => {
if (res.success) {
const { result = [] } = res
showText.value = result.join(',')
} else {
}
})
.catch((err) => {})
}
const initDictInfo = () => {
let arr = props.dict?.split(',')
tableName.value = arr[0]
text.value = arr[1]
code.value = arr[2]
}
const handleHiddenNode = (data) => {
if (props.hiddenNodeKey && data?.length) {
for (let i = 0, len = data.length; i < len; i++) {
const item = data[i]
if (item.key == props.hiddenNodeKey) {
data.splice(i, 1)
i--
len--
return
}
}
}
}
const validateProp = () => {
let mycondition = props.condition
return new Promise<void>((resolve, reject) => {
if (!mycondition) {
resolve()
} else {
try {
let test = JSON.parse(mycondition)
if (typeof test == 'object' && test) {
resolve()
} else {
toast.error('组件TreeSelect-condition传值有误需要一个json字符串!')
reject()
}
} catch (e) {
toast.error('组件TreeSelect-condition传值有误需要一个json字符串!')
reject()
}
}
})
}
watch(
() => props.modelValue,
() => loadItemByCode(),
{ deep: true, immediate: true },
)
watch(
() => props.dict,
() => {
initDictInfo()
loadRoot()
},
)
watch(
() => props.hiddenNodeKey,
() => {
if (treeData.value?.length && props.hiddenNodeKey) {
handleHiddenNode(treeData.value)
treeData.value = [...treeData.value]
}
},
)
//
validateProp().then(() => {
initDictInfo()
loadRoot()
loadItemByCode()
})
</script>
<style lang="scss" scoped>
:deep(.wd-popup-wrapper) {
.wd-popup {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
}
.content {
height: 50vh;
width: 100vw;
display: flex;
flex-direction: column;
.operation {
display: flex;
justify-content: space-between;
line-height: 40px;
padding: 0 5px;
position: relative;
&::before {
content: ' ';
position: absolute;
bottom: 0;
left: 8px;
right: 8px;
height: 1px;
background-color: #e5e5e5;
}
.cancel,
.confrim {
font-size: 15px;
height: 40px;
min-width: 40px;
text-align: center;
}
.confrim {
color: var(--wot-color-theme);
}
}
:deep(.da-tree) {
.da-tree-item__checkbox {
// display: none;
}
}
}
</style>

View File

@ -0,0 +1,169 @@
// FormProperty
const FormProperty = (propertyId, formSchema, required = []) => {
//
const _propertyId = propertyId;
const _formSchem = formSchema;
const _required = required;
// formSchema getter
const getFormSchema = () => {
return _formSchem || {};
};
// key getter
const getKey = () => {
return _propertyId;
};
// type getter
const getType = () => {
return getFormSchema().view;
};
// disabled getter
const getDisabled = () => {
if (_formSchem && _formSchem.ui && _formSchem.ui.widgetattrs && _formSchem.ui.widgetattrs.disabled === true) {
return true;
}
return false;
};
// label getter
const getLabel = () => {
const schema = getFormSchema();
return schema.title || getKey();
};
// placeholder getter
const getPlaceholder = () => {
const viewType = getType();
const label = getLabel();
if (viewType.indexOf('date') >= 0 || viewType.indexOf('select') >= 0 || viewType.indexOf('list') >= 0) {
return "请选择" + label;
} else if (viewType.indexOf('upload') >= 0 || viewType.indexOf('file') >= 0 || viewType.indexOf('image') >= 0) {
return "请上传" + label;
} else {
return "请输入" + label;
}
};
// dictStr getter
const getDictStr = () => {
const viewType = getType();
if (viewType === 'sel_search') {
const schema = getFormSchema();
return schema.dictTable + ',' + schema.dictText + ',' + schema.dictCode;
}
return '';
};
// listSource getter
const getListSource = () => {
const schema = getFormSchema();
if (!schema.enum) {
return [];
}
const arr = [...schema.enum];
for (let a = 0; a < arr.length; a++) {
if (!arr[a].label) {
arr[a].label = arr[a].text;
}
if (schema.type === 'number') {
arr[a].value = parseInt(arr[a].value);
}
}
return arr;
};
// popupCode getter
const getPopupCode = () => {
return getFormSchema().code;
};
// dest getter
const getDest = () => {
return getFormSchema().destFields;
};
// ogn getter
const getOgn = () => {
return getFormSchema().orgFields;
};
// rules getter
const getRules = () => {
const rules = [];
const isRequired = _required?.includes(getKey()) ?? false;
if (isRequired) {
let msg = getLabel() + '为必填项';
rules.push({ required: true, message: msg });
}
let viewType = getType();
if ('list' === viewType || 'markdown' === viewType || 'pca' === viewType) {
return rules;
}
if (viewType.indexOf('upload') >= 0 || viewType.indexOf('file') >= 0 || viewType.indexOf('image') >= 0) {
return rules;
}
const schema = getFormSchema();
if (schema.pattern) {
if (schema.pattern === 'only') {
// checkOnlyMethod
rules.push({ validator: () => {} });
} else if (schema.pattern === 'z') {
if (schema.type === 'number' || schema.type === 'integer') {
// onlyInteger
} else {
rules.push({ pattern: '^-?[1-9]\\d*$', message: '请输入整数' });
}
} else {
let msg = getLabel() + '校验未通过';
rules.push({ pattern: schema.pattern, message: msg });
}
}
return rules;
};
// getter
return {
get formSchema() {
return getFormSchema();
},
get key() {
return getKey();
},
get type() {
return getType();
},
get disabled() {
return getDisabled();
},
get label() {
return getLabel();
},
get placeholder() {
return getPlaceholder();
},
get dictStr() {
return getDictStr();
},
get listSource() {
return getListSource();
},
get popupCode() {
return getPopupCode();
},
get dest() {
return getDest();
},
get ogn() {
return getOgn();
},
get rules() {
return getRules();
}
};
};
export default FormProperty;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,297 @@
//
import {formatDate} from "@/utils";
import {http} from "@/utils/http";
import {useUserStore} from "@/store";
const CustomExpression = {}
// Key
const ceKeys = Object.keys(CustomExpression)
// keya,b,c --> function(a,b,c){}
const ceJoin = ceKeys.join(',')
// key使 apply
const $CE$ = ceKeys.map(key => CustomExpression[key])
/** 普通规则表达式 #{...} */
const normalRegExp = /#{([^}]+)?}/g
/** 用户自定义规则表达式 {{...}} */
const customRegExp = /{{([^}]+)?}}/g
/** 填值规则表达式 ${...} */
const fillRuleRegExp = /\${([^}]+)?}/g
/** action 类型 */
export const ACTION_TYPES = { ADD: 'add', EDIT: 'edit', DETAIL: 'detail', RELOAD: 'reload' }
/**
* 获取单个字段的默认值-通过回调函数返回值
* @param {Object} defVal
* @param {Object} type
* @param {Object} callback
*/
export async function loadOneFieldDefVal(defVal, type, callback) {
if(hasEffectiveValue(defVal)){
let value = await handleDefaultValue(defVal, ACTION_TYPES.ADD, {});
if ('number' === type && value) {
value = Number.parseFloat(value)
}
callback(value)
}
}
/**
* 判断给定的值是不是有效的
*/
function hasEffectiveValue(val) {
if(val || val === 0){
return true;
}
return false;
}
/**
* 获取默认值
* @param {Object} defVal
* @param {Object} action
* @param {Object} getFormData
*/
async function handleDefaultValue(defVal, action, getFormData) {
if (defVal != null) {
//
if (checkExpressionType(defVal)) {
let value = await getDefaultValue(defVal, action, getFormData)
if (value != null) {
return value
}
}
}
return defVal;
}
/**
* 加载form组件默认值
* @param form Form对象
* @param properties 字段配置
* @param action 操作类型ACTION_TYPES除填值规则外其他表达式只在add下才执行
* @param getFormData 获取数据的方法用于填值规则向后台传值
*/
export function loadFieldDefVal({ form, properties, action, getFormData }) {
if (Array.isArray(properties) && properties.length > 0) {
properties.forEach(async prop => {
let { defVal, type } = prop._formSchem
// key online
let key = prop.key
// 2021521 Treeissues/I3NR39
if (!key) {
key = prop._propertyId
}
eachHandler(key, defVal, action, (value) => {
// type=numbervalue
if ('number' === type && value) {
// parseFloat() nullundefined
// NaN
value = Number.parseFloat(value)
}
form.setFieldsValue({ [key]: value })
}, getFormData)
})
}
}
/** 加载JEditableTable组件默认值 */
export function loadFieldDefValForSubTable({ subForms, subTable, row, action, getFormData }) {
if (subTable && Array.isArray(subTable.columns) && subTable.columns.length > 0) {
subTable.columns.forEach(async column => {
let { key, fieldDefaultValue: defVal } = column
eachHandler(key, defVal, action, (value) => {
if (subForms.form) {
subForms.form.setFieldsValue({ [key]: value })
} else {
// update-begin---author:sunjianlei Date:20200725 foronline-----------
let v = [{rowKey: row.id, values: {[key]: value}}];
(subForms.jvt || subForms.jet).setValues(v)
// update-end---author:sunjianlei Date:20200725 foronline------------
}
}, getFormData)
})
}
}
async function eachHandler(key, defVal, action, callback, getFormData) {
if (defVal != null) {
//
if (checkExpressionType(defVal)) {
let value = await getDefaultValue(defVal, action, getFormData)
if (value != null) {
callback(value)
}
} else {
//
callback(defVal)
}
}
}
/**
* 检查表达式类型是否合法规则
* 1填值规则表达式不能和其他表达式混用
* 2每次只能填写一个填值规则表达式
* 3普通表达式和用户自定义表达式可以混用
*/
export function checkExpressionType(defVal) {
//
let normalCount = 0, customCount = 0, fillRuleCount = 0
defVal.replace(fillRuleRegExp, () => fillRuleCount++)
if (fillRuleCount > 1) {
logWarn(`表达式[${defVal}]不合法:只能同时填写一个填值规则表达式!`)
return false
}
defVal.replace(normalRegExp, () => normalCount++)
defVal.replace(customRegExp, () => customCount++)
//
let fillRuleOtherCount = normalCount + customCount
if (fillRuleCount > 0 && fillRuleOtherCount > 0) {
logWarn(`表达式[${defVal}]不合法:填值规则表达式不能和其他表达式混用!`)
return false
}
return true
}
/** 获取所有匹配的表达式 */
function getRegExpMap(text, exp) {
let map = new Map()
if(text && text.length>0){
text.replace(exp, function (match, param, offset, string) {
map.set(match, param.trim())
return match
})
}
return map
}
/** 获取默认值,可以执行表达式,可以执行用户自定义方法,可以异步获取用户信息等 */
async function getDefaultValue(defVal, action, getFormData) {
// add reload
if (action === ACTION_TYPES.ADD || action === ACTION_TYPES.RELOAD) {
//
if (fillRuleRegExp.test(defVal)) {
return await executeRegExp(defVal, fillRuleRegExp, executeFillRuleExpression, [getFormData])
}
}
// add
if (action === ACTION_TYPES.ADD) {
//
defVal = await executeRegExp(defVal, normalRegExp, executeNormalExpression)
//
defVal = await executeRegExp(defVal, customRegExp, executeCustomExpression)
return defVal
}
return null
}
async function executeRegExp(defVal, regExp, execFun, otherParams = []) {
let map = getRegExpMap(defVal, regExp)
// @ts-ignore
for (let origin of map.keys()) {
let exp = map.get(origin)
let result = await execFun.apply(null, [exp, origin, ...otherParams])
// String
if (origin === defVal) {
return result
}
defVal = replaceAll(defVal, origin, result)
}
return defVal
}
/** 执行【普通表达式】#{xxx} */
async function executeNormalExpression(expression, origin) {
let temp = new Date();
switch (expression) {
case 'date':
return formatDate(temp, 'yyyy-MM-dd');
case 'time':
return formatDate(temp, 'HH:mm:ss');
case 'datetime':
return formatDate(temp, 'yyyy-MM-dd HH:mm:ss');
default:
//
let result = getUserInfoByExpression(expression)
if (result != null) {
return result
}
//
return origin
}
}
/** 根据表达式获取相应的用户信息 */
function getUserInfoByExpression(expression) {
let userInfo:any = useUserStore().userInfo;
if (userInfo) {
switch (expression) {
case 'sysUserId':
return userInfo.id
//
case 'sysUserCode':
return userInfo.username
//
case 'sysUserName':
return userInfo.realname
//
case 'sysOrgCode':
return userInfo.orgCode
}
}
return null
}
/** 执行【用户自定义表达式】 {{xxx}} 移动端不支持 */
async function executeCustomExpression(expression, origin) {
return expression;
// eval
/* let fn = eval(`(function (${ceJoin}){ return ${expression} })`)
try {
//
return fn.apply(null, $CE$)
} catch (e) {
//
logError(e)
return origin
} */
}
/** 执行【填值规则表达式】 ${xxx} */
async function executeFillRuleExpression(expression, origin, getFormData) {
let url = `/sys/fillRule/executeRuleByCode/${expression}`
let formData = {}
if (typeof getFormData === 'function') {
formData = getFormData()
}
let res:any = await http.put(url, formData)
let { success, message, result } = res;
console.log(success, message, result)
if (success) {
return result
} else {
logError(`填值规则(${expression})执行失败:${message}`)
return origin
}
}
function logWarn(message) {
console.warn('[loadFieldDefVal]:', message)
}
function logError(message) {
console.error('[loadFieldDefVal]:', message)
}
function replaceAll(text, checker, replacer) {
let lastText = text
text = text.replace(checker, replacer)
if (lastText !== text) {
return replaceAll(text, checker, replacer)
}
return text
}

View File

@ -0,0 +1,914 @@
<template>
<view class="onlineLoader-container">
<view class="form-container">
<wd-form ref="form" :model="formData">
<wd-cell-group border>
<view
class="onlineLoader-form"
v-for="(item, index) in rootProperties"
:key="index"
:class="{ 'mt-14px': index % 2 == 0 }"
>
<!-- 图片 -->
<wd-cell
v-if="item.type == 'image'"
:name="item.key"
:title="get4Label(item.label)"
:title-width="labelWidth"
:required="fieldRequired(item)"
>
<online-image
v-model:value="formData[item.key]"
:name="item.key"
:disabled="componentDisabled(item)"
:key="index"
></online-image>
</wd-cell>
<!-- 文件 -->
<wd-cell
v-else-if="item.type == 'file'"
:name="item.key"
:title="get4Label(item.label)"
:title-width="labelWidth"
:required="fieldRequired(item)"
>
<online-image
v-model:value="formData[item.key]"
uploadFileType="all"
:name="item.key"
:disabled="componentDisabled(item)"
:key="index"
></online-image>
</wd-cell>
<!-- 日期时间 -->
<online-datetime
v-else-if="item.type === 'datetime'"
:label="get4Label(item.label)"
:labelWidth="labelWidth"
:name="item.key"
:disabled="componentDisabled(item)"
v-model:value="formData[item.key]"
:required="fieldRequired(item)"
></online-datetime>
<!-- 日期 -->
<online-date
v-else-if="item.type === 'date'"
:label="get4Label(item.label)"
:labelWidth="labelWidth"
:name="item.key"
:type="getDateExtendType(item.formSchema)"
:disabled="componentDisabled(item)"
v-model:value="formData[item.key]"
:required="fieldRequired(item)"
></online-date>
<!-- 时间 -->
<online-time
v-else-if="item.type === 'time'"
:label="get4Label(item.label)"
:labelWidth="labelWidth"
:name="item.key"
:disabled="componentDisabled(item)"
v-model:value="formData[item.key]"
:required="fieldRequired(item)"
></online-time>
<!-- 下拉选择 -->
<online-select
v-else-if="item.type === 'list' || item.type === 'sel_search'"
:label="get4Label(item.label)"
:labelWidth="labelWidth"
:name="item.key"
:type="item.type"
:dict="item.listSource"
:dictStr="item.dictStr"
:disabled="componentDisabled(item)"
v-model="formData[item.key]"
:required="fieldRequired(item)"
></online-select>
<!-- checkbox -->
<online-checkbox
v-else-if="item.type === 'checkbox'"
:name="item.key"
:type="item.type"
:label="get4Label(item.label)"
:labelWidth="labelWidth"
:dict="item.listSource"
:disabled="componentDisabled(item)"
:required="fieldRequired(item)"
v-model="formData[item.key]"
></online-checkbox>
<!-- radio -->
<online-radio
v-else-if="item.type === 'radio'"
:name="item.key"
:label="get4Label(item.label)"
:labelWidth="labelWidth"
:type="item.type"
:dict="item.listSource"
:disabled="componentDisabled(item)"
:required="fieldRequired(item)"
v-model="formData[item.key]"
></online-radio>
<!-- 下拉多选 -->
<online-multi
v-else-if="item.type === 'list_multi'"
:label="get4Label(item.label)"
:labelWidth="labelWidth"
:name="item.key"
:dict="item.listSource"
:disabled="componentDisabled(item)"
:required="fieldRequired(item)"
v-model="formData[item.key]"
></online-multi>
<!-- 省市区 -->
<online-pca
v-else-if="item.type === 'pca'"
:name="item.key"
:label="get4Label(item.label)"
:labelWidth="labelWidth"
:disabled="componentDisabled(item)"
:required="fieldRequired(item)"
v-model:value="formData[item.key]"
></online-pca>
<!-- 数字框 小数 -->
<wd-input
v-else-if="item.type === 'number' && (!item.onlyInteger || item.onlyInteger == false)"
:label-width="labelWidth"
v-model="formData[item.key]"
:label="get4Label(item.label)"
:name="item.key"
inputMode="decimal"
:disabled="componentDisabled(item)"
:placeholder="item.placeholder"
:rules="item.rules"
/>
<!-- 数字框 整数 -->
<wd-input
v-else-if="item.type === 'number' && item.onlyInteger === true"
:label-width="labelWidth"
:label="get4Label(item.label)"
:name="item.key"
v-model="formData[item.key]"
inputMode="numeric"
:disabled="componentDisabled(item)"
:placeholder="item.placeholder"
:rules="item.rules"
/>
<!-- 开关 -->
<wd-cell
v-else-if="item.type == 'switch'"
:name="item.key"
:title="get4Label(item.label)"
:title-width="labelWidth"
center
:required="fieldRequired(item)"
>
<view style="text-align: left">
<wd-switch
:label="get4Label(item.label)"
:name="item.key"
size="18px"
:disabled="componentDisabled(item)"
v-model="formData[item.key]"
:active-value="switchOpt(item.formSchema?.extendOption, 0)"
:inactive-value="switchOpt(item.formSchema?.extendOption, 1)"
/>
</view>
</wd-cell>
<!-- 多行文本 -->
<wd-textarea
v-else-if="['textarea', 'markdown', 'umeditor'].includes(item.type)"
:label-width="labelWidth"
:label="get4Label(item.label)"
:name="item.key"
v-model="formData[item.key]"
clearable
:maxlength="300"
:disabled="componentDisabled(item)"
:placeholder="item.placeholder"
:rules="item.rules"
/>
<!-- 密码输入框 -->
<wd-input
v-else-if="item.type === 'password'"
:label-width="labelWidth"
v-model="formData[item.key]"
:disabled="componentDisabled(item)"
:label="get4Label(item.label)"
:name="item.key"
:placeholder="item.placeholder"
:rules="item.rules"
show-password
/>
<!-- popup字典 -->
<PopupDict
v-else-if="item.type === 'popup_dict'"
:label-width="labelWidth"
:label="get4Label(item.label)"
:disabled="componentDisabled(item)"
:required="fieldRequired(item)"
v-model="formData[item.key]"
:multi="item.formSchema.popupMulti"
:dictCode="`${item.formSchema.code},${item.formSchema['destFields']},${item.formSchema['orgFields']}`"
></PopupDict>
<!-- popup -->
<Popup
v-else-if="item.type === 'popup'"
:label-width="labelWidth"
:label="get4Label(item.label)"
:disabled="componentDisabled(item)"
:required="fieldRequired(item)"
v-model="formData[item.key]"
:multi="item.formSchema.popupMulti"
:code="`${item.formSchema.code}`"
:setFieldsValue="setFieldsValue"
:fieldConfig="getPopupFieldConfig(item)"
></Popup>
<!-- 关联记录 -->
<online-popup-link-record
v-else-if="item.type === 'link_table'"
:label-width="labelWidth"
:label="get4Label(item.label)"
:name="item.key"
v-model:formSchema="item.formSchema"
:disabled="componentDisabled(item)"
:required="fieldRequired(item)"
v-model:value="formData[item.key]"
@selected="linkRecordChange"
></online-popup-link-record>
<!-- 他表字段 -->
<wd-input
v-else-if="item.type === 'link_table_field'"
:label-width="labelWidth"
v-model="formData[item.key]"
:disabled="true"
:label="get4Label(item.label)"
:name="item.key"
/>
<!-- 用户选择 -->
<select-user
v-else-if="item.type === 'sel_user'"
:label-width="labelWidth"
:name="item.key"
:label="get4Label(item.label)"
:disabled="componentDisabled(item)"
:required="fieldRequired(item)"
v-model="formData[item.key]"
></select-user>
<!-- 部门选择 -->
<select-dept
v-else-if="item.type === 'sel_depart'"
:label-width="labelWidth"
:name="item.key"
:label="get4Label(item.label)"
labelKey="departName"
:disabled="componentDisabled(item)"
:required="fieldRequired(item)"
v-model="formData[item.key]"
></select-dept>
<!-- 分类字典树 -->
<CategorySelect
v-else-if="item.type === 'cat_tree'"
:label-width="labelWidth"
:label="get4Label(item.label)"
:disabled="componentDisabled(item)"
:required="fieldRequired(item)"
v-model="formData[item.key]"
:pid="`${item.formSchema.pidValue}`"
></CategorySelect>
<!-- 自定义树 -->
<TreeSelect
v-else-if="item.type === 'sel_tree'"
:label-width="labelWidth"
:label="get4Label(item.label)"
:disabled="componentDisabled(item)"
:required="fieldRequired(item)"
v-model="formData[item.key]"
:dict="`${item.formSchema.dict}`"
:pidField="`${item.formSchema.pidField}`"
:pidValue="`${item.formSchema.pidValue}`"
:hasChildField="`${item.formSchema.hasChildField}`"
></TreeSelect>
<!-- 普通输入框 -->
<wd-input
v-else
:label-width="labelWidth"
v-model="formData[item.key]"
:disabled="componentDisabled(item)"
:label="get4Label(item.label)"
:name="item.key"
:placeholder="item.placeholder"
:rules="item.rules"
clearable
/>
</view>
</wd-cell-group>
</wd-form>
</view>
<view class="footer" v-if="showFooter">
<wd-button :disabled="loading" block :loading="loading" @click="formSubmit">提交</wd-button>
</view>
</view>
</template>
<script lang="ts" setup>
import FormProperty from '@/components/online/FormProperty'
import OnlineImage from '@/components/online/view/online-image.vue'
import OnlineSelect from '@/components/online/view/online-select.vue'
import OnlineTime from '@/components/online/view/online-time.vue'
import OnlineDatetime from '@/components/online/view/online-datetime.vue'
import OnlineDate from '@/components/online/view/online-date.vue'
import OnlineRadio from '@/components/online/view/online-radio.vue'
import OnlineCheckbox from '@/components/online/view/online-checkbox.vue'
import OnlineMulti from '@/components/online/view/online-multi.vue'
import OnlinePca from '@/components/online/view/online-pca.vue'
import OnlinePopupLinkRecord from '@/components/online/view/online-popup-link-record.vue'
import SelectDept from '@/components/SelectDept/SelectDept.vue'
import SelectUser from '@/components/SelectUser/SelectUser.vue'
import { loadOneFieldDefVal } from './defaultVal'
import { useToast, useMessage, useNotify } from 'wot-design-uni'
import { http } from '@/utils/http'
import { deepClone } from 'wot-design-uni/components/common/util'
import { isArray, isNumber } from '@/utils/is'
import { formatDate } from '@/common/uitls'
import { duplicateCheck } from '@/service/api'
// props
const props = defineProps({
showHeader: {
type: Boolean,
required: false,
default: false,
},
table: {
type: String,
default: '',
required: false,
},
taskId: {
type: String,
default: '',
required: false,
},
showFooter: {
type: Boolean,
required: false,
default: true,
},
edit: {
type: Boolean,
default: false,
required: false,
},
flowEdit: {
type: Boolean,
default: false,
required: false,
},
dataId: {
type: String,
default: '',
required: false,
},
title: {
type: String,
default: '',
required: false,
},
disabled: {
type: Boolean,
default: false,
required: false,
},
onlyBackData: {
type: Boolean,
default: false,
required: false,
},
})
// emits
const emits = defineEmits(['back', 'success', 'formSuccess'])
const toast = useToast()
//
const onlinekey = ref(1)
//
const reFresh = ref(false)
//
const url = ref({
load: '/online/cgform/api/getFormItemBytbname/',
optPre: '/online/cgform/api/form/',
})
// ID
const code = ref('')
//12
const single = ref(false)
//
const treeForm = ref(false)
//
const tableTxt = ref('')
//
const tableName = ref('')
//
const tableType = ref(1)
//Data
const formData = ref<any>({})
//
const hasFillRuleFields = ref('')
//
const hasRequiredFields = ref([])
//
const rootProperties = ref<any>([])
//
const wxProperties = ref<any>([])
//
const loading = ref(false)
//ID
const formDataId = ref('')
//
const navTitle = computed(() => {
if (!props.title || props.title.length === 0) {
return tableTxt.value
} else {
return props.title
}
})
//
const labelWidth = computed(() => {
return '100px'
})
//
const get4Label = computed(() => {
return (lable) => {
return `${lable && lable.length > 4 ? lable.substring(0, 4) : lable}`
}
})
onMounted(() => {
console.log('开始渲染online表单')
})
/**
* 获取日期控件的扩展类型
* @param formSchema
* @returns {string}
*/
const getDateExtendType = (formSchema: any) => {
if (formSchema.fieldExtendJson) {
let fieldExtendJson = JSON.parse(formSchema.fieldExtendJson)
let mapField = {
month: 'year-month',
year: 'year',
quarter: 'date',
week: 'date',
day: 'date',
}
return fieldExtendJson?.picker && mapField[fieldExtendJson?.picker]
? mapField[fieldExtendJson?.picker]
: 'date'
}
return 'date'
}
/**
* 判断是否选中
* @param opts
* @param value
* @returns {boolean|boolean}
*/
const isChecked = (opts: any, value: any) => {
return opts && opts.length > 0 ? value === opts[0] : false
}
/**
* 开关选项
* @param opts
* @param value
* @returns {boolean|boolean}
*/
const switchOpt = (opts: any, index: any) => {
const options = Array.isArray(opts) && opts.length > 0 ? opts : ['Y', 'N']
return options[index] + ''
}
/**
*
* @param item
* @returns {*|boolean}
*/
const componentDisabled = (item: any) => {
if (props.disabled === true || (!props.showFooter && !props.onlyBackData)) {
return true
}
return item.disabled
}
/**
* 判断字段是否必填
* @param item
* @returns {boolean}
*/
const fieldRequired = (item: any) => {
return item?.key && hasRequiredFields.value.includes(item.key)
}
/**
* 关联记录同步修改他表字段
* @param linkRecord
* @param key
*/
const linkRecordChange = (linkRecord, key) => {
let linkFieldArr = rootProperties.value.filter(
(item) => item.type === 'link_table_field' && item?.formSchema?.dictTable == key,
)
linkFieldArr.forEach((field) => {
let value = linkRecord.map((record) => record[field.formSchema.dictText]).join(',')
nextTick(() => {
formData.value[field.key] = value
})
})
}
/**
* 返回按钮点击事件
*/
const backRoute = () => {
emits('back')
}
/**
* 处理多选字段
* @param value
*/
function handleMultiOrDateField() {
let finalData = deepClone(formData.value)
//
let dateFieldArr = rootProperties.value.filter(
(item) => item.type === 'date' || item.type === 'datetime',
)
//
let pcaArr = rootProperties.value.filter((item) => item.type === 'pca')
finalData = Object.keys(finalData).reduce((acc, key) => {
let value = finalData[key]
//
if (value && pcaArr.length > 0 && pcaArr.map((item) => item.key).includes(key)) {
console.log('省市区获取最后一位value', value)
value = isArray(value) ? value[2] : value.split(',')[2]
}
//
if (value && isArray(value)) {
value = value.join(',')
}
//
if (dateFieldArr.length > 0) {
const dateField = dateFieldArr.find((obj) => obj.key === key)
if (dateField) {
value =
value && isNumber(value)
? formatDate(
value,
dateField.type === 'datetime' ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd',
)
: value
}
}
acc[key] = value
return acc
}, {})
return finalData
}
/**
* 表单提交事件
* @param e
*/
const formSubmit = async (e) => {
//
if (await fieldCheck(formData.value)) {
return
}
//
let finalData = await handleMultiOrDateField()
//
if (props.onlyBackData) {
emits('success', finalData)
return
}
if (props.flowEdit === true) {
//
} else if (props.edit === true) {
//
await handleEdit(finalData)
} else {
//
await handleSave(finalData)
}
}
/**
* 校验字段
* @param values
* @returns {boolean}
*/
const fieldCheck = async (values: any) => {
//
let flag = false
for (const item of rootProperties.value) {
//
const tip = (msg) => {
//
toast.warning(msg)
flag = true
}
//
if (fieldRequired(item) && !values[item.key]) {
tip(`${item.label}不能为空!`)
break
}
//
let pattern = item?.formSchema?.pattern
if (pattern) {
if (pattern == 'only') {
const res: any = await duplicateCheck({
tableName: tableName.value,
fieldName: item.key,
fieldVal: values[item.key],
dataId: formDataId.value,
})
if (!res.success) {
tip(`${item.label} ${res.message}`)
break
}
} else {
const regex = new RegExp(pattern)
if (values[item.key] && pattern && !regex.test(values[item.key])) {
let errorInfo = item?.formSchema?.errorInfo || '格式不正确!'
tip(`${item.label}${errorInfo}`)
break
}
}
}
}
return flag
}
/**
* 新增保存
*/
const handleSave = (finalData: any) => {
if (finalData?.bpm_status) {
finalData.bpm_status = '1'
}
console.log('===onlineForm表单组件走新增保存 handleSave===', finalData)
http
.post(`${url.value.optPre}${code.value}?tabletype=${tableType.value}`, finalData)
.then((res: any) => {
if (res.success) {
emits('success', res.result)
} else {
toast.warning(res.message)
}
})
}
/**
* 编辑保存
*/
const handleEdit = (finalData: any) => {
http
.put(`${url.value.optPre}${code.value}?tabletype=${tableType.value}`, finalData)
.then((res: any) => {
if (res.success) {
emits('success', formData.value.id)
} else {
toast.warning(res.message)
}
})
}
/**
* 根据表名加载表单数据
* @param dataID
*/
const loadByTableName = (dataID: any) => {
formDataId.value = props.dataId
// #ifdef MP-WEIXIN
if (dataID && dataID.length > 0) {
formDataId.value = dataID
}
// #endif
let urlStr = url.value.load + props.table
http.get(urlStr, { taskId: props.taskId }).then((res: any) => {
if (res.success) {
console.log('===onlineForm加载表单数据 schema===', res.result.schema)
let config = res.result
code.value = config.head.id
single.value = config.head.tableType === 1
tableType.value = config.head.tableType
createRootProperties(config.schema)
treeForm.value = config.head.isTree === 'Y'
tableTxt.value = config.head.tableTxt
if (props.edit === true || props.flowEdit === true) {
loadFormData()
} else {
//
handleDefaultValue()
}
} else {
// $tip
toast.info(res.message)
}
})
}
/**
* 创建根属性
* @param formSchema
*/
const createRootProperties = (formSchema: any) => {
tableName.value = formSchema.table
formData.value = {}
hasFillRuleFields.value = formSchema.hasFillRuleFields
hasRequiredFields.value = formSchema?.required ?? []
const properties = formSchema.properties
let rootProps = [],
subInfo = []
console.log('===onlineForm表单配置项 properties===', properties)
Object.keys(properties).map((key) => {
if (key) {
const item = properties[key]
//TODO
if (item.view === 'tab') {
subInfo.push(item)
} else {
formData.value[key] = ''
let fp = FormProperty(key, item, formSchema.required)
rootProps.push(fp)
}
}
})
rootProps.sort((one, next) => {
return one.formSchema.order - next.formSchema.order
})
rootProperties.value = [...rootProps]
console.log('--rootProperties--', rootProps)
// #ifdef MP-WEIXIN
// wxProperties.value = rootProperties.value.map((item) => {
// let {
// popupCode,
// formSchema,
// dest,
// ogn,
// key,
// label,
// placeholder,
// type,
// rules,
// listSource,
// dictStr,
// disabled,
// } = item
// let event = {
// formSchema,
// dest,
// key,
// type,
// rules,
// listSource,
// label,
// dictStr,
// disabled,
// placeholder,
// ogn,
// popupCode,
// }
// return event
// })
// console.log('this.wxProperties', wxProperties.value)
nextTick(() => {
reFresh.value = true
onlinekey.value += 1
})
// #endif
emits('formSuccess', true)
}
/**
* 获取字段类型
* @param item
* @returns {string}
*/
const getFieldNumberType = (item: any) => {
return item.onlyInteger === true ? 'digit' : 'number'
}
/**
* 获取表单数据
*/
const loadFormData = () => {
let urlStr = url.value.optPre + code.value + '/' + formDataId.value
urlStr = urlStr.replace(/"/g, '')
http.get(urlStr).then((res: any) => {
if (res.success) {
formData.value = { ...res.result }
console.log('===onlineForm表单组件走获取表单数据 loadFormData===', formData.value)
// #ifdef MP-WEIXIN
reFresh.value = false
nextTick(() => {
reFresh.value = true
})
// #endif
}
})
}
/**
* 获取默认值
*/
const handleDefaultValue = () => {
console.log('===onlineForm表单组件走默认值 handleDefaultValue===')
rootProperties.value.forEach((item) => {
let field = item.key
let { defVal, type } = item.formSchema
loadOneFieldDefVal(defVal, type, (value) => {
formData.value[field] = value
})
})
}
const setFieldsValue = (data) => {
formData.value = { ...formData.value, ...data }
}
const getPopupFieldConfig = (item) => {
const { formSchema } = item
const { destFields = '', orgFields = '' } = formSchema
const result = orgFields.split(',').map((oField, index) => {
return {
source: oField,
target: destFields.split(',')[index],
}
})
return result
}
defineExpose({
navTitle,
getDateExtendType,
isChecked,
componentDisabled,
fieldRequired,
backRoute,
formSubmit,
loadByTableName,
getFieldNumberType,
})
</script>
<style lang="scss" scoped>
.onlineLoader-container {
.form-container {
:deep(.wd-cell-group__body){
background: #F1F1F1;
}
.onlineLoader-form{
:deep(.wd-input__label-inner){
font-size: 16px;
}
:deep(.wd-picker__label){
font-size: 16px;
}
:deep(.wd-select-picker__label){
font-size: 16px;
}
:deep(.wd-cell__title){
font-size: 16px;
}
:deep(.wd-textarea__label-inner){
font-size: 16px;
}
:deep(.wd-input__label.is-required){
padding-left: 0px;
}
:deep(.wd-input__label.is-required::after){
left: -10px;
}
:deep(.wd-textarea__clear){
color: #bfbfbf;
}
:deep(.wd-select-picker__clear){
color: #bfbfbf;
}
:deep(.wd-input__clear){
color: #bfbfbf;
}
:deep(.wd-upload__close){
color: #bfbfbf;
}
}
}
.footer {
padding: 12px;
}
}
</style>

View File

@ -0,0 +1,303 @@
<template>
<wd-popup position="bottom" v-model="show">
<PageLayout
:navTitle="navTitle"
type="popup"
navRightText="确定"
@navRight="handleConfirm"
@navBack="handleCancel"
>
<z-paging
ref="paging"
:fixed="false"
v-model="dataList"
@query="queryList"
:default-page-size="15"
>
<!-- <template #top>-->
<!-- <wd-search-->
<!-- hide-cancel-->
<!-- :placeholder="search.placeholder"-->
<!-- v-model="search.keyword"-->
<!-- @search="handleSearch"-->
<!-- @clear="handleClear"-->
<!-- />-->
<!-- </template>-->
<template v-if="multi">
<wd-checkbox-group shape="square" v-model="checkedValue">
<template v-for="(item, index) in dataList" :key="index">
<view class="list" @click="hanldeCheck(index)">
<view class="left text-gray-5">
<view class="cu-avatar lg mr-4" v-if="imageField && item[imageField]" :style="[{backgroundImage:'url('+ (item[imageField]) +')'}]"></view>
<view class="field-content">
<template v-for="(cItem, cIndex) in columns" :key="cIndex">
<view class="row">
<text class="label">{{ cItem.title }}</text>
<text class="value">{{ item[cItem.dataIndex] }}</text>
</view>
</template>
</view>
</view>
<view class="right" @click.stop>
<wd-checkbox ref="checkboxRef" :modelValue="index"></wd-checkbox>
</view>
</view>
</template>
</wd-checkbox-group>
</template>
<template v-else>
<wd-radio-group shape="dot" v-model="checkedValue">
<template v-for="(item, index) in dataList" :key="index">
<wd-cell>
<view class="list" @click="hanldeCheck(index)">
<view class="left text-gray-5">
<view class="cu-avatar lg mr-4" v-if="imageField && item[imageField]" :style="[{backgroundImage:'url('+ (item[imageField]) +')'}]"></view>
<view class="field-content">
<template v-for="(cItem, cIndex) in columns" :key="cIndex">
<view class="row">
<text class="label">{{ cItem.title }}</text>
<text class="value">{{ item[cItem.dataIndex] }}</text>
</view>
</template>
</view>
</view>
<view class="right" @click.stop>
<wd-radio :value="index"></wd-radio>
</view>
</view>
</wd-cell>
</template>
</wd-radio-group>
</template>
</z-paging>
</PageLayout>
</wd-popup>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { http } from '@/utils/http'
import { isArray } from '@/utils/is'
import { getFileAccessHttpUrl } from '@/common/uitls'
defineOptions({
name: 'popupReportModal',
options: {
styleIsolation: 'shared',
},
})
const props = defineProps({
dictTable:{
type: String,
required:true,
},
dictCode:{
type:String,
required:true,
},
dictText:{
type: String,
required:true,
},
multi:{
type: Boolean,
required: false,
},
imageField:{
type: String,
required:false,
}
})
const emit = defineEmits(['change', 'close'])
const toast = useToast();
const show = ref(true);
const api = {
getColumns: '/online/cgform/api/getColumns',
getData: '/online/cgform/api/getData'
}
console.log('props:::', props)
const navTitle = ref('');
const paging = ref(null);
const dataList = ref([]);
// id
let rpConfigId = null;
let loadedColumns = false;
const columns = ref([]);
const selectArr = ref([]);
const checkedValue: any = ref(props.multi ? [] : '')
const checkboxRef = ref(null)
const search = reactive({
keyword: '',
placeholder: '',
field: '',
})
const handleClose = () => {
setTimeout(() => {
emit('close')
}, 400)
}
const beforeOpen = (arr) => {
selectArr.value = arr || []
}
const handleConfirm = () => {
if (checkedValue.value.length == 0) {
toast.warning('还没选择~')
return
}
const result = []
let value = checkedValue.value
if (!Array.isArray(checkedValue.value)) {
value = [checkedValue.value]
}
value.forEach((index) => {
result.push(dataList.value[index])
})
show.value = false
emit('change', result)
handleClose()
}
const handleCancel = () => {
show.value = false
handleClose()
console.log('取消了~')
}
//
function handleSearch() {
paging.value.reload()
}
//
function handleClear() {
search.keyword = ''
handleSearch()
}
const hanldeCheck = (index) => {
if (props.multi) {
if (Array.isArray(checkboxRef.value)) {
checkboxRef.value[index].toggle()
}
} else {
checkedValue.value = index
}
}
const getRpColumns = () => {
return new Promise<void>((resolve, reject) => {
if (loadedColumns) {
resolve()
} else {
let linkTableSelectFields = props.dictCode + ',' + props.dictText;
http
.get(`${api.getColumns}/${props.dictTable}?linkTableSelectFields=${linkTableSelectFields}`)
.then((res: any) => {
if (res.success) {
loadedColumns = true
const { result } = res
navTitle.value = result.description
rpConfigId = result.code
result.columns?.forEach((item) => {
if (linkTableSelectFields.includes(item.dataIndex)) {
columns.value.push(item)
}
})
resolve()
} else {
reject()
}
})
.catch((err) => {
reject()
})
}
})
}
const queryList = (pageNo, pageSize) => {
const pararms = { pageNo, pageSize,linkTableSelectFields:"" }
if (search.keyword) {
pararms[search.field] = `*${search.keyword}*`
}
getRpColumns()
.then(() => {
let linkTableSelectFields = props.dictCode + ',' + props.dictText;
if(props.imageField){
linkTableSelectFields = linkTableSelectFields + ',' + props.imageField;
}
pararms.linkTableSelectFields = linkTableSelectFields;
http
.get(`${api.getData}/${props.dictTable}`, pararms)
.then((res: any) => {
if (res.success && res.result.records) {
let dataRecords = res.result.records;
if(dataRecords && dataRecords.length>0){
let id = dataRecords[0]['id'];
for(let item of dataRecords){
if(!id){
item.id = new Date().getTime();
}
if(props.imageField && item[props.imageField]){
let imgUrlArr = item[props.imageField].split(",");
item[props.imageField] = imgUrlArr.length>0?getFileAccessHttpUrl(imgUrlArr[0]):"";
}
}
}
//TODO
if(selectArr.value && isArray(selectArr) && selectArr.length>0){
//checkedValue.value = [...selectArr]
}
paging.value.complete(dataRecords ?? [])
} else {
paging.value.complete(false)
}
})
.catch((err) => {})
})
.catch((err) => {})
}
defineExpose({
beforeOpen
})
</script>
<style lang="scss" scoped>
:deep(.wd-cell) {
--wot-color-white: tranparent;
--wot-cell-padding: 0;
.wd-cell__wrapper {
--wot-cell-wrapper-padding: 0;
}
.wd-cell__left {
display: none;
}
}
:deep(.wd-checkbox-group) {
--wot-checkbox-bg: tranparent;
}
:deep(.wd-radio-group) {
--wot-radio-bg: tranparent;
}
.list {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
padding: 16px;
margin-top: 16px;
.left {
display: flex;
justify-content: center;
align-items: center;
.field-content{
.row {
display: flex;
}
}
}
.right {
:deep(.wd-checkbox) {
margin-bottom: 0;
}
}
}
</style>

View File

@ -0,0 +1,138 @@
<template>
<wd-select-picker
:label-width="labelWidth"
:label="label"
v-model="selected"
filterable
clearable
:columns="options"
:disabled="disabled"
placeholder="请选择"
@change="handleChange"
></wd-select-picker>
</template>
<script setup>
import { ref, watch, onMounted } from 'vue'
import { isArray, isString } from 'lodash'
import {http} from "@/utils/http"; // 使 lodash
// props
const props = defineProps({
dict: {
type: [Array, String],
default: () => [],
required: true,
},
label: {
type: String,
default: '',
required: false,
},
labelWidth: {
type: String,
default: '80px',
required: false,
},
name: {
type: String,
default: '',
required: false,
},
dictStr: {
type: String,
default: '',
required: false,
},
type: {
type: String,
default: '',
required: false,
},
value: {
type: [Array, String],
required: false,
},
disabled: {
type: Boolean,
default: false,
required: false,
},
})
// emits
const emit = defineEmits(['input', 'change', 'update:value'])
//
const selected = ref([]);
const options = ref([]);
//
const initSelections = async () => {
options.value = []
if (props.type === 'sel_search' && props.dictStr) {
let temp = props.dictStr
if (temp.indexOf(' ') > 0) {
temp = encodeURI(props.dictStr)
}
try {
const res = await http.get('/sys/dict/getDictItems/' + temp)
if (res.success) {
options.value = res.result
}
} catch (error) {
console.error('请求数据出错:', error)
}
}
else {
if (!props.dict || props.dict.length === 0) {
return
}
if (isString(props.dict)) {
try {
const res = await http.get('/sys/dict/getDictItems/' + props.dict)
if (res.success) {
options.value = res.result
}
} catch (error) {
console.error('请求数据出错:', error)
}
} else {
props.dict.forEach((item) => {
options.value.push(item)
})
}
}
console.log("options.value ",options.value )
}
//
const handleChange = (e) => {
let value = "";
if (selected.value && isArray(selected.value)) {
value = selected.value.join(',')
}
emit('update:value', value);
emit('change', value);
}
// dict value
watch(() => props.dict, () => {
initSelections();
});
// value
watch(
() => props.value,
(val) => {
selected.value = !val? [] : val.split(',');
},
{ immediate: true },
)
//
onMounted(() => {
initSelections()
})
</script>
<style></style>

View File

@ -0,0 +1,72 @@
<template>
<wd-datetime-picker
:disabled="disabled"
:type="type"
:labelWidth="labelWidth"
v-model="currentTime"
:label="label"
@confirm="handleConfirm"
/>
</template>
<script setup>
// props
import { isString } from '@/utils/is'
const props = defineProps({
label: {
type: String,
default: '',
required: false,
},
labelWidth: {
type: String,
default: '80px',
required: false,
},
name: {
type: String,
default: '',
required: false,
},
type: {
type: String,
default: 'date',
required: false,
},
value: {
type: [String, Number],
required: false,
},
disabled: {
type: Boolean,
default: false,
required: false,
},
})
// emits
const emit = defineEmits(['input', 'change', 'update:value'])
// ;
const visible = ref(false)
const currentTime = ref('')
// value
watch(
() => props.value,
(val) => {
if (val) {
currentTime.value = val && isString(val) ? new Date(val).getTime() : val
} else {
currentTime.value = ''
}
},
)
//
const handleConfirm = (e) => {
emit('update:value', currentTime.value)
emit('change', currentTime.value)
}
</script>
<style></style>

View File

@ -0,0 +1,73 @@
<template>
<wd-datetime-picker
:disabled="disabled"
:labelWidth="labelWidth"
v-model="currentTime"
:label="label"
@confirm="handleConfirm"
/>
</template>
<script setup>
// props
import dayjs from 'dayjs'
import { isString } from '@/utils/is'
const props = defineProps({
label: {
type: String,
default: '',
required: false,
},
labelWidth: {
type: String,
default: '80px',
required: false,
},
name: {
type: String,
default: '',
required: false,
},
type: {
type: String,
default: '',
required: false,
},
value: {
type: [String, Number],
required: false,
},
disabled: {
type: Boolean,
default: false,
required: false,
},
})
// emits
const emit = defineEmits(['input', 'change', 'update:value'])
// ;
const visible = ref(false)
const currentTime = ref('')
// value
watch(
() => props.value,
(val) => {
if (val) {
console.log("日期时间变化val",val);
currentTime.value = val && isString(val) ? new Date(val).getTime() : val
} else {
currentTime.value = ''
}
},
)
//
const handleConfirm = (e) => {
emit('update:value', currentTime.value)
emit('change', currentTime.value)
}
</script>
<style></style>

View File

@ -0,0 +1,165 @@
<template>
<wd-upload
v-model:file-list="fileList"
:accept="uploadFileType"
:upload-method="customUpload"
:disabled="disabled"
:before-remove="delFile"
></wd-upload>
</template>
<script lang="ts" setup>
import type { UploadMethod } from '@/uni_modules/wot-design-uni/components/wd-upload/types'
import { getEnvBaseUploadUrl } from '@/utils'
import { useUserStore } from '@/store'
import { getFileAccessHttpUrl } from '@/common/uitls'
import {isString} from "@/utils/is";
import {useToast} from "wot-design-uni";
const toast = useToast()
const VITE_UPLOAD_BASEURL = `${getEnvBaseUploadUrl()}`
// props
const props = defineProps({
title: {
type: String,
default: '',
required: false,
},
value: {
type: String,
required: false,
},
disabled: {
type: Boolean,
default: false,
required: false,
},
name: {
type: String,
default: '',
required: false,
},
uploadFileType: {
type: String,
default: 'image',
required: false,
},
})
// emits
const emit = defineEmits(['change', 'update:value'])
//
const fileList = ref([])
/**
* 自定义上传方法
* @param file
* @param formData
* @param options
*/
const customUpload: UploadMethod = (file, formData, options) => {
const userStore = useUserStore()
const uploadTask = uni.uploadFile({
url: VITE_UPLOAD_BASEURL,
header: {
'X-Access-Token': userStore.userInfo.token,
'X-Tenant-Id': userStore.userInfo.tenantId,
...options.header,
},
name: options.name,
fileName: options.name,
fileType: options.fileType,
formData,
filePath: file.url,
success(res: any) {
if (res.statusCode === options.statusCode) {
let data = res.data;
if (data && isString(data)) {
data = JSON.parse(data)
}
//
if (data && data.success) {
const file = {
id: new Date().getTime(),
name: options.name,
path: data.message,
url: getFileAccessHttpUrl(data.message),
}
fileList.value.unshift(file)
changeOnlineFormValue()
}
} else {
//
options.onError({ ...res, errMsg: res.errMsg || '' }, file, formData)
}
},
fail(err) {
console.info('upload fail', err)
//
options.onError(err, file, formData)
},
})
//
uploadTask.onProgressUpdate((res) => {
options.onProgress(res, file)
})
}
const changeOnlineFormValue = () => {
console.log('changeOnlineFormValue fileList.value', fileList)
const arr = fileList.value.map((item) => item['path'])
const str = arr.join(',')
emit('change', str)
emit('update:value', str)
}
const delFile = ({ file, fileList, resolve }) => {
uni.showModal({
title: '提示',
content: '确定要删除吗?',
cancelText: '取消',
confirmText: '确定',
success: (res) => {
if (res.confirm) {
console.log('当前删除文件', file)
changeOnlineFormValue()
toast.success('删除成功')
resolve(true)
}
},
fail: (err) => {
console.log(err)
resolve(false)
},
})
}
const loadFile = () => {
if (!props.value || props.value.length === 0) {
return
}
const pathArr = props.value.split(',')
const fileArray = []
pathArr.forEach((path) => {
const seg = path.lastIndexOf('/')
fileArray.push({
name: path.substr(seg < 0 ? 0 : seg),
path: path,
url: getFileAccessHttpUrl(path),
})
})
console.log('当前图片回显数据', fileArray)
fileList.value = [...fileArray]
}
// value
watch(
() => props.value,
() => {
loadFile()
},
{ immediate: true },
)
//
onMounted(() => {
loadFile()
})
</script>

View File

@ -0,0 +1,140 @@
<template>
<wd-select-picker
:label-width="labelWidth"
:label="label"
v-model="selected"
filterable
clearable
:columns="options"
:disabled="disabled"
placeholder="请选择"
@change="handleChange"
></wd-select-picker>
</template>
<script setup>
import { ref, watch, onMounted } from 'vue'
import { isArray, isString } from 'lodash'
import {http} from "@/utils/http";
// props
const props = defineProps({
dict: {
type: [Array, String],
default: () => [],
required: true,
},
label: {
type: String,
default: '',
required: false,
},
labelWidth: {
type: String,
default: '80px',
required: false,
},
name: {
type: String,
default: '',
required: false,
},
dictStr: {
type: String,
default: '',
required: false,
},
type: {
type: String,
default: '',
required: false,
},
value: {
type: [Array, String],
required: false,
},
modelValue: {
type: [Array, String],
},
disabled: {
type: Boolean,
default: false,
required: false,
},
})
// emits
const emit = defineEmits(['input', 'change', 'update:value'])
//
const selected = ref([]);
const options = ref([]);
//
const initSelections = async () => {
options.value = []
if (props.type === 'sel_search' && props.dictStr) {
let temp = props.dictStr
if (temp.indexOf(' ') > 0) {
temp = encodeURI(props.dictStr)
}
try {
const res = await http.get('/sys/dict/getDictItems/' + temp)
if (res.success) {
options.value = res.result
}
} catch (error) {
console.error('请求数据出错:', error)
}
}
else {
if (!props.dict || props.dict.length === 0) {
return
}
if (isString(props.dict)) {
try {
const res = await http.get('/sys/dict/getDictItems/' + props.dict)
if (res.success) {
options.value = res.result
}
} catch (error) {
console.error('请求数据出错:', error)
}
} else {
props.dict.forEach((item) => {
options.value.push(item)
})
}
}
console.log("options.value ",options.value )
}
//
const handleChange = (e) => {
let value = "";
if (selected.value && isArray(selected.value)) {
value = selected.value.join(',')
}
emit('update:value', value);
emit('change', value);
}
// dict value
watch(() => props.dict, () => {
initSelections();
});
// value
watch(
() => props.modelValue,
() => {
selected.value = props?.modelValue? props.modelValue.split(','):[];
},
{ deep: true, immediate: true }
)
//
onMounted(() => {
initSelections()
})
</script>
<style></style>

View File

@ -0,0 +1,100 @@
<template>
<view>
<wd-picker
:columns="columns"
:label-width="labelWidth"
:label="label"
:required="required"
v-model="selected"
:column-change="onChangeDistrict"
@confirm="handleConfirm"
/>
</view>
</template>
<script lang="ts" setup>
import { areaData } from '@/components/online/area-picker-data'
import {getAreaArrByCode} from '@/common/areaData/Area'
import {isString} from "@/utils/is";
// props
const props = defineProps({
label: {
type: String,
default: '',
required: false,
},
labelWidth: {
type: String,
default: '80px',
required: false,
},
value: {
type: [String,Array],
required: false,
},
placeholder: {
type: String,
required: false,
default: '请选择省市区',
},
disabled: {
type: Boolean,
default: false,
required: false,
},
required: {
type: Boolean,
default: false,
required: false,
},
})
// emits
const emits = defineEmits(['input', 'change', 'update:value'])
//
const selected = ref([])
const district = { ...areaData }
const columns = ref([
district[0],
district[district[0][0].value],
district[district[district[0][0].value][0].value]
])
const onChangeDistrict = (pickerView, value, columnIndex, resolve) => {
const item = value[columnIndex]
if (columnIndex === 0) {
pickerView.setColumnData(1, district[item.value])
pickerView.setColumnData(2, district[district[item.value][0].value])
} else if (columnIndex === 1) {
pickerView.setColumnData(2, district[item.value])
}
resolve()
}
const handleConfirm = ({value}) => {
emits('update:value', value);
}
// value
watch(
() => props.value,
async (val) => {
if(props.value && isString(props.value)){
let arr = getAreaArrByCode(props.value);
selected.value = arr;
await initColumnData(arr);
}
},
{ immediate: true },
)
/**
* 初始化列数据
* @param val
*/
function initColumnData(val){
if(val && val.length){
let first = district[0];
let second = district[selected.value[0]];
let third = district[selected.value[1]];
columns.value = [first, second, third]
}
}
</script>

View File

@ -0,0 +1,174 @@
<template>
<view class="Popup">
<view @click="handleClick">
<wd-input
:placeholder="`请选择${$attrs.label}`"
type="text"
readonly
v-model="showText"
clearable
v-bind="$attrs"
/>
</view>
<LinkRecordsModal
v-if="reportModal.show"
ref="lrmRef"
:dictCode="dictCode"
:dictTable="dictTable"
:dictText="dictText"
:multi="multi"
:imageField="imageField"
@close="handleClose"
@change="handleChange"
></LinkRecordsModal>
</view>
</template>
<script setup lang="ts">
import { ref, watch, useAttrs } from 'vue'
import { useToast } from 'wot-design-uni'
import LinkRecordsModal from './link-records-modal.vue'
import {http} from "@/utils/http";
defineOptions({
name: 'onlinePopupLinkRecord.vue',
options: {
styleIsolation: 'shared',
},
})
const props = defineProps({
value: {
type: String,
required: false,
},
name: {
type: String,
required: false,
},
disabled: {
type: Boolean,
default: true,
required: false,
},
required: {
type: Boolean,
default: true,
required: false,
},
formSchema: {
type: Object,
required: true,
},
})
const emit = defineEmits(['change', 'update:value','selected'])
const toast = useToast()
const lrmRef = ref()
const showText = ref('')
const selectVal = ref([])
const attrs: any = useAttrs()
const reportModal = reactive({
show: false,
})
//code
const dictCode = computed(() => props.formSchema?.dictCode)
//table
const dictTable = computed(() => props.formSchema?.dictTable)
//
const dictText = computed(() => props.formSchema?.dictText)
//
const multi = computed(() => {
if(props.formSchema?.fieldExtendJson){
const extendJson = JSON.parse(props.formSchema.fieldExtendJson)
return extendJson?.multiSelect
}
return false
})
//
const imageField = computed(() => {
if(props.formSchema?.fieldExtendJson){
const extendJson = JSON.parse(props.formSchema.fieldExtendJson)
return extendJson?.imageField
}
return ''
})
//
const firstLoad = ref(true);
/**
* 监听value数值
*/
watch(
() => props.value,
(val) => {
val && loadValue()
},
{ immediate: true },
)
//
function loadValue(){
console.log('关联记录loadValue',firstLoad.value)
if(!firstLoad.value){
return
}
let linkTableSelectFields = dictCode.value + ',' + dictText.value;
let superQueryParams = [{"field":"id","rule":"in","val": props.value}];
let param = {
linkTableSelectFields,
superQueryMatchType:"and",
superQueryParams: encodeURI(JSON.stringify(superQueryParams))
};
let titleField = props.formSchema?.dictText && props.formSchema?.dictText.split(",")[0];
http.get(`/online/cgform/api/getData/${dictTable.value}`,param).then(res=>{
if(res.success){
let selectedList = res.result.records || [];
let labels = [];
let values = [];
selectedList.forEach(item=>{
if(item.id){
values.push(item.id);
labels.push(item[titleField]);
}
})
showText.value = labels.join(',');
selectVal.value = values;
emit('selected', selectedList,props.name);
}
})
firstLoad.value = false;
}
//
function callBack(rows) {
//popup
let values = []
let labels = []
let titleField = props.formSchema?.dictText && props.formSchema?.dictText.split(",")[0];
rows.forEach(item=>{
if(item.id){
values.push(item.id);
labels.push(item[titleField]);
}
})
showText.value = labels.join(',')
selectVal.value = values
emit('selected', rows,props.name)
emit('change', values.join(','))
emit('update:value', values.join(','))
}
//
const handleClick = () => {
if (!attrs.disabled) {
reportModal.show = true
//lrmRef.value.beforeOpen(selectVal.value)
}
}
//
const handleClose = () => {
reportModal.show = false
}
const handleChange = (data) => {
console.log('选中的值:', data)
callBack(data)
}
</script>
<style scoped></style>

View File

@ -0,0 +1,135 @@
<template>
<wd-select-picker
:label-width="labelWidth"
:label="label"
v-model="selected"
type="radio"
filterable
clearable
:columns="options"
:disabled="disabled"
placeholder="请选择"
@change="handleChange"
></wd-select-picker>
</template>
<script setup>
import { ref, watch, onMounted } from 'vue'
import { isArray, isString } from 'lodash'
import {http} from "@/utils/http"; // 使 lodash
// props
const props = defineProps({
dict: {
type: [Array, String],
default: () => [],
required: true,
},
label: {
type: String,
default: '',
required: false,
},
labelWidth: {
type: String,
default: '80px',
required: false,
},
name: {
type: String,
default: '',
required: false,
},
dictStr: {
type: String,
default: '',
required: false,
},
type: {
type: String,
default: '',
required: false,
},
value: {
type: [Array, String],
required: false,
},
disabled: {
type: Boolean,
default: false,
required: false,
},
})
// emits
const emit = defineEmits(['input', 'change', 'update:value'])
//
const selected = ref([]);
const options = ref([]);
//
const initSelections = async () => {
options.value = []
if (props.type === 'sel_search' && props.dictStr) {
let temp = props.dictStr
if (temp.indexOf(' ') > 0) {
temp = encodeURI(props.dictStr)
}
try {
const res = await http.get('/sys/dict/getDictItems/' + temp)
if (res.success) {
options.value = res.result
}
} catch (error) {
console.error('请求数据出错:', error)
}
}
else {
if (!props.dict || props.dict.length === 0) {
return
}
if (isString(props.dict)) {
try {
const res = await http.get('/sys/dict/getDictItems/' + props.dict)
if (res.success) {
options.value = res.result;
}
} catch (error) {
console.error('请求数据出错:', error)
}
} else {
props.dict.forEach((item) => {
options.value.push(item)
})
}
}
}
//
const handleChange = (e) => {
emit('update:value', selected.value);
emit('change', selected.value);
}
// dict value
watch(() => props.dict, () => {
initSelections();
});
// value
watch(
() => props.value,
(val) => {
console.log("字典单选》》》》》 ",val)
selected.value = val;
},
{ immediate: true },
)
//
onMounted(() => {
initSelections()
})
</script>
<style></style>

View File

@ -0,0 +1,132 @@
<template>
<wd-picker
:label-width="labelWidth"
:label="label"
filterable
v-model="selected"
:columns="options"
:disabled="disabled"
placeholder="请选择"
@change="handleChange"
></wd-picker>
</template>
<script setup>
import { ref, watch, onMounted } from 'vue'
import { isArray, isString } from 'lodash'
import {http} from "@/utils/http"; // 使 lodash
// props
const props = defineProps({
dict: {
type: [Array, String],
default: () => [],
required: true,
},
label: {
type: String,
default: '',
required: false,
},
labelWidth: {
type: String,
default: '80px',
required: false,
},
name: {
type: String,
default: '',
required: false,
},
dictStr: {
type: String,
default: '',
required: false,
},
type: {
type: String,
default: '',
required: false,
},
value: {
type: [String, Number],
required: false,
},
disabled: {
type: Boolean,
default: false,
required: false,
},
})
// emits
const emit = defineEmits(['input', 'change', 'update:value'])
//
const selected = ref('请选择')
const options = ref([])
//
const initSelections = async () => {
options.value = []
if (props.type === 'sel_search' && props.dictStr) {
let temp = props.dictStr
if (temp.indexOf(' ') > 0) {
temp = encodeURI(props.dictStr)
}
try {
const res = await http.get('/sys/dict/getDictItems/' + temp)
if (res.success) {
options.value = res.result;
}
} catch (error) {
console.error('请求数据出错:', error)
}
}
else {
if (!props.dict || props.dict.length === 0) {
return
}
if (isString(props.dict)) {
try {
const res = await http.get('/sys/dict/getDictItems/' + props.dict)
if (res.success) {
options.value = res.result;
}
} catch (error) {
console.error('请求数据出错:', error)
}
} else {
props.dict.forEach((item) => {
options.value.push(item)
})
}
}
}
//
const handleChange = (e) => {
emit('update:value', selected.value);
emit('change', selected.value);
}
// dict value
watch(() => props.dict, () => {
initSelections();
});
// value
watch(
() => props.value,
(val) => {
selected.value = !val? [] : props.value;
},
{ immediate: true },
)
//
onMounted(() => {
initSelections()
})
</script>
<style></style>

View File

@ -0,0 +1,58 @@
<template>
<wd-datetime-picker :disabled="disabled" type="time" :labelWidth="labelWidth" v-model="currentTime" :label="label" @confirm="handleConfirm" />
</template>
<script setup>
// props
const props = defineProps({
label: {
type: String,
default: '',
required: false,
},
labelWidth: {
type: String,
default: '80px',
required: false,
},
name: {
type: String,
default: '',
required: false,
},
type: {
type: String,
default: '',
required: false,
},
value: {
type: [String, Number],
required: false,
},
disabled: {
type: Boolean,
default: false,
required: false,
},
})
// emits
const emit = defineEmits(['input', 'change', 'update:value'])
// ;
const currentTime = ref('')
// value
watch(
() => props.value,
(val) => {
currentTime.value = val?val:'';
}
)
//
const handleConfirm = (e) => {
emit('update:value', currentTime.value+':00')
emit('change', currentTime.value+':00')
}
</script>
<style></style>

View File

@ -0,0 +1,101 @@
import type { App } from 'vue';
import StatusTip from '@/pages-work/components/statusTip.vue';
import JBar from '@/pages-work/components/echarts/JBar/index.vue';
import JBackgroundBar from '@/pages-work/components/echarts/JBackgroundBar/index.vue';
import JDynamicBar from '@/pages-work/components/echarts/JDynamicBar/index.vue';
import JStackBar from '@/pages-work/components/echarts/JStackBar/index.vue';
import JMultipleBar from '@/pages-work/components/echarts/JMultipleBar/index.vue';
import JNegativeBar from '@/pages-work/components/echarts/JNegativeBar/index.vue';
import JMixLineBar from '@/pages-work/components/echarts/JMixLineBar/index.vue';
import JProgress from '@/pages-work/components/echarts/JProgress/index.vue';
import JLine from '@/pages-work/components/echarts/JLine/index.vue';
import JMultipleLine from '@/pages-work/components/echarts/JMultipleLine/index.vue';
import JSmoothLine from '@/pages-work/components/echarts/JSmoothLine/index.vue';
import JStepLine from '@/pages-work/components/echarts/JStepLine/index.vue';
import JPie from '@/pages-work/components/echarts/JPie/index.vue';
import JRing from '@/pages-work/components/echarts/JRing/index.vue';
import JFunnel from '@/pages-work/components/echarts/JFunnel/index.vue';
import JPyramidFunnel from '@/pages-work/components/echarts/JPyramidFunnel/index.vue';
import JRadar from '@/pages-work/components/echarts/JRadar/index.vue';
import JCircleRadar from '@/pages-work/components/echarts/JCircleRadar/index.vue';
import JGauge from '@/pages-work/components/echarts/JGauge/index.vue';
import JColorGauge from '@/pages-work/components/echarts/JColorGauge/index.vue';
import JScatter from '@/pages-work/components/echarts/JScatter/index.vue';
import JBubble from '@/pages-work/components/echarts/JBubble/index.vue';
import DoubleLineBar from '@/pages-work/components/echarts/DoubleLineBar/index.vue';
import JRose from '@/pages-work/components/echarts/JRose/index.vue';
import JHorizontalBar from '@/pages-work/components/echarts/JHorizontalBar/index.vue';
import JArea from '@/pages-work/components/echarts/JArea/index.vue';
import JPictorial from '@/pages-work/components/echarts/JPictorial/index.vue';
import JPictorialBar from '@/pages-work/components/echarts/JPictorialBar/index.vue';
import JAreaMap from '@/pages-work/components/echarts/map/JAreaMap/index.vue';
import JBubbleMap from '@/pages-work/components/echarts/map/JBubbleMap/index.vue';
import JBarMap from '@/pages-work/components/echarts/map/JBarMap/index.vue';
import JHeatMap from '@/pages-work/components/echarts/map/JHeatMap/index.vue';
import JFlyLineMap from '@/pages-work/components/echarts/map/JFlyLineMap/index.vue';
//echart
import JCarousel from '@/pages-work/components/drag/carousel/index.vue';
import JIframe from '@/pages-work/components/drag/iframe/index.vue';
import JDragEditor from '@/pages-work/components/drag/editor/index.vue';
import JImg from '@/pages-work/components/drag/img/index.vue';
import JNumber from '@/pages-work/components/drag/number/index.vue';
import JText from '@/pages-work/components/drag/text/index.vue';
import JCalendar from '@/pages-work/components/drag/calendar/index.vue';
import JCurrentTime from '@/pages-work/components/drag/currentTime/time.vue';
import JList from '@/pages-work/components/drag/list/index.vue';
import JRadioButton from '@/pages-work/components/drag/radiobutton/index.vue';
import JCommonTable from '@/pages-work/components/drag/table/index.vue';
import JQuickNav from '@/pages-work/components/drag/JQuickNav/index.vue';
//
export function registerGlobComp(app: App) {
app.component('statusTip', StatusTip)
app.component('JBar', JBar)
app.component('JMultipleBar', JMultipleBar)
app.component('JNegativeBar', JNegativeBar)
app.component('JLine', JLine)
app.component('JMultipleLine', JMultipleLine)
app.component('JPie', JPie)
app.component('JRing', JRing)
app.component('JFunnel', JFunnel)
app.component('JPyramidFunnel', JPyramidFunnel)
app.component('JRadar', JRadar)
app.component('JCircleRadar', JCircleRadar)
app.component('JGauge', JGauge)
app.component('JColorGauge', JColorGauge)
app.component('JScatter', JScatter)
app.component('JBubble', JBubble)
app.component('DoubleLineBar', DoubleLineBar)
app.component('JRose', JRose)
app.component('JHorizontalBar', JHorizontalBar)
app.component('JArea', JArea)
app.component('JBackgroundBar', JBackgroundBar)
app.component('JDynamicBar', JDynamicBar)
app.component('JMixLineBar', JMixLineBar)
app.component('JStackBar', JStackBar)
app.component('JStepLine', JStepLine)
app.component('JSmoothLine', JSmoothLine)
app.component('JProgress', JProgress)
app.component('JPictorial', JPictorial)
app.component('JPictorialBar', JPictorialBar)
app.component('JAreaMap', JAreaMap)
app.component('JBubbleMap', JBubbleMap)
app.component('JBarMap', JBarMap)
app.component('JHeatMap', JHeatMap)
app.component('JFlyLineMap', JFlyLineMap)
//echart
app.component('JCarousel', JCarousel)
app.component('JIframe', JIframe)
app.component('JDragEditor', JDragEditor)
app.component('JImg', JImg)
app.component('JNumber', JNumber)
app.component('JText', JText)
app.component('JCalendar', JCalendar)
app.component('JCurrentTime', JCurrentTime)
app.component('JList', JList)
app.component('JRadioButton', JRadioButton)
app.component('JCommonTable', JCommonTable)
app.component('JQuickNav', JQuickNav)
}

33
src/env.d.ts vendored Normal file
View File

@ -0,0 +1,33 @@
/// <reference types="vite/client" />
/// <reference types="vite-svg-loader" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
interface ImportMetaEnv {
/** 网站标题,应用名称 */
readonly VITE_APP_TITLE: string
/** 服务端口号 */
readonly VITE_SERVER_PORT: string
/** 后台接口地址 */
readonly VITE_SERVER_BASEURL: string
/** H5是否需要代理 */
readonly VITE_APP_PROXY: 'true' | 'false'
/** H5是否需要代理需要的话有个前缀 */
readonly VITE_APP_PROXY_PREFIX: string // /api
/** 上传图片地址 */
readonly VITE_UPLOAD_BASEURL: string
/** 是否清除console */
readonly VITE_DELETE_CONSOLE: string
/** 是否开启mock */
readonly VITE_USE_MOCK: 'true' | 'false'
// ...
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

44
src/hooks/useRequest.ts Normal file
View File

@ -0,0 +1,44 @@
import { UnwrapRef } from 'vue'
type IUseRequestOptions<T> = {
/** 是否立即执行 */
immediate?: boolean
/** 初始化数据 */
initialData?: T
}
/**
* useRequest是一个定制化的请求钩子用于处理异步请求和响应
* @param func 一个执行异步请求的函数返回一个包含响应数据的Promise
* @param options 包含请求选项的对象 {immediate, initialData}
* @param options.immediate 是否立即执行请求默认为false
* @param options.initialData 初始化数据默认为undefined
* @returns 返回一个对象{loading, error, data, run}包含请求的加载状态错误信息响应数据和手动触发请求的函数
*/
export default function useRequest<T>(
func: () => Promise<IResData<T>>,
options: IUseRequestOptions<T> = { immediate: false },
) {
const loading = ref(false)
const error = ref(false)
const data = ref<T>(options.initialData)
const run = async () => {
loading.value = true
return func()
.then((res) => {
data.value = res.data as UnwrapRef<T>
error.value = false
return data.value
})
.catch((err) => {
error.value = err
throw err
})
.finally(() => {
loading.value = false
})
}
options.immediate && run()
return { loading, error, data, run }
}

86
src/hooks/useUpload.ts Normal file
View File

@ -0,0 +1,86 @@
// TODO: VITE_UPLOAD_BASEURL
import { getEnvBaseUploadUrl } from '@/utils'
import { useUserStore } from '@/store/user'
const VITE_UPLOAD_BASEURL = `${getEnvBaseUploadUrl()}`
/**
* useUpload 是一个定制化的请求钩子用于处理上传图片
* @param formData 额外传递给后台的数据{name: '张三'}
* @returns 返回一个对象{loading, error, data, run}包含请求的加载状态错误信息响应数据和手动触发请求的函数
*/
export default function useUpload<T = string>(
formData: Record<string, any> = {},
{ url, sizeType = ['original', 'compressed'], sourceType = ['album', 'camera'] },
) {
const loading = ref(false)
const error = ref(false)
const data = ref<T>()
const run = () => {
// #ifdef MP-WEIXIN
// 2.21.0 wx.chooseImage 使 uni.chooseMedia
// 20231017使API
uni.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType,
sizeType, //
success: (res) => {
loading.value = true
const tempFilePath = res.tempFiles[0].tempFilePath
const fileName = res.type
formData.fileName = fileName;
uploadFile<T>({ url, tempFilePath, formData, data, error, loading, fileName })
},
fail: (err) => {
console.error('uni.chooseMedia err->', err)
error.value = true
},
})
// #endif
// #ifndef MP-WEIXIN
uni.chooseImage({
count: 1,
sourceType, //
sizeType, //
success: (res) => {
loading.value = true
const tempFilePath = res.tempFilePaths[0]
const fileName = res.tempFiles[0].name
formData.fileName = fileName;
uploadFile<T>({ url, tempFilePath, formData, data, error, loading })
},
fail: (err) => {
console.error('uni.chooseImage err->', err)
error.value = true
},
})
// #endif
}
return { loading, error, data, run }
}
function uploadFile<T>({ url, tempFilePath, formData, data, error, loading }) {
const userStore = useUserStore()
uni.uploadFile({
url: url ?? VITE_UPLOAD_BASEURL,
filePath: tempFilePath,
name: 'file',
formData,
header: {
'X-Access-Token': userStore.userInfo.token,
'X-Tenant-Id': userStore.userInfo.tenantId,
},
success: (uploadFileRes) => {
data.value = JSON.parse(uploadFileRes.data)
},
fail: (err) => {
console.error('uni.uploadFile err->', err)
error.value = true
},
complete: () => {
loading.value = false
},
})
}

View File

@ -0,0 +1,3 @@
export { routeInterceptor } from './route'
export { requestInterceptor } from './request'
export { prototypeInterceptor } from './prototype'

View File

@ -0,0 +1,13 @@
export const prototypeInterceptor = {
install() {
// array.at()
if (typeof Array.prototype.at !== 'function') {
// eslint-disable-next-line no-extend-native
Array.prototype.at = function (index: number) {
if (index < 0) return this[this.length + index]
if (index >= this.length) return undefined
return this[index]
}
}
},
}

View File

@ -0,0 +1,68 @@
/* eslint-disable no-param-reassign */
import qs from 'qs'
import { useUserStore } from '@/store'
import { platform } from '@/utils/platform'
import { getEnvBaseUrl } from '@/utils'
export type CustomRequestOptions = UniApp.RequestOptions & {
query?: Record<string, any>
/** 出错时是否隐藏错误提示 */
hideErrorToast?: boolean
} & IUniUploadFileOptions // uni.uploadFile
//
const baseUrl = getEnvBaseUrl()
//
const httpInterceptor = {
//
invoke(options: CustomRequestOptions) {
// query queryString
if (options.query) {
const queryStr = qs.stringify(options.query)
if (options.url.includes('?')) {
options.url += `&${queryStr}`
} else {
options.url += `?${queryStr}`
}
}
// http
if (!options.url.startsWith('http')) {
// #ifdef H5
// console.log(__VITE_APP_PROXY__)
if (JSON.parse(__VITE_APP_PROXY__)) {
//
} else {
options.url = baseUrl + options.url
}
// #endif
// H5
// #ifndef H5
options.url = baseUrl + options.url
// #endif
// TIPS:
}
// 1.
options.timeout = 10000 // 10s
// 2.
options.header = {
platform, // uniapp
...options.header,
}
// 3. token
const userStore = useUserStore()
const { token } = userStore.userInfo as unknown as IUserInfo
if (token) {
options.header.Authorization = `${token}`
}
},
}
export const requestInterceptor = {
install() {
// request
uni.addInterceptor('request', httpInterceptor)
// uploadFile
uni.addInterceptor('uploadFile', httpInterceptor)
},
}

53
src/interceptors/route.ts Normal file
View File

@ -0,0 +1,53 @@
/**
* 路由拦截通常也是登录拦截
* 可以设置路由白名单或者黑名单看业务需要选哪一个
* 我这里应为大部分都可以随便进入所以使用黑名单
*/
import { useUserStore } from '@/store'
import { needLoginPages as _needLoginPages, getNeedLoginPages } from '@/utils'
// TODO Check
const loginRoute = '/pages/login/index'
const isLogined = () => {
const userStore = useUserStore()
return userStore.isLogined
}
const isDev = import.meta.env.DEV
// -
const navigateToInterceptor = {
// url '/' '/pages/index/index' 'pages.json' path
invoke({ url }: { url: string }) {
// console.log(url) // /pages/route-interceptor/index?name=feige&age=30
const path = url.split('?')[0]
let needLoginPages: string[] = []
// BUG
if (isDev) {
needLoginPages = getNeedLoginPages()
} else {
needLoginPages = _needLoginPages
}
const isNeedLogin = needLoginPages.includes(path)
if (!isNeedLogin) {
return true
}
const hasLogin = isLogined()
if (hasLogin) {
return true
}
const redirectRoute = `${loginRoute}?redirect=${encodeURIComponent(url)}`
uni.navigateTo({ url: redirectRoute })
return false
},
}
export const routeInterceptor = {
install() {
uni.addInterceptor('navigateTo', navigateToInterceptor)
uni.addInterceptor('reLaunch', navigateToInterceptor)
uni.addInterceptor('redirectTo', navigateToInterceptor)
uni.addInterceptor('switchTab', navigateToInterceptor)
},
}

18
src/layouts/default.vue Normal file
View File

@ -0,0 +1,18 @@
<template>
<wd-config-provider :themeVars="themeVars">
<slot />
<!-- <wd-toast />
<wd-message-box />
<wd-notify /> -->
</wd-config-provider>
</template>
<script lang="ts" setup>
import type { ConfigProviderThemeVars } from 'wot-design-uni'
const themeVars: ConfigProviderThemeVars = {
// colorTheme: 'red',
// buttonPrimaryBgColor: '#07c160',
// buttonPrimaryColor: '#07c160',
}
</script>

17
src/layouts/demo.vue Normal file
View File

@ -0,0 +1,17 @@
<template>
<wd-config-provider :themeVars="themeVars">
<slot />
<wd-toast />
<wd-message-box />
</wd-config-provider>
</template>
<script lang="ts" setup>
import type { ConfigProviderThemeVars } from 'wot-design-uni'
const themeVars: ConfigProviderThemeVars = {
// colorTheme: 'red',
// buttonPrimaryBgColor: '#07c160',
// buttonPrimaryColor: '#07c160',
}
</script>

30
src/main.ts Normal file
View File

@ -0,0 +1,30 @@
import '@/style/index.scss'
import '@/style/custom/main.css'
import '@/style/custom/icon.css'
import '@/style/custom/animation.css'
import { VueQueryPlugin } from '@tanstack/vue-query'
import 'virtual:uno.css'
import { createSSRApp } from 'vue'
import App from './App.vue'
import { prototypeInterceptor, requestInterceptor, routeInterceptor } from './interceptors'
import { registerGlobComp } from '@/components/registerGlobComp';
import store from './store'
import router from './router'
export function createApp() {
const app = createSSRApp(App)
app.use(store)
app.use(router)
app.use(routeInterceptor)
app.use(requestInterceptor)
app.use(prototypeInterceptor)
app.use(VueQueryPlugin)
//#ifndef MP-WEIXIN
//
registerGlobComp(app);
// #endif
return {
app,
}
}

139
src/manifest.json Normal file
View File

@ -0,0 +1,139 @@
{
"name": "JeecgBoot-uniapp",
"appid": "__UNI__9F097F0",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
"modules": {
"Maps": {},
"Messaging": {},
"Contacts": {},
"Camera": {}
},
"distribute": {
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
],
"minSdkVersion": 30,
"targetSdkVersion": 30,
"abiFilters": [
"armeabi-v7a",
"arm64-v8a"
]
},
"ios": {},
"sdkConfigs": {
"maps": {
"amap": {
"name": "amap_15931993294Bqxlq8EgG",
"appkey_ios": "c913e46ffdf548ebc56ac1cf4d883e7e",
"appkey_android": "c913e46ffdf548ebc56ac1cf4d883e7e"
}
}
},
"icons": {
"android": {
"hdpi": "src/static/app/icons/72x72.png",
"xhdpi": "src/static/app/icons/96x96.png",
"xxhdpi": "src/static/app/icons/144x144.png",
"xxxhdpi": "src/static/app/icons/192x192.png"
},
"ios": {
"appstore": "src/static/app/icons/1024x1024.png",
"ipad": {
"app": "src/static/app/icons/76x76.png",
"app@2x": "src/static/app/icons/152x152.png",
"notification": "src/static/app/icons/20x20.png",
"notification@2x": "src/static/app/icons/40x40.png",
"proapp@2x": "src/static/app/icons/167x167.png",
"settings": "src/static/app/icons/29x29.png",
"settings@2x": "src/static/app/icons/58x58.png",
"spotlight": "src/static/app/icons/40x40.png",
"spotlight@2x": "src/static/app/icons/80x80.png"
},
"iphone": {
"app@2x": "src/static/app/icons/120x120.png",
"app@3x": "src/static/app/icons/180x180.png",
"notification@2x": "src/static/app/icons/40x40.png",
"notification@3x": "src/static/app/icons/60x60.png",
"settings@2x": "src/static/app/icons/58x58.png",
"settings@3x": "src/static/app/icons/87x87.png",
"spotlight@2x": "src/static/app/icons/80x80.png",
"spotlight@3x": "src/static/app/icons/120x120.png"
}
}
}
},
"compatible": {
"ignoreVersion": true
}
},
"quickapp": {},
"mp-weixin": {
"appid": "wx8e287639924edb51",
"setting": {
"urlCheck": false,
"minified": true
},
"usingComponents": true
},
"mp-alipay": {
"usingComponents": true,
"styleIsolation": "shared"
},
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao": {
"usingComponents": true
},
"uniStatistics": {
"enable": false
},
"vueVersion": "3",
"h5": {
"router": {
"base": "/"
},
"sdkConfigs": {
"maps": {
"amap": {
"key": "21f194a0d33197f874f7bbdd198419be",
"securityJsCode": "a46b425f31a4de445b2966d998fba851",
"serviceHost": ""
}
}
}
},
"app-harmony": {
"distribute": {
"bundleName": "uniapp.demo.test"
}
}
}

19
src/package.json Normal file
View File

@ -0,0 +1,19 @@
{
"id": "da-tree",
"name": "da-tree 树组件支持单选、多选、无限级、主题色Vue3版 ",
"displayName": "da-tree 树组件支持单选、多选、无限级、主题色Vue3版 ",
"version": "1.4.2",
"description": "一个基于 Vue3 的tree(树)组件支持主题换色可能是最适合你的tree(树)组件",
"keywords": [
"tree",
"树",
"树组件",
"da系列"
],
"dcloudext": {
"category": [
"前端组件",
"通用组件"
]
}
}

View File

@ -0,0 +1,20 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '',
},
}
</route>
<template>
<view class=""></view>
</template>
<script lang="ts" setup>
//
</script>
<style lang="scss" scoped>
//
</style>

View File

@ -0,0 +1,396 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '聊天',
navigationStyle: 'custom',
},
}
</route>
<template>
<PageLayout :navTitle="navTitle" backRouteName="message" routeMethod="pushTab">
<view class="wrap">
<!-- prettier-ignore -->
<z-paging ref="paging" v-model="dataList" :fixed="false" use-chat-record-mode use-virtual-list cell-height-mode="dynamic" safe-area-inset-bottom bottom-bg-color="#e5e5e5" @query="queryList" @keyboardHeightChange="keyboardHeightChange" @hidedKeyboard="hidedKeyboard">
<template #cell="{item,index}">
<view style="transform: scaleY(-1)">
<chat-item :item="item" :playMsgid="playMsgid" @playVoice="handlePlayVoice"></chat-item>
</view>
</template>
<template #bottom>
<chat-input-bar ref="inputBar" @send="doSend" @image="handleImage" />
</template>
</z-paging>
</view>
</PageLayout>
</template>
<script lang="ts" setup>
//
import { onLaunch, onShow, onHide, onLoad, onReady } from '@dcloudio/uni-app'
import { nextTick, onMounted, ref } from 'vue'
import { useUserStore } from '@/store/user'
import { http } from '@/utils/http'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { useRouter } from '@/plugin/uni-mini-router'
import { cache, getFileAccessHttpUrl, hasRoute, formatDate } from '@/common/uitls'
import { TENANT_LIST } from '@/common/constants'
import socket from '@/common/socket'
import { textReplaceEmoji, getEmojiImageUrl } from './emojis'
import chatInputBar from './components/chat-input-bar.vue'
import chatItem from './components/chat-item.vue'
import { getEnvBaseUrl } from '@/utils/index'
import { useParamsStore } from '@/store/page-params'
defineOptions({
name: 'chat',
options: {
// apply-shared.()
// shared.()
styleIsolation: 'apply-shared',
},
})
const api = {
chatlog_old: '/eoa/im/api/queryChatLogList',
chatlog: '/eoa/im/newApi/records',
sendMsg: '/eoa/im/newApi/sendMessage',
sendFile: '/eoa/im/newApi/sendFile',
creatFriendSession: '/eoa/im/newApi/creatFriendSession',
uploadUrl: `${getEnvBaseUrl()}/eoa/im/newApi/sendImage`,
}
const toast = useToast()
const userStore = useUserStore()
const paging = ref(null)
//
const chatObj = ref(null)
const navTitle = ref('')
// userid
const chatto = ref()
const myuid = ref(userStore.userInfo.userid)
const msgList = ref([])
// const pageNo = ref(1)
// const pageSize = ref(10)
const loadingShow = ref(false)
const hasRecord = ref(false)
const dataList = ref([])
const inputBar = ref(null)
const AUDIO = uni.createInnerAudioContext()
const playMsgid = ref('')
let stopWatch: any = null
const paramsStore = useParamsStore()
//
const init = () => {
const localData = paramsStore.getPageParams('chat')
const params = localData.data
if (!params) {
return
}
chatObj.value = { ...params }
navTitle.value = params.fromUserName
chatto.value = chatObj.value.msgTo
creatFriendSession(chatObj.value.msgTo)
onSocketOpen()
onSocketReceive()
getMsgList()
}
//
const creatFriendSession = (userId) => {
http.post(api.creatFriendSession, {
type: 'friend',
userId,
})
}
const onSocketOpen = () => {
console.log('启动webSocket')
socket.init('eoaNewChatSocket')
}
const onSocketReceive = () => {
var _this = this
socket.acceptMessage = function (res) {
console.log('页面收到的消息=====》', res)
if (res.event == 'event_talk_revoke') {
//
removeMsg(res)
} else {
if (res.type == 'friend') {
//
screenMsg(res)
unreadClear()
}
}
}
}
const removeMsg = (data) => {
let arr = msgList.value.filter((item) => item.id != data.id)
msgList.value = arr
}
const screenMsg = (msg) => {
//
if (msg.msgFrom == chatto.value && msg.msgTo == myuid.value) {
console.log('用户消息')
let time = formatDate(msg.sendTime, 'yyyy-MM-dd hh:mm:ss')
let id = time.replace(/\:/g, '').replace(/\-/g, '').replace(' ', '')
let content = msg.msgData
if (msg.msgType == 'text') {
content = replaceEmoji(content)
}
if (msg.msgType == 'voice') {
content = JSON.parse(content)
}
msgList.value.push({
fromUserName: msg.fromUserName,
msgTo: msg.msgTo,
msgFrom: msg.msgFrom,
msgData: content,
fromAvatar: msg.fromAvatar,
sendTime: time,
msgType: msg.msgType,
sendTimeId: id,
fileName: msg.fileName,
id: msg.id,
})
//
if (msg.msgFrom != myuid.value) {
console.log('振动')
uni.vibrateLong()
}
// this.$nextTick(function () {
// //
// this.scrollToView = 'msg' + id
// })
}
}
//
const replaceEmoji = (str) => {
let temp = textReplaceEmoji(str)
return '<div style="display:inline-block">' + temp + '</div>'
}
const queryList = (pageNo, pageSize) => {
//
let params = {
type: 'friend',
pageNo: pageNo,
pageSize: pageSize,
msgTo: chatto.value,
id: myuid.value,
sort: 'DESC',
}
console.log('params', params)
http
.get(api.chatlog, params)
.then((res: any) => {
if (res.success && res.result?.records) {
const records = analysis(res.result.records)
paging.value.complete(records)
} else {
paging.value.complete(false)
}
})
.catch((res) => {
paging.value.complete(false)
})
}
const analysis = (data) => {
let arr = data
if (arr.length > 0) {
let list = arr.map((item) => {
let id = item.sendTime.replace(/\:/g, '').replace(/\-/g, '').replace(' ', '')
item.sendTimeId = id
let content = item.msgData
if (item.msgType == 'text') {
content = replaceEmoji(content)
}
if (item.msgType == 'voice') {
content = JSON.parse(content)
}
item.msgData = content
return item
})
for (let i = 0; i < list.length; i++) {
if (list[i].msgType == 'revoke') {
continue
}
if (list[i].referenceMsgId) {
list[i] = handleReplyMsg(list[i], list)
}
}
}
return data
}
//
const getMsgList = () => {}
const handleReplyMsg = (item, list) => {
let tempId = item.referenceMsgId
item.reply = true
let replyContent = ''
for (let i = 0; i < list.length; i++) {
if (list[i].id == tempId) {
replyContent = '"' + list[i].fromUserName + ':' + list[i].msgData + '"'
break
}
}
item.replyContent = replyContent
return item
}
const unreadClear = () => {
http
.post('/eoa/im/newApi/unreadClear', {
type: chatObj.value.type,
msgTo: chatObj.value.msgTo,
msgFrom: chatObj.value.msgFrom,
})
.then((res: any) => {
if (res.success) {
// _this.eventChannel.emit('toPrePageData', { data: 'data from chat page' })
}
})
}
//
const handlePlayVoice = (item) => {
if (item.id == playMsgid.value) {
AUDIO.stop()
playMsgid.value = ''
} else {
playMsgid.value = item.id
AUDIO.src = item.msgData.url
nextTick(function () {
AUDIO.play()
})
}
}
//
AUDIO.onEnded((res) => {
playMsgid.value = ''
})
// uni.onKeyboardHeightChangez-paging
const keyboardHeightChange = (res) => {
inputBar.value.updateKeyboardHeightChange(res)
}
// chatInputBar
const hidedKeyboard = () => {
inputBar.value.hidedKeyboard()
}
const doSend = (textMsg) => {
// paging.value.addChatRecordData([
// {
// time: '',
// icon: '/static/daxiong.jpg',
// name: '',
// content: msg,
// isMe: true,
// },
// ])
let content = replaceEmoji(textMsg)
//let msg = {'text':content}
//content = (content||'').replace(/&(?!#?[a-zA-Z0-9]+;)/g, '&amp;')
console.log('content', content)
let msg = textMsg
let time = formatDate(new Date().getTime(), 'yyyy-MM-dd hh:mm:ss')
let id = time.replace(/\:/g, '').replace(/\-/g, '').replace(' ', '')
//
sendMsg(msg, 'text')
paging.value.addChatRecordData(
analysis([
{
fromUserName: userStore.userInfo.realname,
msgTo: chatto.value,
msgFrom: myuid.value,
msgData: content,
fromAvatar: userStore.userInfo.avatar,
sendTime: time,
sendTimeId: id,
msgType: 'text',
},
]),
)
}
const sendMsg = (content, type) => {
//
var nowDate = new Date()
//
var obj = {
mine: {
avatar: userStore.userInfo.avatar,
content: content,
id: myuid.value,
mine: true,
username: userStore.userInfo.username,
},
to: {
avatar: chatObj.value.avatar,
id: chatObj.value.msgTo,
type: 'friend',
username: chatObj.value.username,
},
}
let sendData = {
type: 'chatMessage',
data: obj,
}
console.log('sendData======>', sendData)
let params = {
type: 'friend',
msgTo: chatObj.value.msgTo,
text: content,
msgType: 'text',
}
http.post(api.sendMsg, params).then((res: any) => {
console.log('消息发送结果:', res)
if (!res.success) {
toast.error(res.message)
}
})
}
const handleImage = (type) => {
let time = formatDate(new Date().getTime(), 'yyyy-MM-dd hh:mm:ss')
let id = time.replace(/\:/g, '').replace(/\-/g, '').replace(' ', '')
let formData = {
type: 'friend',
msgTo: chatto.value,
fileId: id,
msgType: 'images',
fileName: '',
}
const { loading, data, error, run } = useUpload(
{ ...formData, name: 'image' },
{ url: api.uploadUrl, sourceType: [type] },
)
if (stopWatch) stopWatch()
run()
stopWatch = watch(
() => [loading.value, error.value, data.value],
([loading, err, data], oldValue) => {
if (loading == false) {
if (err) {
toast.warning('修改失败')
uni.hideLoading()
} else {
if (data) {
console.log('data::', data)
}
}
}
},
)
}
init()
</script>
<style lang="scss" scoped>
//
.wrap {
height: 100%;
}
</style>

View File

@ -0,0 +1,229 @@
<!-- z-paging聊天输入框 -->
<template>
<view class="chat-input-bar-container">
<view class="chat-input-bar">
<view class="add-container" @click="tooglePanl('more')">
<view class="icon add"></view>
</view>
<view class="chat-input-container">
<!-- :adjust-position="false"必须设置防止键盘弹窗自动上顶交由z-paging内部处理 -->
<input
:focus="focus"
class="chat-input"
v-model="msg"
:adjust-position="false"
confirm-type="send"
type="text"
placeholder="请输入内容"
@confirm="sendClick"
/>
</view>
<!-- 表情图标如果不需要切换表情面板则不用写 -->
<view class="emoji-container">
<image class="emoji-img" :src="getEmoji" @click="tooglePanl('emoji')"></image>
</view>
<view class="chat-input-send" @click="sendClick">
<text class="chat-input-send-text">发送</text>
</view>
</view>
<!-- 表情面板这里使用height控制隐藏显示是为了有高度变化的动画效果如果不需要切换表情面板则不用写 -->
<view
class="emoji-panel-container"
:style="[{ height: ['emoji', 'more'].includes(chatBarType) ? '320rpx' : '0px' }]"
>
<scroll-view scroll-y style="height: 100%; flex: 1">
<template v-if="['emoji'].includes(chatBarType)">
<!-- 表情 -->
<view class="emoji-panel">
<swiper class="emoji-swiper zdybq" :indicator-dots="true" :duration="150">
<swiper-item class="swiperItem" v-for="(page, pid) in emojiArray" :key="pid">
<view class="item" v-for="(em, eid) in page" :key="eid" @tap="emojiClick(em)">
<image mode="scaleToFill" :src="em.url" style="width: 28px; height: 28px"></image>
</view>
</swiper-item>
</swiper>
</view>
</template>
<template v-if="['more'].includes(chatBarType)">
<!-- 相册照相 -->
<view class="more-panel">
<view class="box" @tap="getImage('album')"><view class="icon tupian2"></view></view>
<view class="box" @tap="getImage('camera')"><view class="icon paizhao"></view></view>
</view>
</template>
</scroll-view>
</view>
</view>
</template>
<script setup lang="ts">
import { textReplaceEmoji, getEmojiImageUrl } from '../emojis'
import { computed } from 'vue'
defineOptions({
name: 'chat-input-bar',
options: {
// apply-shared.()
// shared.()
styleIsolation: 'apply-shared',
},
})
const emit = defineEmits(['emojiTypeChange', 'send', 'image'])
let emojiArray = getEmojiImageUrl()
const msg = ref('')
// input focus
const focus = ref(false)
// emojimore
const chatBarType = ref('')
const getEmoji = computed(() => {
let img
if (['', 'more'].includes(chatBarType.value)) {
img = 'emoji'
} else if (['emoji'].includes(chatBarType.value)) {
img = 'keyboard'
}
return `/static/chat/${img}.png`
})
const updateKeyboardHeightChange = (res) => {
if (res.height > 0) {
chatBarType.value = ''
}
}
const hidedKeyboard = () => {
if (['emoji', 'more'].includes(chatBarType.value)) {
chatBarType.value = ''
}
}
// /
const tooglePanl = (val) => {
if (chatBarType.value == val) {
//
focus.value = true
chatBarType.value = ''
} else {
//
focus.value = false
//
uni.hideKeyboard()
chatBarType.value = val
}
}
//
const emojiClick = (em) => {
msg.value += em.alt
}
//
const sendClick = () => {
if (!msg.value.length) return
emit('send', msg.value)
msg.value = ''
}
//
const getImage = (type) => {
emit('image', type)
}
defineExpose({
updateKeyboardHeightChange,
hidedKeyboard,
})
</script>
<style lang="scss" scoped>
.chat-input-bar {
display: flex;
flex-direction: row;
align-items: center;
border-top: solid 1px #f5f5f5;
background-color: #f8f8f8;
padding: 10rpx 20rpx;
}
.add-container {
margin-right: 8px;
.icon {
font-size: 26px;
}
}
.chat-input-container {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
padding: 15rpx;
background-color: white;
border-radius: 10rpx;
}
.chat-input {
flex: 1;
font-size: 28rpx;
}
.emoji-container {
width: 54rpx;
height: 54rpx;
margin: 10rpx 0rpx 10rpx 20rpx;
}
.emoji-img {
width: 54rpx;
height: 54rpx;
}
.chat-input-send {
background-color: #007aff;
margin: 10rpx 10rpx 10rpx 20rpx;
border-radius: 10rpx;
width: 110rpx;
height: 60rpx;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
}
.chat-input-send-text {
color: white;
font-size: 26rpx;
}
.emoji-panel-container {
border-top: 1px solid #e8e8e8;
background-color: #f8f8f8;
overflow: hidden;
transition-property: height;
transition-duration: 0.15s;
}
.emoji-panel {
height: 100%;
padding: 0 8vw;
.swiperItem {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
.item {
width: 12vw;
height: 12vw;
display: flex;
justify-content: center;
align-items: center;
}
}
}
.more-panel {
display: flex;
padding-top: 3vw;
.box {
width: 18vw;
height: 18vw;
border-radius: 10px;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
margin: 0 3vw 2vw 3vw;
.icon {
font-size: 30px;
}
}
}
</style>

View File

@ -0,0 +1,174 @@
<!-- z-paging聊天item -->
<template>
<view class="chat-item">
<text class="chat-time" v-if="item.sendTime && item.sendTime.length">
{{ item.sendTime }}
</text>
<view :class="{ 'chat-container': true, 'chat-location-me': isMe(item) }">
<view class="chat-icon-container">
<image class="chat-icon" :src="item.fromAvatar" mode="aspectFill" />
</view>
<view class="chat-content-container">
<text :class="{ 'chat-user-name': true, 'chat-location-me': isMe(item) }">
{{ item.fromUserName }}
</text>
<view
:class="{
'chat-text-container-super': true,
'flex-end': isMe(item),
'flex-start': !isMe(item),
}"
>
<!---文字-->
<template v-if="['text'].includes(item.msgType)">
<view :class="{ 'chat-text-container': true, 'chat-text-container-me': isMe(item) }">
<text :class="{ 'chat-text': true, 'chat-text-me': isMe(item) }">
<rich-text :nodes="item.msgData"></rich-text>
</text>
</view>
</template>
<!--图片-->
<template v-else-if="['image'].includes(item.msgType)">
<wd-img
width="200"
height="200"
:enable-preview="true"
:radius="10"
:src="getFileAccessHttpUrl(item.msgData)"
></wd-img>
</template>
<!--语音-->
<template v-else-if="['voice'].includes(item.msgType)">
<view
:class="{
'chat-voice-container': true,
'chat-voice-container-me': isMe(item),
play: playMsgid == item.id,
}"
@click="playVoice(item)"
>
<view class="length mr-2">{{ item.msgData.length }}</view>
<view class="icon my-voice"></view>
</view>
</template>
<!--文件-->
<template v-else-if="['file'].includes(item.msgType)"></template>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store/user'
import { cache, getFileAccessHttpUrl } from '@/common/uitls'
defineOptions({ name: 'chat-item' })
const userStore = useUserStore()
const emit = defineEmits(['playVoice'])
const props = defineProps({
playMsgid: {
type: String,
default: '',
},
item: {
type: Object,
default: function () {
return {
sendTime: '',
fromAvatar: '',
fromUserName: '',
msgData: '',
}
},
},
})
const isMe = (item) => {
return item.msgFrom == userStore.userInfo.userid
}
const playVoice = (item) => {
emit('playVoice', item)
}
</script>
<style lang="scss" scoped>
.chat-item {
display: flex;
flex-direction: column;
padding: 20upx;
}
.chat-time {
padding: 4upx 0upx;
text-align: center;
font-size: 22upx;
color: #aaaaaa;
}
.chat-container {
display: flex;
flex-direction: row;
}
.chat-location-me {
flex-direction: row-reverse;
text-align: right;
}
.chat-icon-container {
margin-top: 12upx;
}
.chat-icon {
width: 80upx;
height: 80upx;
border-radius: 8px;
background-color: #eeeeee;
}
.chat-content-container {
margin: 0upx 15upx;
}
.chat-user-name {
font-size: 26upx;
color: #888888;
}
.chat-text-container,
.chat-voice-container {
text-align: left;
background-color: #fff;
border-radius: 8upx;
padding: 7px 10px;
margin-top: 10upx;
/* #ifndef APP-NVUE */
max-width: 500upx;
/* #endif */
}
.chat-text-container-me,
.chat-voice-container {
background-color: #55aaff;
}
.chat-voice-container {
display: flex;
align-items: center;
color: #fff;
}
.chat-text-container-super {
display: flex;
flex-direction: row;
&.flex-end {
justify-content: flex-end;
}
&.flex-start {
justify-content: flex-start;
}
}
.chat-text {
font-size: 28upx;
/* #ifndef APP-NVUE */
word-break: break-all;
/* #endif */
/* #ifdef APP-NVUE */
max-width: 500upx;
/* #endif */
}
.chat-text-me {
color: white;
}
</style>

View File

@ -0,0 +1,439 @@
/**
* 动态表情
*/
export const emojis = {
'[微笑]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/1-1.png'>",
'[撇嘴]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/1-2.png'>",
'[色]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/1-3.png'>",
'[发呆]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/1-4.png'>",
'[得意]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/1-5.png'>",
'[流泪]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/1-6.png'>",
'[害羞]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/1-7.png'>",
'[闭嘴]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/1-8.png'>",
'[睡]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/1-9.png'>",
'[大哭]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/1-10.png'>",
'[尴尬]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/1-11.png'>",
'[发怒]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/1-12.png'>",
'[调皮]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/2-1.png'>",
'[呲牙]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/2-2.png'>",
'[惊讶]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/2-3.png'>",
'[难过]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/2-4.png'>",
'[酷]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/2-5.png'>",
'[冷汗]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/2-6.png'>",
'[抓狂]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/2-7.png'>",
'[吐]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/2-8.png'>",
'[偷笑]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/2-9.png'>",
'[可爱]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/2-10.png'>",
'[白眼]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/2-11.png'>",
'[傲慢]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/2-12.png'>",
'[饥饿]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/3-1.png'>",
'[困]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/3-2.png'>",
'[惊恐]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/3-3.png'>",
'[流汗]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/3-4.png'>",
'[憨笑]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/3-5.png'>",
'[悠闲]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/3-6.png'>",
'[奋斗]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/3-7.png'>",
'[咒骂]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/3-8.png'>",
'[疑问]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/3-9.png'>",
'[嘘]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/3-10.png'>",
'[晕]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/3-11.png'>",
'[折磨]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/3-12.png'>",
'[衰]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/4-1.png'>",
'[骷髅]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/4-2.png'>",
'[敲打]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/4-3.png'>",
'[再见]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/4-4.png'>",
'[擦汗]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/4-5.png'>",
'[抠鼻]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/4-6.png'>",
'[鼓掌]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/4-7.png'>",
'[糗大了]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/4-8.png'>",
'[坏笑]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/4-9.png'>",
'[左哼哼]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/4-10.png'>",
'[右哼哼]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/4-11.png'>",
'[哈欠]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/4-12.png'>",
'[鄙视]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/5-1.png'>",
'[委屈]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/5-2.png'>",
'[快哭了]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/5-3.png'>",
'[阴险]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/5-4.png'>",
'[右亲亲]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/5-5.png'>",
'[左亲亲]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/5-6.png'>",
'[吓]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/5-7.png'>",
'[可怜]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/5-8.png'>",
'[眨眼睛]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/5-9.png'>",
'[笑哭]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/5-10.png'>",
'[doge]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/5-11.png'>",
'[泪奔]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/5-12.png'>",
'[无奈]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/6-1.png'>",
'[托腮]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/6-2.png'>",
'[卖萌]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/6-3.png'>",
'[斜眼笑]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/6-4.png'>",
'[喷血]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/6-5.png'>",
'[惊喜]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/6-6.png'>",
'[骚扰]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/6-7.png'>",
'[小纠结]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/6-8.png'>",
'[我最美]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/6-9.png'>",
'[加油必胜]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/6-10.png'>",
'[加油抱抱]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/6-11.png'>",
'[口罩护体]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/6-12.png'>",
'[搬砖中]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/7-1.png'>",
'[忙到飞起]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/7-2.png'>",
'[脑阔疼]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/7-3.png'>",
'[沧桑]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/7-4.png'>",
'[捂脸]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/7-5.png'>",
'[辣眼睛]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/7-6.png'>",
'[哦呦]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/7-7.png'>",
'[头秃]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/7-8.png'>",
'[问号脸]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/7-9.png'>",
'[暗中观察]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/7-10.png'>",
'[emm]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/7-11.png'>",
'[吃瓜]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/7-12.png'>",
'[呵呵哒]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/8-1.png'>",
'[汪汪]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/8-2.png'>",
'[牛转钱坤]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/8-3.png'>",
'[牛气冲天]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/8-4.png'>",
'[无眼笑]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/8-5.png'>",
'[敬礼]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/8-6.png'>",
'[狂笑]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/8-7.png'>",
'[面无表情]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/8-8.png'>",
'[摸鱼]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/8-9.png'>",
'[摸锦鲤]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/8-10.png'>",
'[魔鬼笑]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/8-11.png'>",
'[哦]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/8-12.png'>",
'[请]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/9-1.png'>",
'[睁眼]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/9-2.png'>",
'[期待]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/9-3.png'>",
'[拜谢]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/9-4.png'>",
'[元宝]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/9-5.png'>",
'[牛啊]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/9-6.png'>",
'[胖三斤]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/9-7.png'>",
'[好闪]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/9-8.png'>",
'[打call]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/9-9.png'>",
'[变形]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/9-10.png'>",
'[仔细分析]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/9-11.png'>",
'[加油]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/9-12.png'>",
'[菜汪]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/10-1.png'>",
'[崇拜]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/10-2.png'>",
'[比心]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/10-3.png'>",
'[庆祝]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/10-4.png'>",
'[吃糖]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/10-5.png'>",
'[花朵脸]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/10-6.png'>",
'[我想开了]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/10-7.png'>",
'[舔屏]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/10-8.png'>",
'[热化了]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/10-9.png'>",
'[我酸了]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/10-10.png'>",
'[拿到红包]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/10-11.png'>",
'[豹富]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/10-12.png'>",
'[握手]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/11-1.png'>",
'[胜利]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/11-2.png'>",
'[抱拳]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/11-3.png'>",
'[勾引]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/11-4.png'>",
'[拳头]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/11-5.png'>",
'[差劲]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/11-6.png'>",
'[爱你]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/11-7.png'>",
'[NO]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/11-8.png'>",
'[OK]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/11-9.png'>",
'[拜托]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/11-10.gif'>",
'[惬意]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/11-11.gif'>",
'[孤寂]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/11-12.gif'>",
'[菜刀]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/12-1.png'>",
'[西瓜]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/12-2.png'>",
'[啤酒]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/12-3.png'>",
'[篮球]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/12-4.png'>",
'[茶]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/12-5.png'>",
'[咖啡]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/12-6.png'>",
'[饭]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/12-7.png'>",
'[玫瑰]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/12-8.png'>",
'[凋谢]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/12-9.png'>",
'[爱心]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/12-10.png'>",
'[心碎]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/12-11.png'>",
'[示爱]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/12-12.png'>",
'[炸弹]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/13-1.png'>",
'[刀]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/13-2.png'>",
'[足球]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/13-3.png'>",
'[瓢虫]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/13-4.png'>",
'[便便]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/13-5.png'>",
'[月亮]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/13-6.png'>",
'[太阳]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/13-7.png'>",
'[礼物]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/13-8.png'>",
'[抱抱]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/13-9.png'>",
'[猪头]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/13-10.png'>",
'[乒乓球]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/13-11.png'>",
'[蛋糕]':
"<img src='https://jeecgos.oss-cn-beijing.aliyuncs.com/files/appimg/chat_emoticon/13-12.png'>",
}
/**
* 符号表情
*/
const symbol = [
'😠',
'😩',
'😲',
'😞',
'😵',
'😰',
'😒',
'😍',
'😤',
'😜',
'😝',
'😋',
'😘',
'😚',
'😷',
'😳',
'😃',
'😅',
'😆',
'😁',
'😂',
'😊',
'☺',
'😄',
'😢',
'😭',
'😨',
'😣',
'😡',
'😌',
'😖',
'😔',
'😱',
'😪',
'😏',
'😓',
'😥',
'😫',
'😉',
'✊',
'✋',
'✌',
'👊',
'👍',
'☝',
'👆',
'👇',
'👈',
'👉',
'👋',
'👏',
'👌',
'👎',
]
const emojisKeys = Object.keys(emojis)
export const emojiList = {
symbol,
emojis,
}
const regEmoji = emojisKeys
.map((value) => '|\\' + value)
.join('')
.replace('|', '')
/**
* 替换表情文字
*
* @param {String} content 需要替换的字符串
*/
export function textReplaceEmoji(content) {
return content.replace(new RegExp(`(${regEmoji})`, 'gi'), ($0, $1) => {
return emojis[$1]
})
}
/**
* 将表情存到数组中 用于选择
* 21个表情是一个数组
*/
export function getEmojiImageUrl() {
let arr = []
let keys = Object.keys(emojis)
let index = 0
let reg = /^.*src=\'(.*)\'>$/
while (index < keys.length) {
let sub = []
for (let i = 0; i < 21; i++) {
let temp = index + i
if (!keys[temp]) {
continue
}
let url = emojis[keys[temp]].match(reg)[1]
sub.push({
url,
alt: keys[temp],
})
}
index += 21
arr.push(sub)
}
return arr
}

View File

@ -0,0 +1,439 @@
const pinyin = {
'a': '\u554a\u963f\u9515',
'ai': '\u57c3\u6328\u54ce\u5509\u54c0\u7691\u764c\u853c\u77ee\u827e\u788d\u7231\u9698\u8bf6\u6371\u55f3\u55cc\u5ad2\u7477\u66a7\u7839\u953f\u972d',
'an': '\u978d\u6c28\u5b89\u4ffa\u6309\u6697\u5cb8\u80fa\u6848\u8c19\u57ef\u63de\u72b4\u5eb5\u6849\u94f5\u9e4c\u9878\u9eef',
'ang': '\u80ae\u6602\u76ce',
'ao': '\u51f9\u6556\u71ac\u7ff1\u8884\u50b2\u5965\u61ca\u6fb3\u5773\u62d7\u55f7\u5662\u5c99\u5ed2\u9068\u5aaa\u9a9c\u8071\u87af\u93ca\u9ccc\u93d6',
'ba': '\u82ad\u634c\u6252\u53ed\u5427\u7b06\u516b\u75a4\u5df4\u62d4\u8dcb\u9776\u628a\u8019\u575d\u9738\u7f62\u7238\u8307\u83dd\u8406\u636d\u5c9c\u705e\u6777\u94af\u7c91\u9c85\u9b43',
'bai': '\u767d\u67cf\u767e\u6446\u4f70\u8d25\u62dc\u7a17\u859c\u63b0\u97b4',
'ban': '\u6591\u73ed\u642c\u6273\u822c\u9881\u677f\u7248\u626e\u62cc\u4f34\u74e3\u534a\u529e\u7eca\u962a\u5742\u8c73\u94a3\u7622\u764d\u8228',
'bang': '\u90a6\u5e2e\u6886\u699c\u8180\u7ed1\u68d2\u78c5\u868c\u9551\u508d\u8c24\u84a1\u8783',
'bao': '\u82de\u80de\u5305\u8912\u96f9\u4fdd\u5821\u9971\u5b9d\u62b1\u62a5\u66b4\u8c79\u9c8d\u7206\u52f9\u8446\u5b80\u5b62\u7172\u9e28\u8913\u8db5\u9f85',
'bo': '\u5265\u8584\u73bb\u83e0\u64ad\u62e8\u94b5\u6ce2\u535a\u52c3\u640f\u94c2\u7b94\u4f2f\u5e1b\u8236\u8116\u818a\u6e24\u6cca\u9a73\u4eb3\u8543\u5575\u997d\u6a97\u64d8\u7934\u94b9\u9e41\u7c38\u8ddb',
'bei': '\u676f\u7891\u60b2\u5351\u5317\u8f88\u80cc\u8d1d\u94a1\u500d\u72c8\u5907\u60eb\u7119\u88ab\u5b5b\u9642\u90b6\u57e4\u84d3\u5457\u602b\u6096\u789a\u9e4e\u8919\u943e',
'ben': '\u5954\u82ef\u672c\u7b28\u755a\u574c\u951b',
'beng': '\u5d29\u7ef7\u752d\u6cf5\u8e66\u8ff8\u552a\u5623\u750f',
'bi': '\u903c\u9f3b\u6bd4\u9119\u7b14\u5f7c\u78a7\u84d6\u853d\u6bd5\u6bd9\u6bd6\u5e01\u5e87\u75f9\u95ed\u655d\u5f0a\u5fc5\u8f9f\u58c1\u81c2\u907f\u965b\u5315\u4ef3\u4ffe\u8298\u835c\u8378\u5421\u54d4\u72f4\u5eb3\u610e\u6ed7\u6fde\u5f3c\u59a3\u5a62\u5b16\u74a7\u8d32\u7540\u94cb\u79d5\u88e8\u7b5a\u7b85\u7be6\u822d\u895e\u8df8\u9ac0',
'bian': '\u97ad\u8fb9\u7f16\u8d2c\u6241\u4fbf\u53d8\u535e\u8fa8\u8fa9\u8fab\u904d\u533e\u5f01\u82c4\u5fed\u6c74\u7f0f\u7178\u782d\u78a5\u7a39\u7a86\u8759\u7b3e\u9cca',
'biao': '\u6807\u5f6a\u8198\u8868\u5a4a\u9aa0\u98d1\u98d9\u98da\u706c\u9556\u9573\u762d\u88f1\u9cd4',
'bie': '\u9cd6\u618b\u522b\u762a\u8e69\u9cd8',
'bin': '\u5f6c\u658c\u6fd2\u6ee8\u5bbe\u6448\u50a7\u6d5c\u7f24\u73a2\u6ba1\u8191\u9554\u9acc\u9b13',
'bing': '\u5175\u51b0\u67c4\u4e19\u79c9\u997c\u70b3\u75c5\u5e76\u7980\u90b4\u6452\u7ee0\u678b\u69df\u71f9',
'bu': '\u6355\u535c\u54fa\u8865\u57e0\u4e0d\u5e03\u6b65\u7c3f\u90e8\u6016\u62ca\u535f\u900b\u74ff\u6661\u949a\u91ad',
'ca': '\u64e6\u5693\u7924',
'cai': '\u731c\u88c1\u6750\u624d\u8d22\u776c\u8e29\u91c7\u5f69\u83dc\u8521',
'can': '\u9910\u53c2\u8695\u6b8b\u60ed\u60e8\u707f\u9a96\u74a8\u7cb2\u9eea',
'cang': '\u82cd\u8231\u4ed3\u6ca7\u85cf\u4f27',
'cao': '\u64cd\u7cd9\u69fd\u66f9\u8349\u8279\u5608\u6f15\u87ac\u825a',
'ce': '\u5395\u7b56\u4fa7\u518c\u6d4b\u5202\u5e3b\u607b',
'ceng': '\u5c42\u8e6d\u564c',
'cha': '\u63d2\u53c9\u832c\u8336\u67e5\u78b4\u643d\u5bdf\u5c94\u5dee\u8be7\u7339\u9987\u6c4a\u59f9\u6748\u6942\u69ce\u6aab\u9497\u9538\u9572\u8869',
'chai': '\u62c6\u67f4\u8c7a\u4faa\u8308\u7625\u867f\u9f87',
'chan': '\u6400\u63ba\u8749\u998b\u8c17\u7f20\u94f2\u4ea7\u9610\u98a4\u5181\u8c04\u8c36\u8487\u5edb\u5fcf\u6f7a\u6fb6\u5b71\u7fbc\u5a75\u5b17\u9aa3\u89c7\u7985\u9561\u88e3\u87fe\u8e94',
'chang': '\u660c\u7316\u573a\u5c1d\u5e38\u957f\u507f\u80a0\u5382\u655e\u7545\u5531\u5021\u4f25\u9b2f\u82cc\u83d6\u5f9c\u6005\u60dd\u960a\u5a3c\u5ae6\u6636\u6c05\u9cb3',
'chao': '\u8d85\u6284\u949e\u671d\u5632\u6f6e\u5de2\u5435\u7092\u600a\u7ec9\u6641\u8016',
'che': '\u8f66\u626f\u64a4\u63a3\u5f7b\u6f88\u577c\u5c6e\u7817',
'chen': '\u90f4\u81e3\u8fb0\u5c18\u6668\u5ff1\u6c89\u9648\u8d81\u886c\u79f0\u8c0c\u62bb\u55d4\u5bb8\u741b\u6987\u809c\u80c2\u789c\u9f80',
'cheng': '\u6491\u57ce\u6a59\u6210\u5448\u4e58\u7a0b\u60e9\u6f84\u8bda\u627f\u901e\u9a8b\u79e4\u57d5\u5d4a\u5fb5\u6d48\u67a8\u67fd\u6a18\u665f\u584d\u77a0\u94d6\u88ce\u86cf\u9172',
'chi': '\u5403\u75f4\u6301\u5319\u6c60\u8fdf\u5f1b\u9a70\u803b\u9f7f\u4f88\u5c3a\u8d64\u7fc5\u65a5\u70bd\u50ba\u5880\u82aa\u830c\u640b\u53f1\u54e7\u557b\u55e4\u5f73\u996c\u6cb2\u5ab8\u6555\u80dd\u7719\u7735\u9e31\u761b\u892b\u86a9\u87ad\u7b1e\u7bea\u8c49\u8e05\u8e1f\u9b51',
'chong': '\u5145\u51b2\u866b\u5d07\u5ba0\u833a\u5fe1\u61a7\u94f3\u825f',
'chou': '\u62bd\u916c\u7574\u8e0c\u7a20\u6101\u7b79\u4ec7\u7ef8\u7785\u4e11\u4fe6\u5733\u5e31\u60c6\u6eb4\u59af\u7633\u96e0\u9c8b',
'chu': '\u81ed\u521d\u51fa\u6a71\u53a8\u8e87\u9504\u96cf\u6ec1\u9664\u695a\u7840\u50a8\u77d7\u6410\u89e6\u5904\u4e8d\u520d\u61b7\u7ecc\u6775\u696e\u6a17\u870d\u8e70\u9edc',
'chuan': '\u63e3\u5ddd\u7a7f\u693d\u4f20\u8239\u5598\u4e32\u63be\u821b\u60f4\u9044\u5ddb\u6c1a\u948f\u9569\u8221',
'chuang': '\u75ae\u7a97\u5e62\u5e8a\u95ef\u521b\u6006',
'chui': '\u5439\u708a\u6376\u9524\u5782\u9672\u68f0\u69cc',
'chun': '\u6625\u693f\u9187\u5507\u6df3\u7eaf\u8822\u4fc3\u83bc\u6c8c\u80ab\u6710\u9e51\u877d',
'chuo': '\u6233\u7ef0\u851f\u8fb6\u8f8d\u955e\u8e14\u9f8a',
'ci': '\u75b5\u8328\u78c1\u96cc\u8f9e\u6148\u74f7\u8bcd\u6b64\u523a\u8d50\u6b21\u8360\u5472\u5d6f\u9e5a\u8785\u7ccd\u8d91',
'cong': '\u806a\u8471\u56f1\u5306\u4ece\u4e1b\u506c\u82c1\u6dd9\u9aa2\u742e\u7481\u679e',
'cu': '\u51d1\u7c97\u918b\u7c07\u731d\u6b82\u8e59',
'cuan': '\u8e7f\u7be1\u7a9c\u6c46\u64ba\u6615\u7228',
'cui': '\u6467\u5d14\u50ac\u8106\u7601\u7cb9\u6dec\u7fe0\u8403\u60b4\u7480\u69b1\u96b9',
'cun': '\u6751\u5b58\u5bf8\u78cb\u5fd6\u76b4',
'cuo': '\u64ae\u6413\u63aa\u632b\u9519\u539d\u811e\u9509\u77ec\u75e4\u9e7e\u8e49\u8e9c',
'da': '\u642d\u8fbe\u7b54\u7629\u6253\u5927\u8037\u54d2\u55d2\u601b\u59b2\u75b8\u8921\u7b2a\u977c\u9791',
'dai': '\u5446\u6b79\u50a3\u6234\u5e26\u6b86\u4ee3\u8d37\u888b\u5f85\u902e\u6020\u57ed\u7519\u5454\u5cb1\u8fe8\u902f\u9a80\u7ed0\u73b3\u9edb',
'dan': '\u803d\u62c5\u4e39\u5355\u90f8\u63b8\u80c6\u65e6\u6c2e\u4f46\u60ee\u6de1\u8bde\u5f39\u86cb\u4ebb\u510b\u5369\u840f\u5556\u6fb9\u6a90\u6b9a\u8d55\u7708\u7605\u8043\u7baa',
'dang': '\u5f53\u6321\u515a\u8361\u6863\u8c20\u51fc\u83ea\u5b95\u7800\u94db\u88c6',
'dao': '\u5200\u6363\u8e48\u5012\u5c9b\u7977\u5bfc\u5230\u7a3b\u60bc\u9053\u76d7\u53e8\u5541\u5fc9\u6d2e\u6c18\u7118\u5fd1\u7e9b',
'de': '\u5fb7\u5f97\u7684\u951d',
'deng': '\u8e6c\u706f\u767b\u7b49\u77aa\u51f3\u9093\u5654\u5d9d\u6225\u78f4\u956b\u7c26',
'di': '\u5824\u4f4e\u6ef4\u8fea\u654c\u7b1b\u72c4\u6da4\u7fdf\u5ae1\u62b5\u5e95\u5730\u8482\u7b2c\u5e1d\u5f1f\u9012\u7f14\u6c10\u7c74\u8bcb\u8c1b\u90b8\u577b\u839c\u837b\u5600\u5a23\u67e2\u68e3\u89cc\u7825\u78b2\u7747\u955d\u7f9d\u9ab6',
'dian': '\u98a0\u6382\u6ec7\u7898\u70b9\u5178\u975b\u57ab\u7535\u4f43\u7538\u5e97\u60e6\u5960\u6dc0\u6bbf\u4e36\u963d\u576b\u57dd\u5dc5\u73b7\u765c\u766b\u7c1f\u8e2e',
'diao': '\u7889\u53fc\u96d5\u51cb\u5201\u6389\u540a\u9493\u8c03\u8f7a\u94de\u8729\u7c9c\u8c82',
'die': '\u8dcc\u7239\u789f\u8776\u8fed\u8c0d\u53e0\u4f5a\u57a4\u581e\u63f2\u558b\u6e2b\u8f76\u7252\u74de\u8936\u800b\u8e40\u9cbd\u9cce',
'ding': '\u4e01\u76ef\u53ee\u9489\u9876\u9f0e\u952d\u5b9a\u8ba2\u4e22\u4ec3\u5576\u738e\u815a\u7887\u753a\u94e4\u7594\u8035\u914a',
'dong': '\u4e1c\u51ac\u8463\u61c2\u52a8\u680b\u4f97\u606b\u51bb\u6d1e\u578c\u549a\u5cbd\u5cd2\u5902\u6c21\u80e8\u80f4\u7850\u9e2b',
'dou': '\u515c\u6296\u6597\u9661\u8c46\u9017\u75d8\u8538\u94ad\u7aa6\u7aac\u86aa\u7bfc\u9161',
'du': '\u90fd\u7763\u6bd2\u728a\u72ec\u8bfb\u5835\u7779\u8d4c\u675c\u9540\u809a\u5ea6\u6e21\u5992\u828f\u561f\u6e0e\u691f\u6a50\u724d\u8839\u7b03\u9ad1\u9ee9',
'duan': '\u7aef\u77ed\u953b\u6bb5\u65ad\u7f0e\u5f56\u6934\u7145\u7c16',
'dui': '\u5806\u5151\u961f\u5bf9\u603c\u619d\u7893',
'dun': '\u58a9\u5428\u8e72\u6566\u987f\u56e4\u949d\u76fe\u9041\u7096\u7818\u7905\u76f9\u9566\u8db8',
'duo': '\u6387\u54c6\u591a\u593a\u579b\u8eb2\u6735\u8dfa\u8235\u5241\u60f0\u5815\u5484\u54da\u7f0d\u67c1\u94ce\u88f0\u8e31',
'e': '\u86fe\u5ce8\u9e45\u4fc4\u989d\u8bb9\u5a25\u6076\u5384\u627c\u904f\u9102\u997f\u5669\u8c14\u57a9\u57ad\u82ca\u83aa\u843c\u5443\u6115\u5c59\u5a40\u8f6d\u66f7\u816d\u786a\u9507\u9537\u9e57\u989a\u9cc4',
'en': '\u6069\u84bd\u6441\u5514\u55ef',
'er': '\u800c\u513f\u8033\u5c14\u9975\u6d31\u4e8c\u8d30\u8fe9\u73e5\u94d2\u9e38\u9c95',
'fa': '\u53d1\u7f5a\u7b4f\u4f10\u4e4f\u9600\u6cd5\u73d0\u57a1\u781d',
'fan': '\u85e9\u5e06\u756a\u7ffb\u6a0a\u77fe\u9492\u7e41\u51e1\u70e6\u53cd\u8fd4\u8303\u8d29\u72af\u996d\u6cdb\u8629\u5e61\u72ad\u68b5\u6535\u71d4\u7548\u8e6f',
'fang': '\u574a\u82b3\u65b9\u80aa\u623f\u9632\u59a8\u4eff\u8bbf\u7eba\u653e\u531a\u90a1\u5f77\u94ab\u822b\u9c82',
'fei': '\u83f2\u975e\u5561\u98de\u80a5\u532a\u8bfd\u5420\u80ba\u5e9f\u6cb8\u8d39\u82be\u72d2\u60b1\u6ddd\u5983\u7ecb\u7eef\u69a7\u8153\u6590\u6249\u7953\u7829\u9544\u75f1\u871a\u7bda\u7fe1\u970f\u9cb1',
'fen': '\u82ac\u915a\u5429\u6c1b\u5206\u7eb7\u575f\u711a\u6c7e\u7c89\u594b\u4efd\u5fff\u6124\u7caa\u507e\u7035\u68fc\u610d\u9cbc\u9f22',
'feng': '\u4e30\u5c01\u67ab\u8702\u5cf0\u950b\u98ce\u75af\u70fd\u9022\u51af\u7f1d\u8bbd\u5949\u51e4\u4ff8\u9146\u8451\u6ca3\u781c',
'fu': '\u4f5b\u5426\u592b\u6577\u80a4\u5b75\u6276\u62c2\u8f90\u5e45\u6c1f\u7b26\u4f0f\u4fd8\u670d\u6d6e\u6daa\u798f\u88b1\u5f17\u752b\u629a\u8f85\u4fef\u91dc\u65a7\u812f\u8151\u5e9c\u8150\u8d74\u526f\u8986\u8d4b\u590d\u5085\u4ed8\u961c\u7236\u8179\u8d1f\u5bcc\u8ba3\u9644\u5987\u7f1a\u5490\u5310\u51eb\u90db\u8299\u82fb\u832f\u83a9\u83d4\u544b\u5e5e\u6ecf\u8274\u5b5a\u9a78\u7ec2\u6874\u8d59\u9efb\u9efc\u7f58\u7a03\u99a5\u864d\u86a8\u8709\u8760\u876e\u9eb8\u8dba\u8dd7\u9cc6',
'ga': '\u5676\u560e\u86e4\u5c2c\u5477\u5c15\u5c1c\u65ee\u9486',
'gai': '\u8be5\u6539\u6982\u9499\u76d6\u6e89\u4e10\u9654\u5793\u6224\u8d45\u80f2',
'gan': '\u5e72\u7518\u6746\u67d1\u7aff\u809d\u8d76\u611f\u79c6\u6562\u8d63\u5769\u82f7\u5c34\u64c0\u6cd4\u6de6\u6f89\u7ec0\u6a44\u65f0\u77f8\u75b3\u9150',
'gang': '\u5188\u521a\u94a2\u7f38\u809b\u7eb2\u5c97\u6e2f\u6206\u7f61\u9883\u7b7b',
'gong': '\u6760\u5de5\u653b\u529f\u606d\u9f9a\u4f9b\u8eac\u516c\u5bab\u5f13\u5de9\u6c5e\u62f1\u8d21\u5171\u857b\u5efe\u54a3\u73d9\u80b1\u86a3\u86e9\u89e5',
'gao': '\u7bd9\u768b\u9ad8\u818f\u7f94\u7cd5\u641e\u9550\u7a3f\u544a\u777e\u8bf0\u90dc\u84bf\u85c1\u7f1f\u69d4\u69c1\u6772\u9506',
'ge': '\u54e5\u6b4c\u6401\u6208\u9e3d\u80f3\u7599\u5272\u9769\u845b\u683c\u9601\u9694\u94ec\u4e2a\u5404\u9b32\u4ee1\u54ff\u5865\u55dd\u7ea5\u643f\u8188\u784c\u94ea\u9549\u88bc\u988c\u867c\u8238\u9abc\u9ac2',
'gei': '\u7ed9',
'gen': '\u6839\u8ddf\u4e98\u831b\u54cf\u826e',
'geng': '\u8015\u66f4\u5e9a\u7fb9\u57c2\u803f\u6897\u54fd\u8d53\u9ca0',
'gou': '\u94a9\u52fe\u6c9f\u82df\u72d7\u57a2\u6784\u8d2d\u591f\u4f5d\u8bdf\u5ca3\u9058\u5abe\u7f11\u89cf\u5f40\u9e32\u7b31\u7bdd\u97b2',
'gu': '\u8f9c\u83c7\u5495\u7b8d\u4f30\u6cbd\u5b64\u59d1\u9f13\u53e4\u86ca\u9aa8\u8c37\u80a1\u6545\u987e\u56fa\u96c7\u560f\u8bc2\u83f0\u54cc\u5d2e\u6c69\u688f\u8f71\u726f\u727f\u80cd\u81cc\u6bc2\u77bd\u7f5f\u94b4\u9522\u74e0\u9e2a\u9e44\u75fc\u86c4\u9164\u89da\u9cb4\u9ab0\u9e58',
'gua': '\u522e\u74dc\u5250\u5be1\u6302\u8902\u5366\u8bd6\u5471\u681d\u9e39',
'guai': '\u4e56\u62d0\u602a\u54d9',
'guan': '\u68fa\u5173\u5b98\u51a0\u89c2\u7ba1\u9986\u7f50\u60ef\u704c\u8d2f\u500c\u839e\u63bc\u6dab\u76e5\u9e73\u9ccf',
'guang': '\u5149\u5e7f\u901b\u72b7\u6844\u80f1\u7592',
'gui': '\u7470\u89c4\u572d\u7845\u5f52\u9f9f\u95fa\u8f68\u9b3c\u8be1\u7678\u6842\u67dc\u8dea\u8d35\u523d\u5326\u523f\u5e8b\u5b84\u59ab\u6867\u7085\u6677\u7688\u7c0b\u9c91\u9cdc',
'gun': '\u8f8a\u6eda\u68cd\u4e28\u886e\u7ef2\u78d9\u9ca7',
'guo': '\u9505\u90ed\u56fd\u679c\u88f9\u8fc7\u9998\u8803\u57da\u63b4\u5459\u56d7\u5e3c\u5d1e\u7313\u6901\u8662\u951e\u8052\u872e\u873e\u8748',
'ha': '\u54c8',
'hai': '\u9ab8\u5b69\u6d77\u6c26\u4ea5\u5bb3\u9a87\u54b4\u55e8\u988f\u91a2',
'han': '\u9163\u61a8\u90af\u97e9\u542b\u6db5\u5bd2\u51fd\u558a\u7f55\u7ff0\u64bc\u634d\u65f1\u61be\u608d\u710a\u6c57\u6c49\u9097\u83e1\u6496\u961a\u701a\u6657\u7113\u9894\u86b6\u9f3e',
'hen': '\u592f\u75d5\u5f88\u72e0\u6068',
'hang': '\u676d\u822a\u6c86\u7ed7\u73e9\u6841',
'hao': '\u58d5\u568e\u8c6a\u6beb\u90dd\u597d\u8017\u53f7\u6d69\u8585\u55e5\u5686\u6fe0\u704f\u660a\u7693\u98a2\u869d',
'he': '\u5475\u559d\u8377\u83cf\u6838\u79be\u548c\u4f55\u5408\u76d2\u8c89\u9602\u6cb3\u6db8\u8d6b\u8910\u9e64\u8d3a\u8bc3\u52be\u58d1\u85ff\u55d1\u55ec\u9616\u76cd\u86b5\u7fee',
'hei': '\u563f\u9ed1',
'heng': '\u54fc\u4ea8\u6a2a\u8861\u6052\u8a07\u8605',
'hong': '\u8f70\u54c4\u70d8\u8679\u9e3f\u6d2a\u5b8f\u5f18\u7ea2\u9ec9\u8ba7\u836d\u85a8\u95f3\u6cd3',
'hou': '\u5589\u4faf\u7334\u543c\u539a\u5019\u540e\u5820\u5f8c\u9005\u760a\u7bcc\u7cc7\u9c8e\u9aba',
'hu': '\u547c\u4e4e\u5ffd\u745a\u58f6\u846b\u80e1\u8774\u72d0\u7cca\u6e56\u5f27\u864e\u552c\u62a4\u4e92\u6caa\u6237\u51b1\u553f\u56eb\u5cb5\u7322\u6019\u60da\u6d52\u6ef9\u7425\u69f2\u8f77\u89f3\u70c0\u7173\u623d\u6248\u795c\u9e55\u9e71\u7b0f\u9190\u659b',
'hua': '\u82b1\u54d7\u534e\u733e\u6ed1\u753b\u5212\u5316\u8bdd\u5290\u6d4d\u9a85\u6866\u94e7\u7a1e',
'huai': '\u69d0\u5f8a\u6000\u6dee\u574f\u8fd8\u8e1d',
'huan': '\u6b22\u73af\u6853\u7f13\u6362\u60a3\u5524\u75ea\u8c62\u7115\u6da3\u5ba6\u5e7b\u90c7\u5942\u57b8\u64d0\u571c\u6d39\u6d63\u6f36\u5bf0\u902d\u7f33\u953e\u9ca9\u9b1f',
'huang': '\u8352\u614c\u9ec4\u78fa\u8757\u7c27\u7687\u51f0\u60f6\u714c\u6643\u5e4c\u604d\u8c0e\u968d\u5fa8\u6e5f\u6f62\u9051\u749c\u8093\u7640\u87e5\u7bc1\u9cc7',
'hui': '\u7070\u6325\u8f89\u5fbd\u6062\u86d4\u56de\u6bc1\u6094\u6167\u5349\u60e0\u6666\u8d3f\u79fd\u4f1a\u70e9\u6c47\u8bb3\u8bf2\u7ed8\u8bd9\u8334\u835f\u8559\u54d5\u5599\u96b3\u6d04\u5f57\u7f0b\u73f2\u6656\u605a\u867a\u87ea\u9ebe',
'hun': '\u8364\u660f\u5a5a\u9b42\u6d51\u6df7\u8be8\u9984\u960d\u6eb7\u7f17',
'huo': '\u8c41\u6d3b\u4f19\u706b\u83b7\u6216\u60d1\u970d\u8d27\u7978\u6509\u56af\u5925\u94ac\u952a\u956c\u8020\u8816',
'ji': '\u51fb\u573e\u57fa\u673a\u7578\u7a3d\u79ef\u7b95\u808c\u9965\u8ff9\u6fc0\u8ba5\u9e21\u59ec\u7ee9\u7f09\u5409\u6781\u68d8\u8f91\u7c4d\u96c6\u53ca\u6025\u75be\u6c72\u5373\u5ac9\u7ea7\u6324\u51e0\u810a\u5df1\u84df\u6280\u5180\u5b63\u4f0e\u796d\u5242\u60b8\u6d4e\u5bc4\u5bc2\u8ba1\u8bb0\u65e2\u5fcc\u9645\u5993\u7ee7\u7eaa\u5c45\u4e0c\u4e69\u525e\u4f76\u4f74\u8114\u58bc\u82a8\u82b0\u8401\u84ba\u857a\u638e\u53fd\u54ad\u54dc\u5527\u5c8c\u5d74\u6d0e\u5f50\u5c50\u9aa5\u757f\u7391\u696b\u6b9b\u621f\u6222\u8d4d\u89ca\u7284\u9f51\u77f6\u7f81\u5d47\u7a37\u7620\u7635\u866e\u7b08\u7b04\u66a8\u8dfb\u8dfd\u9701\u9c9a\u9cab\u9afb\u9e82',
'jia': '\u5609\u67b7\u5939\u4f73\u5bb6\u52a0\u835a\u988a\u8d3e\u7532\u94be\u5047\u7a3c\u4ef7\u67b6\u9a7e\u5ac1\u4f3d\u90cf\u62ee\u5cac\u6d43\u8fe6\u73c8\u621b\u80db\u605d\u94d7\u9553\u75c2\u86f1\u7b33\u8888\u8dcf',
'jian': '\u6b7c\u76d1\u575a\u5c16\u7b3a\u95f4\u714e\u517c\u80a9\u8270\u5978\u7f04\u8327\u68c0\u67ec\u78b1\u7877\u62e3\u6361\u7b80\u4fed\u526a\u51cf\u8350\u69db\u9274\u8df5\u8d31\u89c1\u952e\u7bad\u4ef6\u5065\u8230\u5251\u996f\u6e10\u6e85\u6da7\u5efa\u50ed\u8c0f\u8c2b\u83c5\u84b9\u641b\u56dd\u6e54\u8e47\u8b07\u7f23\u67a7\u67d9\u6957\u620b\u622c\u726e\u728d\u6bfd\u8171\u7751\u950f\u9e63\u88e5\u7b15\u7bb4\u7fe6\u8dbc\u8e3a\u9ca3\u97af',
'jiang': '\u50f5\u59dc\u5c06\u6d46\u6c5f\u7586\u848b\u6868\u5956\u8bb2\u5320\u9171\u964d\u8333\u6d1a\u7edb\u7f30\u729f\u7913\u8029\u7ce8\u8c47',
'jiao': '\u8549\u6912\u7901\u7126\u80f6\u4ea4\u90ca\u6d47\u9a84\u5a07\u56bc\u6405\u94f0\u77eb\u4fa5\u811a\u72e1\u89d2\u997a\u7f34\u7ede\u527f\u6559\u9175\u8f7f\u8f83\u53eb\u4f7c\u50ec\u832d\u6322\u564d\u5ce4\u5fbc\u59e3\u7e9f\u656b\u768e\u9e6a\u86df\u91ae\u8de4\u9c9b',
'jie': '\u7a96\u63ed\u63a5\u7686\u79f8\u8857\u9636\u622a\u52ab\u8282\u6854\u6770\u6377\u776b\u7aed\u6d01\u7ed3\u89e3\u59d0\u6212\u85c9\u82a5\u754c\u501f\u4ecb\u75a5\u8beb\u5c4a\u5048\u8ba6\u8bd8\u5588\u55df\u736c\u5a55\u5b51\u6840\u7352\u78a3\u9534\u7596\u88b7\u9889\u86a7\u7faf\u9c92\u9ab1\u9aeb',
'jin': '\u5dfe\u7b4b\u65a4\u91d1\u4eca\u6d25\u895f\u7d27\u9526\u4ec5\u8c28\u8fdb\u9773\u664b\u7981\u8fd1\u70ec\u6d78\u5c3d\u537a\u8369\u5807\u5664\u9991\u5ed1\u5997\u7f19\u747e\u69ff\u8d46\u89d0\u9485\u9513\u887f\u77dc',
'jing': '\u52b2\u8346\u5162\u830e\u775b\u6676\u9cb8\u4eac\u60ca\u7cbe\u7cb3\u7ecf\u4e95\u8b66\u666f\u9888\u9759\u5883\u656c\u955c\u5f84\u75c9\u9756\u7adf\u7ade\u51c0\u522d\u5106\u9631\u83c1\u734d\u61ac\u6cfe\u8ff3\u5f2a\u5a67\u80bc\u80eb\u8148\u65cc',
'jiong': '\u70af\u7a98\u5182\u8fe5\u6243',
'jiu': '\u63ea\u7a76\u7ea0\u7396\u97ed\u4e45\u7078\u4e5d\u9152\u53a9\u6551\u65e7\u81fc\u8205\u548e\u5c31\u759a\u50e6\u557e\u9604\u67e9\u6855\u9e6b\u8d73\u9b0f',
'ju': '\u97a0\u62d8\u72d9\u75bd\u9a79\u83ca\u5c40\u5480\u77e9\u4e3e\u6cae\u805a\u62d2\u636e\u5de8\u5177\u8ddd\u8e1e\u952f\u4ff1\u53e5\u60e7\u70ac\u5267\u5028\u8bb5\u82e3\u82f4\u8392\u63ac\u907d\u5c66\u741a\u67b8\u6910\u6998\u6989\u6a58\u728b\u98d3\u949c\u9514\u7aad\u88fe\u8d84\u91b5\u8e3d\u9f83\u96ce\u97ab',
'juan': '\u6350\u9e43\u5a1f\u5026\u7737\u5377\u7ee2\u9104\u72f7\u6d93\u684a\u8832\u9529\u954c\u96bd',
'jue': '\u6485\u652b\u6289\u6398\u5014\u7235\u89c9\u51b3\u8bc0\u7edd\u53a5\u5282\u8c32\u77cd\u8568\u5658\u5d1b\u7357\u5b53\u73cf\u6877\u6a5b\u721d\u9562\u8e76\u89d6',
'jun': '\u5747\u83cc\u94a7\u519b\u541b\u5cfb\u4fca\u7ae3\u6d5a\u90e1\u9a8f\u6343\u72fb\u76b2\u7b60\u9e87',
'ka': '\u5580\u5496\u5361\u4f67\u5494\u80e9',
'ke': '\u54af\u5777\u82db\u67ef\u68f5\u78d5\u9897\u79d1\u58f3\u54b3\u53ef\u6e34\u514b\u523b\u5ba2\u8bfe\u5ca2\u606a\u6e98\u9a92\u7f02\u73c2\u8f72\u6c2a\u778c\u94b6\u75b4\u7aa0\u874c\u9ac1',
'kai': '\u5f00\u63e9\u6977\u51ef\u6168\u5240\u57b2\u8488\u5ffe\u607a\u94e0\u950e',
'kan': '\u520a\u582a\u52d8\u574e\u780d\u770b\u4f83\u51f5\u83b0\u83b6\u6221\u9f9b\u77b0',
'kang': '\u5eb7\u6177\u7ce0\u625b\u6297\u4ea2\u7095\u5751\u4f09\u95f6\u94aa',
'kao': '\u8003\u62f7\u70e4\u9760\u5c3b\u6832\u7292\u94d0',
'ken': '\u80af\u5543\u57a6\u6073\u57a0\u88c9\u9880',
'keng': '\u542d\u5fd0\u94ff',
'kong': '\u7a7a\u6050\u5b54\u63a7\u5025\u5d06\u7b9c',
'kou': '\u62a0\u53e3\u6263\u5bc7\u82a4\u853b\u53e9\u770d\u7b58',
'ku': '\u67af\u54ed\u7a9f\u82e6\u9177\u5e93\u88e4\u5233\u5800\u55be\u7ed4\u9ab7',
'kua': '\u5938\u57ae\u630e\u8de8\u80ef\u4f89',
'kuai': '\u5757\u7b77\u4fa9\u5feb\u84af\u90d0\u8489\u72ef\u810d',
'kuan': '\u5bbd\u6b3e\u9acb',
'kuang': '\u5321\u7b50\u72c2\u6846\u77ff\u7736\u65f7\u51b5\u8bd3\u8bf3\u909d\u5739\u593c\u54d0\u7ea9\u8d36',
'kui': '\u4e8f\u76d4\u5cbf\u7aa5\u8475\u594e\u9b41\u5080\u9988\u6127\u6e83\u9997\u532e\u5914\u9697\u63c6\u55b9\u559f\u609d\u6126\u9615\u9035\u668c\u777d\u8069\u8770\u7bd1\u81fe\u8dec',
'kun': '\u5764\u6606\u6346\u56f0\u6083\u9603\u7428\u951f\u918c\u9cb2\u9ae1',
'kuo': '\u62ec\u6269\u5ed3\u9614\u86de',
'la': '\u5783\u62c9\u5587\u8721\u814a\u8fa3\u5566\u524c\u647a\u908b\u65ef\u782c\u760c',
'lai': '\u83b1\u6765\u8d56\u5d03\u5f95\u6d9e\u6fd1\u8d49\u7750\u94fc\u765e\u7c41',
'lan': '\u84dd\u5a6a\u680f\u62e6\u7bee\u9611\u5170\u6f9c\u8c30\u63fd\u89c8\u61d2\u7f06\u70c2\u6ee5\u5549\u5c9a\u61d4\u6f24\u6984\u6593\u7f71\u9567\u8934',
'lang': '\u7405\u6994\u72fc\u5eca\u90ce\u6717\u6d6a\u83a8\u8497\u5577\u9606\u9512\u7a02\u8782',
'lao': '\u635e\u52b3\u7262\u8001\u4f6c\u59e5\u916a\u70d9\u6d9d\u5520\u5d02\u6833\u94d1\u94f9\u75e8\u91aa',
'le': '\u52d2\u4e50\u808b\u4ec2\u53fb\u561e\u6cd0\u9cd3',
'lei': '\u96f7\u956d\u857e\u78ca\u7d2f\u5121\u5792\u64c2\u7c7b\u6cea\u7fb8\u8bd4\u837d\u54a7\u6f2f\u5ad8\u7f27\u6a91\u8012\u9179',
'ling': '\u68f1\u51b7\u62ce\u73b2\u83f1\u96f6\u9f84\u94c3\u4f36\u7f9a\u51cc\u7075\u9675\u5cad\u9886\u53e6\u4ee4\u9143\u5844\u82d3\u5464\u56f9\u6ce0\u7eeb\u67c3\u68c2\u74f4\u8046\u86c9\u7fce\u9cae',
'leng': '\u695e\u6123',
'li': '\u5398\u68a8\u7281\u9ece\u7bf1\u72f8\u79bb\u6f13\u7406\u674e\u91cc\u9ca4\u793c\u8389\u8354\u540f\u6817\u4e3d\u5389\u52b1\u783e\u5386\u5229\u5088\u4f8b\u4fd0\u75e2\u7acb\u7c92\u6ca5\u96b6\u529b\u7483\u54e9\u4fea\u4fda\u90e6\u575c\u82c8\u8385\u84e0\u85dc\u6369\u5456\u5533\u55b1\u7301\u6ea7\u6fa7\u9026\u5a0c\u5ae0\u9a8a\u7f21\u73de\u67a5\u680e\u8f79\u623e\u783a\u8a48\u7f79\u9502\u9e42\u75a0\u75ac\u86ce\u870a\u8821\u7b20\u7be5\u7c9d\u91b4\u8dde\u96f3\u9ca1\u9ce2\u9ee7',
'lian': '\u4fe9\u8054\u83b2\u8fde\u9570\u5ec9\u601c\u6d9f\u5e18\u655b\u8138\u94fe\u604b\u70bc\u7ec3\u631b\u8539\u5941\u6f4b\u6fc2\u5a08\u740f\u695d\u6b93\u81c1\u81a6\u88e2\u880a\u9ca2',
'liang': '\u7cae\u51c9\u6881\u7cb1\u826f\u4e24\u8f86\u91cf\u667e\u4eae\u8c05\u589a\u690b\u8e09\u9753\u9b49',
'liao': '\u64a9\u804a\u50da\u7597\u71ce\u5be5\u8fbd\u6f66\u4e86\u6482\u9563\u5ed6\u6599\u84fc\u5c25\u5639\u7360\u5bee\u7f2d\u948c\u9e69\u8022',
'lie': '\u5217\u88c2\u70c8\u52a3\u730e\u51bd\u57d2\u6d0c\u8d94\u8e90\u9b23',
'lin': '\u7433\u6797\u78f7\u9716\u4e34\u90bb\u9cde\u6dcb\u51db\u8d41\u541d\u853a\u5d99\u5eea\u9074\u6aa9\u8f9a\u77b5\u7cbc\u8e8f\u9e9f',
'liu': '\u6e9c\u7409\u69b4\u786b\u998f\u7559\u5218\u7624\u6d41\u67f3\u516d\u62a1\u507b\u848c\u6cd6\u6d4f\u905b\u9a9d\u7efa\u65d2\u7198\u950d\u954f\u9e68\u938f',
'long': '\u9f99\u804b\u5499\u7b3c\u7abf\u9686\u5784\u62e2\u9647\u5f04\u5785\u830f\u6cf7\u73d1\u680a\u80e7\u783b\u7643',
'lou': '\u697c\u5a04\u6402\u7bd3\u6f0f\u964b\u55bd\u5d5d\u9542\u7618\u8027\u877c\u9ac5',
'lu': '\u82a6\u5362\u9885\u5e90\u7089\u63b3\u5364\u864f\u9c81\u9e93\u788c\u9732\u8def\u8d42\u9e7f\u6f5e\u7984\u5f55\u9646\u622e\u5786\u6445\u64b8\u565c\u6cf8\u6e0c\u6f09\u7490\u680c\u6a79\u8f73\u8f82\u8f98\u6c07\u80ea\u9565\u9e2c\u9e6d\u7c0f\u823b\u9c88',
'lv': '\u9a74\u5415\u94dd\u4fa3\u65c5\u5c65\u5c61\u7f15\u8651\u6c2f\u5f8b\u7387\u6ee4\u7eff\u634b\u95fe\u6988\u8182\u7a06\u891b',
'luan': '\u5ce6\u5b6a\u6ee6\u5375\u4e71\u683e\u9e3e\u92ae',
'lue': '\u63a0\u7565\u950a',
'lun': '\u8f6e\u4f26\u4ed1\u6ca6\u7eb6\u8bba\u56f5',
'luo': '\u841d\u87ba\u7f57\u903b\u9523\u7ba9\u9aa1\u88f8\u843d\u6d1b\u9a86\u7edc\u502e\u8366\u645e\u7321\u6cfa\u6924\u8136\u9559\u7630\u96d2',
'ma': '\u5988\u9ebb\u739b\u7801\u8682\u9a6c\u9a82\u561b\u5417\u551b\u72b8\u5b37\u6769\u9ebd',
'mai': '\u57cb\u4e70\u9ea6\u5356\u8fc8\u8109\u52a2\u836c\u54aa\u973e',
'man': '\u7792\u9992\u86ee\u6ee1\u8513\u66fc\u6162\u6f2b\u8c29\u5881\u5e54\u7f26\u71b3\u9558\u989f\u87a8\u9cd7\u9794',
'mang': '\u8292\u832b\u76f2\u5fd9\u83bd\u9099\u6f2d\u6726\u786d\u87d2',
'meng': '\u6c13\u840c\u8499\u6aac\u76df\u9530\u731b\u68a6\u5b5f\u52d0\u750d\u77a2\u61f5\u791e\u867b\u8722\u8813\u824b\u8268\u9efe',
'miao': '\u732b\u82d7\u63cf\u7784\u85d0\u79d2\u6e3a\u5e99\u5999\u55b5\u9088\u7f08\u7f2a\u676a\u6dfc\u7707\u9e4b\u8731',
'mao': '\u8305\u951a\u6bdb\u77db\u94c6\u536f\u8302\u5192\u5e3d\u8c8c\u8d38\u4f94\u88a4\u52d6\u8306\u5cc1\u7441\u6634\u7266\u8004\u65c4\u61cb\u7780\u86d1\u8765\u87ca\u9ae6',
'me': '\u4e48',
'mei': '\u73ab\u679a\u6885\u9176\u9709\u7164\u6ca1\u7709\u5a92\u9541\u6bcf\u7f8e\u6627\u5bd0\u59b9\u5a9a\u5776\u8393\u5d4b\u7338\u6d7c\u6e44\u6963\u9545\u9e5b\u8882\u9b45',
'men': '\u95e8\u95f7\u4eec\u626a\u739f\u7116\u61d1\u9494',
'mi': '\u772f\u919a\u9761\u7cdc\u8ff7\u8c1c\u5f25\u7c73\u79d8\u89c5\u6ccc\u871c\u5bc6\u5e42\u8288\u5196\u8c27\u863c\u5627\u7315\u736f\u6c68\u5b93\u5f2d\u8112\u6549\u7cf8\u7e3b\u9e8b',
'mian': '\u68c9\u7720\u7ef5\u5195\u514d\u52c9\u5a29\u7f05\u9762\u6c94\u6e4e\u817c\u7704',
'mie': '\u8511\u706d\u54a9\u881b\u7bfe',
'min': '\u6c11\u62bf\u76bf\u654f\u60af\u95fd\u82e0\u5cb7\u95f5\u6cef\u73c9',
'ming': '\u660e\u879f\u9e23\u94ed\u540d\u547d\u51a5\u8317\u6e9f\u669d\u7791\u9169',
'miu': '\u8c2c',
'mo': '\u6478\u6479\u8611\u6a21\u819c\u78e8\u6469\u9b54\u62b9\u672b\u83ab\u58a8\u9ed8\u6cab\u6f20\u5bde\u964c\u8c1f\u8309\u84e6\u998d\u5aeb\u9546\u79e3\u763c\u8031\u87c6\u8c8a\u8c98',
'mou': '\u8c0b\u725f\u67d0\u53b6\u54de\u5a7a\u7738\u936a',
'mu': '\u62c7\u7261\u4ea9\u59c6\u6bcd\u5893\u66ae\u5e55\u52df\u6155\u6728\u76ee\u7766\u7267\u7a46\u4eeb\u82dc\u5452\u6c90\u6bea\u94bc',
'na': '\u62ff\u54ea\u5450\u94a0\u90a3\u5a1c\u7eb3\u5185\u637a\u80ad\u954e\u8872\u7bac',
'nai': '\u6c16\u4e43\u5976\u8010\u5948\u9f10\u827f\u8418\u67f0',
'nan': '\u5357\u7537\u96be\u56ca\u5583\u56e1\u6960\u8169\u877b\u8d67',
'nao': '\u6320\u8111\u607c\u95f9\u5b6c\u57b4\u7331\u7459\u7847\u94d9\u86f2',
'ne': '\u6dd6\u5462\u8bb7',
'nei': '\u9981',
'nen': '\u5ae9\u80fd\u6798\u6041',
'ni': '\u59ae\u9713\u502a\u6ce5\u5c3c\u62df\u4f60\u533f\u817b\u9006\u6eba\u4f32\u576d\u730a\u6029\u6ee0\u6635\u65ce\u7962\u615d\u7768\u94cc\u9cb5',
'nian': '\u852b\u62c8\u5e74\u78be\u64b5\u637b\u5ff5\u5eff\u8f87\u9ecf\u9c87\u9cb6',
'niang': '\u5a18\u917f',
'niao': '\u9e1f\u5c3f\u8311\u5b32\u8132\u8885',
'nie': '\u634f\u8042\u5b7d\u556e\u954a\u954d\u6d85\u4e5c\u9667\u8616\u55eb\u8080\u989e\u81ec\u8e51',
'nin': '\u60a8\u67e0',
'ning': '\u72de\u51dd\u5b81\u62e7\u6cde\u4f5e\u84e5\u549b\u752f\u804d',
'niu': '\u725b\u626d\u94ae\u7ebd\u72c3\u5ff8\u599e\u86b4',
'nong': '\u8113\u6d53\u519c\u4fac',
'nu': '\u5974\u52aa\u6012\u5476\u5e11\u5f29\u80ec\u5b65\u9a7d',
'nv': '\u5973\u6067\u9495\u8844',
'nuan': '\u6696',
'nuenue': '\u8650',
'nue': '\u759f\u8c11',
'nuo': '\u632a\u61e6\u7cef\u8bfa\u50a9\u6426\u558f\u9518',
'ou': '\u54e6\u6b27\u9e25\u6bb4\u85d5\u5455\u5076\u6ca4\u6004\u74ef\u8026',
'pa': '\u556a\u8db4\u722c\u5e15\u6015\u7436\u8469\u7b62',
'pai': '\u62cd\u6392\u724c\u5f98\u6e43\u6d3e\u4ff3\u848e',
'pan': '\u6500\u6f58\u76d8\u78d0\u76fc\u7554\u5224\u53db\u723f\u6cee\u88a2\u897b\u87e0\u8e52',
'pang': '\u4e53\u5e9e\u65c1\u802a\u80d6\u6ec2\u9004',
'pao': '\u629b\u5486\u5228\u70ae\u888d\u8dd1\u6ce1\u530f\u72cd\u5e96\u812c\u75b1',
'pei': '\u5478\u80da\u57f9\u88f4\u8d54\u966a\u914d\u4f69\u6c9b\u638a\u8f94\u5e14\u6de0\u65c6\u952b\u9185\u9708',
'pen': '\u55b7\u76c6\u6e53',
'peng': '\u7830\u62a8\u70f9\u6f8e\u5f6d\u84ec\u68da\u787c\u7bf7\u81a8\u670b\u9e4f\u6367\u78b0\u576f\u580b\u562d\u6026\u87db',
'pi': '\u7812\u9739\u6279\u62ab\u5288\u7435\u6bd7\u5564\u813e\u75b2\u76ae\u5339\u75de\u50fb\u5c41\u8b6c\u4e15\u9674\u90b3\u90eb\u572e\u9f19\u64d7\u567c\u5e80\u5ab2\u7eb0\u6787\u7513\u7765\u7f74\u94cd\u75e6\u7656\u758b\u868d\u8c94',
'pian': '\u7bc7\u504f\u7247\u9a97\u8c1d\u9a88\u728f\u80fc\u890a\u7fe9\u8e41',
'piao': '\u98d8\u6f02\u74e2\u7968\u527d\u560c\u5ad6\u7f25\u6b8d\u779f\u87b5',
'pie': '\u6487\u77a5\u4e3f\u82e4\u6c15',
'pin': '\u62fc\u9891\u8d2b\u54c1\u8058\u62da\u59d8\u5ad4\u6980\u725d\u98a6',
'ping': '\u4e52\u576a\u82f9\u840d\u5e73\u51ed\u74f6\u8bc4\u5c4f\u4fdc\u5a09\u67b0\u9c86',
'po': '\u5761\u6cfc\u9887\u5a46\u7834\u9b44\u8feb\u7c95\u53f5\u9131\u6ea5\u73c0\u948b\u94b7\u76a4\u7b38',
'pou': '\u5256\u88d2\u8e23',
'pu': '\u6251\u94fa\u4ec6\u8386\u8461\u83e9\u84b2\u57d4\u6734\u5703\u666e\u6d66\u8c31\u66dd\u7011\u530d\u5657\u6fee\u749e\u6c06\u9564\u9568\u8e7c',
'qi': '\u671f\u6b3a\u6816\u621a\u59bb\u4e03\u51c4\u6f06\u67d2\u6c8f\u5176\u68cb\u5947\u6b67\u7566\u5d0e\u8110\u9f50\u65d7\u7948\u7941\u9a91\u8d77\u5c82\u4e5e\u4f01\u542f\u5951\u780c\u5668\u6c14\u8fc4\u5f03\u6c7d\u6ce3\u8bab\u4e9f\u4e93\u573b\u8291\u840b\u847a\u5601\u5c7a\u5c90\u6c54\u6dc7\u9a90\u7eee\u742a\u7426\u675e\u6864\u69ed\u6b39\u797a\u61a9\u789b\u86f4\u871e\u7da6\u7dae\u8dbf\u8e4a\u9ccd\u9e92',
'qia': '\u6390\u6070\u6d3d\u845c',
'qian': '\u7275\u6266\u948e\u94c5\u5343\u8fc1\u7b7e\u4edf\u8c26\u4e7e\u9ed4\u94b1\u94b3\u524d\u6f5c\u9063\u6d45\u8c34\u5811\u5d4c\u6b20\u6b49\u4f65\u9621\u828a\u82a1\u8368\u63ae\u5c8d\u60ad\u614a\u9a9e\u6434\u8930\u7f31\u6920\u80b7\u6106\u94a4\u8654\u7b9d',
'qiang': '\u67aa\u545b\u8154\u7f8c\u5899\u8537\u5f3a\u62a2\u5af1\u6a2f\u6217\u709d\u9516\u9535\u956a\u8941\u8723\u7f9f\u8deb\u8dc4',
'qiao': '\u6a47\u9539\u6572\u6084\u6865\u77a7\u4e54\u4fa8\u5de7\u9798\u64ac\u7fd8\u5ced\u4fcf\u7a8d\u5281\u8bee\u8c2f\u835e\u6100\u6194\u7f32\u6a35\u6bf3\u7857\u8df7\u9792',
'qie': '\u5207\u8304\u4e14\u602f\u7a83\u90c4\u553c\u60ec\u59be\u6308\u9532\u7ba7',
'qin': '\u94a6\u4fb5\u4eb2\u79e6\u7434\u52e4\u82b9\u64d2\u79bd\u5bdd\u6c81\u82a9\u84c1\u8572\u63ff\u5423\u55ea\u5659\u6eb1\u6a8e\u8793\u887e',
'qing': '\u9752\u8f7b\u6c22\u503e\u537f\u6e05\u64ce\u6674\u6c30\u60c5\u9877\u8bf7\u5e86\u5029\u82d8\u570a\u6aa0\u78ec\u873b\u7f44\u7b90\u8b26\u9cad\u9ee5',
'qiong': '\u743c\u7a77\u909b\u8315\u7a79\u7b47\u928e',
'qiu': '\u79cb\u4e18\u90b1\u7403\u6c42\u56da\u914b\u6cc5\u4fc5\u6c3d\u5def\u827d\u72b0\u6e6b\u9011\u9052\u6978\u8d47\u9e20\u866c\u86af\u8764\u88d8\u7cd7\u9cc5\u9f3d',
'qu': '\u8d8b\u533a\u86c6\u66f2\u8eaf\u5c48\u9a71\u6e20\u53d6\u5a36\u9f8b\u8da3\u53bb\u8bce\u52ac\u8556\u8627\u5c96\u8862\u9612\u74a9\u89d1\u6c0d\u795b\u78f2\u766f\u86d0\u883c\u9eb4\u77bf\u9ee2',
'quan': '\u5708\u98a7\u6743\u919b\u6cc9\u5168\u75ca\u62f3\u72ac\u5238\u529d\u8be0\u8343\u737e\u609b\u7efb\u8f81\u754e\u94e8\u8737\u7b4c\u9b08',
'que': '\u7f3a\u7094\u7638\u5374\u9e4a\u69b7\u786e\u96c0\u9619\u60ab',
'qun': '\u88d9\u7fa4\u9021',
'ran': '\u7136\u71c3\u5189\u67d3\u82d2\u9aef',
'rang': '\u74e4\u58e4\u6518\u56b7\u8ba9\u79b3\u7a70',
'rao': '\u9976\u6270\u7ed5\u835b\u5a06\u6861',
'ruo': '\u60f9\u82e5\u5f31',
're': '\u70ed\u504c',
'ren': '\u58ec\u4ec1\u4eba\u5fcd\u97e7\u4efb\u8ba4\u5203\u598a\u7eab\u4ede\u834f\u845a\u996a\u8f6b\u7a14\u887d',
'reng': '\u6254\u4ecd',
'ri': '\u65e5',
'rong': '\u620e\u8338\u84c9\u8363\u878d\u7194\u6eb6\u5bb9\u7ed2\u5197\u5d58\u72e8\u7f1b\u6995\u877e',
'rou': '\u63c9\u67d4\u8089\u7cc5\u8e42\u97a3',
'ru': '\u8339\u8815\u5112\u5b7a\u5982\u8fb1\u4e73\u6c5d\u5165\u8925\u84d0\u85b7\u5685\u6d33\u6ebd\u6fe1\u94f7\u8966\u98a5',
'ruan': '\u8f6f\u962e\u670a',
'rui': '\u854a\u745e\u9510\u82ae\u8564\u777f\u868b',
'run': '\u95f0\u6da6',
'sa': '\u6492\u6d12\u8428\u5345\u4ee8\u6332\u98d2',
'sai': '\u816e\u9cc3\u585e\u8d5b\u567b',
'san': '\u4e09\u53c1\u4f1e\u6563\u5f61\u9993\u6c35\u6bf5\u7cc1\u9730',
'sang': '\u6851\u55d3\u4e27\u6421\u78c9\u98a1',
'sao': '\u6414\u9a9a\u626b\u5ac2\u57fd\u81ca\u7619\u9ccb',
'se': '\u745f\u8272\u6da9\u556c\u94e9\u94ef\u7a51',
'sen': '\u68ee',
'seng': '\u50e7',
'sha': '\u838e\u7802\u6740\u5239\u6c99\u7eb1\u50bb\u5565\u715e\u810e\u6b43\u75e7\u88df\u970e\u9ca8',
'shai': '\u7b5b\u6652\u917e',
'shan': '\u73ca\u82eb\u6749\u5c71\u5220\u717d\u886b\u95ea\u9655\u64c5\u8d61\u81b3\u5584\u6c55\u6247\u7f2e\u5261\u8baa\u912f\u57cf\u829f\u6f78\u59d7\u9a9f\u81bb\u9490\u759d\u87ee\u8222\u8dda\u9cdd',
'shang': '\u5892\u4f24\u5546\u8d4f\u664c\u4e0a\u5c1a\u88f3\u57a7\u7ef1\u6b87\u71b5\u89de',
'shao': '\u68a2\u634e\u7a0d\u70e7\u828d\u52fa\u97f6\u5c11\u54e8\u90b5\u7ecd\u52ad\u82d5\u6f72\u86f8\u7b24\u7b72\u8244',
'she': '\u5962\u8d4a\u86c7\u820c\u820d\u8d66\u6444\u5c04\u6151\u6d89\u793e\u8bbe\u538d\u4f58\u731e\u7572\u9e9d',
'shen': '\u7837\u7533\u547b\u4f38\u8eab\u6df1\u5a20\u7ec5\u795e\u6c88\u5ba1\u5a76\u751a\u80be\u614e\u6e17\u8bdc\u8c02\u5432\u54c2\u6e16\u6939\u77e7\u8703',
'sheng': '\u58f0\u751f\u7525\u7272\u5347\u7ef3\u7701\u76db\u5269\u80dc\u5723\u4e1e\u6e11\u5ab5\u771a\u7b19',
'shi': '\u5e08\u5931\u72ee\u65bd\u6e7f\u8bd7\u5c38\u8671\u5341\u77f3\u62fe\u65f6\u4ec0\u98df\u8680\u5b9e\u8bc6\u53f2\u77e2\u4f7f\u5c4e\u9a76\u59cb\u5f0f\u793a\u58eb\u4e16\u67ff\u4e8b\u62ed\u8a93\u901d\u52bf\u662f\u55dc\u566c\u9002\u4ed5\u4f8d\u91ca\u9970\u6c0f\u5e02\u6043\u5ba4\u89c6\u8bd5\u8c25\u57d8\u83b3\u84cd\u5f11\u5511\u9963\u8f7c\u8006\u8d33\u70bb\u793b\u94c8\u94ca\u87ab\u8210\u7b6e\u8c55\u9ca5\u9cba',
'shou': '\u6536\u624b\u9996\u5b88\u5bff\u6388\u552e\u53d7\u7626\u517d\u624c\u72e9\u7ef6\u824f',
'shu': '\u852c\u67a2\u68b3\u6b8a\u6292\u8f93\u53d4\u8212\u6dd1\u758f\u4e66\u8d4e\u5b70\u719f\u85af\u6691\u66d9\u7f72\u8700\u9ecd\u9f20\u5c5e\u672f\u8ff0\u6811\u675f\u620d\u7ad6\u5885\u5eb6\u6570\u6f31\u6055\u500f\u587e\u83fd\u5fc4\u6cad\u6d91\u6f8d\u59dd\u7ebe\u6bf9\u8167\u6bb3\u956f\u79eb\u9e6c',
'shua': '\u5237\u800d\u5530\u6dae',
'shuai': '\u6454\u8870\u7529\u5e05\u87c0',
'shuan': '\u6813\u62f4\u95e9',
'shuang': '\u971c\u53cc\u723d\u5b40',
'shui': '\u8c01\u6c34\u7761\u7a0e',
'shun': '\u542e\u77ac\u987a\u821c\u6042',
'shuo': '\u8bf4\u7855\u6714\u70c1\u84b4\u6420\u55cd\u6fef\u5981\u69ca\u94c4',
'si': '\u65af\u6495\u5636\u601d\u79c1\u53f8\u4e1d\u6b7b\u8086\u5bfa\u55e3\u56db\u4f3a\u4f3c\u9972\u5df3\u53ae\u4fdf\u5155\u83e5\u549d\u6c5c\u6cd7\u6f8c\u59d2\u9a77\u7f0c\u7940\u7960\u9536\u9e36\u801c\u86f3\u7b25',
'song': '\u677e\u8038\u6002\u9882\u9001\u5b8b\u8bbc\u8bf5\u51c7\u83d8\u5d27\u5d69\u5fea\u609a\u6dde\u7ae6',
'sou': '\u641c\u8258\u64de\u55fd\u53df\u55d6\u55fe\u998a\u6eb2\u98d5\u778d\u953c\u878b',
'su': '\u82cf\u9165\u4fd7\u7d20\u901f\u7c9f\u50f3\u5851\u6eaf\u5bbf\u8bc9\u8083\u5919\u8c21\u850c\u55c9\u612b\u7c0c\u89eb\u7a23',
'suan': '\u9178\u849c\u7b97',
'sui': '\u867d\u968b\u968f\u7ee5\u9ad3\u788e\u5c81\u7a57\u9042\u96a7\u795f\u84d1\u51ab\u8c07\u6fc9\u9083\u71e7\u772d\u7762',
'sun': '\u5b59\u635f\u7b0b\u836a\u72f2\u98e7\u69ab\u8de3\u96bc',
'suo': '\u68ad\u5506\u7f29\u7410\u7d22\u9501\u6240\u5522\u55e6\u5a11\u686b\u7743\u7fa7',
'ta': '\u584c\u4ed6\u5b83\u5979\u5854\u736d\u631e\u8e4b\u8e0f\u95fc\u6ebb\u9062\u69bb\u6c93',
'tai': '\u80ce\u82d4\u62ac\u53f0\u6cf0\u915e\u592a\u6001\u6c70\u90b0\u85b9\u80bd\u70b1\u949b\u8dc6\u9c90',
'tan': '\u574d\u644a\u8d2a\u762b\u6ee9\u575b\u6a80\u75f0\u6f6d\u8c2d\u8c08\u5766\u6bef\u8892\u78b3\u63a2\u53f9\u70ad\u90ef\u8548\u6619\u94bd\u952c\u8983',
'tang': '\u6c64\u5858\u642a\u5802\u68e0\u819b\u5510\u7cd6\u50a5\u9967\u6e8f\u746d\u94f4\u9557\u8025\u8797\u87b3\u7fb0\u91a3',
'thang': '\u5018\u8eba\u6dcc',
'theng': '\u8d9f\u70eb',
'tao': '\u638f\u6d9b\u6ed4\u7ee6\u8404\u6843\u9003\u6dd8\u9676\u8ba8\u5957\u6311\u9f17\u5555\u97ec\u9955',
'te': '\u7279',
'teng': '\u85e4\u817e\u75bc\u8a8a\u6ed5',
'ti': '\u68af\u5254\u8e22\u9511\u63d0\u9898\u8e44\u557c\u4f53\u66ff\u568f\u60d5\u6d95\u5243\u5c49\u8351\u608c\u9016\u7ee8\u7f07\u9e48\u88fc\u918d',
'tian': '\u5929\u6dfb\u586b\u7530\u751c\u606c\u8214\u8146\u63ad\u5fdd\u9617\u6b84\u754b\u94bf\u86ba',
'tiao': '\u6761\u8fe2\u773a\u8df3\u4f7b\u7967\u94eb\u7a95\u9f86\u9ca6',
'tie': '\u8d34\u94c1\u5e16\u841c\u992e',
'ting': '\u5385\u542c\u70c3\u6c40\u5ef7\u505c\u4ead\u5ead\u633a\u8247\u839b\u8476\u5a77\u6883\u8713\u9706',
'tong': '\u901a\u6850\u916e\u77b3\u540c\u94dc\u5f64\u7ae5\u6876\u6345\u7b52\u7edf\u75db\u4f5f\u50ee\u4edd\u833c\u55f5\u6078\u6f7c\u783c',
'tou': '\u5077\u6295\u5934\u900f\u4ea0',
'tu': '\u51f8\u79c3\u7a81\u56fe\u5f92\u9014\u6d82\u5c60\u571f\u5410\u5154\u580d\u837c\u83df\u948d\u9174',
'tuan': '\u6e4d\u56e2\u7583',
'tui': '\u63a8\u9893\u817f\u8715\u892a\u9000\u5fd2\u717a',
'tun': '\u541e\u5c6f\u81c0\u9968\u66be\u8c5a\u7a80',
'tuo': '\u62d6\u6258\u8131\u9e35\u9640\u9a6e\u9a7c\u692d\u59a5\u62d3\u553e\u4e47\u4f57\u5768\u5eb9\u6cb1\u67dd\u7823\u7ba8\u8204\u8dce\u9f0d',
'wa': '\u6316\u54c7\u86d9\u6d3c\u5a03\u74e6\u889c\u4f64\u5a32\u817d',
'wai': '\u6b6a\u5916',
'wan': '\u8c4c\u5f2f\u6e7e\u73a9\u987d\u4e38\u70f7\u5b8c\u7897\u633d\u665a\u7696\u60cb\u5b9b\u5a49\u4e07\u8155\u525c\u8284\u82cb\u83c0\u7ea8\u7efe\u742c\u8118\u7579\u873f\u7ba2',
'wang': '\u6c6a\u738b\u4ea1\u6789\u7f51\u5f80\u65fa\u671b\u5fd8\u5984\u7f54\u5c22\u60d8\u8f8b\u9b4d',
'wei': '\u5a01\u5dcd\u5fae\u5371\u97e6\u8fdd\u6845\u56f4\u552f\u60df\u4e3a\u6f4d\u7ef4\u82c7\u840e\u59d4\u4f1f\u4f2a\u5c3e\u7eac\u672a\u851a\u5473\u754f\u80c3\u5582\u9b4f\u4f4d\u6e2d\u8c13\u5c09\u6170\u536b\u502d\u504e\u8bff\u9688\u8473\u8587\u5e0f\u5e37\u5d34\u5d6c\u7325\u732c\u95f1\u6ca9\u6d27\u6da0\u9036\u5a13\u73ae\u97ea\u8ece\u709c\u7168\u71a8\u75ff\u8249\u9c94',
'wen': '\u761f\u6e29\u868a\u6587\u95fb\u7eb9\u543b\u7a33\u7d0a\u95ee\u520e\u6120\u960c\u6c76\u74ba\u97eb\u6b81\u96ef',
'weng': '\u55e1\u7fc1\u74ee\u84ca\u8579',
'wo': '\u631d\u8717\u6da1\u7a9d\u6211\u65a1\u5367\u63e1\u6c83\u83b4\u5e44\u6e25\u674c\u809f\u9f8c',
'wu': '\u5deb\u545c\u94a8\u4e4c\u6c61\u8bec\u5c4b\u65e0\u829c\u68a7\u543e\u5434\u6bcb\u6b66\u4e94\u6342\u5348\u821e\u4f0d\u4fae\u575e\u620a\u96fe\u6664\u7269\u52ff\u52a1\u609f\u8bef\u5140\u4ef5\u9622\u90ac\u572c\u82b4\u5e91\u6003\u5fe4\u6d6f\u5be4\u8fd5\u59a9\u9a9b\u727e\u7110\u9e49\u9e5c\u8708\u92c8\u9f2f',
'xi': '\u6614\u7199\u6790\u897f\u7852\u77fd\u6670\u563b\u5438\u9521\u727a\u7a00\u606f\u5e0c\u6089\u819d\u5915\u60dc\u7184\u70ef\u6eaa\u6c50\u7280\u6a84\u88ad\u5e2d\u4e60\u5ab3\u559c\u94e3\u6d17\u7cfb\u9699\u620f\u7ec6\u50d6\u516e\u96b0\u90d7\u831c\u8478\u84f0\u595a\u550f\u5f99\u9969\u960b\u6d60\u6dc5\u5c63\u5b09\u73ba\u6a28\u66e6\u89cb\u6b37\u71b9\u798a\u79a7\u94b8\u7699\u7a78\u8725\u87cb\u823e\u7fb2\u7c9e\u7fd5\u91af\u9f37',
'xia': '\u778e\u867e\u5323\u971e\u8f96\u6687\u5ce1\u4fa0\u72ed\u4e0b\u53a6\u590f\u5413\u6380\u846d\u55c4\u72ce\u9050\u7455\u7856\u7615\u7f45\u9ee0',
'xian': '\u9528\u5148\u4ed9\u9c9c\u7ea4\u54b8\u8d24\u8854\u8237\u95f2\u6d8e\u5f26\u5acc\u663e\u9669\u73b0\u732e\u53bf\u817a\u9985\u7fa1\u5baa\u9677\u9650\u7ebf\u51bc\u85d3\u5c98\u7303\u66b9\u5a34\u6c19\u7946\u9e47\u75eb\u86ac\u7b45\u7c7c\u9170\u8df9',
'xiang': '\u76f8\u53a2\u9576\u9999\u7bb1\u8944\u6e58\u4e61\u7fd4\u7965\u8be6\u60f3\u54cd\u4eab\u9879\u5df7\u6a61\u50cf\u5411\u8c61\u8297\u8459\u9977\u5ea0\u9aa7\u7f03\u87d3\u9c9e\u98e8',
'xiao': '\u8427\u785d\u9704\u524a\u54ee\u56a3\u9500\u6d88\u5bb5\u6dc6\u6653\u5c0f\u5b5d\u6821\u8096\u5578\u7b11\u6548\u54d3\u54bb\u5d24\u6f47\u900d\u9a81\u7ee1\u67ad\u67b5\u7b71\u7bab\u9b48',
'xie': '\u6954\u4e9b\u6b47\u874e\u978b\u534f\u631f\u643a\u90aa\u659c\u80c1\u8c10\u5199\u68b0\u5378\u87f9\u61c8\u6cc4\u6cfb\u8c22\u5c51\u5055\u4eb5\u52f0\u71ee\u85a4\u64b7\u5ee8\u7023\u9082\u7ec1\u7f2c\u69ad\u698d\u6b59\u8e9e',
'xin': '\u85aa\u82af\u950c\u6b23\u8f9b\u65b0\u5ffb\u5fc3\u4fe1\u8845\u56df\u99a8\u8398\u6b46\u94fd\u946b',
'xing': '\u661f\u8165\u7329\u60fa\u5174\u5211\u578b\u5f62\u90a2\u884c\u9192\u5e78\u674f\u6027\u59d3\u9649\u8347\u8365\u64e4\u60bb\u784e',
'xiong': '\u5144\u51f6\u80f8\u5308\u6c79\u96c4\u718a\u828e',
'xiu': '\u4f11\u4fee\u7f9e\u673d\u55c5\u9508\u79c0\u8896\u7ee3\u83a0\u5cab\u9990\u5ea5\u9e3a\u8c85\u9af9',
'xu': '\u589f\u620c\u9700\u865a\u5618\u987b\u5f90\u8bb8\u84c4\u9157\u53d9\u65ed\u5e8f\u755c\u6064\u7d6e\u5a7f\u7eea\u7eed\u8bb4\u8be9\u5729\u84ff\u6035\u6d2b\u6e86\u987c\u6829\u7166\u7809\u76f1\u80e5\u7cc8\u9191',
'xuan': '\u8f69\u55a7\u5ba3\u60ac\u65cb\u7384\u9009\u7663\u7729\u7eda\u5107\u8c16\u8431\u63ce\u9994\u6ceb\u6d35\u6e32\u6f29\u7487\u6966\u6684\u70ab\u714a\u78b9\u94c9\u955f\u75c3',
'xue': '\u9774\u859b\u5b66\u7a74\u96ea\u8840\u5671\u6cf6\u9cd5',
'xun': '\u52cb\u718f\u5faa\u65ec\u8be2\u5bfb\u9a6f\u5de1\u6b89\u6c5b\u8bad\u8baf\u900a\u8fc5\u5dfd\u57d9\u8340\u85b0\u5ccb\u5f87\u6d54\u66db\u7aa8\u91ba\u9c9f',
'ya': '\u538b\u62bc\u9e26\u9e2d\u5440\u4e2b\u82bd\u7259\u869c\u5d16\u8859\u6daf\u96c5\u54d1\u4e9a\u8bb6\u4f22\u63e0\u5416\u5c88\u8fd3\u5a05\u740a\u6860\u6c29\u7811\u775a\u75d6',
'yan': '\u7109\u54bd\u9609\u70df\u6df9\u76d0\u4e25\u7814\u8712\u5ca9\u5ef6\u8a00\u989c\u960e\u708e\u6cbf\u5944\u63a9\u773c\u884d\u6f14\u8273\u5830\u71d5\u538c\u781a\u96c1\u5501\u5f66\u7130\u5bb4\u8c1a\u9a8c\u53a3\u9765\u8d5d\u4fe8\u5043\u5156\u8ba0\u8c33\u90fe\u9122\u82ab\u83f8\u5d26\u6079\u95eb\u960f\u6d07\u6e6e\u6edf\u598d\u5ae3\u7430\u664f\u80ed\u814c\u7131\u7f68\u7b75\u917d\u9b47\u990d\u9f39',
'yang': '\u6b83\u592e\u9e2f\u79e7\u6768\u626c\u4f6f\u75a1\u7f8a\u6d0b\u9633\u6c27\u4ef0\u75d2\u517b\u6837\u6f3e\u5f89\u600f\u6cf1\u7080\u70ca\u6059\u86d8\u9785',
'yao': '\u9080\u8170\u5996\u7476\u6447\u5c27\u9065\u7a91\u8c23\u59da\u54ac\u8200\u836f\u8981\u8000\u592d\u723b\u5406\u5d3e\u5fad\u7039\u5e7a\u73e7\u6773\u66dc\u80b4\u9e5e\u7a88\u7e47\u9cd0',
'ye': '\u6930\u564e\u8036\u7237\u91ce\u51b6\u4e5f\u9875\u6396\u4e1a\u53f6\u66f3\u814b\u591c\u6db2\u8c12\u90ba\u63f6\u9980\u6654\u70e8\u94d8',
'yi': '\u4e00\u58f9\u533b\u63d6\u94f1\u4f9d\u4f0a\u8863\u9890\u5937\u9057\u79fb\u4eea\u80f0\u7591\u6c82\u5b9c\u59e8\u5f5d\u6905\u8681\u501a\u5df2\u4e59\u77e3\u4ee5\u827a\u6291\u6613\u9091\u5c79\u4ebf\u5f79\u81c6\u9038\u8084\u75ab\u4ea6\u88d4\u610f\u6bc5\u5fc6\u4e49\u76ca\u6ea2\u8be3\u8bae\u8c0a\u8bd1\u5f02\u7ffc\u7fcc\u7ece\u5208\u5293\u4f7e\u8bd2\u572a\u572f\u57f8\u61ff\u82e1\u858f\u5f08\u5955\u6339\u5f0b\u5453\u54a6\u54bf\u566b\u5cc4\u5db7\u7317\u9974\u603f\u6021\u6092\u6f2a\u8fe4\u9a7f\u7f22\u6baa\u8d3b\u65d6\u71a0\u9487\u9552\u9571\u75cd\u7617\u7654\u7fca\u8864\u8734\u8223\u7fbf\u7ff3\u914f\u9edf',
'yin': '\u8335\u836b\u56e0\u6bb7\u97f3\u9634\u59fb\u541f\u94f6\u6deb\u5bc5\u996e\u5c39\u5f15\u9690\u5370\u80e4\u911e\u5819\u831a\u5591\u72fa\u5924\u6c24\u94df\u763e\u8693\u972a\u9f88',
'ying': '\u82f1\u6a31\u5a74\u9e70\u5e94\u7f28\u83b9\u8424\u8425\u8367\u8747\u8fce\u8d62\u76c8\u5f71\u9896\u786c\u6620\u5b34\u90e2\u8314\u83ba\u8426\u6484\u5624\u81ba\u6ee2\u6f46\u701b\u745b\u748e\u6979\u9e66\u763f\u988d\u7f42',
'yo': '\u54df\u5537',
'yong': '\u62e5\u4f63\u81c3\u75c8\u5eb8\u96cd\u8e0a\u86f9\u548f\u6cf3\u6d8c\u6c38\u607f\u52c7\u7528\u4fd1\u58c5\u5889\u6175\u9095\u955b\u752c\u9cd9\u9954',
'you': '\u5e7d\u4f18\u60a0\u5fe7\u5c24\u7531\u90ae\u94c0\u72b9\u6cb9\u6e38\u9149\u6709\u53cb\u53f3\u4f51\u91c9\u8bf1\u53c8\u5e7c\u5363\u6538\u4f91\u83b8\u5466\u56ff\u5ba5\u67da\u7337\u7256\u94d5\u75a3\u8763\u9c7f\u9edd\u9f2c',
'yu': '\u8fc2\u6de4\u4e8e\u76c2\u6986\u865e\u611a\u8206\u4f59\u4fde\u903e\u9c7c\u6109\u6e1d\u6e14\u9685\u4e88\u5a31\u96e8\u4e0e\u5c7f\u79b9\u5b87\u8bed\u7fbd\u7389\u57df\u828b\u90c1\u5401\u9047\u55bb\u5cea\u5fa1\u6108\u6b32\u72f1\u80b2\u8a89\u6d74\u5bd3\u88d5\u9884\u8c6b\u9a6d\u79ba\u6bd3\u4f1b\u4fe3\u8c00\u8c15\u8438\u84e3\u63c4\u5581\u5704\u5709\u5d5b\u72f3\u996b\u5ebe\u9608\u59aa\u59a4\u7ea1\u745c\u6631\u89ce\u8174\u6b24\u65bc\u715c\u71e0\u807f\u94b0\u9e46\u7610\u7600\u7ab3\u8753\u7afd\u8201\u96e9\u9f89',
'yuan': '\u9e33\u6e0a\u51a4\u5143\u57a3\u8881\u539f\u63f4\u8f95\u56ed\u5458\u5706\u733f\u6e90\u7f18\u8fdc\u82d1\u613f\u6028\u9662\u586c\u6c85\u5a9b\u7457\u6a7c\u7230\u7722\u9e22\u8788\u9f0b',
'yue': '\u66f0\u7ea6\u8d8a\u8dc3\u94a5\u5cb3\u7ca4\u6708\u60a6\u9605\u9fa0\u6a3e\u5216\u94ba',
'yun': '\u8018\u4e91\u90e7\u5300\u9668\u5141\u8fd0\u8574\u915d\u6655\u97f5\u5b55\u90d3\u82b8\u72c1\u607d\u7ead\u6b92\u6600\u6c32',
'za': '\u531d\u7838\u6742\u62f6\u5482',
'zai': '\u683d\u54c9\u707e\u5bb0\u8f7d\u518d\u5728\u54b1\u5d3d\u753e',
'zan': '\u6512\u6682\u8d5e\u74d2\u661d\u7c2a\u7ccc\u8db1\u933e',
'zang': '\u8d43\u810f\u846c\u5958\u6215\u81e7',
'zao': '\u906d\u7cdf\u51ff\u85fb\u67a3\u65e9\u6fa1\u86a4\u8e81\u566a\u9020\u7682\u7076\u71e5\u5523\u7f2b',
'ze': '\u8d23\u62e9\u5219\u6cfd\u4ec4\u8d5c\u5567\u8fee\u6603\u7b2e\u7ba6\u8234',
'zei': '\u8d3c',
'zen': '\u600e\u8c2e',
'zeng': '\u589e\u618e\u66fe\u8d60\u7f2f\u7511\u7f7e\u9503',
'zha': '\u624e\u55b3\u6e23\u672d\u8f67\u94e1\u95f8\u7728\u6805\u69a8\u548b\u4e4d\u70b8\u8bc8\u63f8\u5412\u54a4\u54f3\u600d\u781f\u75c4\u86b1\u9f44',
'zhai': '\u6458\u658b\u5b85\u7a84\u503a\u5be8\u7826',
'zhan': '\u77bb\u6be1\u8a79\u7c98\u6cbe\u76cf\u65a9\u8f97\u5d2d\u5c55\u8638\u6808\u5360\u6218\u7ad9\u6e5b\u7efd\u8c35\u640c\u65c3',
'zhang': '\u6a1f\u7ae0\u5f70\u6f33\u5f20\u638c\u6da8\u6756\u4e08\u5e10\u8d26\u4ed7\u80c0\u7634\u969c\u4ec9\u9123\u5e5b\u5d82\u7350\u5adc\u748b\u87d1',
'zhao': '\u62db\u662d\u627e\u6cbc\u8d75\u7167\u7f69\u5146\u8087\u53ec\u722a\u8bcf\u68f9\u948a\u7b0a',
'zhe': '\u906e\u6298\u54f2\u86f0\u8f99\u8005\u9517\u8517\u8fd9\u6d59\u8c2a\u966c\u67d8\u8f84\u78d4\u9e67\u891a\u8707\u8d6d',
'zhen': '\u73cd\u659f\u771f\u7504\u7827\u81fb\u8d1e\u9488\u4fa6\u6795\u75b9\u8bca\u9707\u632f\u9547\u9635\u7f1c\u6862\u699b\u8f78\u8d48\u80d7\u6715\u796f\u755b\u9e29',
'zheng': '\u84b8\u6323\u7741\u5f81\u72f0\u4e89\u6014\u6574\u62ef\u6b63\u653f\u5e27\u75c7\u90d1\u8bc1\u8be4\u5ce5\u94b2\u94ee\u7b5d',
'zhi': '\u829d\u679d\u652f\u5431\u8718\u77e5\u80a2\u8102\u6c41\u4e4b\u7ec7\u804c\u76f4\u690d\u6b96\u6267\u503c\u4f84\u5740\u6307\u6b62\u8dbe\u53ea\u65e8\u7eb8\u5fd7\u631a\u63b7\u81f3\u81f4\u7f6e\u5e1c\u5cd9\u5236\u667a\u79e9\u7a1a\u8d28\u7099\u75d4\u6ede\u6cbb\u7a92\u536e\u965f\u90c5\u57f4\u82b7\u646d\u5e19\u5fee\u5f58\u54ab\u9a98\u6809\u67b3\u6800\u684e\u8f75\u8f7e\u6534\u8d3d\u81a3\u7949\u7957\u9ef9\u96c9\u9e37\u75e3\u86ed\u7d77\u916f\u8dd6\u8e2c\u8e2f\u8c78\u89ef',
'zhong': '\u4e2d\u76c5\u5fe0\u949f\u8877\u7ec8\u79cd\u80bf\u91cd\u4ef2\u4f17\u51a2\u953a\u87bd\u8202\u822f\u8e35',
'zhou': '\u821f\u5468\u5dde\u6d32\u8bcc\u7ca5\u8f74\u8098\u5e1a\u5492\u76b1\u5b99\u663c\u9aa4\u5544\u7740\u501c\u8bf9\u836e\u9b3b\u7ea3\u80c4\u78a1\u7c40\u8233\u914e\u9cb7',
'zhu': '\u73e0\u682a\u86db\u6731\u732a\u8bf8\u8bdb\u9010\u7af9\u70db\u716e\u62c4\u77a9\u5631\u4e3b\u8457\u67f1\u52a9\u86c0\u8d2e\u94f8\u7b51\u4f4f\u6ce8\u795d\u9a7b\u4f2b\u4f8f\u90be\u82ce\u8331\u6d19\u6e1a\u6f74\u9a7a\u677c\u69e0\u6a65\u70b7\u94e2\u75b0\u7603\u86b0\u7afa\u7bb8\u7fe5\u8e85\u9e88',
'zhua': '\u6293',
'zhuai': '\u62fd',
'zhuan': '\u4e13\u7816\u8f6c\u64b0\u8d5a\u7bc6\u629f\u556d\u989b',
'zhuang': '\u6869\u5e84\u88c5\u5986\u649e\u58ee\u72b6\u4e2c',
'zhui': '\u690e\u9525\u8ffd\u8d58\u5760\u7f00\u8411\u9a93\u7f12',
'zhun': '\u8c06\u51c6',
'zhuo': '\u6349\u62d9\u5353\u684c\u7422\u8301\u914c\u707c\u6d4a\u502c\u8bfc\u5ef4\u855e\u64e2\u555c\u6d5e\u6dbf\u6753\u712f\u799a\u65ab',
'zi': '\u5179\u54a8\u8d44\u59ff\u6ecb\u6dc4\u5b5c\u7d2b\u4ed4\u7c7d\u6ed3\u5b50\u81ea\u6e0d\u5b57\u8c18\u5d6b\u59ca\u5b73\u7f01\u6893\u8f8e\u8d40\u6063\u7726\u9531\u79ed\u8014\u7b2b\u7ca2\u89dc\u8a3e\u9cbb\u9aed',
'zong': '\u9b03\u68d5\u8e2a\u5b97\u7efc\u603b\u7eb5\u8159\u7cbd',
'zou': '\u90b9\u8d70\u594f\u63cd\u9139\u9cb0',
'zu': '\u79df\u8db3\u5352\u65cf\u7956\u8bc5\u963b\u7ec4\u4fce\u83f9\u5550\u5f82\u9a75\u8e74',
'zuan': '\u94bb\u7e82\u6525\u7f35',
'zui': '\u5634\u9189\u6700\u7f6a',
'zun': '\u5c0a\u9075\u6499\u6a3d\u9cdf',
'zuo': '\u6628\u5de6\u4f50\u67de\u505a\u4f5c\u5750\u5ea7\u961d\u963c\u80d9\u795a\u9162',
'cou': '\u85ae\u6971\u8f8f\u8160',
'nang': '\u652e\u54dd\u56d4\u9995\u66e9',
'o': '\u5594',
'dia': '\u55f2',
'chuai': '\u562c\u81aa\u8e39',
'cen': '\u5c91\u6d94',
'diu': '\u94e5',
'nou': '\u8028',
'fou': '\u7f36',
'bia': '\u9adf'
};
export default {
chineseToPinYin: function (l1) {
var l2 = l1.length
var I1 = ''
var reg = new RegExp('[a-zA-Z0-9]')
for (var i = 0; i < l2; i++) {
var val = l1.substr(i, 1)
var name = this.arraySearch(val, pinyin)
if (reg.test(val)) {
I1 += val
} else if (name !== false) {
I1 += name
}
}
I1 = I1.replace(/ /g, '-')
while (I1.indexOf('--') > 0) {
I1 = I1.replace('--', '-')
}
return I1
},
arraySearch: function (l1, l2) {
for (var name in pinyin) {
if (pinyin[name].indexOf(l1) !== -1) {
return this.ucfirst(name)
}
}
return false
},
ucfirst: function (l1) {
if (l1.length > 0) {
var first = l1.substr(0, 1).toUpperCase()
var spare = l1.substr(1, l1.length)
return first + spare
}
},
}

View File

@ -0,0 +1,68 @@
<template>
<wd-popup v-model="show" position="right" @close="handleClose">
<view class="content">
<wd-text v-if="title" :text="title"></wd-text>
<wd-cell-group border>
<wd-radio-group v-model="checked">
<template v-for="(item, index) in options">
<wd-cell :title="item.title" clickable @click="handleSelected(item)">
<wd-radio :value="item.key"></wd-radio>
</wd-cell>
</template>
</wd-radio-group>
</wd-cell-group>
</view>
</wd-popup>
</template>
<script setup lang="ts">
import { hasRoute, cache } from '@/common/uitls'
import { ref } from 'vue'
const eimt = defineEmits(['change', 'close'])
const show = ref(true)
const props = defineProps(['title', 'data', 'options', 'checked'])
const checked = ref(props.checked)
const handleClose = () => {
show.value = false
setTimeout(() => {
eimt('close')
}, 300)
}
const handleSelected = (item) => {
checked.value = item.key
eimt('change', { option: item, data: props.data })
handleClose()
}
</script>
<style lang="scss" scoped>
.content {
max-width: 200px;
padding: 10px;
.wd-text.is-default {
font-size: 14px;
color: #666;
}
.wd-cell {
padding-left: 0;
--wot-cell-label-color: #444;
--wot-cell-label-fs: 14px;
&.red {
color: red;
--wot-cell-label-color: red;
}
}
.wd-cell-group {
:deep(.wd-cell__wrapper) {
align-items: center;
.wd-cell__right {
flex: none;
width: 24px;
}
.wd-radio {
margin-top: 0;
}
}
}
}
</style>

View File

@ -0,0 +1,256 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationStyle: 'custom',
navigationBarTitleText: '联系人',
},
}
</route>
<template>
<PageLayout navTitle="联系人" backRouteName="message" routeMethod="pushTab">
<view class="wrap">
<z-paging
ref="paging"
:fixed="false"
v-model="dataList"
@query="queryList"
:default-page-size="100"
>
<template #top>
<wd-search
hide-cancel
placeholder="我要去哪里?"
v-model="keyword"
@search="handleSearch"
@clear="handleClear"
/>
</template>
<view class="wraper">
<wd-index-bar sticky v-if="dataSource.length">
<view v-for="item in dataSource" :key="item.index">
<wd-index-anchor :index="item.index" />
<wd-cell
border
clickable
v-for="inItem in item.data"
:key="item.username"
@click="handleClick(item.index, inItem)"
>
<template #icon>
<wd-img
customClass="avatar"
:width="50"
:height="50"
:src="getFileAccessHttpUrl(inItem.avatar) || defaultAvatar"
></wd-img>
</template>
<template #title>
<view class="content text-gray-4">
<text>{{ inItem.realname }}</text>
<text>{{ inItem.orgCodeTxt ?? '暂无' }}</text>
</view>
</template>
</wd-cell>
</view>
</wd-index-bar>
</view>
</z-paging>
</view>
<template #navRight>
<view
class="cuIcon-filter font-size-20px color-white"
@click="() => (conditionFilter.show = true)"
></view>
</template>
<rightConditionFilter
v-if="conditionFilter.show"
v-bind="conditionFilter"
@close="() => (conditionFilter.show = false)"
@change="handleChange"
></rightConditionFilter>
</PageLayout>
</template>
<script lang="ts" setup>
import { onLaunch, onShow, onHide, onLoad, onReady } from '@dcloudio/uni-app'
import { nextTick, onMounted, ref } from 'vue'
import { useUserStore } from '@/store/user'
import { http } from '@/utils/http'
import { useParamsStore } from '@/store/page-params'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { useRouter } from '@/plugin/uni-mini-router'
import { cache, getFileAccessHttpUrl, hasRoute } from '@/common/uitls'
import vPinyin from '../common/vue-py'
import rightConditionFilter from '@/components/RightConditionFilter/RightConditionFilter.vue'
import { TENANT_LIST } from '@/common/constants'
import defaultAvatar from '@/static/default-avatar.png';
const toast = useToast()
const userStore = useUserStore()
const paramsStore = useParamsStore()
const paging = ref(null)
const router = useRouter()
const dataList = ref([])
//
const originData = ref([])
const keyword = ref('')
const dataSource = ref([])
const conditionFilter = reactive({ show: false, checked: '', options: [] })
const queryList = (pageNo, pageSize) => {
const pararms = { pageNo, pageSize, tenantId: conditionFilter.checked }
if (conditionFilter.checked === '') delete pararms.tenantId
http
.get('/sys/user/appQueryUser', pararms)
.then((res: any) => {
if (res.success && res.result.length) {
paging.value.complete(res.result)
} else {
paging.value.complete(false)
}
})
.catch((res) => {
paging.value.complete(false)
})
}
//
watch(dataList, () => {
let result = handleResult(dataList.value)
result = transformData(result)
result.sort((a, b) => a.index.localeCompare(b.index))
originData.value = [...result]
dataSource.value = result
console.log('dataSource:::', dataSource.value)
})
//
function handleSearch() {
dataSource.value = []
nextTick(() => {
if (keyword.value) {
dataSource.value = originData.value.filter((item) => {
return item.data.some((inItem) => {
return inItem.realname.indexOf(keyword.value) != -1
})
})
} else {
dataSource.value = originData.value
}
})
}
//
function handleClear() {
keyword.value = ''
handleSearch()
}
const handleClick = (index: string, data: any) => {
paramsStore.setPageParams('personPage', { backRouteName: 'contacts', data })
router.push({ name: 'personPage' })
}
const handleChange = ({ option }) => {
conditionFilter.checked = option.key
paging.value.reload()
}
const transformData = (data) => {
const grouped = data.reduce((acc, item) => {
const key = item.szm
if (!acc[key]) {
acc[key] = []
}
acc[key].push(item)
return acc
}, {})
return Object.entries(grouped)
.map(([index, data]) => ({ index, data }))
.sort((a, b) => b.index.localeCompare(a.index))
}
const handleResult = (arr) => {
let newArr = []
arr.forEach((item) => {
let { id, realname, avatar, username, phone, email, post, orgCodeTxt } = item
//
if (username !== userStore.userInfo.username) {
let pinYin = realname
if (realname) {
//TODO
if (/.*[\u4e00-\u9fa5]+.*$/.test(realname)) {
pinYin = vPinyin.chineseToPinYin(realname)
}
}
if (avatar) {
avatar = getFileAccessHttpUrl(avatar)
}
let szm = pinYin.substr(0, 1)
var numReg = /^[0-9]*$/
var numRe = new RegExp(numReg)
szm = !numRe.test(szm) ? szm.toUpperCase() : '#'
newArr.push({
id,
realname,
avatar,
username,
phone,
email,
post,
orgCodeTxt,
szm: szm,
})
}
})
return newArr
}
onLoad(() => {
const tenantList = cache(TENANT_LIST)
const result = tenantList?.map((item) => {
return { key: item.id, title: item.name }
})
conditionFilter.options = [{ key: '', title: '全部' }, ...result]
})
</script>
<style lang="scss" scoped>
.wrap {
height: 100%;
}
.z-paging-content {
// :deep(.zp-paging-container) {
// flex: none;
// height: calc(100% - 42px);
// .zp-paging-container-content {
// height: 100%;
// }
// }
}
:deep(.avatar) {
border-radius: 50%;
overflow: hidden;
margin-right: 10px;
background-color: #eee;
}
.content {
display: flex;
flex-direction: column;
uni-text {
&:first-child {
font-size: 16px;
color: #8799a3;
}
&:last-child {
font-size: 12px;
}
}
}
.wraper {
// height: 100%;
height: calc(100vh - var(--window-top) - constant(safe-area-inset-bottom) - 140px);
height: calc(100vh - var(--window-top) - env(safe-area-inset-bottom) - 140px);
:deep(.wd-cell__right) {
display: none;
}
}
</style>

View File

@ -0,0 +1,126 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
}
</route>
<template>
<PageLayout
:backRouteName="backRouteName"
navLeftText=""
:navBgTransparent="true"
:navFixed="true"
>
<view class="wrap">
<view class="topArea"></view>
<view class="middleArea bg-white">
<wd-img
custom-class="avatar"
width="75px"
height="75px"
radius="50%"
:src="data.avatar"
></wd-img>
<wd-text custom-class="realname center" :text="data.realname"></wd-text>
<wd-button custom-class="" @click="handleGo">发消息</wd-button>
</view>
<view class="bottomArea bg-white">
<view class="list">
<view class="iconBox">
<view class="cuIcon-mobile text-gray-4"></view>
</view>
<view class="content">
<view class="label text-gray-4">手机</view>
<view class="value text-blue-5">{{ data.phone || '未填写' }}</view>
</view>
</view>
<view class="list">
<view class="iconBox">
<view class="cuIcon-mail text-gray-4"></view>
</view>
<view class="content">
<view class="label text-gray-4">手机</view>
<view class="value text-blue-5">{{ data.email || '未填写' }}</view>
</view>
</view>
</view>
</view>
</PageLayout>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useUserStore } from '@/store/user'
import { useParamsStore } from '@/store/page-params'
import { useRouter } from '@/plugin/uni-mini-router'
//
const userStore = useUserStore()
const paramsStore = useParamsStore()
const router = useRouter()
const params = paramsStore.getPageParams('personPage')
const backRouteName = ref(params.backRouteName) ?? {}
let data = params.data ?? {}
const handleGo = () => {
var parmas = {
fromAvatar: data.avatar,
fromUserName: data.realname || data.username,
msgFrom: userStore.userInfo.userid,
msgTo: data.id,
type: 'friend',
}
paramsStore.setPageParams('chat', { back: 'personPage', data: parmas })
router.push({ name: 'chat' })
}
</script>
<style lang="scss" scoped>
//
.topArea {
background: linear-gradient(45deg, #0081ff, #1cbbb4);
min-height: 170px;
}
.middleArea {
position: relative;
display: flex;
flex-direction: column;
padding-bottom: 30px;
.avatar {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -50%);
}
.realname {
padding-top: 50px;
font-size: 18px;
font-weight: 700;
color: #333;
margin-bottom: 20px;
}
}
.bottomArea {
.list {
border-top: 1px solid #f1f1f1;
display: flex;
align-items: center;
padding: 10px;
.iconBox {
font-size: 28px;
margin-right: 10px;
}
.label {
font-size: 15px;
margin-bottom: 4px;
}
}
.value {
color: #3665cb;
}
}
</style>

View File

@ -0,0 +1,117 @@
<template>
<view class="wrap">
<view class="content">
<scroll-view class="scrollArea" scroll-y>
<template v-if="dataSource.length">
<template v-for="(item, index) in dataSource">
<wd-cell border clickable @click="handleClick(item)">
<template #icon>
<wd-img
customClass="mr2"
:radius="getRadius(item)"
:width="30"
:height="30"
:src="getImg(item)"
></wd-img>
</template>
<template #title>
<view class="content text-gray-4">
<text>{{ getName(item) }}</text>
</view>
</template>
</wd-cell>
</template>
</template>
<template v-else>
<wd-status-tip image="content" tip="暂无内容" />
</template>
</scroll-view>
</view>
</view>
<wd-toast />
</template>
<script lang="ts" setup>
import { onLaunch, onShow, onHide, onLoad, onReady } from '@dcloudio/uni-app'
import { nextTick, onMounted, ref } from 'vue'
import { useUserStore } from '@/store/user'
import { http } from '@/utils/http'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { useRouter } from '@/plugin/uni-mini-router'
import { useParamsStore } from '@/store/page-params'
import defaultAvatar from '@/static/default-avatar.png'
import folderImg from '@/static/folder.png'
const router = useRouter()
const userStore = useUserStore()
const toast = useToast()
const props = defineProps(['tenantId'])
const dataSource: any = ref([])
const paramsStore = useParamsStore()
const api = {
depTree: '/sys/sysDepart/queryBookDepTreeSync',
list: '/sys/user/list',
}
const getImg = (item) => {
if (item.parentId != null) {
return folderImg
} else {
return item.avatar
}
}
const getName = (item) => {
if (item.parentId != null) {
return item.departName
} else {
return item.realname
}
}
const getRadius = (item) => {
return item.parentId != null ? null : '50%'
}
const handleClick = (item) => {
if (item.parentId != null) {
query({ id: item.id })
} else {
paramsStore.setPageParams('personPage', { data: item })
router.push({ name: 'personPage' })
}
}
const query = (params: any = {}) => {
const pararms = { pid: params.id ?? '', departId: params.id, tenantId: props.tenantId }
Promise.all([
http.get(api.depTree, pararms),
pararms.pid
? http.get(api.list, pararms)
: Promise.resolve({ success: true, result: { records: [] } }),
])
.then((res: any) => {
if (res[0].success == true && res[1].success == true) {
const result = res[0]?.result ?? []
const records = res[1]?.result?.records ?? []
const data = [...result, ...records]
if (params.id) {
//
if (data.length) {
dataSource.value = data
} else {
toast.warning('下一级无数据~')
}
} else {
dataSource.value = data
}
}
})
.catch((res) => {})
}
query()
</script>
<style lang="scss" scoped>
:deep(.wd-cell) {
.wd-cell__right {
display: none;
}
}
</style>

View File

@ -0,0 +1,234 @@
<template>
<view class="wrap">
<!-- prettier-ignore -->
<z-paging ref="paging" :fixed="false" v-model="dataList" @query="queryList" :default-page-size="100">
<template #top>
<wd-search
hide-cancel
placeholder="我要去哪里?"
v-model="keyword"
@search="handleSearch"
@clear="handleClear"
/>
</template>
<view class="wraper">
<wd-index-bar sticky v-if="dataSource.length">
<view v-for="item in dataSource" :key="item.index">
<wd-index-anchor :index="item.index" />
<wd-cell
border
clickable
v-for="inItem in item.data"
:key="item.username"
@click="handleClick(item.index, inItem)"
>
<template #icon>
<wd-img customClass="avatar" :width="50" :height="50" :src="inItem.avatar || defaultAvatar"></wd-img>
</template>
<template #title>
<view class="content text-gray-4">
<text>{{ inItem.realname }}</text>
<text>{{ inItem.orgCodeTxt ?? '暂无' }}</text>
</view>
</template>
</wd-cell>
</view>
</wd-index-bar>
<template v-else>
<wd-status-tip image="content" tip="暂无内容" />
</template>
</view>
</z-paging>
</view>
</template>
<script lang="ts" setup>
import { onLaunch, onShow, onHide, onLoad, onReady } from '@dcloudio/uni-app'
import { nextTick, onMounted, ref } from 'vue'
import { useUserStore } from '@/store/user'
import { http } from '@/utils/http'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { useRouter } from '@/plugin/uni-mini-router'
import { cache, getFileAccessHttpUrl, hasRoute } from '@/common/uitls'
import vPinyin from '../../common/vue-py'
import { TENANT_LIST } from '@/common/constants'
import { useParamsStore } from '@/store/page-params'
import defaultAvatar from '@/static/default-avatar.png'
const props = defineProps(['workType', 'tenantId'])
const toast = useToast()
const userStore = useUserStore()
const paramsStore = useParamsStore()
const paging = ref(null)
const router = useRouter()
const dataList = ref([])
//
const originData = ref([])
const keyword = ref('')
const dataSource = ref([])
const api = {
appQueryUser: '/sys/user/appQueryUser',
getMyGroupList: '/eoa/im/newApi/getMyGroupList',
}
const queryList = (pageNo, pageSize) => {
const pararms = { pageNo, pageSize, tenantId: props.tenantId }
let url
if (props.workType) {
url = api.getMyGroupList
pararms['type'] = props.workType
} else {
url = api.appQueryUser
}
http
.get(url, pararms)
.then((res: any) => {
if (res.success && res.result.length) {
paging.value.complete(res.result)
} else {
paging.value.complete(false)
}
})
.catch((res) => {
paging.value.complete(false)
})
}
//
watch(dataList, () => {
let result = handleResult(dataList.value)
result = transformData(result)
result.sort((a, b) => a.index.localeCompare(b.index))
originData.value = [...result]
dataSource.value = result
})
//
function handleSearch() {
dataSource.value = []
nextTick(() => {
if (keyword.value) {
dataSource.value = originData.value.filter((item) => {
return item.data.some((inItem) => {
return inItem.realname.indexOf(keyword.value) != -1
})
})
} else {
dataSource.value = originData.value
}
})
}
//
function handleClear() {
keyword.value = ''
handleSearch()
}
const handleClick = (index: string, data: any) => {
if (props.workType) {
toast.warning('群组聊天暂未开发~')
} else {
paramsStore.setPageParams('personPage', { data })
router.push({ name: 'personPage' })
}
}
const handleChange = ({ option }) => {
paging.value.reload()
}
const transformData = (data) => {
const grouped = data.reduce((acc, item) => {
const key = item.szm
if (!acc[key]) {
acc[key] = []
}
acc[key].push(item)
return acc
}, {})
return Object.entries(grouped)
.map(([index, data]) => ({ index, data }))
.sort((a, b) => b.index.localeCompare(a.index))
}
const handleResult = (arr) => {
let newArr = []
arr.forEach((item) => {
let { id, realname, avatar, username, phone, email, post, orgCodeTxt } = item
//
if (username !== userStore.userInfo.username) {
let pinYin = realname
if (realname) {
//TODO
if (/.*[\u4e00-\u9fa5]+.*$/.test(realname)) {
pinYin = vPinyin.chineseToPinYin(realname)
}
}
if (avatar) {
avatar = getFileAccessHttpUrl(avatar)
}
let szm = pinYin.substr(0, 1)
var numReg = /^[0-9]*$/
var numRe = new RegExp(numReg)
szm = !numRe.test(szm) ? szm.toUpperCase() : '#'
newArr.push({
id,
realname,
avatar,
username,
phone,
email,
post,
orgCodeTxt,
szm: szm,
})
}
})
return newArr
}
onLoad(() => {
const tenantList = cache(TENANT_LIST)
const result = tenantList?.map((item) => {
return { key: item.id, title: item.name }
})
})
</script>
<style lang="scss" scoped>
.wrap {
height: 100%;
}
.z-paging-content {
// :deep(.zp-paging-container) {
// flex: none;
// height: calc(100% - 42px);
// .zp-paging-container-content {
// height: 100%;
// }
// }
}
:deep(.avatar) {
border-radius: 50%;
overflow: hidden;
margin-right: 10px;
}
.content {
display: flex;
flex-direction: column;
uni-text {
&:first-child {
font-size: 16px;
color: #8799a3;
}
&:last-child {
font-size: 12px;
}
}
}
.wraper {
// height: 100%;
height: calc(100vh - var(--window-top) - constant(safe-area-inset-bottom) - 140px);
height: calc(100vh - var(--window-top) - env(safe-area-inset-bottom) - 140px);
:deep(.wd-cell__right) {
display: none;
}
}
</style>

View File

@ -0,0 +1,125 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
styleIsolation: 'apply-shared',
},
}
</route>
<template>
<PageLayout :navTitle="title" backRouteName="message" routeMethod="pushTab">
<wd-tabs customClass="" v-model="tabActive">
<template v-for="(item, index) in tabList" :key="index">
<wd-tab :title="item.title" :name="item.key">
<workmate
v-if="tabActive == '1'"
:tenantId="tenantId"
:key="reloadKey"
:workType="workType"
></workmate>
<department v-if="tabActive == '2'" :tenantId="tenantId"></department>
</wd-tab>
</template>
</wd-tabs>
<template #navRight>
<view
class="cuIcon-filter font-size-20px color-white"
@click="() => (conditionFilter.show = true)"
></view>
</template>
<rightConditionFilter
v-if="conditionFilter.show"
v-bind="conditionFilter"
@close="() => (conditionFilter.show = false)"
@change="handleChange"
></rightConditionFilter>
</PageLayout>
</template>
<script setup lang="ts">
import { onShow, onHide, onLoad, onReady } from '@dcloudio/uni-app'
import { ref } from 'vue'
import workmate from './components/workmate.vue'
import department from './components/department.vue'
import rightConditionFilter from '@/components/RightConditionFilter/RightConditionFilter.vue'
defineOptions({
name: 'tenant',
options: {
// apply-shared.()
// shared.()
styleIsolation: 'shared',
},
})
const reloadKey = ref(1)
const tabList = ref([
{ key: '1', title: '全部' },
{ key: '2', title: '按部门' },
])
const conditionFilter = reactive({
show: false,
checked: 'all',
options: [
{ key: 'all', title: '所有同事' },
{ key: 'group', title: '群组' },
],
})
const tabActive = ref('2')
const tenantId = ref()
const title = ref()
const workType = ref('')
const handleChange = ({ option }) => {
conditionFilter.checked = option.key
if (option.key == 'all') {
tabList.value[1]['title'] = '按部门'
workType.value = ''
} else if (option.key == 'group') {
tabList.value[1]['title'] = '我创建的'
if (tabActive.value == '1') {
workType.value = 'allGroup'
} else {
workType.value = 'createdGroup'
}
}
tabList.value = [...tabList.value]
//
reloadKey.value = Math.random()
}
const init = () => {}
init()
onLoad((options) => {
tenantId.value = options.id
title.value = options.title
})
</script>
<style lang="scss" scoped>
:deep(.wd-tabs) {
height: 100%;
display: flex;
flex-direction: column;
.wd-tabs__nav {
border-bottom: 1px solid #f1f1f1;
}
.wd-tabs__container {
flex: 1;
width: 100%;
}
.wd-tabs__body {
position: relative;
}
}
:deep(.wd-tab) {
.wd-tab__body {
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
background-color: #f1f1f1;
}
}
</style>

View File

@ -0,0 +1,181 @@
<template>
<view class="onlineTableCell">
<!--图片-->
<template v-if="column?.scopedSlots?.customRender === 'imgSlot'">
<template v-if="record[column.dataIndex]">
<wd-img
width="30"
height="30"
:src="getFirstImg(record[column.dataIndex])"
@click="handleClickImg"
></wd-img>
<ImgPreview
v-if="imgPreview.show"
:urls="imgPreview.urls"
@close="() => (imgPreview.show = false)"
></ImgPreview>
</template>
<template v-else>
<text>无图片</text>
</template>
</template>
<!--下载-->
<template v-else-if="column?.scopedSlots?.customRender === 'fileSlot'">
<template v-if="record[column.dataIndex]">
<wd-button @click="handleDownload(record[column.dataIndex])">下载</wd-button>
</template>
<template v-else>
<text>无文件</text>
</template>
</template>
<template v-else-if="column?.scopedSlots?.customRender === 'htmlSlot'">
<!-- 增加富文本控件配置href跳转 -->
<template v-if="column.fieldHref">
<text class="ellipsis-2">暂不支持</text>
</template>
<template v-else>
<rich-text :nodes="record[column.dataIndex]"></rich-text>
</template>
</template>
<template v-else-if="column?.scopedSlots?.customRender === 'pcaSlot'">
<text class="ellipsis-2">{{ getPcaText(record[column.dataIndex]) || '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'}}</text>
</template>
<template v-else-if="column?.scopedSlots?.customRender === 'dateSlot'">
<text class="ellipsis-2">{{ getFormatDate(record[column.dataIndex], column) || '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'}}</text>
</template>
<template v-else>
<text class="ellipsis-2">{{ renderVal(record, column) || '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' }}</text>
</template>
</view>
</template>
<script setup lang="ts">
import { getFormatDate, filterMultiDictText } from '../utils/index'
import { isString } from '@/utils/is'
import { getFileAccessHttpUrl } from '@/common/uitls'
import { getAreaTextByCode } from '@/common/areaData/Area'
defineOptions({
name: 'onlineTableCell',
options: {
styleIsolation: 'shared',
},
})
const props = defineProps({
columnsInfo: {
type: Object,
default: () => {},
},
column: {
type: Object,
default: () => {},
},
record: {
type: Object,
default: () => {},
},
})
const imgPreview = ref({
show: false,
urls: [],
})
//
const handleDownload = (text) => {
uni.downloadFile({
url: text,
success: (res) => {
if (res.statusCode === 200) {
console.log('下载成功')
console.log(res);
}
},
})
}
//
const getPcaText = (code) => {
if (!code) {
return ''
}
return getAreaTextByCode(code)
}
//
const getFirstImg = (text) => {
if (isString(text)) {
var imgs = text.split(',')
return getFileAccessHttpUrl(imgs[0])
} else {
return ''
}
}
//
const handleClickImg = () => {
imgPreview.value.show = true
}
//
const renderVal = (record, column) => {
const { customRender, hrefSlotName, dataIndex, fieldType } = column
let text = record[dataIndex]
if (['date', 'Date'].includes(column['fieldType'])) {
// onlineyyyy-MM-dd (yyyy-MM-dd HH:mm:ss)
if (!text) {
return ''
}
if (text.length > 10) {
return text.substring(0, 10)
}
return text
} else if (['popup_dict'].includes(column['fieldType'])) {
const dict = record[dataIndex + '_dictText']
if (dict != undefined) {
return record[dataIndex + '_dictText']
}
return text
} else if (customRender) {
//
let dictCode = customRender as string
let replaceFlag = '_replace_text_'
let value = text
// dictCode
if (dictCode) {
if (dictCode.startsWith(replaceFlag)) {
let textFieldName = dictCode.replace(replaceFlag, '')
value = record[textFieldName]
} else {
value = filterMultiDictText(unref(props.columnsInfo.dictOptions)[dictCode], text + '')
}
}
//
if (column.showLength) {
if (value && value.length > column.showLength) {
value = value.substr(0, column.showLength) + '...'
}
}
return value
} else {
return text
}
}
//
const init = () => {
const field = props.column.dataIndex
if (props.column?.scopedSlots?.customRender === 'imgSlot') {
const text = props.record[field]
if (isString(text)) {
imgPreview.value.urls = text.split(',').map((item) => getFileAccessHttpUrl(item))
} else {
return ''
}
}
}
init()
</script>
<style lang="scss" scoped>
:deep(.wd-button) {
--wot-button-medium-height: 30px;
--wot-button-medium-fs: 12px;
--wot-button-medium-padding: 8px;
&.is-medium.is-round {
min-width: 80px;
}
}
</style>

View File

@ -0,0 +1,86 @@
import { ref, computed } from 'vue'
import { http } from '@/utils/http'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { useRouter } from '@/plugin/uni-mini-router'
import { useUserStore } from '@/store/user'
import { useParamsStore } from '@/store/page-params'
export function useOnline() {
const toast = useToast()
const router = useRouter()
const userStore = useUserStore()
const globalData = getApp().globalData
const { systemInfo } = globalData
const { safeArea } = systemInfo
const paramsStore = useParamsStore()
const columns = ref([])
const columnsInfo = ref({})
const pageNo = ref(1)
const pageSize = ref(10)
const pageTotal = ref(1)
let pageParams = paramsStore.getPageParams('onlineCard')?.data ?? {}
const dataList = ref([])
const queryParams = () => {
return {
pageNo: pageNo.value,
pageSize: pageSize.value,
order: 'asc',
column: 'id',
hasQuery: true,
}
}
//
const getColumns = () => {
return new Promise<void>((resove, reject) => {
if (columns.value.length) {
resove()
return
}
const analysis = (data) => {
const len = data.length
const maxShowColumn = 3
let space = 1
if (len == 1) {
space = 2
}
const width = safeArea.width / (len > maxShowColumn ? maxShowColumn : len) - space
columns.value = data.map((item) => {
return {
...item,
prop: item.dataIndex,
align: item.align,
label: item.title,
width,
}
})
}
http
.get(`/online/cgform/api/getColumns/${pageParams.id}`)
.then((res: any) => {
if (res.success) {
if (res.result?.columns?.length) {
columnsInfo.value = res.result
analysis(res.result.columns)
resove()
}
} else {
toast.warning(res.message)
reject()
}
})
.catch((res) => {
toast.error('加载列头失败~')
reject()
})
})
}
return {
toast,
router,
userStore,
paramsStore,
columns,
columnsInfo,
}
}

View File

@ -0,0 +1,249 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: 'online',
navigationStyle: 'custom',
},
}
</route>
<template>
<PageLayout navTitle="online表单开发" backRouteName="index" routeMethod="pushTab">
<view class="wrap">
<z-paging
ref="paging"
:fixed="false"
v-model="dataList"
@query="queryList"
:default-page-size="20"
>
<template #top>
<wd-search
hide-cancel
placeholder="请输入表描述"
v-model.trim="keyword"
@search="handleSearch"
@clear="handleClear"
/>
</template>
<template v-for="(item, index) in dataList" :key="item.id">
<template v-if="item.tableType != 3">
<wd-swipe-action>
<view class="list" @click="handleGo(item)">
<view
class="cIcon"
:style="{ 'background-color': getBackgroundColor(item, index) }"
>
<view class="u-iconfont u-icon-table"></view>
</view>
<view class="tableTxt ellipsis">{{ item.tableTxt }}</view>
<view class="createTime ellipsis">{{ item.createTime.substring(0, 10) }}</view>
</view>
<template #right>
<view class="action">
<view class="button" @click="handleAction('del', item)">删除</view>
<view class="button" @click="handleAction('remove', item)">移除</view>
</view>
</template>
</wd-swipe-action>
</template>
</template>
</z-paging>
</view>
</PageLayout>
</template>
<script lang="ts" setup>
import { http } from '@/utils/http'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { useRouter } from '@/plugin/uni-mini-router'
import { useUserStore } from '@/store/user'
import { useParamsStore } from '@/store/page-params'
import { getRandomColor } from '@/common/uitls'
defineOptions({
name: 'online',
options: {
styleIsolation: 'shared',
},
})
const toast = useToast()
const router = useRouter()
const userStore = useUserStore()
const paramsStore = useParamsStore()
const paging = ref(null)
const dataList: any = ref([])
const keyword = ref('')
const itemBgColor = []
//
const handleGo = (item) => {
if (item.tableType === 3) {
toast.warning('附表无列表页~')
} else {
paramsStore.setPageParams('onlineCard', { data: item })
router.push({ name: 'onlineCard' })
}
}
//
function handleClear() {
keyword.value = ''
handleSearch()
}
//
function handleSearch() {
queryList(1, 10)
}
const handleAction = (val, item) => {
if (val == 'del') {
http
.delete('/online/cgform/head/delete', { id: item })
.then((res: any) => {
if (res.success) {
toast.success('删除成功~')
paging.value.reload()
} else {
toast.warning('删除失败~')
}
})
.catch((res) => {
toast.error('删除失败~')
})
} else if ((val = 'remove')) {
http
.delete('/online/cgform/head/removeRecord', { id: item })
.then((res: any) => {
if (res.success) {
toast.success('移除成功~')
paging.value.reload()
} else {
toast.warning('移除失败~')
}
})
.catch((res) => {
toast.error('移除失败~')
})
}
}
const getParams = ({ pageNo, pageSize }) => {
const params: any = {
pageNo,
pageSize,
order: 'desc',
column: 'createTime',
}
if (keyword.value.length) {
params.tableTxt = `*${keyword.value}*`
}
return params
}
const queryList = (pageNo, pageSize) => {
const params = getParams({ pageNo, pageSize })
http
.get('/online/cgform/head/list', { ...params })
.then((res: any) => {
if (res.success && res.result?.records) {
if (pageNo === 1) {
dataList.value = []
}
paging.value.complete(res.result.records)
} else {
paging.value.complete(false)
}
})
.catch((res) => {
paging.value.complete(false)
})
}
const getType = (record) => {
const type = { 1: '单表', 2: '主表', 3: '附表' }
let tbTypeText = type[record.tableType]
// if (record.isTree === 'Y') {
// tbTypeText += '()'
// }
// if (record.themeTemplate === 'innerTable') {
// tbTypeText += '()'
// } else if (record.themeTemplate === 'erp') {
// tbTypeText += '(ERP)'
// } else if (record.themeTemplate === 'tab') {
// tbTypeText += '(TAB)'
// }
return tbTypeText
}
const getBackgroundColor = (item, index) => {
return itemBgColor[index % itemBgColor.length]
}
for (let i = 0; i < 50; i++) {
itemBgColor.push(getRandomColor())
}
</script>
<style lang="scss" scoped>
//
:deep(.wd-search) {
border-bottom: 1px solid #f4f2f2;
}
.wd-swipe-action {
&:first-child {
margin-top: 10px;
}
}
.list {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
border-bottom: 1px solid #eee;
padding: 16px 12px;
line-height: 20px;
margin-bottom: 10px;
.cIcon {
text-align: center;
line-height: 24px;
color: #fff;
margin-right: 8px;
width: 24px;
height: 24px;
border-radius: 50%;
.u-iconfont {
font-size: 14px;
}
}
.tableTxt {
flex: 1;
margin-right: 40px;
}
.createTime {
text-align: right;
width: 75px;
font-size: 12px;
color: #919191;
}
}
.action {
width: 100px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
.button {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
height: 100%;
color: #fff;
&:first-child {
background-color: #fa4350;
}
&:last-child {
background-color: #f0883a;
}
}
}
.wrap {
height: 100%;
}
</style>

View File

@ -0,0 +1,264 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
}
</route>
<template>
<PageLayout backRouteName="online" navTitle="online在线表单">
<view class="wrap">
<z-paging
ref="paging"
:fixed="false"
v-model="dataList"
@query="queryList"
:default-page-size="15"
>
<template v-for="(item, index) in dataList" :key="item.id">
<wd-swipe-action>
<view class="list" @click="handleEdit(item)">
<template v-for="(cItem, cIndex) in columns" :key="index">
<view v-if="cIndex < 3" class="box" :style="getBoxStyle">
<view class="field ellipsis">{{ cItem['title'] }}</view>
<view class="value text-grey">
<onlineTableCell
:columnsInfo="columnsInfo"
:record="item"
:column="cItem"
:key="item.id"
></onlineTableCell>
</view>
</view>
</template>
</view>
<template #right>
<view class="action">
<view class="button" @click="handleAction('del', item)">删除</view>
<!-- <view class="button" @click="handleAction('view', item)">查看</view> -->
</view>
</template>
</wd-swipe-action>
</template>
</z-paging>
<view class="add u-iconfont u-icon-add" @click="handleAdd"></view>
<view class="goTable u-iconfont u-icon-table" @click="handleGoTable"></view>
</view>
</PageLayout>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { http } from '@/utils/http'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { useRouter } from '@/plugin/uni-mini-router'
import { useUserStore } from '@/store/user'
import { useParamsStore } from '@/store/page-params'
import onlineTableCell from './components/onlineTableCell.vue'
defineOptions({
name: 'onlineCard',
options: {
styleIsolation: 'shared',
},
})
const toast = useToast()
const router = useRouter()
const userStore = useUserStore()
const paramsStore = useParamsStore()
const globalData = getApp().globalData
const { systemInfo } = globalData
const { safeArea } = systemInfo
const paging = ref(null)
const columns = ref([])
const columnsInfo = ref({})
const pageNo = ref(1)
const pageSize = ref(10)
const pageTotal = ref(1)
let pageParams = paramsStore.getPageParams('onlineCard')?.data ?? {}
const dataList = ref([])
const getBoxStyle = computed(() => {
let len = columns.value.length
if (len > 3) len = 3
return { width: `calc(${100 / len}% - 5px)` }
})
const queryParams = () => {
return {
pageNo: pageNo.value,
pageSize: pageSize.value,
order: 'asc',
column: 'id',
hasQuery: true,
}
}
const getColumns = () => {
return new Promise<void>((resove, reject) => {
if (columns.value.length) {
resove()
return
}
const analysis = (data) => {
const len = data.length
const maxShowColumn = 3
let space = 1
if (len == 1) {
space = 2
}
const width = safeArea.width / (len > maxShowColumn ? maxShowColumn : len) - space
columns.value = data.map((item) => {
return {
...item,
prop: item.dataIndex,
align: item.align,
label: item.title,
width,
}
})
}
http
.get(`/online/cgform/api/getColumns/${pageParams.id}`)
.then((res: any) => {
if (res.success) {
if (res.result?.columns?.length) {
columnsInfo.value = res.result
analysis(res.result.columns)
resove()
}
} else {
toast.warning(res.message)
reject()
}
})
.catch((res) => {
toast.error('加载列头失败~')
reject()
})
})
}
const getData = () => {
http
.get(`/online/cgform/api/getData/${pageParams.id}`, { ...queryParams() })
.then((res: any) => {
if (res.success) {
paging.value.complete(res.result?.records ?? [])
} else {
toast.warning(res.message)
}
})
.catch((res) => {
toast.error('加载表格数据失败~')
})
}
const handleAction = (val, item) => {
if (val == 'del') {
http.delete(`/online/cgform/api/form/${pageParams.id}/${item.id}`).then((res) => {
toast.success('删除成功~')
paging.value.reload()
})
}
}
const queryList = (_pageNo, _pageSize) => {
pageNo.value = _pageNo
pageSize.value = _pageSize
getColumns().then(() => {
getData()
})
}
// go
const handleAdd = () => {
router.push({
name: 'onlineAdd',
params: {
desformCode: pageParams.tableName,
desformName: pageParams.tableTxt,
backRouteName: 'onlineCard',
},
})
}
// go table
const handleGoTable = (params) => {
paramsStore.setPageParams('onlineTable', { data: pageParams })
router.push({ name: 'onlineTable' })
}
//go
const handleEdit = (record) => {
router.push({
name: 'onlineEdit',
params: {
desformCode: pageParams.tableName,
desformName: pageParams.tableTxt,
id: record.id,
backRouteName: 'onlineCard',
},
})
}
onMounted(() => {
//
uni.$on('refreshList', () => {
getData()
})
})
</script>
<style lang="scss" scoped>
.wrap {
height: 100%;
}
:deep(.wd-swipe-action) {
margin-top: 10px;
background-color: #fff;
}
.list {
padding: 10px 10px;
width: 100%;
text-align: left;
display: flex;
justify-content: space-between;
.box {
width: 33%;
.field {
margin-bottom: 10px;
line-height: 20px;
}
}
}
.action {
width: 60px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
.button {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
height: 100%;
color: #fff;
&:first-child {
background-color: #fa4350;
}
&:last-child {
// background-color: #f0883a;
}
}
}
.add,
.goTable {
height: 70upx;
width: 70upx;
text-align: center;
line-height: 70upx;
background-color: #fff;
border-radius: 50%;
position: fixed;
bottom: 80upx;
right: 30upx;
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.1);
color: #666;
}
.goTable {
bottom: 180upx;
}
</style>

View File

@ -0,0 +1,257 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
}
</route>
<template>
<PageLayout backRouteName="online" navTitle="online在线表单">
<wd-table :index="tableIndex" :data="dataList" @row-click="handleRow">
<template v-for="(item, index) in columns" :key="item.prop">
<wd-table-col
:prop="item.prop"
:label="item.label"
:width="item.width"
:fixed="item.fixed"
:align="item.align"
:sortable="item.sortable"
>
<template #value="{ row, index }">
<onlineTableCell
:columnsInfo="columnsInfo"
:record="row"
:column="item"
@longpress.prevent="handleLongPress(row)"
></onlineTableCell>
</template>
</wd-table-col>
</template>
</wd-table>
<wd-status-tip v-if="dataList.length == 0" image="content" tip="暂无内容" />
<wd-pagination
v-model="pageNo"
:total="pageTotal"
:page-size="pageSize"
@change="handlePaginChange"
show-icon
></wd-pagination>
<view class="add u-iconfont u-icon-add" @click="handleGo"></view>
</PageLayout>
<BottomOperate
v-if="bottomOperatePopup.show"
v-bind="bottomOperatePopup"
@close="() => (bottomOperatePopup.show = false)"
@change="handleChange"
></BottomOperate>
</template>
<script lang="ts" setup>
import { http } from '@/utils/http'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { useRouter } from '@/plugin/uni-mini-router'
import { useUserStore } from '@/store/user'
import { useParamsStore } from '@/store/page-params'
import onlineTableCell from './components/onlineTableCell.vue'
defineOptions({
name: 'onlineTable',
options: {
styleIsolation: 'shared',
},
})
const toast = useToast()
const router = useRouter()
const userStore = useUserStore()
const paramsStore = useParamsStore()
const globalData = getApp().globalData
const { systemInfo } = globalData
const { safeArea } = systemInfo
const columns = ref([])
const columnsInfo = ref({})
const tableIndex = ref(false)
const pageNo = ref(1)
const pageSize = ref(10)
const pageTotal = ref(1)
let pageParams: any = {}
let rowIndex: any = ref(0)
//
const bottomOperatePopup = reactive({
show: false,
title: '操作',
data: {},
options: [
{ key: 'edit', icon: 'edit', label: '编辑' },
{ key: 'detail', icon: 'view', label: '查看' },
{ key: 'delete', icon: 'delete', label: '删除', color: 'red' },
],
})
const queryParams = () => {
return {
pageNo: pageNo.value,
pageSize: pageSize.value,
order: 'asc',
column: 'id',
hasQuery: true,
}
}
const dataList = ref([])
const getColumns = () => {
return new Promise<void>((resove, reject) => {
const analysis = (data) => {
const len = data.length
const maxShowColumn = 3
let space = 1
if (len == 1) {
space = 2
}
const width = safeArea.width / (len > maxShowColumn ? maxShowColumn : len) - space
columns.value = data.map((item) => {
return {
...item,
prop: item.dataIndex,
align: item.align,
label: item.title,
width,
}
})
}
http
.get(`/online/cgform/api/getColumns/${pageParams.id}`)
.then((res: any) => {
if (res.success) {
if (res.result?.columns?.length) {
columnsInfo.value = res.result
analysis(res.result.columns)
}
} else {
toast.warning(res.message)
}
})
.catch((res) => {
toast.error('加载列头失败~')
})
})
}
const getData = () => {
http
.get(`/online/cgform/api/getData/${pageParams.id}`, { ...queryParams() })
.then((res: any) => {
if (res.success) {
dataList.value = res.result?.records ?? []
pageTotal.value = res.result.total
} else {
toast.warning(res.message)
}
})
.catch((res) => {
toast.error('加载表格数据失败~')
})
}
const handlePaginChange = ({ value }) => {
pageNo.value = value
getData()
}
//go
const handleGo = () => {
router.push({
name: 'onlineAdd',
params: { desformCode: pageParams.tableName, desformName: pageParams.tableTxt },
})
}
//go
const handleEdit = (record) => {
router.push({
name: 'onlineEdit',
params: { desformCode: pageParams.tableName, desformName: pageParams.tableTxt, id: record.id },
})
}
//go
const handleView = (record) => {
router.push({
name: 'onlineDetail',
params: { desformCode: pageParams.tableName, desformName: pageParams.tableTxt, id: record.id },
})
}
//
const handleLongPress = (item) => {
bottomOperatePopup.show = true
bottomOperatePopup.data = item
}
//
const handleChange = ({ option, data }) => {
if (option.key === 'edit') {
handleEdit(data)
} else if (option.key === 'delete') {
uni.showModal({
title: '提示',
content: '确定要删除吗?',
cancelText: '取消',
confirmText: '确定',
success: (res) => {
if (res.confirm) {
http.delete(`/online/cgform/api/form/${pageParams.id}/${data.id}`).then((res) => {
toast.success('删除成功~')
getData()
})
}
},
fail: (err) => {
console.log(err)
},
})
} else if (option.key === 'detail') {
handleView(data)
}
}
const handleRow = ({ rowIndex }) => {
handleEdit(dataList.value[rowIndex])
}
const init = () => {
pageParams = paramsStore.getPageParams('onlineTable')?.data ?? {}
console.log('pageParams:', pageParams)
getColumns()
getData()
}
init()
onMounted(() => {
//
uni.$on('refreshList', () => {
getData()
})
})
</script>
<style lang="scss" scoped>
//
.add {
height: 70upx;
width: 70upx;
text-align: center;
line-height: 70upx;
background-color: #fff;
border-radius: 50%;
position: fixed;
bottom: 80upx;
right: 30upx;
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.1);
color: #666;
}
:deep(.wd-table__content) {
.wot-theme-light {
// width: 100%;
// height: 100%;
}
}
:deep(.wd-table) {
--wot-table-font-size: 14px;
.wd-table__body {
--wot-table-color: var(--color-gray);
}
}
</style>

View File

@ -0,0 +1,64 @@
import { getWeekMonthQuarterYear } from '@/common/uitls'
/**
* 日期格式化
* @param text
*/
export function getFormatDate(text, column) {
if (!text) {
return ''
}
let a = text
if (a.length > 10) {
a = a.substring(0, 10)
}
let fieldExtendJson = column?.fieldExtendJson
if (fieldExtendJson) {
fieldExtendJson = JSON.parse(fieldExtendJson)
if (fieldExtendJson.picker && fieldExtendJson.picker != 'default') {
const result = getWeekMonthQuarterYear(a)
return result[fieldExtendJson.picker]
}
}
return a
}
/**
* 字典值替换文本通用方法(多选)
* @param dictOptions 字典数组
* @param text 字典值
* @return String
*/
export function filterMultiDictText(dictOptions, text) {
//js !text 0
if (text === 0 || text === '0') {
if (dictOptions) {
for (let dictItem of dictOptions) {
if (text == dictItem.value) {
return dictItem.text
}
}
}
}
if (!text || text == 'undefined' || text == 'null' || !dictOptions || dictOptions.length == 0) {
return ''
}
let re = ''
text = text.toString()
let arr = text.split(',')
dictOptions.forEach(function (option) {
if (option) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === option.value) {
re += option.text + ','
break
}
}
}
})
if (re == '') {
return text
}
return re.substring(0, re.length - 1)
}

View File

@ -0,0 +1,89 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
}
</route>
<template>
<PageLayout navTitle="定位" backRouteName="people" routeMethod="pushTab">
<map
style="width: 100%; height: 100%"
:latitude="latitude"
:longitude="longitude"
:markers="marker"
:scale="scale"
></map>
</PageLayout>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { onShow, onHide, onLoad, onReady } from '@dcloudio/uni-app'
const latitude = ref(40.009704)
const longitude = ref(116.374999)
const marker = reactive([
{
id: 0,
latitude: latitude.value, //
longitude: longitude.value, //
iconPath: '/static/location.png', //
rotate: 0, //
width: 20, //
height: 20, //
title: '你在哪了', //
alpha: 0.5, //
/* label:{//为标记点旁边增加标签 //H5
   content:'北京国炬公司',//
    color:'red',//
  fontSize:24,//
x:5,//label marker
y:1,//label marker
borderWidth:12,//
borderColor:'pink',//
  borderRadius:20,//
  bgColor:'black',//
  padding:5,//
textAlign:'right'//
}, */
callout: {
//
content: '北京国炬公司', //
color: '#ffffff', //
fontSize: 14, //
borderRadius: 2, //
bgColor: '#00c16f', //
display: 'ALWAYS', //
},
// anchor:{//
// x:0,
// y:0,
// }
},
])
const scale = 16
const getLocation = () => {
uni.getLocation({
type: 'gcj02',
success: function (res) {
console.log('当前位置的经度:' + res.longitude)
console.log('当前位置的纬度:' + res.latitude)
},
fail: function (res) {
console.log('当前位置的经度')
},
})
}
onLoad(() => {
getLocation()
})
</script>
<style lang="scss" scoped>
//
</style>

View File

@ -0,0 +1,124 @@
<template>
<view class="wrap">
<template v-if="imgSrc">
<view class="showArea">
<view class="iconBox" @click="handleDel">
<view class="cuIcon-close"></view>
</view>
<wd-img
:radius="4"
enable-preview
label="头像"
width="60px"
height="60px"
:src="imgSrc"
></wd-img>
</view>
</template>
<template v-else>
<div class="uploadArea" @click="handleUpload">
<view class="iconBox">
<view class="cuIcon-cameraadd"></view>
</view>
</div>
</template>
<wd-message-box></wd-message-box>
</view>
</template>
<script setup lang="ts">
import { useMessage, useToast } from 'wot-design-uni'
import { computed, watch } from 'vue'
import { getEnvBaseUrl } from '@/utils/index'
defineOptions({
name: 'avatar',
options: {
// apply-shared.()
// shared.()
styleIsolation: 'shared',
},
})
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const toast = useToast()
const message = useMessage()
const api = {
uploadUrl: `${getEnvBaseUrl()}/sys/common/upload`,
}
let stopWatch
const imgSrc = computed(() => {
return props.modelValue
})
const handleDel = () => {
message
.confirm({
msg: '确定要删除这个头像吗?',
title: '提示',
})
.then(() => {
emit('update:modelValue', '')
})
.catch(() => {
console.log('点击了取消按钮')
})
}
const handleUpload = () => {
const { loading, data, error, run } = useUpload({ name: 'file' }, { url: api.uploadUrl })
if (stopWatch) stopWatch()
run()
stopWatch = watch(
() => [loading.value, error.value, data.value],
([loading, err, data], oldValue) => {
if (loading == false) {
if (err) {
toast.warning('修改失败')
} else {
if (data) {
emit('update:modelValue', data.message)
}
}
}
},
)
}
</script>
<style lang="scss" scoped>
.showArea {
position: relative;
width: fit-content;
.iconBox {
position: absolute;
right: 0;
top: 0;
border-bottom-left-radius: 3px;
padding: 3px 6px;
height: auto;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1;
color: #fff;
font-size: 12px;
line-height: 1;
border-radius: 2px;
}
}
.uploadArea {
width: 60px;
height: 60px;
.iconBox {
height: 100%;
width: 100%;
border: 2px solid #eee;
color: #8799a3;
margin: auto;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-size: 27px;
}
}
</style>

View File

@ -0,0 +1,188 @@
<route lang="json5" type="page">
{
layout: 'default',
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
}
</route>
<template>
<PageLayout navTitle="编辑资料" backRouteName="people" routeMethod="pushTab">
<wd-form custom-class="pt3" ref="form" :model="model">
<wd-cell-group border>
<wd-input
label="用户名"
prop="username"
clearable
label-width="70px"
v-model="model.username"
:readonly="true"
placeholder="请输入用户名"
/>
<wd-input
label="名称"
prop="realname"
clearable
label-width="70px"
v-model="model.realname"
placeholder="请输入用户名"
:rules="[{ validator: rules.realname }]"
/>
<wd-cell title="头像">
<avatar v-model="model.avatar"></avatar>
</wd-cell>
<wd-select-picker
label="性别"
type="radio"
v-model="model.sex"
:columns="columns"
title="请选择性别"
:safe-area-inset-bottom="false"
></wd-select-picker>
<wd-input
label="手机号"
prop="phone"
clearable
label-width="70px"
v-model="model.phone"
:readonly="true"
placeholder="请输入手机号"
:rules="[{ validator: rules.phone }]"
/>
<wd-input
label="邮箱"
prop="email"
clearable
label-width="70px"
v-model="model.email"
:readonly="true"
placeholder="请输入邮箱"
:rules="[{ validator: rules.email }]"
/>
</wd-cell-group>
<view class="footer p5">
<wd-button type="primary" size="large" @click="handleSubmit" block>提交</wd-button>
</view>
</wd-form>
</PageLayout>
</template>
<script lang="ts" setup>
import { http } from '@/utils/http'
import { useToast, useMessage, useNotify, dayjs } from 'wot-design-uni'
import { useRouter } from '@/plugin/uni-mini-router'
import { useUserStore } from '@/store/user'
import avatar from './components/avatar.vue'
defineOptions({
name: 'chatList',
options: {
styleIsolation: 'shared',
},
})
const toast = useToast()
const router = useRouter()
const userStore = useUserStore()
const columns = [
{ label: '男', value: 1 },
{ label: '女', value: 2 },
]
const model = reactive({
username: userStore.userInfo.username,
realname: userStore.userInfo.realname,
avatar: userStore.userInfo.avatar,
sex: userStore.userInfo.sex ?? 1,
phone: userStore.userInfo.phone,
email: userStore.userInfo.email,
})
const rules = {
realname: (value) => {
if (value?.trim()?.length) {
return true
} else {
toast.warning('请输入名称')
return false
}
},
phone: (value) => {
if (value?.trim()?.length) {
if (/^1[3-9]\d{9}$/.test(value)) {
return true
} else {
toast.warning('请输入正确的手机号')
return false
}
} else {
toast.warning('请输入手机号')
return false
}
},
email: (value) => {
if (value?.trim()?.length) {
if (/^[a-zA-Z0-9_%+-]+(\.[a-zA-Z0-9_%+-]+)*@([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/.test(value)) {
return true
} else {
toast.warning('请输入正确的邮箱')
return false
}
} else {
toast.warning('请输入邮箱')
return false
}
},
}
const form = ref()
function handleSubmit() {
form.value
.validate()
.then(({ valid, errors }) => {
if (valid) {
if (!model.avatar) {
toast.warning('上传头像')
return
}
// toast.success('')
const data = { ...model, id: userStore.userInfo.userid }
delete data.username
uni.showLoading()
http
.post('/sys/user/login/setting/userEdit', { ...data })
.then((res: any) => {
uni.hideLoading()
if (res.success) {
toast.success('修改成功~')
setTimeout(() => {
userStore.editUserInfo({ ...data })
router.replaceAll({ name: 'people' })
}, 1e3)
} else {
toast.warning(res.message)
}
})
.catch(() => {
toast.error('提交失败')
uni.hideLoading()
})
}
})
.catch((error) => {
console.log(error, 'error')
})
}
</script>
<style lang="scss" scoped>
//
:deep(.wd-cell) {
.wd-cell__left {
width: 70px;
flex: none;
}
.wd-cell__value {
text-align: left;
}
}
</style>

View File

@ -0,0 +1,28 @@
<template>
<view class="empty-container">
<image class="img" src="/static/nocontent-1.png"></image>
<view class="text-center text-bold">
{{title}}
</view>
</view>
</template>
<script setup>
const props = defineProps({
title:{
type:String,
default:"暂无数据"
}
})
</script>
<style lang="scss" scoped>
.empty-container{
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.img {max-width: 240upx;max-height: 240upx;}
}
</style>

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