Compare commits
24 Commits
73a22b5ed9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a9c19e4f3 | ||
|
|
f24a21a41c | ||
|
|
531cac7341 | ||
|
|
395a52b4c6 | ||
| 18ed2502c1 | |||
| dde35d639b | |||
| 99987d9d2e | |||
| 2240e58355 | |||
| 765bc6aaa6 | |||
|
|
e79698c19f | ||
|
|
ec04a7e1ad | ||
| d55223a1c0 | |||
| 672259c94f | |||
| 840e68a450 | |||
| 51029374fd | |||
| 15cc5c2879 | |||
| efcd73a565 | |||
| 3bce685f3a | |||
| c54d54b40f | |||
|
|
a9b3720446 | ||
| 372dc10be2 | |||
| 816af5df6e | |||
| 2ec2b6a628 | |||
| 7d7198b802 |
106
JeecgUniapp-master/.commitlintrc.cjs
Normal file
106
JeecgUniapp-master/.commitlintrc.cjs
Normal 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
JeecgUniapp-master/.editorconfig
Normal file
13
JeecgUniapp-master/.editorconfig
Normal 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
JeecgUniapp-master/.eslintignore
Normal file
1
JeecgUniapp-master/.eslintignore
Normal file
@@ -0,0 +1 @@
|
||||
src/uni_modules/
|
||||
102
JeecgUniapp-master/.eslintrc-auto-import.json
Normal file
102
JeecgUniapp-master/.eslintrc-auto-import.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"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,
|
||||
"useGeoPosition": true,
|
||||
"useId": true,
|
||||
"useModel": true,
|
||||
"usePageList": true,
|
||||
"useRequest": true,
|
||||
"useSlots": true,
|
||||
"useTemplateRef": true,
|
||||
"useUpload": true,
|
||||
"watch": true,
|
||||
"watchEffect": true,
|
||||
"watchPostEffect": true,
|
||||
"watchSyncEffect": true
|
||||
}
|
||||
}
|
||||
97
JeecgUniapp-master/.eslintrc.cjs
Normal file
97
JeecgUniapp-master/.eslintrc.cjs
Normal 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
JeecgUniapp-master/.gitattributes
vendored
Normal file
3
JeecgUniapp-master/.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.js linguist-language=vue
|
||||
*.css linguist-language=vue
|
||||
*.ts linguist-language=vue
|
||||
15
JeecgUniapp-master/.gitignore
vendored
Normal file
15
JeecgUniapp-master/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
node_modules
|
||||
dist
|
||||
.cache
|
||||
|
||||
## ide
|
||||
**/.idea
|
||||
*.iml
|
||||
|
||||
## backend
|
||||
**/target
|
||||
**/logs
|
||||
**/dist
|
||||
|
||||
## front
|
||||
/yarn-error.log
|
||||
16
JeecgUniapp-master/.hbuilderx/launch.json
Normal file
16
JeecgUniapp-master/.hbuilderx/launch.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version" : "1.0",
|
||||
"configurations" : [
|
||||
{
|
||||
"playground" : "standard",
|
||||
"type" : "uni-app:app-android"
|
||||
},
|
||||
{
|
||||
"app-plus" :
|
||||
{
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"type" : "uniCloud"
|
||||
}
|
||||
]
|
||||
}
|
||||
6
JeecgUniapp-master/.npmrc
Normal file
6
JeecgUniapp-master/.npmrc
Normal file
@@ -0,0 +1,6 @@
|
||||
# registry = https://registry.npmjs.org
|
||||
registry = https://registry.npmmirror.com
|
||||
|
||||
strict-peer-dependencies=false
|
||||
auto-install-peers=true
|
||||
shamefully-hoist=true
|
||||
12
JeecgUniapp-master/.prettierignore
Normal file
12
JeecgUniapp-master/.prettierignore
Normal 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
JeecgUniapp-master/.prettierrc.cjs
Normal file
19
JeecgUniapp-master/.prettierrc.cjs
Normal 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
JeecgUniapp-master/.stylelintignore
Normal file
1
JeecgUniapp-master/.stylelintignore
Normal file
@@ -0,0 +1 @@
|
||||
src/uni_modules/
|
||||
58
JeecgUniapp-master/.stylelintrc.cjs
Normal file
58
JeecgUniapp-master/.stylelintrc.cjs
Normal 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,
|
||||
},
|
||||
}
|
||||
17
JeecgUniapp-master/.vscode/extensions.json
vendored
Normal file
17
JeecgUniapp-master/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"stylelint.vscode-stylelint",
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"antfu.unocss",
|
||||
"antfu.iconify",
|
||||
"evils.uniapp-vscode",
|
||||
"uni-helper.uni-helper-vscode",
|
||||
"uni-helper.uni-app-schemas-vscode",
|
||||
"uni-helper.uni-highlight-vscode",
|
||||
"uni-helper.uni-ui-snippets-vscode",
|
||||
"uni-helper.uni-app-snippets-vscode",
|
||||
"mrmlnc.vscode-json5",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
]
|
||||
}
|
||||
31
JeecgUniapp-master/.vscode/launch.json
vendored
Normal file
31
JeecgUniapp-master/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "启动程序",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["run", "dev"],
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"program": "${workspaceFolder}/vite.config.ts",
|
||||
// "preLaunchTask": "tsc: build - tsconfig.json",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
62
JeecgUniapp-master/.vscode/settings.json
vendored
Normal file
62
JeecgUniapp-master/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
// 默认格式化工具选择prettier
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
// 保存的时候自动格式化
|
||||
"editor.formatOnSave": false,
|
||||
//开启自动修复
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "explicit",
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.fixAll.stylelint": "explicit"
|
||||
},
|
||||
// 配置stylelint检查的文件类型范围
|
||||
"stylelint.validate": ["css", "scss", "vue", "html"], // 与package.json的scripts对应
|
||||
"stylelint.enable": true,
|
||||
"css.validate": false,
|
||||
"less.validate": false,
|
||||
"scss.validate": false,
|
||||
"[shellscript]": {
|
||||
"editor.defaultFormatter": "foxundermoon.shell-format"
|
||||
},
|
||||
"[dotenv]": {
|
||||
"editor.defaultFormatter": "foxundermoon.shell-format"
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
// 配置语言的文件关联
|
||||
"files.associations": {
|
||||
"pages.json": "jsonc", // pages.json 可以写注释
|
||||
"manifest.json": "jsonc" // manifest.json 可以写注释
|
||||
},
|
||||
"cSpell.words": [
|
||||
"Aplipay",
|
||||
"climblee",
|
||||
"commitlint",
|
||||
"dcloudio",
|
||||
"iconfont",
|
||||
"qrcode",
|
||||
"refresherrefresh",
|
||||
"scrolltolower",
|
||||
"tabbar",
|
||||
"Toutiao",
|
||||
"uvui",
|
||||
"vitepress",
|
||||
"Wechat",
|
||||
"WechatMiniprogram",
|
||||
"Weixin"
|
||||
],
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.expand": false,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc",
|
||||
".eslintrc.cjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,.stylelintrc.*,.eslintrc-auto-import.json,.editorconfig,.commitlint.cjs"
|
||||
}
|
||||
}
|
||||
62
JeecgUniapp-master/.vscode/vue3.code-snippets
vendored
Normal file
62
JeecgUniapp-master/.vscode/vue3.code-snippets
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"Print unibest Vue3 SFC": {
|
||||
"scope": "vue",
|
||||
"prefix": "v3",
|
||||
"body": [
|
||||
"<route lang=\"json5\" type=\"page\">",
|
||||
"{",
|
||||
" layout: 'default',",
|
||||
" style: {",
|
||||
" navigationBarTitleText: '',",
|
||||
" navigationStyle: 'custom',",
|
||||
" disableScroll: true, // 微信禁止页面滚动",
|
||||
" 'app-plus': {",
|
||||
" bounce: 'none', // 禁用 iOS 弹性效果",
|
||||
" },",
|
||||
" },",
|
||||
"}",
|
||||
"</route>\n",
|
||||
"<template>",
|
||||
" <PageLayout navTitle=\"页面标题\" backRouteName=\"index\"></PageLayout>",
|
||||
"</template>\n",
|
||||
"<script lang=\"ts\" setup>",
|
||||
"import { ref, reactive } from 'vue'",
|
||||
"import { onShow, onHide, onLoad, onReady } from '@dcloudio/uni-app'",
|
||||
"import { useMessage, useToast } from 'wot-design-uni'",
|
||||
"import { useRouter } from '@/plugin/uni-mini-router'",
|
||||
"import { useUserStore } from '@/store/user'",
|
||||
"import { http } from '@/utils/http'",
|
||||
"import { useParamsStore } from '@/store/page-params'\n",
|
||||
"defineOptions({",
|
||||
" name: '组件名',",
|
||||
" options: {",
|
||||
" styleIsolation: 'shared',",
|
||||
" },",
|
||||
"})",
|
||||
"const router = useRouter()",
|
||||
"const paramsStore = useParamsStore()",
|
||||
"const userStore = useUserStore()",
|
||||
"const toast = useToast()",
|
||||
"const message = useMessage()",
|
||||
"</script>\n",
|
||||
"<style lang=\"scss\" scoped>",
|
||||
"//",
|
||||
"</style>\n",
|
||||
],
|
||||
},
|
||||
"Print unibest style": {
|
||||
"scope": "vue",
|
||||
"prefix": "st",
|
||||
"body": ["<style lang=\"scss\" scoped>", "//", "</style>\n"],
|
||||
},
|
||||
"Print unibest script": {
|
||||
"scope": "vue",
|
||||
"prefix": "sc",
|
||||
"body": ["<script lang=\"ts\" setup>", "//$3", "</script>\n"],
|
||||
},
|
||||
"Print unibest template": {
|
||||
"scope": "vue",
|
||||
"prefix": "te",
|
||||
"body": ["<template>", " <view class=\"\">$1</view>", "</template>\n"],
|
||||
},
|
||||
}
|
||||
214
JeecgUniapp-master/LICENSE
Normal file
214
JeecgUniapp-master/LICENSE
Normal 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
JeecgUniapp-master/README.md
Normal file
188
JeecgUniapp-master/README.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# 项目介绍
|
||||
|
||||
JeecgUniapp 是`JeecgBoot低代码平台`的配套`APP移动框架`,项目采用 Uniapp、Vue3.0、Vite、 Wot-design-uni、TypeScript 等最新技术栈,包括二次封装组件、路由拦截、请求拦截等功能。实现了与 `JeecgBoot` 完美对接:
|
||||
目前已经实现登录、用户信息、通讯录、公告、移动首页、九宫格、聊天、Online表单、仪表盘等功能,提供了丰富的组件!
|
||||
|
||||
当前最新版本: 3.0.0(发布日期:2025-03-10)
|
||||
|
||||
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||
[](http://www.jeecg.com)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
|
||||
|
||||
|
||||
### 视频介绍
|
||||
|
||||
[](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
|
||||
|
||||
### 环境要求
|
||||
|
||||
- 本地环境安装 node(18+)、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)
|
||||
|
||||
|
||||
|
||||
|
||||
### 效果预览
|
||||
|
||||
- 基础功能
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
- 聊天功能
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
- Online表单
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
- 仪表盘
|
||||
|
||||

|
||||

|
||||
|
||||
- 动画展示
|
||||
|
||||

|
||||
|
||||
|
||||
### 功能模块
|
||||
```
|
||||
├─框架实现
|
||||
│ ├─APP开发框架搭建
|
||||
│ ├─登录对接
|
||||
│ ├─TOKEN接口机制
|
||||
│ ├─热更新\覆盖更新
|
||||
├─基础功能
|
||||
│ ├─菜单栏目
|
||||
│ ├─登录页面
|
||||
│ ├─移动首页
|
||||
│ ├─个人信息设置
|
||||
├─消息中心
|
||||
│ ├─通讯录
|
||||
│ ├─系统公告
|
||||
│ ├─消息推送
|
||||
│ ├─在线聊天
|
||||
├─低代码功能
|
||||
│ ├─Online表单(列表+表单渲染)
|
||||
│ ├─仪表盘(移动展示)
|
||||
├─示例代码
|
||||
│ ├─调用摄像头扫码(扫码)
|
||||
│ ├─获取地理位置(定位)
|
||||
├─新增组件
|
||||
│ ├─页面滚动
|
||||
│ ├─日历
|
||||
│ ├─时间选择
|
||||
│ ├─下拉选择
|
||||
│ ├─图片上传
|
||||
├─。。。
|
||||
```
|
||||
136
JeecgUniapp-master/docs/DT50扫描识别-广播参数与接入说明.md
Normal file
136
JeecgUniapp-master/docs/DT50扫描识别-广播参数与接入说明.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# DT50 扫描识别 —— 广播参数与接入说明
|
||||
|
||||
> 面向 APP「协作」页「扫描识别」功能。
|
||||
> 设备:优博讯 UROVO **DT50**(Android 11)。
|
||||
> 参数来源:**设备实测 adb logcat 抓真实扫码** + **优博讯官方 ScanManager 文档** + **DCloud UniApp 官方示例** 三方交叉验证。
|
||||
> 记录日期:2026-07-20。
|
||||
|
||||
---
|
||||
|
||||
## 一、核心参数(最重要)
|
||||
|
||||
| 项目 | 值 | 说明 |
|
||||
|---|---|---|
|
||||
| 输出方式 | **Intent / 广播** | 设备当前**已是此模式**,无需在设置页切换 |
|
||||
| 广播 Action | `android.intent.ACTION_DECODE_DATA` | 实测 + 官方一致 |
|
||||
| 条码文本(String) | `barcode_string` | 官方常量 `BARCODE_STRING_TAG`,**首选** |
|
||||
| 原始字节(byte[]) | `barcode` | 官方常量 `DECODE_DATA_TAG`,兜底用 |
|
||||
| 长度(int) | `length` | 官方常量 `BARCODE_LENGTH_TAG` |
|
||||
| 码制(int/String) | `barcodeType` | 官方常量 `BARCODE_TYPE_TAG`,实测读出 `Code128` |
|
||||
| 扫描键 keycode | `523` | KEYCODE_SCAN(机身侧键) |
|
||||
|
||||
## 二、设备侧验证结论
|
||||
|
||||
- **硬件**:成像头正常,成功解出 Code128 条码 ✅
|
||||
- **触发**:侧键 keycode = 523,按下发 `ACTION_KEYCODE_SCAN_PRESSED`
|
||||
- **广播**:成功解码时实测发出 `android.intent.ACTION_DECODE_DATA (has extras)` 并被接收 ✅
|
||||
- **模式**:当前即广播输出(非键盘输出),**直接 registerReceiver 即可**
|
||||
- **无需**:原生 `.aar` 插件 / 自定义基座 / USB 驱动 / 完整 AndroidStudio 套壳;标准基座即可
|
||||
|
||||
相关系统组件(备查):
|
||||
- 扫描设置 App:`com.ubx.barcodescanner.tool`
|
||||
- 扫描服务:`com.ubx.uscanner`
|
||||
- DataWedge 式配置:`com.ubx.datawedge`
|
||||
|
||||
## 三、UniApp 监听代码(标准基座,无需插件)
|
||||
|
||||
```js
|
||||
let scanReceiver = null;
|
||||
|
||||
/** 开始监听 DT50 扫描广播 */
|
||||
function startScanListener(onBarcode) {
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
const IntentFilter = plus.android.importClass('android.content.IntentFilter');
|
||||
const filter = new IntentFilter();
|
||||
filter.addAction('android.intent.ACTION_DECODE_DATA'); // DT50 广播 Action
|
||||
|
||||
scanReceiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', {
|
||||
onReceive: function (context, intent) {
|
||||
plus.android.importClass(intent);
|
||||
// 首选直接取字符串
|
||||
let code = intent.getStringExtra('barcode_string');
|
||||
// 兜底:部分场景用字节数组
|
||||
if (!code) {
|
||||
const bytes = intent.getByteArrayExtra('barcode');
|
||||
if (bytes) code = plus.android.newObject('java.lang.String', bytes);
|
||||
}
|
||||
const type = intent.getStringExtra('barcodeType'); // 码制,可选
|
||||
if (code) onBarcode(code, type);
|
||||
}
|
||||
});
|
||||
main.registerReceiver(scanReceiver, filter);
|
||||
}
|
||||
|
||||
/** 停止监听(务必在页面卸载时调用,避免重复注册 / 内存泄漏) */
|
||||
function stopScanListener() {
|
||||
if (scanReceiver) {
|
||||
plus.android.runtimeMainActivity().unregisterReceiver(scanReceiver);
|
||||
scanReceiver = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
页面接入:
|
||||
|
||||
```js
|
||||
onLoad() {
|
||||
startScanListener((code, type) => {
|
||||
this.barcode = code;
|
||||
// TODO: 调 MES 后端接口 / 查料
|
||||
});
|
||||
},
|
||||
onUnload() {
|
||||
stopScanListener();
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:仅 App(plus 环境)下可用,H5/小程序无 `plus`,接入时需做平台判断(`#ifdef APP-PLUS`)。
|
||||
|
||||
## 四、故障排查
|
||||
|
||||
| 现象 | 原因 / 处理 |
|
||||
|---|---|
|
||||
| 收不到广播 | 确认扫描设置里输出为「Intent/广播」而非「键盘/输入法」;确认 Action 拼写 |
|
||||
| 扫描后文本框被输入内容 | 键盘输出模式也开了,去扫描设置关掉键盘输出 |
|
||||
| `barcode_string` 为空 | 用兜底的 `barcode`(byte[]) 转 String |
|
||||
| 一直解码失败(红光有但不出码) | 距离 15~20cm,红光完整压住条码,拿稳 1~2 秒;避免手机屏幕反光码 |
|
||||
|
||||
## 五、参考
|
||||
|
||||
- 优博讯官方 ScanManager 文档:https://en.urovo.com/developer/android/device/ScanManager.html
|
||||
- DCloud UniApp 监听优博讯扫码广播(含示例):https://ask.dcloud.net.cn/article/36468
|
||||
|
||||
## 六、本仓库接入位置(2026-07-20)
|
||||
|
||||
| 项 | 位置 |
|
||||
|---|---|
|
||||
| 菜单入口 | `src/common/work.ts` → 其他服务「合同」后「扫描识别」 |
|
||||
| 页面 | `src/pages/scanIdentify/scanIdentify.vue` |
|
||||
| 广播工具 | `src/utils/dt50Scan.ts` |
|
||||
| 行为 | 仅广播展示条码/码制/时间,不调后端、不弹输入法 |
|
||||
| 生命周期 | `onShow` 注册 / `onHide`+`onUnload` 注销;同码防抖 |
|
||||
|
||||
## 七、键盘输出 vs 广播输出
|
||||
|
||||
| | 键盘输出(Keyboard / 楔入) | 广播输出(Intent / Broadcast) |
|
||||
|---|---|---|
|
||||
| 原理 | 把条码当成「敲键盘」打进当前焦点输入框 | 系统发广播,App 用 `registerReceiver` 收 |
|
||||
| App 是否要抢焦点 | 要,通常还会弹出软键盘 | **不需要**输入框焦点 |
|
||||
| 适合场景 | 记事本、任意输入框快速验证 | 业务页稳定取码、拿码制等扩展字段 |
|
||||
| 本页策略 | 已关闭楔入,避免弹输入法 | **本页只走广播** |
|
||||
|
||||
设备设置建议:扫描设置 → 关闭「键盘输出」,开启「Intent/广播输出」。
|
||||
本页进入时会主动 `hideKeyboard`,不自动聚焦任何输入框。
|
||||
|
||||
## 附:参数是如何抓到的(可复现)
|
||||
|
||||
```bash
|
||||
# adb 路径(本机 Android SDK)
|
||||
ADB="C:/Users/hp/AppData/Local/Android/Sdk/platform-tools/adb.exe"
|
||||
|
||||
$ADB devices -l # 确认 DT50 已连(USB 调试)
|
||||
$ADB logcat -c # 清空日志缓冲
|
||||
$ADB shell "timeout 40 logcat -v threadtime '*:V'" > scan.log # 抓 40s
|
||||
# 抓取期间用 DT50 侧键扫几次条码,然后:
|
||||
grep -E "ACTION_DECODE_DATA|barcodeType|DECODER" scan.log
|
||||
```
|
||||
29
JeecgUniapp-master/env/.env
vendored
Normal file
29
JeecgUniapp-master/env/.env
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
VITE_APP_TITLE = 'JeecgBoot-uniapp'
|
||||
VITE_APP_PORT = 9000
|
||||
|
||||
VITE_UNI_APPID = '__UNI__E0CC280'
|
||||
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= true
|
||||
VITE_APP_PROXY_PREFIX = '/api'
|
||||
|
||||
# 是否启用mock (1.仅支持h5 2.启用必须要开启代理,否则生效。)
|
||||
VITE_USE_MOCK = false
|
||||
8
JeecgUniapp-master/env/.env.development
vendored
Normal file
8
JeecgUniapp-master/env/.env.development
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV = 'development'
|
||||
# 是否去除console 和 debugger
|
||||
VITE_DELETE_CONSOLE = false
|
||||
# 是否开启sourcemap(App 真机编译易 OOM,开发期先关闭)
|
||||
VITE_SHOW_SOURCEMAP = false
|
||||
|
||||
VITE_SERVER_BASEURL = 'http://192.168.1.3:8888/jeecg-boot'
|
||||
8
JeecgUniapp-master/env/.env.production
vendored
Normal file
8
JeecgUniapp-master/env/.env.production
vendored
Normal 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
JeecgUniapp-master/env/.env.test
vendored
Normal file
4
JeecgUniapp-master/env/.env.test
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV = 'development'
|
||||
# 是否去除console 和 debugger
|
||||
VITE_DELETE_CONSOLE = false
|
||||
BIN
JeecgUniapp-master/favicon.ico
Normal file
BIN
JeecgUniapp-master/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 487 B |
26
JeecgUniapp-master/index.html
Normal file
26
JeecgUniapp-master/index.html
Normal 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>
|
||||
168
JeecgUniapp-master/manifest.config.ts
Normal file
168
JeecgUniapp-master/manifest.config.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
// 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,
|
||||
webView: {
|
||||
render: 'always',
|
||||
userAgent: '',
|
||||
},
|
||||
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: 26,
|
||||
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,
|
||||
es6: true,
|
||||
},
|
||||
usingComponents: true,
|
||||
lazyCodeLoading: 'requiredComponents',
|
||||
// __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: '20854e7d231ee339bfa3b277c840070c',
|
||||
securityJsCode: '7a542edee4a82e56ed88fef8ef42b5a5',
|
||||
serviceHost: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'app-harmony': {
|
||||
distribute: {
|
||||
bundleName: 'uniapp.demo.test',
|
||||
},
|
||||
},
|
||||
uniStatistics: {
|
||||
enable: false,
|
||||
},
|
||||
vueVersion: '3',
|
||||
})
|
||||
117
JeecgUniapp-master/mock/getChatList.ts
Normal file
117
JeecgUniapp-master/mock/getChatList.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
// mock/getChatLists.js
|
||||
import Mock from 'mockjs'
|
||||
import { readFileSync } from 'fs'
|
||||
import path from 'path'
|
||||
import { parse } from 'dotenv'
|
||||
// 指定 .env 文件路径
|
||||
const envPath = path.resolve(__dirname, '../env/.env')
|
||||
const envContent = readFileSync(envPath, 'utf8')
|
||||
const parsedEnv = parse(envContent)
|
||||
const proxyPrefix = parsedEnv.VITE_APP_PROXY_PREFIX ?? '/api'
|
||||
|
||||
export default [
|
||||
{
|
||||
url: `${proxyPrefix}/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: `${proxyPrefix}/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: `${proxyPrefix}/eoa/im/newApi/creatFriendSession`,
|
||||
method: 'post', // 或 post
|
||||
response: () => {
|
||||
return Mock.mock({
|
||||
code: 200,
|
||||
success: true,
|
||||
result: 'success',
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
url: `${proxyPrefix}/eoa/im/newApi/sendMessage`,
|
||||
method: 'post', // 或 post
|
||||
response: () => {
|
||||
return Mock.mock({
|
||||
code: 200,
|
||||
success: true,
|
||||
result: 'success',
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
13
JeecgUniapp-master/openapi-ts-request.config.ts
Normal file
13
JeecgUniapp-master/openapi-ts-request.config.ts
Normal 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[]
|
||||
157
JeecgUniapp-master/package.json
Normal file
157
JeecgUniapp-master/package.json
Normal file
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"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",
|
||||
"dayjs": "1.11.10",
|
||||
"echarts": "^5.6.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"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.8.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",
|
||||
"dotenv": "^16.4.7",
|
||||
"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
JeecgUniapp-master/pages.config.ts
Normal file
55
JeecgUniapp-master/pages.config.ts
Normal 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: 'white',
|
||||
backgroundColor: '#000000',
|
||||
},
|
||||
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-super/flow/flowIndex',
|
||||
// text: '审批',
|
||||
// },
|
||||
{
|
||||
iconPath: 'static/tabbar/tabbar-user-2.png',
|
||||
selectedIconPath: 'static/tabbar/tabbar-user.png',
|
||||
pagePath: 'pages/user/people',
|
||||
text: '个人',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
15205
JeecgUniapp-master/pnpm-lock.yaml
generated
Normal file
15205
JeecgUniapp-master/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
36
JeecgUniapp-master/scripts/postupgrade.js
Normal file
36
JeecgUniapp-master/scripts/postupgrade.js
Normal 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}`)
|
||||
})
|
||||
107
JeecgUniapp-master/src/App.vue
Normal file
107
JeecgUniapp-master/src/App.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<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'
|
||||
// #ifdef APP-PLUS
|
||||
import appUpdate from "@/common/appUpdate";
|
||||
// #endif
|
||||
export default {
|
||||
onLaunch: function (options) {
|
||||
console.log('App Launch')
|
||||
console.log('应用启动路径:', options.path)
|
||||
// #ifdef APP-PLUS
|
||||
// 检测升级
|
||||
appUpdate()
|
||||
// #endif
|
||||
},
|
||||
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)
|
||||
},
|
||||
onHide: function () {
|
||||
console.log('App Hide')
|
||||
},
|
||||
// 全局变量
|
||||
globalData: {
|
||||
isLocalConfig: true,
|
||||
systemInfo: uni.getSystemInfoSync(),
|
||||
// 导航的高度
|
||||
navHeight: 44,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
:root,
|
||||
page {
|
||||
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>
|
||||
103
JeecgUniapp-master/src/common/appUpdate.ts
Normal file
103
JeecgUniapp-master/src/common/appUpdate.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
//APP更新
|
||||
export default function appUpdate() {
|
||||
|
||||
console.log('plus.runtime.appid', plus.runtime.appid)
|
||||
console.log('plus.runtime.version', plus.runtime.version)
|
||||
console.log('plus.device.imei', plus.device.imei)
|
||||
|
||||
uni.request({
|
||||
url: 'http://app.myhjdc.cn/update.json', //检查更新的服务器地址
|
||||
data: {
|
||||
appid: plus.runtime.appid,
|
||||
version: plus.runtime.version,
|
||||
imei: plus.device.imei,
|
||||
},
|
||||
success: (res: any) => {
|
||||
plus.runtime.getProperty(plus.runtime.appid, function (wgtinfo) {
|
||||
console.log('客户端版本信息 wgtinfo', wgtinfo)
|
||||
console.log('服务端版本信息 res', res)
|
||||
// 客户端版本
|
||||
let client_version = wgtinfo.version
|
||||
|
||||
//------------------------前两位大版本有改动-----------------------------------------------
|
||||
// 大版本是否一致 版本号1.1.0,前两位如果有改动,就是大版本更新;否则就是小版本热更新
|
||||
const flag_update =
|
||||
client_version.split('.').splice(0, 2).join('.') !=
|
||||
res.data.version.split('.').splice(0, 2).join('.')
|
||||
//------------------------前两位大版本有改动-----------------------------------------------
|
||||
|
||||
//------------------------第三位小版本有改动-热更新-----------------------------------------------
|
||||
// 客户端版本号
|
||||
let client_version_number = Number(client_version.split('.')[2])
|
||||
// 服务器版本号
|
||||
let current_version_number = Number(res.data.version.split('.')[2])
|
||||
// 如果客户端版本号小于服务器版本号,则提醒用户更新
|
||||
const flag_hot = !flag_update && client_version_number < current_version_number
|
||||
//------------------------第三位小版本有改动-热更新-----------------------------------------------
|
||||
|
||||
|
||||
console.log('客户端版本 client_version', client_version)
|
||||
console.log('客户端版本号 client_version_number', client_version_number)
|
||||
console.log('服务器版本号 current_version_number', current_version_number)
|
||||
console.log('升级更新 flag_update', flag_update)
|
||||
console.log('热更新 flag_hot', flag_hot)
|
||||
|
||||
if (flag_update) {
|
||||
console.log('*****开始大版本更新*****')
|
||||
// 提醒用户更新
|
||||
uni.showModal({
|
||||
title: '更新提示',
|
||||
content: res.data.note,
|
||||
success: (showResult) => {
|
||||
if (showResult.confirm) {
|
||||
plus.nativeUI.toast('正在准备环境,请稍后!')
|
||||
console.log(res.data.url)
|
||||
const downloadTask = plus.downloader.createDownload(
|
||||
res.data.url,
|
||||
{
|
||||
method: 'GET',
|
||||
filename: '_doc/update/',
|
||||
},
|
||||
function (d, status) {
|
||||
if (status == 200) {
|
||||
const path = d.filename; //下载apk
|
||||
plus.runtime.install(path) // 自动安装apk文件
|
||||
} else {
|
||||
plus.nativeUI.alert('版本更新失败:' + status)
|
||||
}
|
||||
},
|
||||
)
|
||||
downloadTask.start()
|
||||
}
|
||||
},
|
||||
})
|
||||
} else if (flag_hot) {
|
||||
console.log('*****开始小版本热更新*****')
|
||||
uni.downloadFile({
|
||||
url: res.data.wgtUrl,
|
||||
success: (downloadResult) => {
|
||||
console.log("热更新downloadResult",downloadResult.tempFilePath)
|
||||
if (downloadResult.statusCode === 200) {
|
||||
plus.nativeUI.toast(`正在热更新!${res.data.versionCode}`)
|
||||
plus.runtime.install(
|
||||
downloadResult.tempFilePath,
|
||||
{
|
||||
force: false,
|
||||
},
|
||||
function () {
|
||||
plus.nativeUI.toast('热更新成功')
|
||||
plus.runtime.restart()
|
||||
},
|
||||
function (e) {
|
||||
console.log(e)
|
||||
plus.nativeUI.toast(`热更新失败:${e.message}`)
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
179
JeecgUniapp-master/src/common/areaData/Area.ts
Normal file
179
JeecgUniapp-master/src/common/areaData/Area.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
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;
|
||||
}
|
||||
|
||||
getPca() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
interface RegionItem {
|
||||
id: string;
|
||||
text: string;
|
||||
pid: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
interface TransformedItem {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
type TransformedData = Record<string, TransformedItem[]>;
|
||||
|
||||
export function transformRegionData(originalData: RegionItem[]): TransformedData {
|
||||
const result: TransformedData = {};
|
||||
|
||||
// 首先处理省级数据 (pid 为 '86' 的项)
|
||||
const provinces = originalData.filter(item => item.pid === '86');
|
||||
result['0'] = provinces.map(province => ({
|
||||
label: province.text,
|
||||
value: province.id
|
||||
}));
|
||||
|
||||
// 然后处理市级数据
|
||||
const cities = originalData.filter(item =>
|
||||
provinces.some(province => province.id === item.pid)
|
||||
);
|
||||
|
||||
cities.forEach(city => {
|
||||
if (!result[city.pid]) {
|
||||
result[city.pid] = [];
|
||||
}
|
||||
result[city.pid].push({
|
||||
label: city.text,
|
||||
value: city.id
|
||||
});
|
||||
});
|
||||
|
||||
// 最后处理区县级数据
|
||||
const districts = originalData.filter(item =>
|
||||
cities.some(city => city.id === item.pid)
|
||||
);
|
||||
|
||||
districts.forEach(district => {
|
||||
if (!result[district.pid]) {
|
||||
result[district.pid] = [];
|
||||
}
|
||||
result[district.pid].push({
|
||||
label: district.text,
|
||||
value: district.id
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const jeecgAreaData = new Area();
|
||||
|
||||
// 根据code找文本
|
||||
const getAreaTextByCode = function (code) {
|
||||
let index = 3;
|
||||
//update-begin-author:liusq---date:20220531--for: 判断code是否是多code逗号分割的字符串,是的话,获取最后一位的code ---
|
||||
if (code && code.includes(',')) {
|
||||
index = code.split(",").length;
|
||||
code = code.substr(code.lastIndexOf(',') + 1);
|
||||
}
|
||||
//update-end-author:liusq---date:20220531--for: 判断code是否是多code逗号分割的字符串,是的话,获取最后一位的code ---
|
||||
return jeecgAreaData.getText(code,index);
|
||||
};
|
||||
// 根据code找文本
|
||||
const getAreaArrByCode = function (code) {
|
||||
return jeecgAreaData.getRealCode(code);
|
||||
};
|
||||
// 获取下拉地图option
|
||||
const getPcaOptionData = function () {
|
||||
let pca:any = jeecgAreaData.getPca();
|
||||
return transformRegionData(pca);
|
||||
};
|
||||
|
||||
export { getAreaTextByCode,getAreaArrByCode,getPcaOptionData };
|
||||
57
JeecgUniapp-master/src/common/areaData/pcaUtils.ts
Normal file
57
JeecgUniapp-master/src/common/areaData/pcaUtils.ts
Normal 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
|
||||
}
|
||||
140
JeecgUniapp-master/src/common/constants.ts
Normal file
140
JeecgUniapp-master/src/common/constants.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
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"
|
||||
/**
|
||||
* 组件名称前缀
|
||||
*/
|
||||
export const COMP_NAME_PREFIX = 'jeecg-drag';
|
||||
//首页配置项缓存时间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",
|
||||
"JProgress",
|
||||
|
||||
"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
JeecgUniapp-master/src/common/is.ts
Normal file
108
JeecgUniapp-master/src/common/is.ts
Normal 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)
|
||||
}
|
||||
18
JeecgUniapp-master/src/common/js-sdk/js-amap/amap-h5.js
Normal file
18
JeecgUniapp-master/src/common/js-sdk/js-amap/amap-h5.js
Normal file
@@ -0,0 +1,18 @@
|
||||
//获取地图
|
||||
export default function MapLoader() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (window.AMap) {
|
||||
resolve(window.AMap);
|
||||
} else {
|
||||
var script = document.createElement('script');
|
||||
script.type = "text/javascript";
|
||||
script.async = true;
|
||||
script.src = "https://webapi.amap.com/maps?v=1.4.15&key=21f194a0d33197f874f7bbdd198419be&callback=initAMap";
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
window.initAMap = () => {
|
||||
resolve(window.AMap);
|
||||
};
|
||||
})
|
||||
}
|
||||
418
JeecgUniapp-master/src/common/js-sdk/js-amap/amap-wx.js
Normal file
418
JeecgUniapp-master/src/common/js-sdk/js-amap/amap-wx.js
Normal file
@@ -0,0 +1,418 @@
|
||||
function AMapWX(a) {
|
||||
;(this.key = a.key),
|
||||
(this.requestConfig = {
|
||||
key: a.key,
|
||||
s: 'rsx',
|
||||
platform: 'WXJS',
|
||||
appname: a.key,
|
||||
sdkversion: '1.2.0',
|
||||
logversion: '2.0',
|
||||
})
|
||||
}
|
||||
;(AMapWX.prototype.getWxLocation = function (a, b) {
|
||||
wx.getLocation({
|
||||
type: 'gcj02',
|
||||
success: function (a) {
|
||||
var c = a.longitude + ',' + a.latitude
|
||||
wx.setStorage({ key: 'userLocation', data: c }), b(c)
|
||||
},
|
||||
fail: function (c) {
|
||||
wx.getStorage({
|
||||
key: 'userLocation',
|
||||
success: function (a) {
|
||||
a.data && b(a.data)
|
||||
},
|
||||
}),
|
||||
a.fail({ errCode: '0', errMsg: c.errMsg || '' })
|
||||
},
|
||||
})
|
||||
}),
|
||||
(AMapWX.prototype.getRegeo = function (a) {
|
||||
function c(c) {
|
||||
var d = b.requestConfig
|
||||
wx.request({
|
||||
url: 'https://restapi.amap.com/v3/geocode/regeo',
|
||||
data: {
|
||||
key: b.key,
|
||||
location: c,
|
||||
extensions: 'all',
|
||||
s: d.s,
|
||||
platform: d.platform,
|
||||
appname: b.key,
|
||||
sdkversion: d.sdkversion,
|
||||
logversion: d.logversion,
|
||||
},
|
||||
method: 'GET',
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: function (b) {
|
||||
var d, e, f, g, h, i, j, k, l
|
||||
b.data.status && '1' == b.data.status
|
||||
? ((d = b.data.regeocode),
|
||||
(e = d.addressComponent),
|
||||
(f = []),
|
||||
(g = ''),
|
||||
d && d.roads[0] && d.roads[0].name && (g = d.roads[0].name + '附近'),
|
||||
(h = c.split(',')[0]),
|
||||
(i = c.split(',')[1]),
|
||||
d.pois &&
|
||||
d.pois[0] &&
|
||||
((g = d.pois[0].name + '附近'),
|
||||
(j = d.pois[0].location),
|
||||
j && ((h = parseFloat(j.split(',')[0])), (i = parseFloat(j.split(',')[1])))),
|
||||
e.provice && f.push(e.provice),
|
||||
e.city && f.push(e.city),
|
||||
e.district && f.push(e.district),
|
||||
e.streetNumber && e.streetNumber.street && e.streetNumber.number
|
||||
? (f.push(e.streetNumber.street), f.push(e.streetNumber.number))
|
||||
: ((k = ''),
|
||||
d && d.roads[0] && d.roads[0].name && (k = d.roads[0].name),
|
||||
f.push(k)),
|
||||
(f = f.join('')),
|
||||
(l = [
|
||||
{
|
||||
iconPath: a.iconPath,
|
||||
width: a.iconWidth,
|
||||
height: a.iconHeight,
|
||||
name: f,
|
||||
desc: g,
|
||||
longitude: h,
|
||||
latitude: i,
|
||||
id: 0,
|
||||
regeocodeData: d,
|
||||
},
|
||||
]),
|
||||
a.success(l))
|
||||
: a.fail({ errCode: b.data.infocode, errMsg: b.data.info })
|
||||
},
|
||||
fail: function (b) {
|
||||
a.fail({ errCode: '0', errMsg: b.errMsg || '' })
|
||||
},
|
||||
})
|
||||
}
|
||||
var b = this
|
||||
a.location
|
||||
? c(a.location)
|
||||
: b.getWxLocation(a, function (a) {
|
||||
c(a)
|
||||
})
|
||||
}),
|
||||
(AMapWX.prototype.getWeather = function (a) {
|
||||
function d(d) {
|
||||
var e = 'base'
|
||||
a.type && 'forecast' == a.type && (e = 'all'),
|
||||
wx.request({
|
||||
url: 'https://restapi.amap.com/v3/weather/weatherInfo',
|
||||
data: {
|
||||
key: b.key,
|
||||
city: d,
|
||||
extensions: e,
|
||||
s: c.s,
|
||||
platform: c.platform,
|
||||
appname: b.key,
|
||||
sdkversion: c.sdkversion,
|
||||
logversion: c.logversion,
|
||||
},
|
||||
method: 'GET',
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: function (b) {
|
||||
function c(a) {
|
||||
var b = {
|
||||
city: { text: '城市', data: a.city },
|
||||
weather: { text: '天气', data: a.weather },
|
||||
temperature: { text: '温度', data: a.temperature },
|
||||
winddirection: { text: '风向', data: a.winddirection + '风' },
|
||||
windpower: { text: '风力', data: a.windpower + '级' },
|
||||
humidity: { text: '湿度', data: a.humidity + '%' },
|
||||
}
|
||||
return b
|
||||
}
|
||||
var d, e
|
||||
b.data.status && '1' == b.data.status
|
||||
? b.data.lives
|
||||
? ((d = b.data.lives),
|
||||
d && d.length > 0 && ((d = d[0]), (e = c(d)), (e['liveData'] = d), a.success(e)))
|
||||
: b.data.forecasts &&
|
||||
b.data.forecasts[0] &&
|
||||
a.success({ forecast: b.data.forecasts[0] })
|
||||
: a.fail({ errCode: b.data.infocode, errMsg: b.data.info })
|
||||
},
|
||||
fail: function (b) {
|
||||
a.fail({ errCode: '0', errMsg: b.errMsg || '' })
|
||||
},
|
||||
})
|
||||
}
|
||||
function e(e) {
|
||||
wx.request({
|
||||
url: 'https://restapi.amap.com/v3/geocode/regeo',
|
||||
data: {
|
||||
key: b.key,
|
||||
location: e,
|
||||
extensions: 'all',
|
||||
s: c.s,
|
||||
platform: c.platform,
|
||||
appname: b.key,
|
||||
sdkversion: c.sdkversion,
|
||||
logversion: c.logversion,
|
||||
},
|
||||
method: 'GET',
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: function (b) {
|
||||
var c, e
|
||||
b.data.status && '1' == b.data.status
|
||||
? ((e = b.data.regeocode),
|
||||
e.addressComponent
|
||||
? (c = e.addressComponent.adcode)
|
||||
: e.aois && e.aois.length > 0 && (c = e.aois[0].adcode),
|
||||
d(c))
|
||||
: a.fail({ errCode: b.data.infocode, errMsg: b.data.info })
|
||||
},
|
||||
fail: function (b) {
|
||||
a.fail({ errCode: '0', errMsg: b.errMsg || '' })
|
||||
},
|
||||
})
|
||||
}
|
||||
var b = this,
|
||||
c = b.requestConfig
|
||||
a.city
|
||||
? d(a.city)
|
||||
: b.getWxLocation(a, function (a) {
|
||||
e(a)
|
||||
})
|
||||
}),
|
||||
(AMapWX.prototype.getPoiAround = function (a) {
|
||||
function d(d) {
|
||||
var e = {
|
||||
key: b.key,
|
||||
location: d,
|
||||
s: c.s,
|
||||
platform: c.platform,
|
||||
appname: b.key,
|
||||
sdkversion: c.sdkversion,
|
||||
logversion: c.logversion,
|
||||
}
|
||||
a.querytypes && (e['types'] = a.querytypes),
|
||||
a.querykeywords && (e['keywords'] = a.querykeywords),
|
||||
wx.request({
|
||||
url: 'https://restapi.amap.com/v3/place/around',
|
||||
data: e,
|
||||
method: 'GET',
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: function (b) {
|
||||
var c, d, e, f
|
||||
if (b.data.status && '1' == b.data.status) {
|
||||
if (((b = b.data), b && b.pois)) {
|
||||
for (c = [], d = 0; d < b.pois.length; d++)
|
||||
(e = 0 == d ? a.iconPathSelected : a.iconPath),
|
||||
c.push({
|
||||
latitude: parseFloat(b.pois[d].location.split(',')[1]),
|
||||
longitude: parseFloat(b.pois[d].location.split(',')[0]),
|
||||
iconPath: e,
|
||||
width: 22,
|
||||
height: 32,
|
||||
id: d,
|
||||
name: b.pois[d].name,
|
||||
address: b.pois[d].address,
|
||||
})
|
||||
;(f = { markers: c, poisData: b.pois }), a.success(f)
|
||||
}
|
||||
} else a.fail({ errCode: b.data.infocode, errMsg: b.data.info })
|
||||
},
|
||||
fail: function (b) {
|
||||
a.fail({ errCode: '0', errMsg: b.errMsg || '' })
|
||||
},
|
||||
})
|
||||
}
|
||||
var b = this,
|
||||
c = b.requestConfig
|
||||
a.location
|
||||
? d(a.location)
|
||||
: b.getWxLocation(a, function (a) {
|
||||
d(a)
|
||||
})
|
||||
}),
|
||||
(AMapWX.prototype.getStaticmap = function (a) {
|
||||
function f(b) {
|
||||
c.push('location=' + b),
|
||||
a.zoom && c.push('zoom=' + a.zoom),
|
||||
a.size && c.push('size=' + a.size),
|
||||
a.scale && c.push('scale=' + a.scale),
|
||||
a.markers && c.push('markers=' + a.markers),
|
||||
a.labels && c.push('labels=' + a.labels),
|
||||
a.paths && c.push('paths=' + a.paths),
|
||||
a.traffic && c.push('traffic=' + a.traffic)
|
||||
var e = d + c.join('&')
|
||||
a.success({ url: e })
|
||||
}
|
||||
var e,
|
||||
b = this,
|
||||
c = [],
|
||||
d = 'https://restapi.amap.com/v3/staticmap?'
|
||||
c.push('key=' + b.key),
|
||||
(e = b.requestConfig),
|
||||
c.push('s=' + e.s),
|
||||
c.push('platform=' + e.platform),
|
||||
c.push('appname=' + e.appname),
|
||||
c.push('sdkversion=' + e.sdkversion),
|
||||
c.push('logversion=' + e.logversion),
|
||||
a.location
|
||||
? f(a.location)
|
||||
: b.getWxLocation(a, function (a) {
|
||||
f(a)
|
||||
})
|
||||
}),
|
||||
(AMapWX.prototype.getInputtips = function (a) {
|
||||
var b = this,
|
||||
c = b.requestConfig,
|
||||
d = {
|
||||
key: b.key,
|
||||
s: c.s,
|
||||
platform: c.platform,
|
||||
appname: b.key,
|
||||
sdkversion: c.sdkversion,
|
||||
logversion: c.logversion,
|
||||
}
|
||||
a.location && (d['location'] = a.location),
|
||||
a.keywords && (d['keywords'] = a.keywords),
|
||||
a.type && (d['type'] = a.type),
|
||||
a.city && (d['city'] = a.city),
|
||||
a.citylimit && (d['citylimit'] = a.citylimit),
|
||||
wx.request({
|
||||
url: 'https://restapi.amap.com/v3/assistant/inputtips',
|
||||
data: d,
|
||||
method: 'GET',
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: function (b) {
|
||||
b && b.data && b.data.tips && a.success({ tips: b.data.tips })
|
||||
},
|
||||
fail: function (b) {
|
||||
a.fail({ errCode: '0', errMsg: b.errMsg || '' })
|
||||
},
|
||||
})
|
||||
}),
|
||||
(AMapWX.prototype.getDrivingRoute = function (a) {
|
||||
var b = this,
|
||||
c = b.requestConfig,
|
||||
d = {
|
||||
key: b.key,
|
||||
s: c.s,
|
||||
platform: c.platform,
|
||||
appname: b.key,
|
||||
sdkversion: c.sdkversion,
|
||||
logversion: c.logversion,
|
||||
}
|
||||
a.origin && (d['origin'] = a.origin),
|
||||
a.destination && (d['destination'] = a.destination),
|
||||
a.strategy && (d['strategy'] = a.strategy),
|
||||
a.waypoints && (d['waypoints'] = a.waypoints),
|
||||
a.avoidpolygons && (d['avoidpolygons'] = a.avoidpolygons),
|
||||
a.avoidroad && (d['avoidroad'] = a.avoidroad),
|
||||
wx.request({
|
||||
url: 'https://restapi.amap.com/v3/direction/driving',
|
||||
data: d,
|
||||
method: 'GET',
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: function (b) {
|
||||
b &&
|
||||
b.data &&
|
||||
b.data.route &&
|
||||
a.success({ paths: b.data.route.paths, taxi_cost: b.data.route.taxi_cost || '' })
|
||||
},
|
||||
fail: function (b) {
|
||||
a.fail({ errCode: '0', errMsg: b.errMsg || '' })
|
||||
},
|
||||
})
|
||||
}),
|
||||
(AMapWX.prototype.getWalkingRoute = function (a) {
|
||||
var b = this,
|
||||
c = b.requestConfig,
|
||||
d = {
|
||||
key: b.key,
|
||||
s: c.s,
|
||||
platform: c.platform,
|
||||
appname: b.key,
|
||||
sdkversion: c.sdkversion,
|
||||
logversion: c.logversion,
|
||||
}
|
||||
a.origin && (d['origin'] = a.origin),
|
||||
a.destination && (d['destination'] = a.destination),
|
||||
wx.request({
|
||||
url: 'https://restapi.amap.com/v3/direction/walking',
|
||||
data: d,
|
||||
method: 'GET',
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: function (b) {
|
||||
b && b.data && b.data.route && a.success({ paths: b.data.route.paths })
|
||||
},
|
||||
fail: function (b) {
|
||||
a.fail({ errCode: '0', errMsg: b.errMsg || '' })
|
||||
},
|
||||
})
|
||||
}),
|
||||
(AMapWX.prototype.getTransitRoute = function (a) {
|
||||
var b = this,
|
||||
c = b.requestConfig,
|
||||
d = {
|
||||
key: b.key,
|
||||
s: c.s,
|
||||
platform: c.platform,
|
||||
appname: b.key,
|
||||
sdkversion: c.sdkversion,
|
||||
logversion: c.logversion,
|
||||
}
|
||||
a.origin && (d['origin'] = a.origin),
|
||||
a.destination && (d['destination'] = a.destination),
|
||||
a.strategy && (d['strategy'] = a.strategy),
|
||||
a.city && (d['city'] = a.city),
|
||||
a.cityd && (d['cityd'] = a.cityd),
|
||||
wx.request({
|
||||
url: 'https://restapi.amap.com/v3/direction/transit/integrated',
|
||||
data: d,
|
||||
method: 'GET',
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: function (b) {
|
||||
if (b && b.data && b.data.route) {
|
||||
var c = b.data.route
|
||||
a.success({
|
||||
distance: c.distance || '',
|
||||
taxi_cost: c.taxi_cost || '',
|
||||
transits: c.transits,
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: function (b) {
|
||||
a.fail({ errCode: '0', errMsg: b.errMsg || '' })
|
||||
},
|
||||
})
|
||||
}),
|
||||
(AMapWX.prototype.getRidingRoute = function (a) {
|
||||
var b = this,
|
||||
c = b.requestConfig,
|
||||
d = {
|
||||
key: b.key,
|
||||
s: c.s,
|
||||
platform: c.platform,
|
||||
appname: b.key,
|
||||
sdkversion: c.sdkversion,
|
||||
logversion: c.logversion,
|
||||
}
|
||||
a.origin && (d['origin'] = a.origin),
|
||||
a.destination && (d['destination'] = a.destination),
|
||||
wx.request({
|
||||
url: 'https://restapi.amap.com/v4/direction/bicycling',
|
||||
data: d,
|
||||
method: 'GET',
|
||||
header: { 'content-type': 'application/json' },
|
||||
success: function (b) {
|
||||
b && b.data && b.data.data && a.success({ paths: b.data.data.paths })
|
||||
},
|
||||
fail: function (b) {
|
||||
a.fail({ errCode: '0', errMsg: b.errMsg || '' })
|
||||
},
|
||||
})
|
||||
})
|
||||
// 修改导出方式
|
||||
const amap = {
|
||||
AMapWX: AMapWX
|
||||
};
|
||||
// ES6 导出方式
|
||||
export default amap;
|
||||
63
JeecgUniapp-master/src/common/renderUtils.ts
Normal file
63
JeecgUniapp-master/src/common/renderUtils.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { h } from 'vue'
|
||||
import { filterMultiDictText, getFileAccessHttpUrl } from '@/common/uitls'
|
||||
|
||||
const render = {
|
||||
/**
|
||||
* 渲染图片
|
||||
* @param text
|
||||
*/
|
||||
renderImage: ({ text }) => {
|
||||
if (!text) {
|
||||
return ;
|
||||
}
|
||||
let avatarList = text.split(',')
|
||||
return h(
|
||||
'span',
|
||||
avatarList.map((item) => {
|
||||
return h(Image, {
|
||||
src: getFileAccessHttpUrl(item),
|
||||
width: 30,
|
||||
height: 30,
|
||||
style: { marginRight: '5px' },
|
||||
})
|
||||
}),
|
||||
)
|
||||
//update-end-author:taoyan date:2022-5-24 for: VUEN-1084 【vue3】online表单测试发现的新问题 41、生成的代码,树默认图大小未改
|
||||
},
|
||||
/**
|
||||
* 渲染a标签
|
||||
* @param text
|
||||
*/
|
||||
renderHref: ({ text }) => {
|
||||
if (!text) {
|
||||
return ''
|
||||
}
|
||||
const len = 20
|
||||
if (text.length > len) {
|
||||
text = text.substr(0, len)
|
||||
}
|
||||
return h('a', { href: text, target: '_blank' }, text)
|
||||
},
|
||||
/**
|
||||
* 根据字典渲染
|
||||
* @param v
|
||||
* @param array
|
||||
*/
|
||||
renderDictNative: (v, array, renderTag = false) => {
|
||||
let text = ''
|
||||
let color = ''
|
||||
let obj = array.filter((item) => {
|
||||
return item.value == v
|
||||
})
|
||||
if (obj.length > 0) {
|
||||
text = obj[0].label
|
||||
color = obj[0].color
|
||||
}
|
||||
return h('span', text)
|
||||
},
|
||||
renderSwitch: (text, arr) => {
|
||||
return text ? filterMultiDictText(arr, text) : ''
|
||||
},
|
||||
}
|
||||
|
||||
export { render }
|
||||
166
JeecgUniapp-master/src/common/socket.ts
Normal file
166
JeecgUniapp-master/src/common/socket.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
// @ts-nocheck
|
||||
import md5 from 'md5'
|
||||
import { useUserStore } from '@/store/user'
|
||||
|
||||
const baseUrl = import.meta.env.VITE_SERVER_BASEURL
|
||||
|
||||
class socket {
|
||||
constructor() {
|
||||
this.socketUrl = baseUrl
|
||||
this.socketStart = false
|
||||
this.socketType = 'websocket'
|
||||
this._heartbeatTimer = null
|
||||
this.monitorSocketError()
|
||||
this.monitorSocketClose()
|
||||
this.socketReceive()
|
||||
}
|
||||
|
||||
//update-begin---author:xsl ---date:2026-07-20 for:【APP对接真实IM】系统 WebSocket 地址与 PC 一致-----------
|
||||
/** 构建系统 WebSocket 地址:/websocket/{userId}_{md5(token)} */
|
||||
buildSystemWebSocketUrl() {
|
||||
const userStore = useUserStore()
|
||||
const userId = userStore.userInfo.userid
|
||||
const token = userStore.userInfo.token || ''
|
||||
if (!userId || !this.socketUrl) {
|
||||
return ''
|
||||
}
|
||||
const wsClientId = md5(token)
|
||||
const base = this.socketUrl.replace('https://', 'wss://').replace('http://', 'ws://').replace(/\/$/, '')
|
||||
return `${base}/websocket/${userId}_${wsClientId}`
|
||||
}
|
||||
//update-end---author:xsl ---date:2026-07-20 for:【APP对接真实IM】系统 WebSocket 地址与 PC 一致-----------
|
||||
|
||||
init(socket_type, callback?) {
|
||||
const userStore = useUserStore()
|
||||
if (!baseUrl) {
|
||||
console.log('config/baseUrl socketUrl为空')
|
||||
return
|
||||
}
|
||||
if (this.socketStart) {
|
||||
console.log('webSocket已经启动了')
|
||||
callback && callback()
|
||||
return
|
||||
}
|
||||
this.socketType = socket_type || 'websocket'
|
||||
//update-begin---author:xsl ---date:2026-07-20 for:【APP对接真实IM】连接原生 /websocket-----------
|
||||
let url = this.buildSystemWebSocketUrl()
|
||||
if (this.socketType !== 'websocket') {
|
||||
// 兼容旧路径(如历史业务仍传入自定义 type)
|
||||
url =
|
||||
this.socketUrl.replace('https://', 'wss://').replace('http://', 'ws://') +
|
||||
'/' +
|
||||
this.socketType +
|
||||
'/' +
|
||||
userStore.userInfo.userid +
|
||||
'_app'
|
||||
}
|
||||
//update-end---author:xsl ---date:2026-07-20 for:【APP对接真实IM】连接原生 /websocket-----------
|
||||
if (!url) {
|
||||
console.log('WebSocket url 为空,跳过连接')
|
||||
return
|
||||
}
|
||||
console.log('启动this.socketUrl连接地址:', url)
|
||||
const token = userStore.userInfo.token
|
||||
uni.connectSocket({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
protocols: [token],
|
||||
})
|
||||
uni.onSocketOpen((res) => {
|
||||
this.socketStart = true
|
||||
callback && callback()
|
||||
console.log('WebSocket连接已打开!')
|
||||
this.startHeartbeat()
|
||||
})
|
||||
}
|
||||
|
||||
// Socket给服务器发送消息
|
||||
send(data, callback) {
|
||||
const userStore = useUserStore()
|
||||
if (userStore.userInfo.userid && typeof data === 'object' && data !== null) {
|
||||
data.userUid = userStore.userInfo.userid
|
||||
}
|
||||
const payload = typeof data === 'string' ? data : JSON.stringify(data)
|
||||
uni.sendSocketMessage({
|
||||
data: payload,
|
||||
success: () => {
|
||||
callback && callback(true)
|
||||
},
|
||||
fail: () => {
|
||||
callback && callback(false)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
acceptMessage(msg) {
|
||||
console.log(msg)
|
||||
}
|
||||
|
||||
// Socket接收服务器发送过来的消息
|
||||
socketReceive() {
|
||||
uni.onSocketMessage((res) => {
|
||||
//update-begin---author:xsl ---date:2026-07-20 for:【APP对接真实IM】忽略心跳并解析 JSON-----------
|
||||
if (res.data === 'ping' || res.data === 'pong') {
|
||||
return
|
||||
}
|
||||
let data
|
||||
try {
|
||||
data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
|
||||
} catch (e) {
|
||||
console.log('WebSocket 非 JSON 消息:', res.data)
|
||||
return
|
||||
}
|
||||
//update-end---author:xsl ---date:2026-07-20 for:【APP对接真实IM】忽略心跳并解析 JSON-----------
|
||||
this.acceptMessage && this.acceptMessage(data)
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭Socket
|
||||
closeSocket() {
|
||||
this.stopHeartbeat()
|
||||
uni.closeSocket()
|
||||
this.socketStart = false
|
||||
}
|
||||
|
||||
// 监听Socket关闭
|
||||
monitorSocketClose() {
|
||||
uni.onSocketClose((res) => {
|
||||
console.log('WebSocket 已关闭!')
|
||||
this.socketStart = false
|
||||
this.stopHeartbeat()
|
||||
setTimeout(() => {
|
||||
this.init(this.socketType)
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
|
||||
// 监听Socket错误
|
||||
monitorSocketError() {
|
||||
uni.onSocketError((res) => {
|
||||
this.socketStart = false
|
||||
console.log('WebSocket连接打开失败,请检查!')
|
||||
})
|
||||
}
|
||||
|
||||
//update-begin---author:xsl ---date:2026-07-20 for:【APP对接真实IM】心跳 ping-----------
|
||||
startHeartbeat() {
|
||||
this.stopHeartbeat()
|
||||
this._heartbeatTimer = setInterval(() => {
|
||||
if (!this.socketStart) {
|
||||
return
|
||||
}
|
||||
this.send('ping')
|
||||
}, 55000)
|
||||
}
|
||||
|
||||
stopHeartbeat() {
|
||||
if (this._heartbeatTimer) {
|
||||
clearInterval(this._heartbeatTimer)
|
||||
this._heartbeatTimer = null
|
||||
}
|
||||
}
|
||||
//update-end---author:xsl ---date:2026-07-20 for:【APP对接真实IM】心跳 ping-----------
|
||||
}
|
||||
|
||||
const mySocket = new socket()
|
||||
export default mySocket
|
||||
132
JeecgUniapp-master/src/common/tip.ts
Normal file
132
JeecgUniapp-master/src/common/tip.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* 提示与加载工具类
|
||||
*/
|
||||
export default class Tips {
|
||||
// 声明 isLoading 属性
|
||||
static isLoading: boolean
|
||||
|
||||
// 移除无用的构造函数,因为静态属性不需要在构造函数中初始化
|
||||
/**
|
||||
* 弹出提示框
|
||||
*/
|
||||
|
||||
static success(title, duration = 1000) {
|
||||
setTimeout(() => {
|
||||
uni.showToast({
|
||||
title,
|
||||
icon: 'success',
|
||||
mask: true,
|
||||
duration,
|
||||
})
|
||||
}, 300)
|
||||
if (duration > 0) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, duration)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出确认窗口
|
||||
*/
|
||||
static confirm(text, showCancel, payload = {}, title = '提示') {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.showModal({
|
||||
title,
|
||||
content: text,
|
||||
showCancel,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
resolve(payload)
|
||||
} else if (res.cancel) {
|
||||
reject(payload)
|
||||
}
|
||||
},
|
||||
fail: (res) => {
|
||||
reject(payload)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
static toast(title, onHide, icon = 'none') {
|
||||
setTimeout(() => {
|
||||
uni.showToast({
|
||||
title,
|
||||
// 确保 icon 的值是合法的
|
||||
icon: icon as 'success' | 'loading' | 'error' | 'none' | 'fail' | 'exception',
|
||||
mask: true,
|
||||
duration: 1000,
|
||||
})
|
||||
}, 300)
|
||||
|
||||
// 隐藏结束回调
|
||||
if (onHide) {
|
||||
setTimeout(() => {
|
||||
onHide()
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 警告框
|
||||
*/
|
||||
static alert(title) {
|
||||
uni.showToast({
|
||||
title,
|
||||
image: '/static/alert.png',
|
||||
mask: true,
|
||||
duration: 1500,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误框
|
||||
*/
|
||||
|
||||
static error(title, onHide) {
|
||||
uni.showToast({
|
||||
title,
|
||||
image: '/static/error.png',
|
||||
mask: true,
|
||||
duration: 1500,
|
||||
})
|
||||
// 隐藏结束回调
|
||||
if (onHide) {
|
||||
setTimeout(() => {
|
||||
onHide()
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出加载提示
|
||||
*/
|
||||
static loading(title = '加载中') {
|
||||
if (Tips.isLoading) {
|
||||
return
|
||||
}
|
||||
Tips.isLoading = true
|
||||
uni.showLoading({
|
||||
title,
|
||||
mask: true,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载完毕
|
||||
*/
|
||||
static loaded() {
|
||||
if (Tips.isLoading) {
|
||||
Tips.isLoading = false
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态变量,是否加载中
|
||||
*/
|
||||
Tips.isLoading = false
|
||||
756
JeecgUniapp-master/src/common/uitls.ts
Normal file
756
JeecgUniapp-master/src/common/uitls.ts
Normal file
@@ -0,0 +1,756 @@
|
||||
import pagesJson from '../pages.json'
|
||||
// 引入uni-parse-pages
|
||||
import pagesJsonToRoutes from 'uni-parse-pages'
|
||||
import { colorPanel } from './constants'
|
||||
import tip from './tip'
|
||||
import { platform, isH5, isApp, isMp, isMpWeixin, isMpAplipay, isMpToutiao } from '@/utils/platform'
|
||||
import { isString } from './is'
|
||||
|
||||
/**
|
||||
* 缓存,默认有效期2小时
|
||||
* @param key 缓存key
|
||||
* @param value 缓存值
|
||||
* @param seconds 缓存时间(秒)
|
||||
* @returns {*}
|
||||
*/
|
||||
export function cache(key, value = null, seconds = 2 * 3600) {
|
||||
const timestamp = +new Date() / 1000
|
||||
if (key && value === null) {
|
||||
// 获取缓存
|
||||
const val = uni.getStorageSync(key)
|
||||
if (val && val.length > 0) {
|
||||
const 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) {
|
||||
// 设置缓存
|
||||
const 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, '/')
|
||||
const 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()) {
|
||||
const 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
|
||||
const 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
|
||||
const [length] = arguments
|
||||
// 生成指定长度的随机数字,首位一定不是 0
|
||||
// @ts-ignore
|
||||
const 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
|
||||
const [min, max] = arguments
|
||||
return random(min, max)
|
||||
} else {
|
||||
return Number.NaN
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间格式化
|
||||
* @param value
|
||||
* @param fmt
|
||||
* @returns {*}
|
||||
*/
|
||||
export function formatDate(value, fmt) {
|
||||
const regPos = /^\d+(\.\d+)?$/
|
||||
if (regPos.test(value)) {
|
||||
// 如果是数字
|
||||
const getDate = new Date(value)
|
||||
const 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 (const 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 // 月份是从0开始的,所以要加1
|
||||
// 计算季度
|
||||
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() {
|
||||
const colorType = ['classic', 'technology', 'business', 'botany', 'natural', 'colour']
|
||||
// 生成一个随机索引,范围是从 0 到数组长度减 1
|
||||
const randomIndex = Math.floor(Math.random() * colorType.length)
|
||||
// 根据随机索引从数组中获取一个随机类型
|
||||
const 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}`
|
||||
}
|
||||
/**
|
||||
* 日期格式化
|
||||
* @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
|
||||
console.log('getFormat Datetext', text)
|
||||
console.log('getFormatDate fieldExtendJson', 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 (const 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()
|
||||
const 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)
|
||||
}
|
||||
|
||||
/** 获取url参数
|
||||
* @param {Object} url
|
||||
*/
|
||||
export function getQueryVariable(url) {
|
||||
if (!url) return
|
||||
let t
|
||||
let n
|
||||
let r
|
||||
const i = url.split('?')[1]
|
||||
const s = {}
|
||||
;(t = i.split('&')), (r = null), (n = null)
|
||||
for (const o in t) {
|
||||
const u = t[o].indexOf('=')
|
||||
u !== -1 && ((r = t[o].substr(0, u)), (n = t[o].substr(u + 1)), (s[r] = n))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否oauth环境
|
||||
*/
|
||||
export function isOAuth2AppEnv() {
|
||||
let isOAuthEnv = false
|
||||
// #ifdef H5
|
||||
isOAuthEnv = /wxwork|dingtalk/i.test(navigator.userAgent)
|
||||
// #endif
|
||||
return isOAuthEnv
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取url中的参数
|
||||
* @param url
|
||||
*/
|
||||
export const getUrlParams = (url) => {
|
||||
const result = {
|
||||
url: '',
|
||||
params: {},
|
||||
}
|
||||
const list = url.split('?')
|
||||
result.url = list[0]
|
||||
const params = list[1]
|
||||
if (params) {
|
||||
const list = params.split('&')
|
||||
list.forEach((ele) => {
|
||||
const dic = ele.split('=')
|
||||
const label = dic[0]
|
||||
const value = dic[1]
|
||||
result.params[label] = value
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件地址是否支持存在
|
||||
* @param url
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export function downloadAbled(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let xmlHttp
|
||||
if (window.XMLHttpRequest) {
|
||||
xmlHttp = new XMLHttpRequest() // 其他浏览器
|
||||
} else if ((window as any).ActiveXObject) {
|
||||
// try {
|
||||
// xmlHttp = new window.ActiveXObject("Microsoft.XMLHTTP");//IE
|
||||
// }catch (e) {
|
||||
// console.log('创建xmlHttp失败',e)
|
||||
// }
|
||||
}
|
||||
if (!xmlHttp) {
|
||||
reject('创建xmlHttp失败')
|
||||
}
|
||||
xmlHttp.open('GET', url, false)
|
||||
xmlHttp.send()
|
||||
if (xmlHttp.readyState == 4) {
|
||||
if (xmlHttp.status == 200) {
|
||||
resolve(true)
|
||||
} else if (xmlHttp.status == 404) {
|
||||
reject('文件不存在!')
|
||||
} else {
|
||||
reject('请求失败,status:' + xmlHttp.status)
|
||||
}
|
||||
} else {
|
||||
reject('请求失败,readyState:' + xmlHttp.readyState)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* app获取url地址的传参问题
|
||||
* @param url
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function appGetUrlParams(url) {
|
||||
const theRequest = new Object()
|
||||
const index = url.indexOf('?')
|
||||
if (index != -1) {
|
||||
const str = url.substring(index + 1)
|
||||
const strs = str.split('&')
|
||||
for (let i = 0; i < strs.length; i++) {
|
||||
theRequest[strs[i].split('=')[0]] = unescape(strs[i].split('=')[1])
|
||||
}
|
||||
}
|
||||
return theRequest
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param lat1 纬度1
|
||||
* @param lng1 经度1
|
||||
* @param lat2 纬度2
|
||||
* @param lng2 经度2
|
||||
* 返回 米
|
||||
*/
|
||||
export function geoDistance(lng1, lat1, lng2, lat2) {
|
||||
let radLat1 = rad(lat1)
|
||||
let radLat2 = rad(lat2)
|
||||
let a = radLat1 - radLat2
|
||||
let b = rad(lng1) - rad(lng2)
|
||||
let s =
|
||||
2 *
|
||||
Math.asin(
|
||||
Math.sqrt(
|
||||
Math.pow(Math.sin(a / 2), 2) +
|
||||
Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2),
|
||||
),
|
||||
)
|
||||
s = s * 6378.137 // EARTH_RADIUS;
|
||||
s = Math.round(s * 10000) / 10
|
||||
return s
|
||||
}
|
||||
//经纬度转换成三角函数中度分表形式。
|
||||
function rad(d) {
|
||||
return (d * Math.PI) / 180.0
|
||||
}
|
||||
|
||||
export function downloadFile(obj) {
|
||||
let url = ''
|
||||
if (isMp) {
|
||||
if (obj.currentTarget) {
|
||||
url = encodeURI(obj.currentTarget.dataset.url)
|
||||
downloadNH5(url)
|
||||
} else if (isString(obj)) {
|
||||
// 为字符串类型时,可认为就是url
|
||||
url = getFileAccessHttpUrl(obj)
|
||||
downloadNH5(url)
|
||||
}
|
||||
} else if (isApp) {
|
||||
url = encodeURI(obj)
|
||||
downloadNH5(url)
|
||||
} else if (isH5) {
|
||||
url = getFileAccessHttpUrl(obj)
|
||||
window.open(url)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 非H5文件下载地址
|
||||
* @param 文件路径 url
|
||||
*/
|
||||
function downloadNH5(url) {
|
||||
const image_arr = ['png', 'jpg', 'jpeg']
|
||||
const fileType = url.split('.').pop()
|
||||
uni.downloadFile({
|
||||
url,
|
||||
success: (res) => {
|
||||
if (res.statusCode == 200) {
|
||||
let filePath = res.tempFilePath
|
||||
const system = uni.getSystemInfoSync().platform
|
||||
if (system == 'ios') {
|
||||
filePath = encodeURI(filePath)
|
||||
}
|
||||
if (isMp) {
|
||||
const suffix = getSuffix(url).toLowerCase()
|
||||
if (image_arr.indexOf(suffix) != -1) {
|
||||
// 预览图片
|
||||
uni.previewImage({
|
||||
urls: [filePath],
|
||||
})
|
||||
} else {
|
||||
uni.openDocument({
|
||||
filePath,
|
||||
fileType,
|
||||
success: (res) => {
|
||||
console.log('打开文档成功')
|
||||
},
|
||||
fail: (res) => {
|
||||
console.log('打开文档失败')
|
||||
tip.error('不支持打开此格式', true)
|
||||
},
|
||||
})
|
||||
}
|
||||
} else if (isApp) {
|
||||
const suffix = getSuffix(url).toLowerCase()
|
||||
if (image_arr.indexOf(suffix) != -1) {
|
||||
// 预览图片
|
||||
uni.previewImage({
|
||||
urls: [filePath],
|
||||
})
|
||||
} else {
|
||||
uni.saveFile({
|
||||
tempFilePath: filePath,
|
||||
success: (res) => {
|
||||
// 保存成功并打开文件
|
||||
tip.success('保存成功')
|
||||
uni.openDocument({
|
||||
filePath: res.savedFilePath,
|
||||
success: function (res) {
|
||||
console.log('openDocument', res)
|
||||
},
|
||||
fail() {
|
||||
uni.showToast({
|
||||
title: '暂不支持打开此类型',
|
||||
duration: 2000,
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
fail: () => tip.alert('保存失败'),
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tip.alert('文件异常')
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
tip.alert('下载失败')
|
||||
},
|
||||
})
|
||||
}
|
||||
// 获取后缀
|
||||
export function getSuffix(text) {
|
||||
if (text) {
|
||||
const arr = text.split('.')
|
||||
const suffix = arr[arr.length - 1]
|
||||
return suffix
|
||||
}
|
||||
return text
|
||||
}
|
||||
/**
|
||||
* 获取后缀图标
|
||||
* @param {Object} doctype
|
||||
*/
|
||||
export function getFileIcon(doctype) {
|
||||
const file_doc_type_arr = ['doc', 'excel', 'file', 'folder', 'image', 'pdf', 'video']
|
||||
|
||||
if (file_doc_type_arr.indexOf(doctype) >= 0) {
|
||||
return `/static/${doctype}.png`
|
||||
} else {
|
||||
return '/static/file.png'
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取uuid
|
||||
*/
|
||||
export function uuid() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = (Math.random() * 16) | 0,
|
||||
v = c == 'x' ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
||||
|
||||
export const queryString = (params) => {
|
||||
return Object.entries(params)
|
||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value as string)}`)
|
||||
.join('&')
|
||||
}
|
||||
|
||||
/**
|
||||
* 将任意日期转换为对应季度的首日
|
||||
* @param {Date|string} date - 日期对象或日期字符串
|
||||
* @returns {string} 格式为'YYYY-MM-DD'的季度首日
|
||||
*/
|
||||
export function dateToQuarterStart(date) {
|
||||
if (!date){
|
||||
return ''
|
||||
}
|
||||
// 如果传入的是字符串,转换为Date对象
|
||||
if (typeof date === 'string') {
|
||||
date = new Date(date)
|
||||
}
|
||||
|
||||
const year = date.getFullYear()
|
||||
const month = date.getMonth() // 0-11
|
||||
|
||||
// 计算季度
|
||||
const quarter = Math.floor(month / 3)
|
||||
|
||||
// 确定季度首月的月份 (0=1月, 3=4月, 6=7月, 9=10月)
|
||||
const quarterStartMonth = quarter * 3
|
||||
|
||||
// 格式化为'YYYY-MM-DD'
|
||||
return `${year}-${String(quarterStartMonth + 1).padStart(2, '0')}-01`
|
||||
}
|
||||
|
||||
//时间数组
|
||||
export function timeData(startTime = '08:00', endTime = '18:30', timeInterval = 0.5) {
|
||||
const time = []
|
||||
const date = timeStamp(Date.now()).allDate
|
||||
const startDate = `${date} ${startTime}`
|
||||
const endDate = `${date} ${endTime}`
|
||||
const startTimeStamp = new Date(startDate).getTime()
|
||||
const endTimeStamp = new Date(endDate).getTime()
|
||||
const timeStr = 3600 * 1000 * timeInterval
|
||||
console.log(startTimeStamp)
|
||||
for (let i = startTimeStamp; i <= endTimeStamp; i = i + timeStr) {
|
||||
const timeObj:any = {}
|
||||
timeObj.time = timeStamp(i).hour
|
||||
timeObj.disable = 0
|
||||
time.push(timeObj)
|
||||
}
|
||||
return time
|
||||
}
|
||||
//字符串拼接
|
||||
function strFormat(str) {
|
||||
return str < 10 ? `0${str}` : str
|
||||
}
|
||||
//时间戳转字符串
|
||||
export function timeStamp(time) {
|
||||
const dates = new Date(time)
|
||||
const year = dates.getFullYear()
|
||||
const month = dates.getMonth() + 1
|
||||
const date = dates.getDate()
|
||||
const day = dates.getDay()
|
||||
const hour = dates.getHours()
|
||||
const min = dates.getMinutes()
|
||||
const days = ['日', '一', '二', '三', '四', '五', '六']
|
||||
return {
|
||||
allDate: `${year}/${strFormat(month)}/${strFormat(date)}`,
|
||||
date: `${strFormat(month)}-${strFormat(date)}`, //返回的日期 07-01
|
||||
day: `星期${days[day]}`, //返回的礼拜天数 星期一
|
||||
hour: strFormat(hour) + ':' + strFormat(min) //返回的时钟 08:00
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 字符串转时间
|
||||
* @param {Object} dateString
|
||||
*/
|
||||
export function stringToDate(dateString) {
|
||||
var newDate = new Date();
|
||||
var yearNum = Number(dateString.substr(0,4));
|
||||
var monthNum = Number(dateString.substr(5,2))-1;
|
||||
var dayNum = Number(dateString.substr(8,2));
|
||||
var hourNum = Number(dateString.substr(11,2));
|
||||
var minuteNum = Number(dateString.substr(14,2));
|
||||
|
||||
//var secondNum = Number(dateString.substr(12,2));
|
||||
newDate.setFullYear(yearNum);
|
||||
newDate.setMonth(monthNum);
|
||||
newDate.setDate(dayNum);
|
||||
newDate.setHours(hourNum);
|
||||
newDate.setMinutes(minuteNum);
|
||||
//newDate.setSeconds(secondNum);
|
||||
return newDate;
|
||||
}
|
||||
|
||||
135
JeecgUniapp-master/src/common/work.ts
Normal file
135
JeecgUniapp-master/src/common/work.ts
Normal 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: 'online',
|
||||
useCount: 10000,
|
||||
routeIndex: 'online',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
title: '仪表盘',
|
||||
icon: icon_prefix + 'chart.png',
|
||||
description: '仪表盘',
|
||||
useCount: 10000,
|
||||
routeIndex: 'workHome',
|
||||
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: 'flowIndex',
|
||||
},
|
||||
{
|
||||
title: '知识库',
|
||||
icon: icon_prefix + 'qingjia1.png',
|
||||
description: '知识库',
|
||||
useCount: 10000,
|
||||
routeIndex: 'knowledge',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
title: '通知公告',
|
||||
icon: icon_prefix + 'tongzhi.png',
|
||||
description: '查看企业对员工下发的通知公告',
|
||||
useCount: 10000,
|
||||
routeIndex: 'annotationList',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
title: '请假申请',
|
||||
icon: icon_prefix + 'richang.png',
|
||||
description: '请假申请',
|
||||
useCount: 10000,
|
||||
routeIndex: 'leave',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
title: '出差申请',
|
||||
icon: icon_prefix + 'zhoubao.png',
|
||||
description: '出差申请',
|
||||
useCount: 10000,
|
||||
routeIndex: 'businesStrip',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
title: '公文发文',
|
||||
icon: icon_prefix + 'zhoubao.png',
|
||||
description: '公文发文',
|
||||
useCount: 10000,
|
||||
routeIndex: 'docSend',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
title: '日程',
|
||||
icon: icon_prefix + 'richeng.png',
|
||||
description: '建立和查看个人工作安排',
|
||||
useCount: 10000,
|
||||
routeIndex: 'schedule',
|
||||
},
|
||||
{
|
||||
title: '考勤',
|
||||
icon: icon_prefix + 'kaoqin.png',
|
||||
description: '工作考勤',
|
||||
routeIndex: 'ClockIn',
|
||||
useCount: 10000,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
title: '内部邮件',
|
||||
icon: icon_prefix + 'youjian.png',
|
||||
description: '查看内部消息',
|
||||
useCount: 10000,
|
||||
dot: false,
|
||||
routeIndex: 'mailHome',
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
* 生产服务(原其他服务)
|
||||
* 首页「更多」按钮仍按 type=other 追加,勿删空数组
|
||||
*/
|
||||
export const os = {
|
||||
data: [
|
||||
//update-begin---author:xsl ---date:20260720 for:【APP首页】生产服务菜单调整-----------
|
||||
{
|
||||
title: '原材料送检',
|
||||
icon: icon_prefix + 'renwu.png',
|
||||
description: '扫码送检与录入检验结果',
|
||||
useCount: 10000,
|
||||
routeIndex: 'rawMaterialInspect',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
title: '扫描识别',
|
||||
icon: icon_prefix + 'wendang.png',
|
||||
description: '手持机扫描头识别条码',
|
||||
useCount: 10000,
|
||||
routeIndex: 'scanIdentify',
|
||||
enabled: true,
|
||||
},
|
||||
//update-end---author:xsl ---date:20260720 for:【APP首页】生产服务菜单调整-----------
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<wd-popup v-model="show" position="bottom" @close="handleClose">
|
||||
<view class="contetn">
|
||||
<wd-text custom-class="title" 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-top: 10px;
|
||||
:deep(.title) {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
:deep(.wd-cell) {
|
||||
padding-left: 10px;
|
||||
--wot-cell-icon-size: 15px;
|
||||
--wot-cell-label-color: #444;
|
||||
--wot-cell-label-fs: 14px;
|
||||
.wd-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.wd-cell__label {
|
||||
margin-top: 0;
|
||||
}
|
||||
&.red {
|
||||
color: red;
|
||||
--wot-cell-label-color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
69
JeecgUniapp-master/src/components/Breadcrumb/Breadcrumb.vue
Normal file
69
JeecgUniapp-master/src/components/Breadcrumb/Breadcrumb.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="breadcrumb">
|
||||
<template v-for="(item, index) in items" :key="index">
|
||||
<span
|
||||
class="breadcrumb-item"
|
||||
:class="{ 'is-link': index < items.length - 1 }"
|
||||
@click="handleClick(item, index)"
|
||||
>
|
||||
<slot :name="'item-' + index" :item="item">
|
||||
{{ item.title }}
|
||||
</slot>
|
||||
</span>
|
||||
<span v-if="index < items.length - 1" class="breadcrumb-separator">{{ separator }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'Breadcrumb',
|
||||
})
|
||||
|
||||
interface BreadcrumbItem {
|
||||
title: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
items: {
|
||||
type: Array as PropType<BreadcrumbItem[]>,
|
||||
default: () => [],
|
||||
},
|
||||
separator: {
|
||||
type: String,
|
||||
default: '/',
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
const handleClick = (item: BreadcrumbItem, index: number) => {
|
||||
if (index < props.items.length - 1) {
|
||||
emit('click', item, index)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
padding: 8px;
|
||||
&-item {
|
||||
&.is-link {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
&:last-child {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
}
|
||||
&-separator {
|
||||
margin: 0 8px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<view class="CategorySelect">
|
||||
<view class="pickerArea" :class="{ clear: !!showText }" @click="handleClick">
|
||||
<wd-input
|
||||
:placeholder="`请选择${$attrs.label}`"
|
||||
v-bind="$attrs"
|
||||
readonly
|
||||
v-model="showText"
|
||||
></wd-input>
|
||||
<view v-if="!!showText && !$attrs.disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</view>
|
||||
<wd-popup v-if="popupShow" 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"
|
||||
:defaultCheckedKeys="defaultCheckedKeys"
|
||||
: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],
|
||||
},
|
||||
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 attrs = useAttrs()
|
||||
const defaultCheckedKeys: any = ref([])
|
||||
const handleClick = () => {
|
||||
if (!attrs.disabled) {
|
||||
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(value){
|
||||
if (isArray(props.modelValue)) {
|
||||
// @ts-ignore
|
||||
value = value.join()
|
||||
}
|
||||
if (value === treeData.value.map((item) => item.key).join(',')) {
|
||||
// 说明是刚选完,内部已有翻译。不需要再请求
|
||||
return
|
||||
}
|
||||
value = (value as string).trim()
|
||||
if (value) {
|
||||
http
|
||||
.get(api.loadDictItem, { ids: value })
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
const { result = [] } = res
|
||||
showText.value = result.join(',')
|
||||
defaultCheckedKeys.value = value
|
||||
} else {
|
||||
}
|
||||
})
|
||||
.catch((err) => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
showText.value = ''
|
||||
treeValue.value = []
|
||||
confirm()
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
341
JeecgUniapp-master/src/components/DateTime/DateTime.vue
Normal file
341
JeecgUniapp-master/src/components/DateTime/DateTime.vue
Normal file
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<view class="DateTime">
|
||||
<view class="inputArea" :class="{ clear: !!showText }" @click="handleClick">
|
||||
<wd-input
|
||||
:placeholder="getPlaceholder($attrs)"
|
||||
v-bind="$attrs"
|
||||
readonly
|
||||
v-model="showText"
|
||||
></wd-input>
|
||||
<view v-if="!!showText && !disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</view>
|
||||
<wd-popup v-if="popupShow" 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>
|
||||
<picker-view class="picker-view" :value="pickerValue" @change="handleChange">
|
||||
<!-- 年 -->
|
||||
<picker-view-column v-if="showYear">
|
||||
<view class="picker-item" v-for="(item, index) in years" :key="index">
|
||||
{{ item }}年
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<!-- 月 -->
|
||||
<picker-view-column v-if="showMonth">
|
||||
<view class="picker-item" v-for="(item, index) in months" :key="index">
|
||||
{{ item }}月
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<!-- 日 -->
|
||||
<picker-view-column v-if="showDay">
|
||||
<view class="picker-item" v-for="(item, index) in days" :key="index">{{ item }}日</view>
|
||||
</picker-view-column>
|
||||
<!-- 时 -->
|
||||
<picker-view-column v-if="showHour">
|
||||
<view class="picker-item" v-for="(item, index) in hours" :key="index">
|
||||
{{ item }}时
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<!-- 分 -->
|
||||
<picker-view-column v-if="showMinute">
|
||||
<view class="picker-item" v-for="(item, index) in minutes" :key="index">
|
||||
{{ item }}分
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<!-- 秒 -->
|
||||
<picker-view-column v-if="showSecond">
|
||||
<view class="picker-item" v-for="(item, index) in seconds" :key="index">
|
||||
{{ item }}秒
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import dayjs from 'dayjs'
|
||||
import { getPlaceholder } from '@/common/uitls'
|
||||
|
||||
defineOptions({
|
||||
name: 'DateTime',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// 显示格式:year/month/day/hour/minute/second
|
||||
format: {
|
||||
type: String,
|
||||
default: 'YYYY-MM-DD HH:mm:ss',
|
||||
},
|
||||
// 开始时间
|
||||
startTime: {
|
||||
type: String,
|
||||
default: '1970-01-01 00:00:00',
|
||||
},
|
||||
// 结束时间
|
||||
endTime: {
|
||||
type: String,
|
||||
default: '2100-12-31 23:59:59',
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['change', 'update:modelValue'])
|
||||
const toast = useToast()
|
||||
|
||||
// 显示控制
|
||||
const showYear = computed(() => props.format.includes('YYYY'))
|
||||
const showMonth = computed(() => props.format.includes('MM'))
|
||||
const showDay = computed(() => props.format.includes('DD'))
|
||||
const showHour = computed(() => props.format.includes('HH'))
|
||||
const showMinute = computed(() => props.format.includes('mm'))
|
||||
const showSecond = computed(() => props.format.includes('ss'))
|
||||
|
||||
// 数据源
|
||||
const years = ref<number[]>([])
|
||||
const months = ref<number[]>([])
|
||||
const days = ref<number[]>([])
|
||||
const hours = ref<number[]>([])
|
||||
const minutes = ref<number[]>([])
|
||||
const seconds = ref<number[]>([])
|
||||
|
||||
// 当前选中的值
|
||||
const pickerValue = ref<number[]>([])
|
||||
const showText = computed(() => props.modelValue)
|
||||
const popupShow = ref(false)
|
||||
|
||||
// 初始化数据
|
||||
const initData = () => {
|
||||
const start = dayjs(props.startTime)
|
||||
const end = dayjs(props.endTime)
|
||||
|
||||
// 初始化年份
|
||||
if (showYear.value) {
|
||||
years.value = Array.from({ length: end.year() - start.year() + 1 }, (_, i) => start.year() + i)
|
||||
}
|
||||
|
||||
// 初始化月份
|
||||
if (showMonth.value) {
|
||||
months.value = Array.from({ length: 12 }, (_, i) => i + 1)
|
||||
}
|
||||
|
||||
// 初始化日期
|
||||
if (showDay.value) {
|
||||
const currentYear = props.modelValue ? dayjs(props.modelValue).year() : start.year()
|
||||
const currentMonth = props.modelValue ? dayjs(props.modelValue).month() + 1 : 1
|
||||
const daysInMonth = dayjs(`${currentYear}-${currentMonth}`).daysInMonth()
|
||||
days.value = Array.from({ length: daysInMonth }, (_, i) => i + 1)
|
||||
}
|
||||
|
||||
// 初始化小时
|
||||
if (showHour.value) {
|
||||
hours.value = Array.from({ length: 24 }, (_, i) => i)
|
||||
}
|
||||
|
||||
// 初始化分钟
|
||||
if (showMinute.value) {
|
||||
minutes.value = Array.from({ length: 60 }, (_, i) => i)
|
||||
}
|
||||
|
||||
// 初始化秒
|
||||
if (showSecond.value) {
|
||||
seconds.value = Array.from({ length: 60 }, (_, i) => i)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新日期
|
||||
const updateDays = () => {
|
||||
if (!showYear.value || !showMonth.value) return
|
||||
|
||||
const year = years.value[pickerValue.value[0] || 0]
|
||||
const month = months.value[pickerValue.value[1] || 0]
|
||||
const daysInMonth = dayjs(`${year}-${month}`).daysInMonth()
|
||||
days.value = Array.from({ length: daysInMonth }, (_, i) => i + 1)
|
||||
}
|
||||
|
||||
// 初始化pickerValue
|
||||
const initializePickerValue = () => {
|
||||
let newPickerValue = []
|
||||
if (
|
||||
[
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
'YYYY-MM-DD HH:mm',
|
||||
'YYYY-MM-DD HH',
|
||||
'YYYY',
|
||||
'YYYY-MM',
|
||||
'YYYY-MM-DD',
|
||||
].includes(props.format)
|
||||
) {
|
||||
const dateTime = props.modelValue ? dayjs(props.modelValue) : dayjs()
|
||||
if (showYear.value) {
|
||||
const yearIndex = years.value.indexOf(dateTime.year())
|
||||
newPickerValue.push(yearIndex >= 0 ? yearIndex : 0)
|
||||
}
|
||||
|
||||
if (showMonth.value) {
|
||||
const monthIndex = months.value.indexOf(dateTime.month() + 1)
|
||||
newPickerValue.push(monthIndex >= 0 ? monthIndex : 0)
|
||||
}
|
||||
|
||||
if (showDay.value) {
|
||||
const dayIndex = days.value.indexOf(dateTime.date())
|
||||
newPickerValue.push(dayIndex >= 0 ? dayIndex : 0)
|
||||
}
|
||||
|
||||
if (showHour.value) {
|
||||
newPickerValue.push(dateTime.hour())
|
||||
}
|
||||
|
||||
if (showMinute.value) {
|
||||
newPickerValue.push(dateTime.minute())
|
||||
}
|
||||
if (showSecond.value) {
|
||||
newPickerValue.push(dateTime.second())
|
||||
}
|
||||
} else if (['HH:mm:ss', 'HH:mm', 'mm:ss', 'HH', 'mm', 'ss'].includes(props.format)) {
|
||||
const dateTime = props.modelValue
|
||||
if (dateTime) {
|
||||
newPickerValue = dateTime.split(':').map((item) => +item)
|
||||
} else {
|
||||
// 获取当前时间
|
||||
const now = dayjs()
|
||||
if (showHour.value) {
|
||||
newPickerValue.push(now.hour())
|
||||
}
|
||||
if (showMinute.value) {
|
||||
newPickerValue.push(now.minute())
|
||||
}
|
||||
if (showSecond.value) {
|
||||
newPickerValue.push(now.second())
|
||||
}
|
||||
}
|
||||
}
|
||||
pickerValue.value = newPickerValue
|
||||
}
|
||||
|
||||
// 处理选择变化
|
||||
const handleChange = (e: any) => {
|
||||
const values = e.detail.value
|
||||
pickerValue.value = values
|
||||
|
||||
// 如果选择了年月,需要更新日期
|
||||
if (showYear.value && showMonth.value && showDay.value) {
|
||||
updateDays()
|
||||
}
|
||||
}
|
||||
|
||||
// 点击确认
|
||||
const confirm = () => {
|
||||
const values = pickerValue.value
|
||||
let selectedDate = dayjs()
|
||||
let index = 0
|
||||
|
||||
// 只设置用户选择的时间部分,未选择的保持当前时间
|
||||
if (showYear.value && values[index] !== undefined) {
|
||||
selectedDate = selectedDate.year(years.value[values[index]])
|
||||
index++
|
||||
}
|
||||
if (showMonth.value && values[index] !== undefined) {
|
||||
selectedDate = selectedDate.month(months.value[values[index]] - 1)
|
||||
index++
|
||||
}
|
||||
if (showDay.value && values[index] !== undefined) {
|
||||
selectedDate = selectedDate.date(days.value[values[index]])
|
||||
index++
|
||||
}
|
||||
if (showHour.value && values[index] !== undefined) {
|
||||
selectedDate = selectedDate.hour(hours.value[values[index]])
|
||||
index++
|
||||
}
|
||||
if (showMinute.value && values[index] !== undefined) {
|
||||
selectedDate = selectedDate.minute(minutes.value[values[index]])
|
||||
index++
|
||||
}
|
||||
if (showSecond.value && values[index] !== undefined) {
|
||||
selectedDate = selectedDate.second(seconds.value[values[index]])
|
||||
index++
|
||||
}
|
||||
|
||||
popupShow.value = false
|
||||
const output = selectedDate.format(props.format)
|
||||
emit('update:modelValue', output)
|
||||
emit('change', output)
|
||||
}
|
||||
|
||||
// 点击取消
|
||||
const cancel = () => {
|
||||
popupShow.value = false
|
||||
}
|
||||
|
||||
// 清空选择
|
||||
const handleClear = () => {
|
||||
emit('update:modelValue', '')
|
||||
emit('change', '')
|
||||
}
|
||||
|
||||
// 点击输入框
|
||||
const handleClick = () => {
|
||||
if (!props.disabled) {
|
||||
initData()
|
||||
initializePickerValue()
|
||||
popupShow.value = true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.DateTime {
|
||||
.picker-view {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
}
|
||||
.picker-item {
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.operation {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
.cancel,
|
||||
.confrim {
|
||||
padding: 10rpx 30rpx;
|
||||
}
|
||||
.confrim {
|
||||
color: #2979ff;
|
||||
}
|
||||
}
|
||||
.inputArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
100
JeecgUniapp-master/src/components/Grid/Grid.vue
Normal file
100
JeecgUniapp-master/src/components/Grid/Grid.vue
Normal 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>
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<view class="help-wrap">
|
||||
<view class="help-icon" @click.stop="open">
|
||||
<text class="q">?</text>
|
||||
</view>
|
||||
<view v-if="visible" class="mask" @click="close" @touchmove.stop.prevent="">
|
||||
<view class="bubble" :style="bubbleStyle" @click.stop="">
|
||||
<text class="bubble-text">{{ text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
defineOptions({ name: 'HelpTipBubble' })
|
||||
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
top: {
|
||||
type: String,
|
||||
default: '180upx',
|
||||
},
|
||||
})
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
const bubbleStyle = computed(() => ({
|
||||
top: props.top,
|
||||
}))
|
||||
|
||||
function open() {
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.help-wrap {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.help-icon {
|
||||
width: 36upx;
|
||||
height: 36upx;
|
||||
border-radius: 50%;
|
||||
border: 2upx solid #888;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 8upx;
|
||||
}
|
||||
.q {
|
||||
font-size: 22upx;
|
||||
color: #666;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
.mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
background: transparent;
|
||||
}
|
||||
.bubble {
|
||||
position: absolute;
|
||||
left: 32upx;
|
||||
right: 32upx;
|
||||
padding: 24upx 28upx;
|
||||
border-radius: 16upx;
|
||||
background: rgb(180, 180, 180);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.bubble-text {
|
||||
font-size: 26upx;
|
||||
color: #fff;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
144
JeecgUniapp-master/src/components/ImgPreview/ImgPreview.vue
Normal file
144
JeecgUniapp-master/src/components/ImgPreview/ImgPreview.vue
Normal 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>
|
||||
49
JeecgUniapp-master/src/components/JMap/index.vue
Normal file
49
JeecgUniapp-master/src/components/JMap/index.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<view :style="[{backgroundColor:backgroundColor}]">
|
||||
<map
|
||||
v-if="latitude && longitude"
|
||||
class="map-content"
|
||||
:latitude="latitude"
|
||||
:longitude="longitude"
|
||||
:markers="marker"
|
||||
:scale="scale"
|
||||
:circles="circles"
|
||||
>
|
||||
</map>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import useGeo from '@/hooks/useGeoPosition'
|
||||
// 定义 props
|
||||
const props = defineProps({
|
||||
compLatitude: {
|
||||
type: Number,
|
||||
default: 40.009390,
|
||||
required: false
|
||||
},
|
||||
compLongitude: {
|
||||
type: Number,
|
||||
default: 116.374322,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(['success','fail']);
|
||||
const backgroundColor = 'transparent';
|
||||
// 引入地理定位逻辑
|
||||
let [{inCircle,getAddress,getMyAddress,latitude,longitude,marker,scale,circles},{refreshLocation}] = useGeo({ compLatitude: props.compLatitude, compLongitude: props.compLongitude }, emit);
|
||||
|
||||
defineExpose({
|
||||
inCircle,
|
||||
getAddress,
|
||||
getMyAddress,
|
||||
refreshLocation
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.map-content {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
}
|
||||
</style>
|
||||
341
JeecgUniapp-master/src/components/LFile/LFile.vue
Normal file
341
JeecgUniapp-master/src/components/LFile/LFile.vue
Normal file
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<view class='t-toptips' :style="{top: top,background: cubgColor}" :class="[show?'t-top-show':'']">
|
||||
<view v-if="loading" class="flex flex-sub">
|
||||
<view class="cu-progress flex-sub round striped active sm">
|
||||
<view :style="{ background: color,width: value + '%'}"></view>
|
||||
</view>
|
||||
<text class="margin-left">{{value}}%</text>
|
||||
</view>
|
||||
<block v-else>{{msg}}</block>
|
||||
<!-- #ifdef H5 -->
|
||||
<view ref="input" class="input"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, nextTick } from 'vue'
|
||||
import {useToast} from "wot-design-uni";
|
||||
import {http} from "@/utils/http";
|
||||
|
||||
const props = defineProps({
|
||||
top: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'rgba(49, 126, 243, 0.5)',
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#e54d42',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'file'
|
||||
}
|
||||
})
|
||||
const toast = useToast()
|
||||
const emits = defineEmits(['up-success', 'up-error'])
|
||||
|
||||
const cubgColor = ref('')
|
||||
const loading = ref(false)
|
||||
const value = ref(5)
|
||||
const show = ref(false)
|
||||
const msg = ref('执行完毕~')
|
||||
const input = ref(null)
|
||||
|
||||
const getRequest = (url) => {
|
||||
let theRequest = {}
|
||||
let index = url.indexOf("?")
|
||||
if (index != -1) {
|
||||
let str = url.substring(index+1)
|
||||
let strs = str.split("&")
|
||||
for(let i = 0; i < strs.length; i++) {
|
||||
theRequest[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1])
|
||||
}
|
||||
}
|
||||
return theRequest
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param currentWebview 当前窗口webview对象
|
||||
* @param url 上传接口地址
|
||||
* @param name 上传文件key值
|
||||
* @param header 上传接口请求头
|
||||
* @param formData body内其他参数
|
||||
*/
|
||||
const appChooseFile = ({currentWebview, url, name = 'file', header, ...formData} = {} as any) => {
|
||||
// #ifdef APP-PLUS
|
||||
let wv = plus.webview.create("","/hybrid/html/index.html",{
|
||||
//'uni-app': 'none',
|
||||
top: '0px',
|
||||
height: '100%',
|
||||
background: 'transparent'
|
||||
},{
|
||||
url,
|
||||
header,
|
||||
formData,
|
||||
key: name,
|
||||
})
|
||||
wv.loadURL("/hybrid/html/index.html")
|
||||
currentWebview.append(wv)
|
||||
wv.overrideUrlLoading({mode:'reject'},(e)=>{
|
||||
let {fileName, id, fileSize} = getRequest(e.url) as any
|
||||
fileName = unescape(fileName)
|
||||
id = unescape(id)
|
||||
fileSize = unescape(fileSize)
|
||||
console.log("fileSize", fileSize)
|
||||
return onCommit(
|
||||
emits('up-success', {fileName, fileSize, data: {id, statusCode: 200}})
|
||||
)
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param url 上传接口地址
|
||||
* @param name 上传文件key值
|
||||
* @param header 上传接口请求头
|
||||
* @param formData body内其他参数
|
||||
*/
|
||||
const h5ChooseFile = ({url, name = 'file', header, ...formData} = {} as any) => {
|
||||
// #ifdef H5
|
||||
const inputEl = document.createElement('input');
|
||||
inputEl.type = 'file'
|
||||
inputEl.style.display = 'none'
|
||||
inputEl.id = 'file'
|
||||
if(props.type === 'image'){
|
||||
inputEl.accept = 'image/*'
|
||||
}
|
||||
input.value.$el.appendChild(inputEl)
|
||||
|
||||
document.getElementById("file").click()
|
||||
inputEl.onchange = (event) => {
|
||||
uploadH5(event, url, header)
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param url
|
||||
* @param name
|
||||
* @param header
|
||||
* @param formData
|
||||
*/
|
||||
const wxChooseFile = ({url, name = 'file', header, ...formData} = {} as any) => {
|
||||
wx.chooseMessageFile({
|
||||
count: 1,
|
||||
type: 'file',
|
||||
success: ({tempFiles}) => {
|
||||
let [{path: filePath, name: fileName}] = tempFiles
|
||||
setDefUI()
|
||||
console.log("tempFiles==>::", tempFiles)
|
||||
console.log("filePath==>::", filePath)
|
||||
console.log("url==>::", url)
|
||||
return http.upload(url, {
|
||||
filePath: filePath,
|
||||
name: 'file'
|
||||
}).then((res : any) => {
|
||||
if (res.statusCode === 200) {
|
||||
console.log("wxChooseFile返回值success::", res)
|
||||
let data = res.data
|
||||
return onCommit(emits('up-success', {fileName, data}))
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log("wxChooseFile返回值err", err)
|
||||
return errorHandler('文件上传失败', upErr)
|
||||
})
|
||||
},
|
||||
fail: () => errorHandler('文件选择失败', upErr)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 上传
|
||||
* @param param
|
||||
*/
|
||||
const upload = (param = {} as any) => {
|
||||
if (!param.url) {
|
||||
toast.warning('上传地址不正确')
|
||||
return
|
||||
}
|
||||
|
||||
if (loading.value) {
|
||||
toast.warning('还有个文件玩命处理中,请稍候..')
|
||||
return
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
return appChooseFile(param)
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
* 打开文件
|
||||
* @param filePath
|
||||
*/
|
||||
const open = (filePath) => {
|
||||
let system = uni.getSystemInfoSync().platform
|
||||
if(system == 'ios'){
|
||||
filePath = encodeURI(filePath)
|
||||
}
|
||||
uni.openDocument({
|
||||
filePath,
|
||||
success: (res) => { console.log('打开文档成功') }
|
||||
})
|
||||
}
|
||||
/**
|
||||
* type: temporary=返回临时地址,local=长期保存到本地
|
||||
* @param url
|
||||
* @param type
|
||||
*/
|
||||
const download = (url, type = 'temporary') => {
|
||||
if (loading.value) {
|
||||
toast.warning('还有个文件玩命处理中,请稍候..')
|
||||
return
|
||||
}
|
||||
setDefUI()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let downloadTask = uni.downloadFile({
|
||||
url,
|
||||
success: ({statusCode, tempFilePath}) => {
|
||||
if (statusCode === 200) {
|
||||
if (type == 'local') {
|
||||
uni.saveFile({
|
||||
tempFilePath,
|
||||
success: ({savedFilePath}) => onCommit(resolve(savedFilePath)),
|
||||
fail: () => errorHandler('下载失败', reject)
|
||||
})
|
||||
}
|
||||
else {
|
||||
onCommit(resolve(tempFilePath))
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: () => errorHandler('下载失败', reject)
|
||||
})
|
||||
|
||||
downloadTask.onProgressUpdate(({progress = 0}) => {
|
||||
if (progress <= 100) {
|
||||
nextTick(() => {
|
||||
value.value = progress
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const onCommit = (resolve) => {
|
||||
msg.value = '执行完毕~'
|
||||
loading.value = false
|
||||
cubgColor.value = 'rgba(57, 181, 74, 0.5)'
|
||||
setTimeout(() => { show.value = false }, 1500)
|
||||
return resolve
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
const uploadH5 = (event, url, header) => {
|
||||
let _file = event.target.files[0]
|
||||
let formData = new FormData()
|
||||
formData.append("file", _file)
|
||||
let xhr = new XMLHttpRequest()
|
||||
xhr.open("post", url, true)
|
||||
|
||||
for (let keys in header) {
|
||||
xhr.setRequestHeader(keys, header[keys])
|
||||
}
|
||||
|
||||
xhr.upload.onloadstart = () => {
|
||||
toast.loading('上传中...')
|
||||
}
|
||||
|
||||
xhr.upload.onprogress = (res) => {
|
||||
if(res.loaded / res.total == 1){
|
||||
setTimeout(() => {
|
||||
toast.close()
|
||||
}, 5000)
|
||||
}
|
||||
}
|
||||
|
||||
xhr.onload = (res:any) => {
|
||||
console.log("上传完成", res)
|
||||
console.log(res.target.response)
|
||||
|
||||
toast.close()
|
||||
if (res.target.status === 200) {
|
||||
let data = JSON.parse(res.target.response)
|
||||
console.log("data", data)
|
||||
let fileName = data.message
|
||||
onCommit(emits('up-success', {fileSize: _file.size, fileName, data}))
|
||||
} else {
|
||||
toast.error('文件上传失败')
|
||||
}
|
||||
const inputEl = document.getElementById("file");
|
||||
input.value.$el.removeChild(inputEl)
|
||||
}
|
||||
|
||||
xhr.onerror = (data) => {
|
||||
toast.close()
|
||||
toast.error('上传失败')
|
||||
}
|
||||
|
||||
xhr.ontimeout = function(event) {
|
||||
toast.close()
|
||||
toast.error('上传超时,请刷新重试')
|
||||
}
|
||||
|
||||
xhr.send(formData)
|
||||
}
|
||||
// #endif
|
||||
|
||||
const setDefUI = () => {
|
||||
cubgColor.value = props.bgColor
|
||||
value.value = 0
|
||||
loading.value = true
|
||||
show.value = true
|
||||
}
|
||||
|
||||
const upErr = (errText) => {
|
||||
emits('up-error', errText)
|
||||
}
|
||||
|
||||
const errorHandler = (errText, reject) => {
|
||||
msg.value = errText
|
||||
loading.value = false
|
||||
cubgColor.value = 'rgba(229, 77, 66, 0.5)'
|
||||
setTimeout(() => { show.value = false }, 1500)
|
||||
return reject(errText)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
upload,
|
||||
open,
|
||||
download
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.t-toptips {
|
||||
width: 100%;
|
||||
padding: 18upx 30upx;
|
||||
box-sizing: border-box;
|
||||
position: fixed;
|
||||
z-index: 90;
|
||||
color: #fff;
|
||||
font-size: 30upx;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
word-break: break-all;
|
||||
display: none;
|
||||
transform: translateZ(0) translateY(-100%);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.t-top-show {
|
||||
transform: translateZ(0) translateY(0);
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
296
JeecgUniapp-master/src/components/PageLayout/PageLayout.vue
Normal file
296
JeecgUniapp-master/src/components/PageLayout/PageLayout.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<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="false"
|
||||
:title="navTitle"
|
||||
:leftText="navLeftText"
|
||||
:leftArrow="navLeftArrow"
|
||||
:rightText="isMp ? '' : navRightText"
|
||||
@clickLeft="handleClickLeft"
|
||||
@clickRight="handleClickRight"
|
||||
:custom-class="getClass()"
|
||||
>
|
||||
<template v-if="isMp === false && $slots.navRight" #right>
|
||||
<slot name="navRight"></slot>
|
||||
</template>
|
||||
<template v-if="isMp" #left>
|
||||
<!-- 为了在小程序上美观 -->
|
||||
<template v-if="$slots.navRight && isShowNavRightTextMp">
|
||||
<view class="btnGroup">
|
||||
<view class="left" @click.stop="handleClickLeft">{{ navLeftText }}</view>
|
||||
<view class="right" @click.stop="() => emit('navRightMp')">{{ navRightTextMp }}</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else-if="$slots.navLeft">
|
||||
<slot name="navLeft"></slot>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="mpNavLeft" @click.stop="handleClickLeft">
|
||||
<wd-icon v-if="navLeftArrow" name="thin-arrow-left" size="14px"></wd-icon>
|
||||
<view>{{ navLeftText }}</view>
|
||||
</view>
|
||||
</template>
|
||||
</template>
|
||||
</wd-navbar>
|
||||
</view>
|
||||
<view class="pageContent">
|
||||
<view class="content">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<!--微信中导航右侧的在这儿生成-->
|
||||
<view class="navRight" v-if="isMp && !$slots.navRight && mpNavRightIsBottom && navRightText">
|
||||
<wd-button @click="handleClickRight">{{ navRightText }}</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tabbar"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSlots } from 'vue'
|
||||
import { useRouter } from '@/plugin/uni-mini-router'
|
||||
import { useParamsStore } from '@/store/page-params'
|
||||
import { isMp } from '@/utils/platform'
|
||||
// const isMp = true
|
||||
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: '',
|
||||
},
|
||||
// 当有右侧有slot时,在小程序上不显示slot,显示文字 (默认显示)
|
||||
isShowNavRightTextMp: {
|
||||
typeof: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// 当有右侧有slot时,在小程序上不显示slot,显示文字
|
||||
navRightTextMp: {
|
||||
typeof: String,
|
||||
default: '确定',
|
||||
},
|
||||
// 右侧的文本在小程序端是否生成在页面底部(否:就得业务中自己写)
|
||||
mpNavRightIsBottom: {
|
||||
typeof: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
const slot = useSlots()
|
||||
const globalData = getApp().globalData
|
||||
const { systemInfo, navHeight } = globalData
|
||||
const { statusBarHeight } = systemInfo
|
||||
const emit = defineEmits(['navBack', 'navRight', 'navRightMp'])
|
||||
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')
|
||||
}
|
||||
const getClass = () => {
|
||||
const cls = `nav ${isMp ? 'mp' : ''} ${slot.navRight ? 'slot_navRight' : ''}`
|
||||
return cls
|
||||
}
|
||||
console.log('props:', props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pageLayout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
.pageNav {
|
||||
background-image: linear-gradient(45deg, #0081ff, #1cbbb4);
|
||||
&.transparent {
|
||||
background-image: none;
|
||||
}
|
||||
&.fixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
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;
|
||||
&.mp {
|
||||
&.slot_navRight {
|
||||
padding-right: 90px;
|
||||
.wd-navbar__text {
|
||||
display: none;
|
||||
}
|
||||
.wd-navbar__content {
|
||||
display: flex;
|
||||
}
|
||||
.wd-navbar__left,
|
||||
.wd-navbar__title,
|
||||
.wd-navbar__right {
|
||||
position: static;
|
||||
}
|
||||
.wd-navbar__title {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.pageContent {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background-color: #f1f1f1;
|
||||
.content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.navRight {
|
||||
padding: 15px;
|
||||
padding-top: 10px;
|
||||
display: flex;
|
||||
margin-bottom: calc(constant(safe-area-inset-bottom));
|
||||
margin-bottom: calc(env(safe-area-inset-bottom));
|
||||
justify-content: flex-end;
|
||||
:deep(.wd-button) {
|
||||
--wot-button-medium-height: 38px;
|
||||
width: 70%;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabbar {
|
||||
/* #ifdef H5 */
|
||||
height: var(--window-bottom);
|
||||
/* #endif */
|
||||
}
|
||||
.btnGroup {
|
||||
display: flex;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
color: #fff;
|
||||
align-items: center;
|
||||
.left {
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.left,
|
||||
.right {
|
||||
padding: 0 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
.mpNavLeft {
|
||||
display: flex;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<view :style="{ height: addUnit(height) }">
|
||||
<view :class="`wd-navbar ${customClass} ${fixed ? 'is-fixed' : ''} ${bordered ? 'is-border' : ''}`" :style="rootStyle">
|
||||
<view class="wd-navbar__content">
|
||||
<view class="wd-navbar__capsule" v-if="$slots.capsule">
|
||||
<slot name="capsule" />
|
||||
</view>
|
||||
|
||||
<view :class="`wd-navbar__left ${leftDisabled ? 'is-disabled' : ''}`" @click="handleClickLeft" v-else-if="!$slots.left">
|
||||
<wd-icon v-if="leftArrow" name="arrow-left" custom-class="wd-navbar__arrow" />
|
||||
<view v-if="leftText" class="wd-navbar__text">{{ leftText }}</view>
|
||||
</view>
|
||||
|
||||
<view v-else :class="`wd-navbar__left ${leftDisabled ? 'is-disabled' : ''}`" @click="handleClickLeft">
|
||||
<slot name="left" />
|
||||
</view>
|
||||
|
||||
<view class="wd-navbar__title">
|
||||
<slot name="title" />
|
||||
<block v-if="!$slots.title && title">{{ title }}</block>
|
||||
</view>
|
||||
<view :class="`wd-navbar__right ${rightDisabled ? 'is-disabled' : ''}`" @click="handleClickRight" v-if="$slots.right || rightText">
|
||||
<slot name="right" />
|
||||
|
||||
<view v-if="!$slots.right && rightText" class="wd-navbar__text" hover-class="wd-navbar__text--hover" :hover-stay-time="70">
|
||||
{{ rightText }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'wd-navbar',
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { type CSSProperties, computed, getCurrentInstance, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { getRect, addUnit, isDef, objToStyle } from 'wot-design-uni/components/common/util'
|
||||
import { navbarProps } from './types'
|
||||
|
||||
const props = defineProps(navbarProps)
|
||||
const emit = defineEmits(['click-left', 'click-right'])
|
||||
|
||||
const height = ref<number | ''>('') // 占位高度
|
||||
|
||||
const { statusBarHeight } = uni.getSystemInfoSync()
|
||||
|
||||
watch(
|
||||
[() => props.fixed, () => props.placeholder],
|
||||
() => {
|
||||
setPlaceholderHeight()
|
||||
},
|
||||
{ deep: true, immediate: false }
|
||||
)
|
||||
|
||||
const rootStyle = computed(() => {
|
||||
const style: CSSProperties = {}
|
||||
if (props.fixed && isDef(props.zIndex)) {
|
||||
style['z-index'] = props.zIndex
|
||||
}
|
||||
if (props.safeAreaInsetTop) {
|
||||
style['padding-top'] = addUnit(statusBarHeight || 0)
|
||||
}
|
||||
return `${objToStyle(style)};${props.customStyle}`
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.fixed && props.placeholder) {
|
||||
nextTick(() => {
|
||||
setPlaceholderHeight()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function handleClickLeft() {
|
||||
if (!props.leftDisabled) {
|
||||
emit('click-left')
|
||||
}
|
||||
}
|
||||
|
||||
function handleClickRight() {
|
||||
if (!props.rightDisabled) {
|
||||
emit('click-right')
|
||||
}
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as any
|
||||
|
||||
function setPlaceholderHeight() {
|
||||
if (!props.fixed || !props.placeholder) {
|
||||
return
|
||||
}
|
||||
|
||||
getRect('.wd-navbar', false, proxy).then((res) => {
|
||||
height.value = res.height as number
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
@import "wot-design-uni/components/common/abstracts/_mixin.scss";
|
||||
@import "wot-design-uni/components/common/abstracts/variable.scss";
|
||||
|
||||
.wot-theme-dark {
|
||||
@include b(navbar) {
|
||||
background-color: $-dark-background;
|
||||
|
||||
@include e(title) {
|
||||
color: $-dark-color;
|
||||
}
|
||||
|
||||
@include e(text) {
|
||||
color: $-dark-color;
|
||||
}
|
||||
|
||||
:deep(.wd-navbar__arrow) {
|
||||
color: $-dark-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@include b(navbar) {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
height: $-navbar-height;
|
||||
line-height: $-navbar-height;
|
||||
background-color: $-navbar-background;
|
||||
box-sizing: content-box;
|
||||
|
||||
@include e(content) {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include e(title) {
|
||||
max-width: 60%;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
color: $-navbar-color;
|
||||
font-weight: $-navbar-title-font-weight;
|
||||
font-size: $-navbar-title-font-size;
|
||||
@include lineEllipsis();
|
||||
}
|
||||
|
||||
@include e(text) {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
color: $-navbar-desc-font-color;
|
||||
}
|
||||
|
||||
@include e(left, right, capsule) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
font-size: $-navbar-desc-font-size;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
|
||||
@include when(disabled) {
|
||||
opacity: $-navbar-disabled-opacity;
|
||||
}
|
||||
}
|
||||
|
||||
@include e(left, capsule) {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
@include e(right) {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@include edeep(arrow) {
|
||||
font-size: $-navbar-arrow-size;
|
||||
color: $-navbar-color;
|
||||
}
|
||||
|
||||
|
||||
@include when(border) {
|
||||
@include halfPixelBorder('bottom');
|
||||
}
|
||||
|
||||
@include when(fixed) {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 500;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { baseProps, makeBooleanProp, makeNumberProp } from 'wot-design-uni/components/common/props'
|
||||
|
||||
export const navbarProps = {
|
||||
...baseProps,
|
||||
/**
|
||||
* 标题文字
|
||||
*/
|
||||
title: String,
|
||||
/**
|
||||
* 左侧文案
|
||||
*/
|
||||
leftText: String,
|
||||
/**
|
||||
* 右侧文案
|
||||
*/
|
||||
rightText: String,
|
||||
/**
|
||||
* 是否显示左侧箭头
|
||||
*/
|
||||
leftArrow: makeBooleanProp(false),
|
||||
/**
|
||||
* 是否显示下边框
|
||||
*/
|
||||
bordered: makeBooleanProp(true),
|
||||
/**
|
||||
* 是否固定到顶部
|
||||
*/
|
||||
fixed: makeBooleanProp(false),
|
||||
/**
|
||||
* 固定在顶部时,是否在标签位置生成一个等高的占位元素
|
||||
*/
|
||||
placeholder: makeBooleanProp(false),
|
||||
/**
|
||||
* 导航栏 z-index
|
||||
*/
|
||||
zIndex: makeNumberProp(500),
|
||||
/**
|
||||
* 是否开启顶部安全区适配
|
||||
*/
|
||||
safeAreaInsetTop: makeBooleanProp(false),
|
||||
/**
|
||||
* 是否禁用左侧按钮,禁用时透明度降低,且无法点击
|
||||
*/
|
||||
leftDisabled: makeBooleanProp(false),
|
||||
/**
|
||||
* 是否禁用右侧按钮,禁用时透明度降低,且无法点击
|
||||
*/
|
||||
rightDisabled: makeBooleanProp(false)
|
||||
}
|
||||
|
||||
export type NavbarProps = ExtractPropTypes<typeof navbarProps>
|
||||
142
JeecgUniapp-master/src/components/Popup/Popup.vue
Normal file
142
JeecgUniapp-master/src/components/Popup/Popup.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<view class="Popup">
|
||||
<view class="inputArea" :class="{ clear: !!showText }" @click="handleClick">
|
||||
<wd-input
|
||||
:placeholder="`请选择${$attrs.label}`"
|
||||
type="text"
|
||||
readonly
|
||||
v-model="showText"
|
||||
clearable
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
<view v-if="!!showText && !$attrs.disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</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['source']).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 handleClear = () => {
|
||||
showText.value = ''
|
||||
handleChange([])
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
if (!attrs.disabled) {
|
||||
reportModal.show = true
|
||||
}
|
||||
}
|
||||
const handleClose = () => {
|
||||
reportModal.show = false
|
||||
}
|
||||
const handleChange = (data) => {
|
||||
console.log('选中的值:', data)
|
||||
callBack(data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.inputArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,303 @@
|
||||
<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(',')[0]
|
||||
result.columns?.forEach((item) => {
|
||||
if (fileds.includes(item.dataIndex)) {
|
||||
columns.value.push(item)
|
||||
}
|
||||
})
|
||||
// 最少展示三个字段(字段够多时)
|
||||
const minNum = 3
|
||||
if (columns.value.length < minNum) {
|
||||
result.columns?.forEach((item) => {
|
||||
const findItem = columns.value.find((o) => o.dataIndex == item.dataIndex);
|
||||
if (!findItem) {
|
||||
if (columns.value.length < minNum) {
|
||||
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;
|
||||
line-height: 1.8;
|
||||
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>
|
||||
219
JeecgUniapp-master/src/components/PopupDict/PopupDict.vue
Normal file
219
JeecgUniapp-master/src/components/PopupDict/PopupDict.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<view class="PopupDict">
|
||||
<view class="inputArea" :class="{ clear: !!showText }" @click.stop="handleClick">
|
||||
<wd-select-picker
|
||||
v-model="showText"
|
||||
:columns="options"
|
||||
readonly
|
||||
:type="multi ? 'checkbox' : 'radio'"
|
||||
@click="handleClick"
|
||||
v-bind="$attrs"
|
||||
></wd-select-picker>
|
||||
<view v-if="!!showText && !disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</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: ',',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
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 handleClear = () => {
|
||||
if (!props.disabled) {
|
||||
showText.value = ''
|
||||
handleChange([])
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
if (!props.disabled) {
|
||||
reportModal.show = true
|
||||
}
|
||||
}
|
||||
const handleClose = () => {
|
||||
reportModal.show = false
|
||||
}
|
||||
const handleChange = (data) => {
|
||||
console.log('选中的值:', data)
|
||||
callBack(data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.inputArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
136
JeecgUniapp-master/src/components/ProgressMap/ProgressMap.vue
Normal file
136
JeecgUniapp-master/src/components/ProgressMap/ProgressMap.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<view class="ProgressMap">
|
||||
<view v-if="title.length" 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 }">
|
||||
<template v-for="(inItem, inIndex) in item.data" :key="inIndex">
|
||||
<view v-if="inItem.label" class="item">
|
||||
{{ inItem.label }}
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
<view v-if="item.file" class="file">
|
||||
<template v-for="(file, inIndex) in item.file" :key="inIndex">
|
||||
<view :data-url="file.filePath" class="item" @click="handClick(file)">
|
||||
<view class="u-iconfont u-icon-link"></view>
|
||||
<view class="text ellipsis">{{ file.fileName }}</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<ImgPreview
|
||||
v-if="imgPreview.show"
|
||||
:urls="imgPreview.urls"
|
||||
@close="() => (imgPreview.show = false)"
|
||||
></ImgPreview>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { downloadFile } from '@/common/uitls'
|
||||
import { getFileAccessHttpUrl } from '@/common/uitls'
|
||||
|
||||
const props: any = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => [{ activeStep: false, data: [] }],
|
||||
},
|
||||
})
|
||||
const imgPreview = reactive({
|
||||
show: false,
|
||||
urls: [],
|
||||
})
|
||||
const handClick = (file) => {
|
||||
const suffix = file.filePath.split('.').pop()
|
||||
if (['gif', 'png', 'jpg', 'jpeg'].includes(suffix)) {
|
||||
imgPreview.urls = [getFileAccessHttpUrl(file.filePath)]
|
||||
imgPreview.show = true
|
||||
} else {
|
||||
downloadFile(file.filePath)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ProgressMap {
|
||||
background-color: #fff;
|
||||
}
|
||||
.title {
|
||||
padding: 20upx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.stepBox {
|
||||
position: relative;
|
||||
line-height: 1;
|
||||
padding: 32upx 32upx 32upx 120upx;
|
||||
color: #aaaaaa;
|
||||
&.active {
|
||||
color: #39b54a;
|
||||
}
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 38upx;
|
||||
background-color: #fff;
|
||||
z-index: 9;
|
||||
top: 50upx;
|
||||
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: 13px;
|
||||
line-height: 1.6;
|
||||
&.active {
|
||||
font-weight: 500;
|
||||
background-color: #39b54a;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 5px #39b54a;
|
||||
}
|
||||
}
|
||||
.file {
|
||||
padding-top: 10px;
|
||||
.item {
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
color: var(--color-blue);
|
||||
line-height: 1.2;
|
||||
.u-iconfont {
|
||||
font-size: 14px;
|
||||
margin-right: 5px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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>
|
||||
285
JeecgUniapp-master/src/components/SelectDept/SelectDept.vue
Normal file
285
JeecgUniapp-master/src/components/SelectDept/SelectDept.vue
Normal file
@@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<view class="CategorySelect">
|
||||
<view class="pickerArea" :class="{ clear: !!showText }" @click.stop="handleClick">
|
||||
<wd-input
|
||||
:placeholder="getPlaceholder($attrs)"
|
||||
v-bind="$attrs"
|
||||
v-model="showText"
|
||||
clearable
|
||||
readonly
|
||||
></wd-input>
|
||||
<view v-if="!!showText && !disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</view>
|
||||
<wd-popup v-if="popupShow" 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"
|
||||
:defaultCheckedKeys="defaultCheckedKeys"
|
||||
: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',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
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 defaultCheckedKeys: any = ref(props.multiple ? [] : '')
|
||||
const handleClick = () => {
|
||||
if (!props.disabled) {
|
||||
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
|
||||
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
|
||||
if(value){
|
||||
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
|
||||
}
|
||||
value = (value as string).trim()
|
||||
if (value) {
|
||||
http
|
||||
.get(api.queryDepartTreeSync, { ids: value })
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
const { result = [] } = res
|
||||
showText.value = result.map((item) => item[props.labelKey]).join(',')
|
||||
if (props.multiple) {
|
||||
defaultCheckedKeys.value = typeof value === 'string' ? value.split(',') : [value]
|
||||
} else {
|
||||
defaultCheckedKeys.value = value
|
||||
}
|
||||
} else {
|
||||
}
|
||||
})
|
||||
.catch((err) => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
showText.value = ''
|
||||
treeValue.value = []
|
||||
confirm()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
loadItemByCode()
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
loadRoot()
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
268
JeecgUniapp-master/src/components/SelectUser/SelectUser.vue
Normal file
268
JeecgUniapp-master/src/components/SelectUser/SelectUser.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<view class="SelectUser">
|
||||
<template v-if="showType === 'form'">
|
||||
<view class="inputArea" :class="{ clear: !!showText }" @click.stop="handleClick">
|
||||
<wd-input
|
||||
:placeholder="getPlaceholder($attrs)"
|
||||
v-bind="$attrs"
|
||||
readonly
|
||||
v-model="showText"
|
||||
></wd-input>
|
||||
<view
|
||||
v-if="!!showText && !disabled"
|
||||
class="u-iconfont u-icon-close"
|
||||
@click.stop="handleClear"
|
||||
></view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="list">
|
||||
<template v-for="(item, index) in selectedUser" :key="item.id">
|
||||
<view
|
||||
v-if="maxShowUser === -1 || index < maxShowUser"
|
||||
class="user"
|
||||
@click="handleRemove(index)"
|
||||
>
|
||||
<view v-if="isDelUser" class="u-iconfont u-icon-close"></view>
|
||||
<wd-img
|
||||
v-if="getAvatar(item.avatar)"
|
||||
radius="50%"
|
||||
width="40px"
|
||||
height="40px"
|
||||
:src="getAvatar(item.avatar)"
|
||||
></wd-img>
|
||||
<view class="name">{{ item.realname }}</view>
|
||||
</view>
|
||||
</template>
|
||||
<view v-if="isAddUser" class="u-iconfont u-icon-newAdd" @click="handleClick"></view>
|
||||
</view>
|
||||
</template>
|
||||
<SelectUserModal
|
||||
v-if="modalShow"
|
||||
:selected="modelValue"
|
||||
:defaultSelectedValue="defaultValue"
|
||||
:selectedUser="selectedUser"
|
||||
:modalTitle="modalTitle"
|
||||
:maxSelectCount="maxSelectCount"
|
||||
:multi="!isRadioSelection"
|
||||
:rowKey="rowKey"
|
||||
:readonlyUser="readonlyUser"
|
||||
@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, isNumber } from '@/utils/is'
|
||||
import SelectUserModal from './components/SelectUserModal.vue'
|
||||
import { getPlaceholder, getFileAccessHttpUrl } from '@/common/uitls'
|
||||
import defaultAvatar from '@/static/default-avatar.png'
|
||||
|
||||
defineOptions({
|
||||
name: 'SelectUser',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
default: '',
|
||||
},
|
||||
defaultValue: {
|
||||
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,
|
||||
},
|
||||
showType: {
|
||||
type: String,
|
||||
default: 'form', // form、card
|
||||
},
|
||||
// showType为card时有效
|
||||
isDelUser: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// showType为card时有效
|
||||
isAddUser: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// showType为card时有效
|
||||
maxShowUser: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
// 只读用户(添加的用户不可取消)
|
||||
readonlyUser: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue', 'change', 'getSelectdAllData'])
|
||||
const api = {
|
||||
list: '/sys/user/list',
|
||||
}
|
||||
const showText = ref('')
|
||||
const modalShow = ref(false)
|
||||
const selectedUser = ref([])
|
||||
const getAvatar = (url) => {
|
||||
let result = getFileAccessHttpUrl(url)
|
||||
if (result.length) {
|
||||
return result
|
||||
} else {
|
||||
return defaultAvatar
|
||||
}
|
||||
}
|
||||
const handleRemove = (index?) => {
|
||||
if (props.isDelUser) {
|
||||
if (isNumber(index)) {
|
||||
selectedUser.value.splice(index, 1)
|
||||
} else {
|
||||
selectedUser.value.pop()
|
||||
}
|
||||
handleChange(selectedUser.value)
|
||||
}
|
||||
}
|
||||
// 翻译
|
||||
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(',')
|
||||
selectedUser.value = records
|
||||
} else {
|
||||
console.log('翻译失败~')
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
showText.value = ''
|
||||
}
|
||||
}
|
||||
// 打开popup
|
||||
const handleClick = () => {
|
||||
console.log('handleClick', !props.disabled)
|
||||
console.log('handleClick', props)
|
||||
if (!props.disabled) {
|
||||
modalShow.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
showText.value = ''
|
||||
handleChange([])
|
||||
}
|
||||
|
||||
const handleChange = (data) => {
|
||||
const rowkey = data.map((item) => item[props.rowKey]).join(',')
|
||||
const labelKey = data.map((item) => item[props.labelKey]).join(',')
|
||||
showText.value = labelKey
|
||||
selectedUser.value = data
|
||||
emit('update:modelValue', rowkey)
|
||||
emit('change', rowkey)
|
||||
emit('getSelectdAllData', data)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
transform()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
color: #666;
|
||||
.user {
|
||||
padding: 7px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
.u-iconfont {
|
||||
font-size: 14px;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 7px;
|
||||
z-index: 1;
|
||||
/* background: white; */
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
line-height: 1;
|
||||
}
|
||||
.name {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.u-iconfont {
|
||||
font-size: 22px;
|
||||
}
|
||||
.u-icon-newAdd {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.u-icon-remove {
|
||||
color: var(--color-red);
|
||||
}
|
||||
}
|
||||
.inputArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,338 @@
|
||||
<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[rowKey])">
|
||||
<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"
|
||||
:disabled="readonlyUser.includes(item[rowKey])"
|
||||
:modelValue="item[rowKey]"
|
||||
></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[rowKey])">
|
||||
<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[rowKey]"></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,
|
||||
},
|
||||
// 这是用户id
|
||||
selected: {
|
||||
type: [Array, String],
|
||||
default: '',
|
||||
},
|
||||
// 这是用户id(只是默认勾选,在弹窗中勾选)
|
||||
defaultSelectedValue: {
|
||||
type: [Array, String],
|
||||
default: '',
|
||||
},
|
||||
// 这是用户全数据(项包含id,username、realname)
|
||||
selectedUser: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'username',
|
||||
},
|
||||
// 只读用户
|
||||
readonlyUser: {
|
||||
type: Array,
|
||||
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((rowKey, index) => {
|
||||
const findIndex = dataList.value.findIndex((item) => item[props.rowKey] === rowKey)
|
||||
if (findIndex == -1) {
|
||||
// 传进来选中的用户可能在第二页(还没加载进来)
|
||||
const index = props.selectedUser.findIndex((item) => item[props.rowKey] === rowKey)
|
||||
if (index != -1) {
|
||||
result.push(props.selectedUser[index])
|
||||
} else {
|
||||
// 传进来defaultSelectedValue的用户可能在第二页(还没加载进来)
|
||||
if (isArray(props.defaultSelectedValue)) {
|
||||
const index = props.defaultSelectedValue.findIndex((item) => item === rowKey)
|
||||
index != -1 && result.push({ [props.rowKey]: props.defaultSelectedValue[index] })
|
||||
} else {
|
||||
props.defaultSelectedValue == rowKey && result.push({ [props.rowKey]: rowKey })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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(',')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (props.defaultSelectedValue.length) {
|
||||
if (props.multi) {
|
||||
if (isArray(props.defaultSelectedValue)) {
|
||||
checkedValue.value = props.defaultSelectedValue
|
||||
} else if (isString(props.defaultSelectedValue)) {
|
||||
checkedValue.value = props.defaultSelectedValue.split(',')
|
||||
}
|
||||
} else {
|
||||
if (isString(props.defaultSelectedValue)) {
|
||||
checkedValue.value = props.defaultSelectedValue
|
||||
} else if (isArray(props.defaultSelectedValue)) {
|
||||
checkedValue.value = props.defaultSelectedValue.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;
|
||||
:deep(.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>
|
||||
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<view class="SelectUser">
|
||||
<view class="inputArea" :class="{ clear: !!showText }" @click.stop="handleClick">
|
||||
<wd-input
|
||||
:placeholder="getPlaceholder($attrs)"
|
||||
v-bind="$attrs"
|
||||
readonly
|
||||
v-model="showText"
|
||||
></wd-input>
|
||||
<view
|
||||
v-if="!!showText && !disabled"
|
||||
class="u-iconfont u-icon-close"
|
||||
@click.stop="handleClear"
|
||||
></view>
|
||||
</view>
|
||||
<SelectUserModal
|
||||
v-if="modalShow"
|
||||
:selected="modelValue"
|
||||
:selectedUser="selectedUser"
|
||||
:modalTitle="modalTitle"
|
||||
:rowKey="rowKey"
|
||||
@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, isNumber } from '@/utils/is'
|
||||
import SelectUserModal from './components/SelectUserModal.vue'
|
||||
import { getPlaceholder, getFileAccessHttpUrl } from '@/common/uitls'
|
||||
import defaultAvatar from '@/static/default-avatar.png'
|
||||
|
||||
defineOptions({
|
||||
name: 'SelectUserByDepart',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
default: '',
|
||||
},
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'realname',
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'username',
|
||||
},
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '选择用户',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
const api = {
|
||||
list: '/sys/user/list',
|
||||
}
|
||||
const showText = ref('')
|
||||
const modalShow = ref(false)
|
||||
const selectedUser = ref([])
|
||||
// 翻译
|
||||
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) {
|
||||
// 如果value的值在selectedUser中存在,则不请求翻译
|
||||
let isNotRequestTransform = false
|
||||
isNotRequestTransform = value
|
||||
.split(',')
|
||||
.every((value) => !!selectedUser.value.find((item) => item[props.rowKey] === value))
|
||||
if (isNotRequestTransform) {
|
||||
return
|
||||
}
|
||||
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(',')
|
||||
selectedUser.value = records
|
||||
} else {
|
||||
console.log('翻译失败~')
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
showText.value = ''
|
||||
}
|
||||
}
|
||||
// 打开popup
|
||||
const handleClick = () => {
|
||||
console.log('handleClick', !props.disabled)
|
||||
console.log('handleClick', props)
|
||||
if (!props.disabled) {
|
||||
modalShow.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
showText.value = ''
|
||||
handleChange([])
|
||||
}
|
||||
|
||||
const handleChange = (data) => {
|
||||
const rowkey = data.map((item) => item[props.rowKey]).join(',')
|
||||
const labelKey = data.map((item) => item[props.labelKey]).join(',')
|
||||
showText.value = labelKey
|
||||
selectedUser.value = data
|
||||
emit('update:modelValue', rowkey)
|
||||
emit('change', rowkey)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
transform()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.inputArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,841 @@
|
||||
<template>
|
||||
<wd-popup position="bottom" v-model="show">
|
||||
<PageLayout :navTitle="modalTitle" type="popup" @navBack="handleCancel">
|
||||
<view class="wrap">
|
||||
<view class="wrap-content">
|
||||
<wd-search
|
||||
hide-cancel
|
||||
placeholder="请输入姓名/部门"
|
||||
v-model="searchText"
|
||||
@search="handleSearch"
|
||||
@clear="handleSearch"
|
||||
/>
|
||||
<view class="main-content">
|
||||
<scroll-view scroll-y>
|
||||
<template v-if="searchResult.depart.length || searchResult.user.length">
|
||||
<view class="search-result">
|
||||
<template v-if="searchResult.user.length">
|
||||
<view class="search-user solid-top">
|
||||
<text class="search-user-title">人员</text>
|
||||
<template v-for="item in searchResult.user" :key="item.id">
|
||||
<view
|
||||
class="search-user-item solid-top"
|
||||
@click="handleSearchUserCheck(item)"
|
||||
>
|
||||
<view @click.stop>
|
||||
<wd-checkbox
|
||||
shape="square"
|
||||
v-model="item.checked"
|
||||
@change="($event) => handleSearchUserCheck(item, $event)"
|
||||
/>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view class="search-user-item-circle">
|
||||
<wd-img
|
||||
width="36"
|
||||
height="36px"
|
||||
v-if="item.avatar"
|
||||
:src="getFileAccessHttpUrl(item.avatar)"
|
||||
/>
|
||||
</view>
|
||||
<view class="search-user-item-info">
|
||||
<view class="search-user-item-name">{{ item.realname }}</view>
|
||||
<view class="search-user-item-org">{{ item.orgCodeTxt }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
<template v-if="searchResult.depart.length">
|
||||
<view class="search-depart solid-top">
|
||||
<text class="search-depart-title">部门</text>
|
||||
<template v-for="item in searchResult.depart" :key="item.id">
|
||||
<view
|
||||
class="search-depart-item solid-top"
|
||||
@click="handleSearchDepartClick(item)"
|
||||
>
|
||||
<view @click.stop>
|
||||
<wd-checkbox
|
||||
shape="square"
|
||||
v-model="item.checked"
|
||||
@change="($event) => handleSearchDepartCheck($event, item)"
|
||||
/>
|
||||
</view>
|
||||
<view class="search-depart-item-name">{{ item.departName }}</view>
|
||||
<wd-icon name="arrow-right" size="16px"></wd-icon>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view v-if="breadcrumb.length" class="breadcrumb-wrap solid-top">
|
||||
<Breadcrumb
|
||||
separator="/"
|
||||
:items="[
|
||||
{ title: '首页', icon: 'home' },
|
||||
...breadcrumb.map((item) => ({ title: item.departName, ...item })),
|
||||
]"
|
||||
@click="(item, index) => handleBreadcrumbClick(index === 0 ? undefined : item)"
|
||||
>
|
||||
<template #item-0="{ item }">
|
||||
<wd-icon name="home" size="18px"></wd-icon>
|
||||
</template>
|
||||
</Breadcrumb>
|
||||
</view>
|
||||
<div v-if="currentDepartUsers.length">
|
||||
<!-- 当前部门用户树 -->
|
||||
<div class="depart-users-tree solid-top">
|
||||
<div v-if="!currentDepartTree.length" class="allChecked">
|
||||
<wd-checkbox
|
||||
shape="square"
|
||||
v-model="currentDepartAllUsers"
|
||||
@change="handleAllUsers"
|
||||
>
|
||||
全选
|
||||
</wd-checkbox>
|
||||
</div>
|
||||
<template v-for="item in currentDepartUsers" :key="item.id">
|
||||
<div
|
||||
class="depart-users-tree-item solid-top"
|
||||
@click="handleDepartUsersTreeCheck(item)"
|
||||
>
|
||||
<view @click.stop>
|
||||
<wd-checkbox
|
||||
shape="square"
|
||||
v-model="item.checked"
|
||||
@change="($event) => handleDepartUsersTreeCheck(item, $event)"
|
||||
/>
|
||||
</view>
|
||||
<div class="right">
|
||||
<div class="depart-users-tree-item-circle">
|
||||
<wd-img
|
||||
width="36"
|
||||
height="36px"
|
||||
v-if="item.avatar"
|
||||
:src="getFileAccessHttpUrl(item.avatar)"
|
||||
/>
|
||||
</div>
|
||||
<div class="depart-users-tree-item-name">{{ item.realname }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 部门树 -->
|
||||
<div v-if="currentDepartTree.length" class="depart-tree">
|
||||
<template v-for="item in currentDepartTree" :key="item.id">
|
||||
<div class="depart-tree-item solid-top" @click="handleDepartTreeClick(item)">
|
||||
<view @click.stop>
|
||||
<wd-checkbox
|
||||
shape="square"
|
||||
v-model="item.checked"
|
||||
@change="($event) => handleDepartTreeCheck($event, item)"
|
||||
/>
|
||||
</view>
|
||||
<div class="depart-tree-item-name">{{ item.departName }}</div>
|
||||
<wd-icon name="arrow-right" size="16px"></wd-icon>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
v-if="currentDepartTree.length === 0 && currentDepartUsers.length === 0"
|
||||
class="no-data"
|
||||
>
|
||||
<wd-status-tip image="content" tip="暂无内容" />
|
||||
</div>
|
||||
</template>
|
||||
</scroll-view>
|
||||
<view v-if="showSelectedUser" class="selected-user">
|
||||
<SelectedUser :selectedUsers="selectedUsers" @del="handleDelUser"></SelectedUser>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="wrap-footer">
|
||||
<view class="text" @click="() => (showSelectedUser = !showSelectedUser)">
|
||||
<view>已选</view>
|
||||
<view class="num">{{ selectedUsers.length }}</view>
|
||||
<view>人</view>
|
||||
<text class="tip">(查看选中用户)</text>
|
||||
</view>
|
||||
<wd-button type="primary" @click="handleConfirm">确定</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</PageLayout>
|
||||
<wd-toast :selector="selector"></wd-toast>
|
||||
</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, uuid } from '@/common/uitls'
|
||||
import defaultAvatar from '@/static/default-avatar.png'
|
||||
import SelectedUser from './SelectedUser.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'SelectUserModal',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
multi: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '选择用户',
|
||||
},
|
||||
maxSelectCount: {
|
||||
type: Number,
|
||||
},
|
||||
// 这是用户全数据(项包含id,username、realname)
|
||||
selectedUser: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'username',
|
||||
},
|
||||
})
|
||||
const api = {
|
||||
selectUserList: '/sys/user/selectUserList',
|
||||
userlist: '/sys/user/list',
|
||||
queryTreeList: '/sys/sysDepart/queryTreeList',
|
||||
getTableList: '/sys/user/queryUserComponentData',
|
||||
}
|
||||
const emit = defineEmits(['change', 'close'])
|
||||
const selector = uuid()
|
||||
const toast = useToast(selector)
|
||||
const show = ref(true)
|
||||
// 搜索文本
|
||||
const searchText = ref('')
|
||||
const breadcrumb = ref<any[]>([])
|
||||
// 部门树(整颗树)
|
||||
const departTree = ref([])
|
||||
// 当前部门树
|
||||
const currentDepartTree = ref<any[]>([])
|
||||
// 选中的部门节点
|
||||
const checkedDepartIds = ref<string[]>([])
|
||||
// 当前部门用户
|
||||
const currentDepartUsers = ref([])
|
||||
// 已选用户
|
||||
const selectedUsers = ref<any[]>([])
|
||||
// 全选
|
||||
const currentDepartAllUsers = ref(false)
|
||||
// 搜索结构
|
||||
const searchResult: any = reactive({
|
||||
depart: [],
|
||||
user: [],
|
||||
})
|
||||
// 映射部门和人员的关系
|
||||
const cacheDepartUser = {}
|
||||
// 是否显示已选用户
|
||||
const showSelectedUser = ref(false)
|
||||
const handleClose = () => {
|
||||
setTimeout(() => {
|
||||
emit('close')
|
||||
}, 400)
|
||||
}
|
||||
const handleConfirm = () => {
|
||||
if (selectedUsers.value.length == 0) {
|
||||
toast.warning('还没选择用户~')
|
||||
return
|
||||
}
|
||||
show.value = false
|
||||
let result = []
|
||||
let value = selectedUsers.value
|
||||
if (!Array.isArray(selectedUsers.value)) {
|
||||
value = [selectedUsers.value]
|
||||
}
|
||||
result = value
|
||||
emit('change', result)
|
||||
handleClose()
|
||||
}
|
||||
const handleCancel = () => {
|
||||
show.value = false
|
||||
handleClose()
|
||||
console.log('取消了~')
|
||||
}
|
||||
const getAvatar = (url) => {
|
||||
let result = getFileAccessHttpUrl(url)
|
||||
if (result.length) {
|
||||
return result
|
||||
} else {
|
||||
return defaultAvatar
|
||||
}
|
||||
}
|
||||
// 搜索人员/部门
|
||||
const handleSearch = () => {
|
||||
if (searchText.value) {
|
||||
http
|
||||
.get(`/sys/user/listAll`, {
|
||||
column: 'createTime',
|
||||
order: 'desc',
|
||||
pageNo: 1,
|
||||
pageSize: 100,
|
||||
realname: `*${searchText.value}*`,
|
||||
})
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
res.result.records?.forEach((item) => {
|
||||
const findItem = selectedUsers.value.find((user) => user.id == item.id)
|
||||
if (findItem) {
|
||||
// 能在右侧找到说明选中了,左侧同样需要选中。
|
||||
item.checked = true
|
||||
} else {
|
||||
item.checked = false
|
||||
}
|
||||
})
|
||||
searchResult.user = res.result.records ?? []
|
||||
} else {
|
||||
toast.warning(res.message)
|
||||
}
|
||||
})
|
||||
searchResult.depart = getDepartByName(searchText.value) ?? []
|
||||
showSelectedUser.value = false
|
||||
} else {
|
||||
searchResult.user = []
|
||||
searchResult.depart = []
|
||||
}
|
||||
}
|
||||
// 面包屑
|
||||
const handleBreadcrumbClick = (item?) => {
|
||||
// 先清空
|
||||
currentDepartUsers.value = []
|
||||
if (item) {
|
||||
const findIndex = breadcrumb.value.findIndex((o) => o.id === item.id)
|
||||
if (findIndex != -1) {
|
||||
breadcrumb.value = breadcrumb.value.filter((item, index) => {
|
||||
console.log(item)
|
||||
return index <= findIndex
|
||||
})
|
||||
}
|
||||
const data = getDepartTreeNodeById(item.id, departTree.value)
|
||||
currentDepartTree.value = data.children
|
||||
} else {
|
||||
// 根节点
|
||||
currentDepartTree.value = departTree.value
|
||||
breadcrumb.value = []
|
||||
}
|
||||
}
|
||||
// 点击部门树复选框触发
|
||||
const handleDepartTreeCheck = ({ value }, item) => {
|
||||
const target = { checked: value }
|
||||
if (target.checked) {
|
||||
// 选中
|
||||
getUsersByDeptId(item['id']).then((users) => {
|
||||
addUsers(users)
|
||||
})
|
||||
checkedDepartIds.value.push((item as any).id)
|
||||
// 检查父节点下所有子节点是否选中
|
||||
const parentItem = getDepartTreeParentById(item.id)
|
||||
if (parentItem?.children) {
|
||||
const isChildAllChecked = parentItem.children.every((item) => item.checked)
|
||||
if (isChildAllChecked) {
|
||||
parentItem.checked = true
|
||||
} else {
|
||||
parentItem.checked = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 取消选中
|
||||
const findIndex = checkedDepartIds.value.findIndex((o: any) => o.id === item.id)
|
||||
if (findIndex != -1) {
|
||||
checkedDepartIds.value.splice(findIndex, 1)
|
||||
}
|
||||
// 如果父节点是选中,则需要取消
|
||||
const parentItem = getDepartTreeParentById(item.id)
|
||||
if (parentItem) {
|
||||
parentItem.checked = false
|
||||
}
|
||||
getUsersByDeptId(item['id']).then((users) => {
|
||||
users.forEach((item) => {
|
||||
const findIndex = selectedUsers.value.findIndex((user) => user.id === item.id)
|
||||
if (findIndex != -1) {
|
||||
selectedUsers.value.splice(findIndex, 1)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
// 点击部门树节点触发
|
||||
const handleDepartTreeClick = (item) => {
|
||||
breadcrumb.value = [...breadcrumb.value, item]
|
||||
if (item.children) {
|
||||
// 有子节点,则显示部门
|
||||
if (item.checked) {
|
||||
// 父节点勾选,则子节点全部勾选
|
||||
item.children.forEach((item) => {
|
||||
item.checked = true
|
||||
})
|
||||
}
|
||||
currentDepartTree.value = item.children
|
||||
http
|
||||
.get('/sys/sysDepart/getUsersByDepartId', {
|
||||
id: item['id'],
|
||||
})
|
||||
.then((res: any) => {
|
||||
const result = res.result ?? []
|
||||
if (item.checked) {
|
||||
// 父节点勾选,则默认勾选
|
||||
result.forEach((item) => {
|
||||
item.checked = true
|
||||
})
|
||||
}
|
||||
currentDepartUsers.value = result
|
||||
})
|
||||
} else {
|
||||
// 没有子节点,则显示用户
|
||||
currentDepartTree.value = []
|
||||
getTableList({
|
||||
departId: item['id'],
|
||||
}).then((res: any) => {
|
||||
if (res.success) {
|
||||
if (res?.result.records) {
|
||||
let checked = true
|
||||
res.result.records.forEach((item) => {
|
||||
const findItem = selectedUsers.value.find((user) => user.id == item.id)
|
||||
if (findItem) {
|
||||
// 能在右侧找到说明选中了,左侧同样需要选中。
|
||||
item.checked = true
|
||||
} else {
|
||||
item.checked = false
|
||||
checked = false
|
||||
}
|
||||
})
|
||||
currentDepartAllUsers.value = checked
|
||||
currentDepartUsers.value = res.result.records
|
||||
} else {
|
||||
toast.warning(res.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// 点击部门用户树复选框触发
|
||||
const handleDepartUsersTreeCheck = (item, e?) => {
|
||||
if (e) {
|
||||
// 点击复选框时
|
||||
item.checked = e.value
|
||||
} else {
|
||||
// 点击整条数据时
|
||||
item.checked = !item.checked
|
||||
}
|
||||
if (item.checked) {
|
||||
addUsers(item)
|
||||
} else {
|
||||
selectedUsers.value = selectedUsers.value.filter((user) => user.id !== item.id)
|
||||
}
|
||||
if (item.checked == false) {
|
||||
// 有一个是false,则全选false
|
||||
currentDepartAllUsers.value = false
|
||||
}
|
||||
}
|
||||
// 全选
|
||||
const handleAllUsers = ({ target }) => {
|
||||
const { checked } = target
|
||||
if (checked) {
|
||||
currentDepartUsers.value.forEach((item: any) => (item.checked = true))
|
||||
addUsers(currentDepartUsers.value)
|
||||
} else {
|
||||
currentDepartUsers.value.forEach((item: any) => (item.checked = false))
|
||||
selectedUsers.value = selectedUsers.value.filter((user) => {
|
||||
const userId = user.id
|
||||
const findItem = currentDepartUsers.value.find((item: any) => item.id === userId)
|
||||
if (findItem) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// 删除人员
|
||||
const handleDelUser = (item) => {
|
||||
const findIndex = selectedUsers.value.findIndex((user) => user.id === item.id)
|
||||
if (findIndex != -1) {
|
||||
selectedUsers.value.splice(findIndex, 1)
|
||||
}
|
||||
const findItem: any = currentDepartUsers.value.find((user: any) => user.id === item.id)
|
||||
if (findItem) {
|
||||
findItem.checked = false
|
||||
currentDepartAllUsers.value = false
|
||||
}
|
||||
}
|
||||
// 点击搜索用户复选框
|
||||
const handleSearchUserCheck = (item, e?) => {
|
||||
if (!e) {
|
||||
item.checked = !item.checked
|
||||
}
|
||||
if (item.checked) {
|
||||
addUsers(item)
|
||||
} else {
|
||||
selectedUsers.value = selectedUsers.value.filter((user) => user.id !== item.id)
|
||||
}
|
||||
}
|
||||
// 点击搜索部门复选框
|
||||
const handleSearchDepartCheck = (e, item) => {
|
||||
handleDepartTreeCheck(e, item)
|
||||
}
|
||||
// 点击搜索部门
|
||||
const handleSearchDepartClick = (item) => {
|
||||
searchResult.depart = []
|
||||
searchResult.user = []
|
||||
breadcrumb.value = getPathToNodeById(item.id)
|
||||
handleDepartTreeClick(item)
|
||||
}
|
||||
// 添加人员到右侧
|
||||
const addUsers = (users) => {
|
||||
let newUsers: any = []
|
||||
if (isArray(users)) {
|
||||
// selectedUsers里面没有才添加(防止重复)
|
||||
newUsers = users.filter((user: any) => !selectedUsers.value.find((item) => item.id === user.id))
|
||||
} else {
|
||||
if (!selectedUsers.value.find((user) => user.id === users.id)) {
|
||||
// selectedUsers里面没有才添加(防止重复)
|
||||
newUsers = [users]
|
||||
}
|
||||
}
|
||||
selectedUsers.value = [...selectedUsers.value, ...newUsers]
|
||||
const result = currentDepartUsers.value.every((item: any) => !!item.checked)
|
||||
currentDepartAllUsers.value = result
|
||||
}
|
||||
// 解析参数
|
||||
const parseParams = (params) => {
|
||||
return params
|
||||
}
|
||||
const getQueryTreeList = (params?) => {
|
||||
params = parseParams(params)
|
||||
queryTreeList({ ...params }).then((res: any) => {
|
||||
if (res.success) {
|
||||
departTree.value = res.result
|
||||
currentDepartTree.value = res.result
|
||||
} else {
|
||||
toast.warning(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 根据部门id获取用户
|
||||
const getTableList = (params) => {
|
||||
params = parseParams(params)
|
||||
return getTableListOrigin({ ...params })
|
||||
}
|
||||
const getUsersByDeptId = (id) => {
|
||||
return new Promise<any[]>((resolve) => {
|
||||
if (cacheDepartUser[id]) {
|
||||
resolve(cacheDepartUser[id])
|
||||
} else {
|
||||
getTableList({
|
||||
departId: id,
|
||||
}).then((res: any) => {
|
||||
if (res.success) {
|
||||
cacheDepartUser[id] = res?.result?.records ?? []
|
||||
if (res?.result?.records?.length) {
|
||||
resolve(res.result.records ?? [])
|
||||
}
|
||||
} else {
|
||||
toast.warning(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
// 根据id获取根节点到当前节点路径
|
||||
const getPathToNodeById = (id: string, tree = departTree.value, path = []): any[] => {
|
||||
for (const node of tree) {
|
||||
if ((node as any).id === id) {
|
||||
return [...path]
|
||||
}
|
||||
if ((node as any).children) {
|
||||
const foundPath = getPathToNodeById(id, (node as any).children, [...path, node])
|
||||
if (foundPath.length) {
|
||||
return foundPath
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
// 根据id获取部门树父节点数据
|
||||
const getDepartTreeParentById = (id: string, tree = departTree.value, parent = null): any => {
|
||||
for (const node of tree) {
|
||||
if ((node as any).id === id) {
|
||||
return parent
|
||||
}
|
||||
if ((node as any).children) {
|
||||
const found = getDepartTreeParentById(id, (node as any).children, node)
|
||||
if (found) {
|
||||
return found
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
// 通过名称搜索部门支持模糊
|
||||
const getDepartByName = (name: string, tree = departTree.value): any[] => {
|
||||
const result: any[] = []
|
||||
const search = (nodes: any[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.departName?.toLowerCase().includes(name.toLowerCase())) {
|
||||
result.push(node)
|
||||
}
|
||||
if (node.children?.length) {
|
||||
search(node.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
search(tree)
|
||||
return result
|
||||
}
|
||||
// 根据id获取部门树当前节点数据
|
||||
const getDepartTreeNodeById = (id: string, tree = departTree.value): any => {
|
||||
for (const node of tree) {
|
||||
if ((node as any).id === id) {
|
||||
return node
|
||||
}
|
||||
if ((node as any).children) {
|
||||
const found = getDepartTreeNodeById(id, (node as any).children)
|
||||
if (found) {
|
||||
return found
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
// 获取部门树列表
|
||||
const queryTreeList = (params = {}) => {
|
||||
return http.get(api.queryTreeList, { ...params })
|
||||
}
|
||||
// 根据部门id获取用户列表(包含子孙部门)
|
||||
const getTableListOrigin = (params = {}) => {
|
||||
return http.get(api.getTableList, { ...params })
|
||||
}
|
||||
// 初始化
|
||||
const init = () => {
|
||||
if (props.selectedUser.length) {
|
||||
// 编辑时,传进来已选中的数据
|
||||
selectedUsers.value = props.selectedUser
|
||||
}
|
||||
getQueryTreeList()
|
||||
}
|
||||
init()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wrap {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.wrap-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.main-content {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: var(--color-grey);
|
||||
.breadcrumb-wrap {
|
||||
background-color: #fff;
|
||||
}
|
||||
.selected-user {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
}
|
||||
.no-data {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.depart-tree {
|
||||
.depart-tree-item {
|
||||
background-color: #fff;
|
||||
padding: 0 16px;
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
}
|
||||
.depart-tree-item-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
.depart-users-tree {
|
||||
.allChecked {
|
||||
padding: 0 16px;
|
||||
padding-bottom: 16px;
|
||||
padding-top: 12px;
|
||||
background-color: #fff;
|
||||
:deep(.ant-checkbox-wrapper) {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.depart-users-tree-item {
|
||||
line-height: 50px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
.right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 8px;
|
||||
}
|
||||
.depart-users-tree-item-circle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background-color: #aaa;
|
||||
overflow: hidden;
|
||||
:deep(image) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.depart-users-tree-item-name {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.search-depart {
|
||||
margin-bottom: 8px;
|
||||
.search-depart-title {
|
||||
padding-left: 16px;
|
||||
line-height: 50px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
color: var(--UI-FG-1);
|
||||
}
|
||||
.search-depart-item {
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
.search-depart-item-name {
|
||||
margin-left: 8px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
.search-result {
|
||||
background-color: #fff;
|
||||
min-height: 100%;
|
||||
}
|
||||
.search-user {
|
||||
margin-bottom: 8px;
|
||||
.search-user-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
padding-left: 16px;
|
||||
line-height: 50px;
|
||||
color: var(--UI-FG-1);
|
||||
}
|
||||
.search-user-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #f4f6fa;
|
||||
}
|
||||
.right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 8px;
|
||||
}
|
||||
.search-user-item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.search-user-item-circle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background-color: #aaa;
|
||||
}
|
||||
.search-user-item-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.search-user-item-org {
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wrap-footer {
|
||||
padding: 10px;
|
||||
padding-bottom: calc(constant(safe-area-inset-bottom) + 10px);
|
||||
padding-bottom: calc(env(safe-area-inset-bottom) + 10px);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
background-color: #fff;
|
||||
.text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.num {
|
||||
font-size: 18px;
|
||||
margin: 0 5px;
|
||||
color: var(--wot-color-theme);
|
||||
}
|
||||
}
|
||||
.tip {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
.home-icon {
|
||||
margin-right: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,115 @@
|
||||
<route lang="json5" type="page">
|
||||
{
|
||||
layout: 'default',
|
||||
style: {
|
||||
navigationBarTitleText: '',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
}
|
||||
</route>
|
||||
|
||||
<template>
|
||||
<scroll-view scroll-y class="selected-user-container solid-top">
|
||||
<view class="user-container">
|
||||
<template v-if="selectedUsers.length">
|
||||
<view class="user-item" v-for="item in selectedUsers" :key="item.id">
|
||||
<view class="user-item-avatar" @click="handledelUser(item)">
|
||||
<wd-img
|
||||
width="36px"
|
||||
height="36px"
|
||||
radius="50%"
|
||||
:src="getFileAccessHttpUrl(item.avatar)"
|
||||
></wd-img>
|
||||
<view class="u-iconfont u-icon-close"></view>
|
||||
</view>
|
||||
<view class="user-item-name ellipsis">{{ item.realname }}</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="empty-data">
|
||||
<wd-status-tip image="content" tip="无选中用户" />
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { onShow, onHide, onLoad, onReady } from '@dcloudio/uni-app'
|
||||
import { useMessage, useToast } from 'wot-design-uni'
|
||||
import { useRouter } from '@/plugin/uni-mini-router'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { http } from '@/utils/http'
|
||||
import { useParamsStore } from '@/store/page-params'
|
||||
import { cache, getFileAccessHttpUrl } from '@/common/uitls'
|
||||
defineOptions({
|
||||
name: 'User',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps(['selectedUsers'])
|
||||
const emit = defineEmits(['del'])
|
||||
const router = useRouter()
|
||||
const paramsStore = useParamsStore()
|
||||
const userStore = useUserStore()
|
||||
const toast = useToast()
|
||||
const message = useMessage()
|
||||
// 删除用户
|
||||
const handledelUser = (item: any) => {
|
||||
emit('del', item)
|
||||
}
|
||||
// 获取用户名的最后一个字符
|
||||
const getLastCharacterOfName = (name: string): string => {
|
||||
if (!name) return ''
|
||||
return name.charAt(name.length - 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
//
|
||||
.selected-user-container {
|
||||
height: 100%;
|
||||
}
|
||||
.user-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
align-content: flex-start;
|
||||
padding: 10px;
|
||||
min-height: 100%;
|
||||
.user-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 20%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
.user-item-avatar {
|
||||
position: relative;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 50%;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
right: -5px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
.empty-data {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
386
JeecgUniapp-master/src/components/TreeSelect/TreeSelect.vue
Normal file
386
JeecgUniapp-master/src/components/TreeSelect/TreeSelect.vue
Normal file
@@ -0,0 +1,386 @@
|
||||
<template>
|
||||
<view class="TreeSelect">
|
||||
<view class="pickerArea" :class="{ clear: !!showText }" @click="handleClick">
|
||||
<wd-input
|
||||
:placeholder="`请选择${$attrs.label}`"
|
||||
v-bind="$attrs"
|
||||
readonly
|
||||
v-model="showText"
|
||||
></wd-input>
|
||||
<view v-if="!!showText && !$attrs.disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</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
|
||||
v-if="popupShow"
|
||||
:data="treeData"
|
||||
labelField="title"
|
||||
valueField="key"
|
||||
loadMode
|
||||
:showCheckbox="multiple"
|
||||
:showRadioIcon="false"
|
||||
:checkStrictly="true"
|
||||
:loadApi="asyncLoadTreeData"
|
||||
:defaultCheckedKeys="defaultCheckedKeys"
|
||||
@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 attrs = useAttrs()
|
||||
const defaultCheckedKeys: any = ref(props.multiple ? [] : '')
|
||||
|
||||
const handleClick = () => {
|
||||
if (!attrs.disabled) {
|
||||
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(value){
|
||||
if (isArray(props.modelValue)) {
|
||||
// @ts-ignore
|
||||
value = value.join()
|
||||
}
|
||||
if (value === treeData.value.map((item) => item.key).join(',')) {
|
||||
// 说明是刚选完,内部已有翻译。不需要再请求
|
||||
return
|
||||
}
|
||||
value = (value as string).trim()
|
||||
if (value) {
|
||||
http
|
||||
.get(`${api.view}${props.dict}`, { key: value })
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
const { result = [] } = res
|
||||
showText.value = result.join(',')
|
||||
if (props.multiple) {
|
||||
defaultCheckedKeys.value = typeof value === 'string' ? value.split(',') : [value]
|
||||
} else {
|
||||
defaultCheckedKeys.value = value
|
||||
}
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
showText.value = ''
|
||||
treeValue.value = []
|
||||
confirm()
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
.pickerArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(14px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-input__body) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
23
JeecgUniapp-master/src/components/components.d.ts
vendored
Normal file
23
JeecgUniapp-master/src/components/components.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
BottomOperate: typeof import('./BottomOperate/BottomOperate.vue')['default']
|
||||
Breadcrumb: typeof import('./Breadcrumb/Breadcrumb.vue')['default']
|
||||
CategorySelect: typeof import('./CategorySelect/CategorySelect.vue')['default']
|
||||
DateTime: typeof import('./DateTime/DateTime.vue')['default']
|
||||
Grid: typeof import('./Grid/Grid.vue')['default']
|
||||
HelpTipBubble: typeof import('./HelpTipBubble/HelpTipBubble.vue')['default']
|
||||
ImgPreview: typeof import('./ImgPreview/ImgPreview.vue')['default']
|
||||
LFile: typeof import('./LFile/LFile.vue')['default']
|
||||
PageLayout: typeof import('./PageLayout/PageLayout.vue')['default']
|
||||
Popup: typeof import('./Popup/Popup.vue')['default']
|
||||
PopupDict: typeof import('./PopupDict/PopupDict.vue')['default']
|
||||
ProgressMap: typeof import('./ProgressMap/ProgressMap.vue')['default']
|
||||
RightConditionFilter: typeof import('./RightConditionFilter/RightConditionFilter.vue')['default']
|
||||
SelectDept: typeof import('./SelectDept/SelectDept.vue')['default']
|
||||
SelectUser: typeof import('./SelectUser/SelectUser.vue')['default']
|
||||
SelectUserByDepart: typeof import('./SelectUserByDepart/SelectUserByDepart.vue')['default']
|
||||
TreeSelect: typeof import('./TreeSelect/TreeSelect.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
169
JeecgUniapp-master/src/components/online/FormProperty.ts
Normal file
169
JeecgUniapp-master/src/components/online/FormProperty.ts
Normal 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;
|
||||
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<view class="onlineTableCell">
|
||||
<!--图片-->
|
||||
<template v-if="column?.type === 'image'">
|
||||
<template v-if="record[column.key]">
|
||||
<wd-img
|
||||
width="30"
|
||||
height="30"
|
||||
:src="getFirstImg(record[column.key])"
|
||||
@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?.type === 'file'">
|
||||
<template v-if="record[column.key]">
|
||||
<wd-button @click="handleDownload(record[column.key])">下载</wd-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text>无文件</text>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="['markdown', 'umeditor'].includes(column?.type)">
|
||||
<rich-text :nodes="record[column.key]"></rich-text>
|
||||
</template>
|
||||
<template v-else-if="column?.type === 'pca'">
|
||||
<text class="ellipsis-2">{{ getPcaText(record[column.key])}}</text>
|
||||
</template>
|
||||
<template v-else-if="['datetime', 'date'].includes(column?.type)">
|
||||
<text class="ellipsis-2">{{ getFormatDate(record[column.key], column) }}</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="ellipsis-2">{{ renderVal(record, column)}}</text>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getFormatDate, filterMultiDictText } from '@/common/uitls'
|
||||
import { isString } from '@/utils/is'
|
||||
import { getFileAccessHttpUrl } from '@/common/uitls'
|
||||
import { getAreaTextByCode } from '@/common/areaData/Area'
|
||||
defineOptions({
|
||||
name: 'OnlineSubTableCell',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
columnsInfo: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
column: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
record: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
})
|
||||
console.log("根据配置动态加载表单record ",props.record );
|
||||
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 { type , key } = column
|
||||
let text = record[key]
|
||||
if (['date', 'Date'].includes(type)) {
|
||||
if (!text) {
|
||||
return ''
|
||||
}
|
||||
if (text.length > 10) {
|
||||
return text.substring(0, 10)
|
||||
}
|
||||
return text
|
||||
} else if (['popup_dict'].includes(type)) {
|
||||
const dict = record[key + '_dictText']
|
||||
if (dict != undefined) {
|
||||
return record[key + '_dictText']
|
||||
}
|
||||
return text
|
||||
}
|
||||
//字典值翻譯
|
||||
if(props.columnsInfo?.dictOptions && props.columnsInfo?.dictOptions[key]){
|
||||
return filterMultiDictText(props.columnsInfo.dictOptions[key], text + '')
|
||||
}
|
||||
return text
|
||||
}
|
||||
// 初始化
|
||||
const init = () => {
|
||||
const field = props.column.dataIndex
|
||||
if (props.column?.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>
|
||||
@@ -0,0 +1,867 @@
|
||||
<template>
|
||||
<wd-popup position="bottom" v-model="show">
|
||||
<PageLayout
|
||||
:navTitle="navTitle"
|
||||
type="popup"
|
||||
navRightText="确定"
|
||||
@navRight="handleConfirm"
|
||||
@navBack="handleCancel"
|
||||
>
|
||||
<scroll-view scroll-y class="wrap">
|
||||
<wd-form ref="form" :model="formData">
|
||||
<wd-cell-group class="mb-14px" 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)"
|
||||
>
|
||||
<view style="text-align: left">
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<online-file
|
||||
v-model:value="formData[item.key]"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
:key="index"
|
||||
:maxNum="getFieldExtendJson(item, 'uploadnum')"
|
||||
></online-file>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<online-file-custom
|
||||
v-model:value="formData[item.key]"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
:key="index"
|
||||
:maxNum="getFieldExtendJson(item, 'uploadnum')"
|
||||
></online-file-custom>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</wd-cell>
|
||||
<!-- 开关 -->
|
||||
<wd-cell
|
||||
v-else-if="['switch', 'checkbox'].includes(item.type)"
|
||||
: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>
|
||||
|
||||
<!-- 日期时间 -->
|
||||
<DateTime
|
||||
v-else-if="item.type === 'datetime'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
startTime="1949-01-01 00:00:00"
|
||||
endTime="2050-01-01 00:00:00"
|
||||
:type="item.type"
|
||||
:name="item.key"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></DateTime>
|
||||
|
||||
<!-- 时间 -->
|
||||
<DateTime
|
||||
v-else-if="item.type === 'time'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
format="HH:mm:ss"
|
||||
:type="item.type"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></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.options"
|
||||
: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.options"
|
||||
: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.options"
|
||||
: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.options"
|
||||
: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.validateRules"
|
||||
/>
|
||||
|
||||
<!-- 数字框 整数 -->
|
||||
<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.validateRules"
|
||||
/>
|
||||
|
||||
<!-- 多行文本 -->
|
||||
<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.validateRules"
|
||||
/>
|
||||
<!-- 密码输入框 -->
|
||||
<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.validateRules"
|
||||
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.popupCode}`"
|
||||
: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-if="item.type !== 'hidden'"
|
||||
:label-width="labelWidth"
|
||||
v-model="formData[item.key]"
|
||||
:disabled="componentDisabled(item)"
|
||||
:label="get4Label(item.label)"
|
||||
:name="item.key"
|
||||
:placeholder="item.placeholder"
|
||||
:rules="item.validateRules"
|
||||
clearable
|
||||
/>
|
||||
</view>
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
</scroll-view>
|
||||
</PageLayout>
|
||||
</wd-popup>
|
||||
<wd-toast></wd-toast>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import OnlineCheckbox from '@/components/online/view/online-checkbox.vue'
|
||||
import OnlineMulti from '@/components/online/view/online-multi.vue'
|
||||
import SelectUser from '@/components/SelectUser/SelectUser.vue'
|
||||
import OnlinePopupLinkRecord from '@/components/online/view/online-popup-link-record.vue'
|
||||
import OnlineSelect from '@/components/online/view/online-select.vue'
|
||||
import OnlineDate from '@/components/online/view/online-date.vue'
|
||||
import OnlineTime from '@/components/online/view/online-time.vue'
|
||||
import OnlineRadio from '@/components/online/view/online-radio.vue'
|
||||
import SelectDept from '@/components/SelectDept/SelectDept.vue'
|
||||
import OnlineImage from '@/components/online/view/online-image.vue'
|
||||
import OnlineFile from '@/components/online/view/online-file.vue'
|
||||
import OnlineFileCustom from '@/components/online/view/online-file-custom.vue'
|
||||
import OnlinePca from '../view/online-pca.vue'
|
||||
import { loadOneFieldDefVal } from '../defaultVal'
|
||||
import { isArray, isNumber, isString } from '@/utils/is'
|
||||
import { formatDate } from '@/common/uitls'
|
||||
import { deepClone } from 'wot-design-uni/components/common/util'
|
||||
import { duplicateCheck } from '@/service/api'
|
||||
import { isObject } from '@/common/is'
|
||||
defineOptions({
|
||||
name: 'OnlineSubformPopup',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
schema: {
|
||||
type: Object,
|
||||
default: () => ({}) as any,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['change', 'close'])
|
||||
const toast = useToast()
|
||||
const show = ref(true)
|
||||
//表数据Data
|
||||
const formData = ref<any>({})
|
||||
//是否填值规则字段
|
||||
const hasFillRuleFields = ref([])
|
||||
//是否必填字段
|
||||
const hasRequiredFields = ref([])
|
||||
//字段属性
|
||||
const rootProperties = ref<any>([])
|
||||
//表描述
|
||||
const tableTxt = ref('')
|
||||
//表名
|
||||
const tableName = ref('')
|
||||
//数据Id
|
||||
const dataId = ref('')
|
||||
//是否已加载
|
||||
const loaded = ref(false)
|
||||
//是否编辑
|
||||
const isUpdate = ref(false)
|
||||
//临时数据
|
||||
const tempData = ref({})
|
||||
// 标题宽度
|
||||
const labelWidth = computed(() => {
|
||||
return '100px'
|
||||
})
|
||||
// 导航标题
|
||||
const get4Label = computed(() => {
|
||||
return (lable) => {
|
||||
return `${lable && lable.length > 4 ? lable.substring(0, 4) : lable}:`
|
||||
}
|
||||
})
|
||||
// 导航标题
|
||||
const navTitle = computed(() => {
|
||||
if (!props.title || props.title.length === 0) {
|
||||
return tableTxt.value
|
||||
} else {
|
||||
return props.title
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取日期控件的扩展类型
|
||||
* @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: 'quarter',
|
||||
week: 'week',
|
||||
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) {
|
||||
return true
|
||||
}
|
||||
return item.disabled
|
||||
}
|
||||
/**
|
||||
* 获取扩展字段
|
||||
* @param item
|
||||
* @param field
|
||||
*/
|
||||
const getFieldExtendJson = (item, field) => {
|
||||
let json = item.formSchema.fieldExtendJson ?? '{}'
|
||||
if (isString(json) && json.trim().length === 0) {
|
||||
json = '{}'
|
||||
}
|
||||
json = JSON.parse(json)
|
||||
const result = json[field]
|
||||
return result
|
||||
}
|
||||
/**
|
||||
* 判断字段是否必填
|
||||
* @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,
|
||||
)
|
||||
console.log('linkRecordChange****》》》》', linkRecordChange)
|
||||
linkFieldArr.forEach((field) => {
|
||||
let value = linkRecord.map((record) => record[field.formSchema.dictText]).join(',')
|
||||
nextTick(() => {
|
||||
formData.value[field.key] = value
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 校验字段
|
||||
* @param values
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const fieldCheck = async (values: any) => {
|
||||
// 校验字段
|
||||
let flag = false
|
||||
console.log('一对多子表rootProperties.value', rootProperties.value)
|
||||
console.log('一对多子表values', values)
|
||||
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 || item?.pattern
|
||||
if (pattern) {
|
||||
//可能存在是一个对象的情况,把正则表达式赋值一下
|
||||
if (isObject(pattern)) {
|
||||
pattern = pattern?.pattern
|
||||
}
|
||||
if (pattern == 'only') {
|
||||
const res: any = await duplicateCheck({
|
||||
tableName: tableName.value,
|
||||
fieldName: item.key,
|
||||
fieldVal: values[item.key],
|
||||
dataId: dataId.value,
|
||||
})
|
||||
if (!res.success) {
|
||||
tip(`${item.label} ${res.message}`)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
const regex = compilePattern(pattern)
|
||||
if (values[item.key] && !regex.test(values[item.key])) {
|
||||
let errorInfo = item?.formSchema?.errorInfo || '格式不正确!'
|
||||
tip(`${item.label}${errorInfo}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return flag
|
||||
}
|
||||
/**
|
||||
* 省市区获取最后一位
|
||||
* @param value
|
||||
*/
|
||||
function pcaValue(value) {
|
||||
if (value.includes(',')) {
|
||||
const parts = value.split(',')
|
||||
if (parts.length >= 3) {
|
||||
// 如果包含至少两个逗号,截取第三部分并去掉最后一位
|
||||
return parts[2]
|
||||
}
|
||||
}
|
||||
// 如果不包含逗号或不符合条件,返回原值
|
||||
return value
|
||||
}
|
||||
/**
|
||||
* 处理多选字段
|
||||
* @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)) {
|
||||
value = isArray(value) ? value[2] : pcaValue(value)
|
||||
}
|
||||
//是数组的就转换成字符串
|
||||
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
|
||||
}
|
||||
//打开弹窗前
|
||||
function beforeOpen(row, edit) {
|
||||
isUpdate.value = edit
|
||||
tempData.value = row
|
||||
formData.value = { ...row }
|
||||
}
|
||||
/**
|
||||
* 关闭
|
||||
*/
|
||||
const handleClose = () => {
|
||||
setTimeout(() => {
|
||||
emit('close')
|
||||
}, 400)
|
||||
}
|
||||
/**
|
||||
* 提交
|
||||
*/
|
||||
const handleConfirm = async () => {
|
||||
// 判断字段必填和正则
|
||||
if (await fieldCheck(formData.value)) {
|
||||
return
|
||||
}
|
||||
// 处理特殊字段
|
||||
let finalData = await handleMultiOrDateField()
|
||||
emit('change', finalData, isUpdate.value)
|
||||
//关闭弹窗
|
||||
handleClose()
|
||||
}
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
const handleCancel = () => {
|
||||
show.value = false
|
||||
loaded.value = false
|
||||
handleClose()
|
||||
console.log('取消了~')
|
||||
}
|
||||
/**
|
||||
* 设置pop组件数值
|
||||
* @param data
|
||||
*/
|
||||
const setFieldsValue = (data) => {
|
||||
formData.value = { ...formData.value, ...data }
|
||||
}
|
||||
/**
|
||||
* popup组件配置
|
||||
* @param item
|
||||
*/
|
||||
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
|
||||
}
|
||||
//监听配置变化
|
||||
watchEffect(() => {
|
||||
props.schema && !loaded.value && loadTableInfo(props.schema)
|
||||
})
|
||||
/**
|
||||
* 初始化表单数据
|
||||
*/
|
||||
function initFormData(row) {
|
||||
dataId.value = row?.id
|
||||
formData.value = deepClone(row)
|
||||
}
|
||||
/**
|
||||
* 根据配置动态加载表单
|
||||
* @param dataID
|
||||
*/
|
||||
function loadTableInfo(formSchema: any) {
|
||||
console.log('===子表many加载表单数据 schema===', formSchema)
|
||||
createRootProperties(formSchema)
|
||||
tableTxt.value = formSchema?.describe
|
||||
if (isUpdate.value) {
|
||||
initFormData(tempData.value)
|
||||
} else {
|
||||
// 新增页面处理表单默认值
|
||||
handleDefaultValue()
|
||||
}
|
||||
loaded.value = true
|
||||
}
|
||||
/**
|
||||
* 创建根属性
|
||||
* @param formSchema
|
||||
*/
|
||||
function createRootProperties(formSchema: any) {
|
||||
console.log('===子表配置项 formSchema===', formSchema)
|
||||
formData.value = {}
|
||||
hasRequiredFields.value = []
|
||||
hasFillRuleFields.value = formSchema?.hasFillRuleFields ?? []
|
||||
tableName.value = formSchema.key
|
||||
const properties = formSchema.columns
|
||||
let rootProps = []
|
||||
properties.forEach((item) => {
|
||||
if (item?.key && item?.key != 'action') {
|
||||
formData.value[item?.key] = ''
|
||||
let patternInfo = null
|
||||
if (item?.validateRules && item?.validateRules.length > 0) {
|
||||
//字段是否必填
|
||||
let isRequired = item?.validateRules.some((valid) => valid?.required)
|
||||
isRequired && hasRequiredFields.value.push(item.key)
|
||||
//正则校验其他配置信息
|
||||
patternInfo = item?.validateRules.find((valid) => valid.pattern)
|
||||
if (patternInfo && patternInfo?.pattern && patternInfo.pattern == '*') {
|
||||
hasRequiredFields.value.push(item.key)
|
||||
}
|
||||
}
|
||||
rootProps.push({
|
||||
...item,
|
||||
type: item?.view || item.type,
|
||||
label: item.title,
|
||||
pattern: patternInfo,
|
||||
formSchema: { ...item, pattern: patternInfo?.pattern, errorInfo: patternInfo?.message },
|
||||
})
|
||||
}
|
||||
})
|
||||
rootProperties.value = [...rootProps]
|
||||
console.log('--子表 rootProperties--', rootProps)
|
||||
}
|
||||
/**
|
||||
* 获取默认值
|
||||
*/
|
||||
function handleDefaultValue() {
|
||||
rootProperties.value.forEach((item) => {
|
||||
let field = item.key
|
||||
let { defaultValue, type, fieldDefaultValue } = item.formSchema
|
||||
let defVal = defaultValue || fieldDefaultValue
|
||||
loadOneFieldDefVal(defVal, type, (value) => {
|
||||
formData.value[field] = value
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 根据规则表将 value 转换为对应的正则表达式
|
||||
* @param {string} value 输入的值(如 "n6-16", "s6-18", "*6-16", "url", "e" 等)
|
||||
* @returns {RegExp} 返回对应的正则表达式
|
||||
*/
|
||||
function compilePattern(value) {
|
||||
let builtIn = patternArr.some((item) => item.value == value)
|
||||
if (!builtIn) {
|
||||
return new RegExp(value)
|
||||
}
|
||||
// 数字类型(n)
|
||||
if (value.startsWith('n')) {
|
||||
const rangeMatch = value.match(/^n(\d+)-(\d+)$/)
|
||||
if (rangeMatch) {
|
||||
const min = rangeMatch[1]
|
||||
const max = rangeMatch[2]
|
||||
return new RegExp(`^\\d{${min},${max}}$`)
|
||||
}
|
||||
return /^\d+$/ // 默认:纯数字
|
||||
}
|
||||
|
||||
// 字母类型(s)
|
||||
if (value.startsWith('s')) {
|
||||
const rangeMatch = value.match(/^s(\d+)-(\d+)$/)
|
||||
if (rangeMatch) {
|
||||
const min = rangeMatch[1]
|
||||
const max = rangeMatch[2]
|
||||
return new RegExp(`^[a-zA-Z]{${min},${max}}$`)
|
||||
}
|
||||
return /^[a-zA-Z]+$/ // 默认:纯字母
|
||||
}
|
||||
|
||||
// 任意字符(*)
|
||||
if (value.startsWith('*')) {
|
||||
if (value === '*') return /^.+$/ // 非空
|
||||
const rangeMatch = value.match(/^\*(\d+)-(\d+)$/)
|
||||
if (rangeMatch) {
|
||||
const min = rangeMatch[1]
|
||||
const max = rangeMatch[2]
|
||||
return new RegExp(`^.{${min},${max}}$`)
|
||||
}
|
||||
}
|
||||
|
||||
// 其他预定义规则
|
||||
const predefinedPatterns = {
|
||||
url: /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i,
|
||||
e: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
||||
m: /^1[3-9]\d{9}$/,
|
||||
p: /^[1-9]\d{5}$/,
|
||||
z: /^-?\d+$/,
|
||||
money: /^-?\d+(\.\d{1,2})?$/,
|
||||
}
|
||||
|
||||
return predefinedPatterns[value] || new RegExp(`^${escapeRegExp(value)}$`)
|
||||
}
|
||||
|
||||
// 辅助函数:转义正则特殊字符
|
||||
function escapeRegExp(str) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
|
||||
const patternArr = [
|
||||
{ label: '6到16位数字', value: 'n6-16' },
|
||||
{ label: '6到18位字母', value: 's6-18' },
|
||||
{ label: '6到16位任意字符', value: '*6-16' },
|
||||
{ label: '网址', value: 'url' },
|
||||
{ label: '电子邮件', value: 'e' },
|
||||
{ label: '手机号码', value: 'm' },
|
||||
{ label: '邮政编码', value: 'p' },
|
||||
{ label: '字母', value: 's' },
|
||||
{ label: '数字', value: 'n' },
|
||||
{ label: '整数', value: 'z' },
|
||||
{ label: '非空', value: '*' },
|
||||
{ label: '金额', value: 'money' },
|
||||
]
|
||||
|
||||
defineExpose({
|
||||
beforeOpen,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
30
JeecgUniapp-master/src/components/online/customExpression.ts
Normal file
30
JeecgUniapp-master/src/components/online/customExpression.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
*
|
||||
* 这里填写用户自定义的表达式
|
||||
* 可用在Online表单的默认值表达式中使用
|
||||
* 需要外部使用的变量或方法一定要 export,否则无法识别
|
||||
* 示例:
|
||||
* export const name = '张三'; // const 是常量
|
||||
* export let age = 17; // 看情况 export const 还是 let ,两者都可正常使用
|
||||
* export function content(arg) { // export 方法,可传参数,使用时要加括号,值一定要return回去,可以返回Promise
|
||||
* return 'content' + arg;
|
||||
* }
|
||||
* export const address = (arg) => content(arg) + ' | 北京市'; // export 箭头函数也可以
|
||||
*
|
||||
*/
|
||||
|
||||
/** 字段默认值官方示例:获取地址 */
|
||||
export function demoFieldDefVal_getAddress(arg) {
|
||||
if (!arg) {
|
||||
arg = '朝阳区';
|
||||
}
|
||||
return `北京市 ${arg}`;
|
||||
}
|
||||
|
||||
/** 自定义JS函数示例 */
|
||||
export function sayHi(name) {
|
||||
if (!name) {
|
||||
name = '张三';
|
||||
}
|
||||
return `您好,我叫: ${name}`;
|
||||
}
|
||||
302
JeecgUniapp-master/src/components/online/defaultVal.ts
Normal file
302
JeecgUniapp-master/src/components/online/defaultVal.ts
Normal file
@@ -0,0 +1,302 @@
|
||||
|
||||
// 移动端不支持自定义表达式设置的默认值
|
||||
import {http} from "@/utils/http";
|
||||
import {useUserStore} from "@/store";
|
||||
import * as CustomExpression from './customExpression';
|
||||
import dayjs from "dayjs";
|
||||
|
||||
// 获取所有用户自定义表达式的Key
|
||||
const ceKeys = Object.keys(CustomExpression)
|
||||
// 将key用逗号拼接,可以拼接成方法参数,例:a,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
|
||||
// 2021年5月21日 Tree类型表单,系统编码不生效。【issues/I3NR39】
|
||||
if (!key) {
|
||||
key = prop._propertyId
|
||||
}
|
||||
eachHandler(key, defVal, action, (value) => {
|
||||
// 处理数字类型,如果type=number并且value有值
|
||||
if ('number' === type && value) {
|
||||
// parseFloat() 可以直接处理字符串、整数、小数、null和undefined,
|
||||
// 非数字类型直接返回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 for:online功能测试,行操作切换成新的行编辑-----------
|
||||
let v = [{rowKey: row.id, values: {[key]: value}}];
|
||||
(subForms.jvt || subForms.jet).setValues(v)
|
||||
// update-end---author:sunjianlei Date:20200725 for:online功能测试,行操作切换成新的行编辑------------
|
||||
}
|
||||
}, 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) {
|
||||
switch (expression) {
|
||||
case 'date':
|
||||
return dayjs().format('YYYY-MM-DD');
|
||||
case 'time':
|
||||
return dayjs().format('HH:mm:ss');
|
||||
case 'datetime':
|
||||
return dayjs().format('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
|
||||
}
|
||||
/**
|
||||
* 2023-09-04
|
||||
* liaozhiyang
|
||||
* 用new Function替换eval
|
||||
*/
|
||||
function _eval(str: string) {
|
||||
return new Function(`return ${str}`)();
|
||||
}
|
||||
/** 执行【用户自定义表达式】 {{xxx}} 移动端不支持 */
|
||||
async function executeCustomExpression(expression, origin) {
|
||||
// 利用 eval 生成一个方法,这个方法的参数就是用户自定义的所有的表达式
|
||||
let fn = _eval(`(function (${ceJoin}){ return ${expression} })`);
|
||||
try {
|
||||
// 然后调用这个方法,并把表达式传递进去,从而完成表达式的执行
|
||||
return fn.apply(null, $CE$)
|
||||
} catch (e) {
|
||||
// 执行失败,输出错误并返回原始值
|
||||
logWarn(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
|
||||
}
|
||||
|
||||
1139
JeecgUniapp-master/src/components/online/online-loader.vue
Normal file
1139
JeecgUniapp-master/src/components/online/online-loader.vue
Normal file
File diff suppressed because it is too large
Load Diff
245
JeecgUniapp-master/src/components/online/online-sub-many.vue
Normal file
245
JeecgUniapp-master/src/components/online/online-sub-many.vue
Normal file
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<view class="online-sub-many-container">
|
||||
<wd-button :disabled="componentDisabled" customClass="add" icon="add" size="small" @click="handleAdd">新增</wd-button>
|
||||
<wd-table :data="dataList" height="400px">
|
||||
<template v-for="(col,index) in columns" :key="index">
|
||||
<wd-table-col :prop="col.key" :label="col.title" align="center" :fixed="col.key == 'action'">
|
||||
<template #value="value" v-if="col.key == 'action'">
|
||||
<view class="custom-action">
|
||||
<wd-button size="small" type="icon" icon="edit" @click="handleEdit(value)"></wd-button>
|
||||
<wd-button size="small" type="icon" icon="delete" style="color:rgba(255,0,0,0.5)" @click="handleDel(value)"></wd-button>
|
||||
</view>
|
||||
</template>
|
||||
<template #value="{ row, index }" :key="index" v-else>
|
||||
<online-sub-table-cell
|
||||
:columnsInfo="columnsInfo"
|
||||
:record="row"
|
||||
:column="col"
|
||||
></online-sub-table-cell>
|
||||
</template>
|
||||
</wd-table-col>
|
||||
</template>
|
||||
</wd-table>
|
||||
</view>
|
||||
<online-subform-popup
|
||||
v-if="popupShow"
|
||||
ref="subFormPop"
|
||||
:title="tableTxt"
|
||||
:schema="formSchema"
|
||||
:disabled="disabled"
|
||||
@close="handleClose"
|
||||
@change="handleChange"
|
||||
></online-subform-popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {useMessage, useToast} from 'wot-design-uni';
|
||||
import OnlineSubformPopup from './components/online-subform-popup.vue'
|
||||
import OnlineSubTableCell from './components/online-sub-table-cell.vue'
|
||||
import { http } from '@/utils/http'
|
||||
import {deepClone} from "wot-design-uni/components/common/util";
|
||||
defineOptions({
|
||||
name: 'online-sub-many',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
// 接收 props
|
||||
const props = defineProps({
|
||||
tableInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({} as any)
|
||||
},
|
||||
dataInfo: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => ([])
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
edit: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
showFooter: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
}
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emits = defineEmits(['back', 'success'])
|
||||
//提示
|
||||
const toast = useToast()
|
||||
//pop弹窗
|
||||
const subFormPop = ref(null)
|
||||
//表ID
|
||||
const tableId = ref('')
|
||||
//表描述
|
||||
const tableTxt = ref('')
|
||||
//表名称
|
||||
const tableName = ref('')
|
||||
//列信息
|
||||
const columns = ref([])
|
||||
//表单配置信息
|
||||
const formSchema = ref([])
|
||||
//弹窗显示
|
||||
const popupShow = ref(false)
|
||||
//数据列表
|
||||
const dataList = ref([])
|
||||
//数据列表
|
||||
const editIndex = ref(null)
|
||||
//列信息
|
||||
const columnsInfo = ref({})
|
||||
/**
|
||||
*
|
||||
* @param item
|
||||
* @returns {*|boolean}
|
||||
*/
|
||||
const componentDisabled = computed(()=>{
|
||||
console.log("一对多many组件",props.disabled)
|
||||
console.log("一对多many组件",props.showFooter)
|
||||
if (props.disabled === true || !props.showFooter) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
//监听配置修改
|
||||
watchEffect(()=>{
|
||||
props.tableInfo && loadTableInfo(props.tableInfo)
|
||||
})
|
||||
/**
|
||||
* 根据配置动态加载表单
|
||||
* @param dataID
|
||||
*/
|
||||
function loadTableInfo(tableInfo: any){
|
||||
console.log('===子表many加载表单数据 schema===', tableInfo)
|
||||
formSchema.value = deepClone(tableInfo)
|
||||
tableId.value = tableInfo.id;
|
||||
tableName.value = tableInfo.key;
|
||||
tableTxt.value = tableInfo.describe;
|
||||
columns.value = tableInfo?.columns || [];
|
||||
http
|
||||
.get(`/online/cgform/api/getColumns/${tableId.value}`)
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
if (res.result?.columns?.length) {
|
||||
columnsInfo.value = res.result;
|
||||
}
|
||||
}
|
||||
})
|
||||
let hasAction = columns.value.some(item=>item.key == 'action');
|
||||
!hasAction && columns.value.unshift({title:'操作', key:'action'});
|
||||
console.log("根据配置动态加载表单columns.value ",columns.value );
|
||||
if (props.edit === true) {
|
||||
initFormData(props.dataInfo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param row
|
||||
* @param index
|
||||
*/
|
||||
function handleDel({index}){
|
||||
if(componentDisabled.value){
|
||||
return;
|
||||
}
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除吗?',
|
||||
cancelText: '取消',
|
||||
confirmText: '确定',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
dataList.value.splice(index,1);
|
||||
toast.success('删除成功')
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log(err)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
function handleAdd(){
|
||||
popupShow.value = true;
|
||||
nextTick(()=>{
|
||||
subFormPop.value.beforeOpen(null,false)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 编辑
|
||||
* @param row
|
||||
* @param index
|
||||
*/
|
||||
function handleEdit({row,index}){
|
||||
if(!componentDisabled.value){
|
||||
editIndex.value = index;
|
||||
popupShow.value = true;
|
||||
nextTick(()=>{
|
||||
subFormPop.value.beforeOpen(row,true)
|
||||
})
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 关闭弹窗
|
||||
* @param row
|
||||
* @param index
|
||||
*/
|
||||
function handleClose(){
|
||||
popupShow.value = false;
|
||||
}
|
||||
/**
|
||||
* 表单内容修改
|
||||
* @param row
|
||||
* @param index
|
||||
*/
|
||||
function handleChange(record,isUpdate){
|
||||
if(isUpdate){
|
||||
dataList.value[unref(editIndex)] = record;
|
||||
}else{
|
||||
dataList.value.push(record);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交前处理
|
||||
*/
|
||||
function beforeSubmit(){
|
||||
console.log("一对多beforeSubmit",dataList.value);
|
||||
return {status:true,data:dataList.value}
|
||||
}
|
||||
/**
|
||||
* 初始化表单数据
|
||||
*/
|
||||
function initFormData(data) {
|
||||
if (data && data.length>0)
|
||||
dataList.value = deepClone(data)
|
||||
}
|
||||
defineExpose({
|
||||
beforeSubmit,
|
||||
initFormData
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.online-sub-many-container {
|
||||
min-height: 300px;
|
||||
.custom-action{
|
||||
display: flex;
|
||||
}
|
||||
:deep(.add) {
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
808
JeecgUniapp-master/src/components/online/online-sub-one.vue
Normal file
808
JeecgUniapp-master/src/components/online/online-sub-one.vue
Normal file
@@ -0,0 +1,808 @@
|
||||
<template>
|
||||
<view class="online-sub-one-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, 'mt-2px': index == 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)"
|
||||
>
|
||||
<view style="text-align: left">
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<online-file
|
||||
v-model:value="formData[item.key]"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
:key="index"
|
||||
:maxNum="getFieldExtendJson(item, 'uploadnum')"
|
||||
></online-file>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<online-file-custom
|
||||
v-model:value="formData[item.key]"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
:key="index"
|
||||
:maxNum="getFieldExtendJson(item, 'uploadnum')"
|
||||
></online-file-custom>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</wd-cell>
|
||||
|
||||
<!-- 日期时间 -->
|
||||
<DateTime
|
||||
v-else-if="item.type === 'datetime'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
startTime="1949-01-01 00:00:00"
|
||||
endTime="2050-01-01 00:00:00"
|
||||
:type="item.type"
|
||||
:name="item.key"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></DateTime>
|
||||
|
||||
<!-- 时间 -->
|
||||
<DateTime
|
||||
v-else-if="item.type === 'time'"
|
||||
:label="get4Label(item.label)"
|
||||
:labelWidth="labelWidth"
|
||||
format="HH:mm:ss"
|
||||
:type="item.type"
|
||||
:name="item.key"
|
||||
:disabled="componentDisabled(item)"
|
||||
v-model="formData[item.key]"
|
||||
:required="fieldRequired(item)"
|
||||
></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-if="item.type !== 'hidden'"
|
||||
: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>
|
||||
<wd-toast></wd-toast>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import FormProperty from './FormProperty'
|
||||
import OnlineImage from '@/components/online/view/online-image.vue'
|
||||
import OnlineFile from '@/components/online/view/online-file.vue'
|
||||
import OnlineFileCustom from '@/components/online/view/online-file-custom.vue'
|
||||
import OnlineSelect from '@/components/online/view/online-select.vue'
|
||||
import OnlineTime from '@/components/online/view/online-time.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 './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 { deepClone } from 'wot-design-uni/components/common/util'
|
||||
import { isArray, isNumber, isString } from '@/utils/is'
|
||||
import { formatDate } from '@/common/uitls'
|
||||
import { duplicateCheck } from '@/service/api'
|
||||
defineOptions({
|
||||
name: 'online-sub-one',
|
||||
options: {
|
||||
// apply-shared:当前页面样式会影响到子组件样式.(小程序)
|
||||
// shared:当前页面样式影响到子组件,子组件样式也会影响到当前页面.(小程序)
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
// 接收 props
|
||||
const props = defineProps({
|
||||
tableInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}) as any,
|
||||
},
|
||||
dataInfo: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
edit: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
showFooter: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emits = defineEmits(['back', 'success'])
|
||||
//定义提示
|
||||
const toast = useToast()
|
||||
//表描述
|
||||
const tableTxt = ref('')
|
||||
//表名称
|
||||
const tableName = ref('')
|
||||
//表数据Data
|
||||
const formData = ref<any>({})
|
||||
//是否填值规则字段
|
||||
const hasFillRuleFields = ref('')
|
||||
//是否必填字段
|
||||
const hasRequiredFields = ref([])
|
||||
//字段属性
|
||||
const rootProperties = ref<any>([])
|
||||
//表单数据ID
|
||||
const formDataId = ref('')
|
||||
//表单数据ID
|
||||
const loaded = ref(false)
|
||||
// 标题宽度
|
||||
const labelWidth = computed(() => {
|
||||
return '100px'
|
||||
})
|
||||
// 导航标题
|
||||
const get4Label = computed(() => {
|
||||
return (lable) => {
|
||||
return `${lable && lable.length > 4 ? lable.substring(0, 4) : lable}:`
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取日期控件的扩展类型
|
||||
* @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: 'quarter',
|
||||
week: 'week',
|
||||
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) {
|
||||
return true
|
||||
}
|
||||
return item.disabled
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字段是否必填
|
||||
* @param item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const fieldRequired = (item: any) => {
|
||||
return item?.key && hasRequiredFields.value.includes(item.key)
|
||||
}
|
||||
/**
|
||||
* 获取扩展字段
|
||||
* @param item
|
||||
* @param field
|
||||
*/
|
||||
const getFieldExtendJson = (item, field) => {
|
||||
let json = item.formSchema.fieldExtendJson ?? '{}'
|
||||
if (isString(json) && json.trim().length === 0) {
|
||||
json = '{}'
|
||||
}
|
||||
json = JSON.parse(json)
|
||||
const result = json[field]
|
||||
return result
|
||||
}
|
||||
/**
|
||||
* 关联记录同步修改他表字段
|
||||
* @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
|
||||
})
|
||||
})
|
||||
}
|
||||
//监听配置修改
|
||||
watchEffect(() => {
|
||||
props.tableInfo && !loaded.value && loadTableInfo(props.tableInfo)
|
||||
})
|
||||
/**
|
||||
* 省市区获取最后一位
|
||||
* @param value
|
||||
*/
|
||||
function pcaValue(value) {
|
||||
if (value.includes(',')) {
|
||||
const parts = value.split(',')
|
||||
if (parts.length >= 3) {
|
||||
// 如果包含至少两个逗号,截取第三部分并去掉最后一位
|
||||
return parts[2]
|
||||
}
|
||||
}
|
||||
// 如果不包含逗号或不符合条件,返回原值
|
||||
return value
|
||||
}
|
||||
/**
|
||||
* 处理多选字段
|
||||
* @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)) {
|
||||
value = isArray(value) ? value[2] : pcaValue(value)
|
||||
}
|
||||
//是数组的就转换成字符串
|
||||
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 beforeSubmit = async () => {
|
||||
// 判断字段必填和正则
|
||||
if (await fieldCheck(formData.value)) {
|
||||
return { status: false, data: [] }
|
||||
}
|
||||
// 处理多选字段
|
||||
let finalData = await handleMultiOrDateField()
|
||||
console.log('一对一beforeSubmit', finalData)
|
||||
return { status: true, data: [finalData] }
|
||||
}
|
||||
/**
|
||||
* 校验字段
|
||||
* @param values
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const fieldCheck = async (values: any) => {
|
||||
// 校验字段
|
||||
let flag = false
|
||||
for (const item of rootProperties.value) {
|
||||
// 校验提示
|
||||
const tip = (msg) => {
|
||||
// 提示校验未通过
|
||||
toast.warning(`${tableTxt.value}:${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
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化表单数据
|
||||
*/
|
||||
function initFormData(data) {
|
||||
console.log('一对一子表:initFormData', data)
|
||||
if (data && data.length > 0) {
|
||||
formData.value = deepClone(data[0])
|
||||
formDataId.value = formData.value?.id
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建根属性
|
||||
* @param formSchema
|
||||
*/
|
||||
function createRootProperties(formSchema: any) {
|
||||
formData.value = {}
|
||||
hasFillRuleFields.value = formSchema?.hasFillRuleFields ?? []
|
||||
hasRequiredFields.value = formSchema?.required ?? []
|
||||
const properties = formSchema.properties
|
||||
let rootProps = []
|
||||
console.log('===子表one配置项 properties===', properties)
|
||||
Object.keys(properties).map((key) => {
|
||||
if (key) {
|
||||
const item = properties[key]
|
||||
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('--子表one rootProperties--', rootProps)
|
||||
}
|
||||
/**
|
||||
* 获取字段类型
|
||||
* @param item
|
||||
* @returns {string}
|
||||
*/
|
||||
const getFieldNumberType = (item: any) => {
|
||||
return item.onlyInteger === true ? 'digit' : 'number'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认值
|
||||
*/
|
||||
function handleDefaultValue() {
|
||||
rootProperties.value.forEach((item) => {
|
||||
let field = item.key
|
||||
let { defVal, type } = item.formSchema
|
||||
loadOneFieldDefVal(defVal, type, (value) => {
|
||||
formData.value[field] = value
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段数值
|
||||
* @param data
|
||||
*/
|
||||
const setFieldsValue = (data) => {
|
||||
console.log('一对一子表popup设置值', data)
|
||||
formData.value = { ...formData.value, ...data }
|
||||
}
|
||||
/**
|
||||
* 获取popup字段配置项
|
||||
* @param item
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置动态加载表单
|
||||
* @param dataID
|
||||
*/
|
||||
function loadTableInfo(formSchema: any) {
|
||||
console.log('===子表one加载表单数据 schema===', formSchema)
|
||||
createRootProperties(formSchema)
|
||||
tableTxt.value = formSchema?.describe
|
||||
if (props.edit === true) {
|
||||
initFormData(props.dataInfo)
|
||||
} else {
|
||||
// 新增页面处理表单默认值
|
||||
handleDefaultValue()
|
||||
}
|
||||
loaded.value = true
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
beforeSubmit,
|
||||
initFormData,
|
||||
loadTableInfo,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.online-sub-one-container {
|
||||
.form-container {
|
||||
:deep(.wd-cell-group__body) {
|
||||
background-color: #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>
|
||||
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<wd-input
|
||||
v-bind="$attrs"
|
||||
:label-width="labelWidth"
|
||||
v-model="currentText"
|
||||
:disabled="disabled"
|
||||
:label="label"
|
||||
:inputMode="inputMode"
|
||||
:placeholder="placeholder"
|
||||
@input="debouncedInput"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { debounce } from 'lodash-es'
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: '80px',
|
||||
required: false,
|
||||
},
|
||||
inputMode: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
required: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
}
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['input', 'update:value'])
|
||||
// 定义响应式数据;
|
||||
const currentText = ref(null)
|
||||
|
||||
// 监听 value 的变化
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
currentText.value = val;
|
||||
},
|
||||
{
|
||||
immediate:true
|
||||
}
|
||||
)
|
||||
|
||||
// 更新 value 的方法
|
||||
const updateValue = () => {
|
||||
emit('update:value', currentText.value)
|
||||
}
|
||||
|
||||
// 创建防抖函数
|
||||
const debouncedInput = debounce(updateValue, 1000)
|
||||
|
||||
// 记得在组件卸载时取消防抖
|
||||
onBeforeUnmount(() => {
|
||||
debouncedInput.cancel()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -0,0 +1,345 @@
|
||||
<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, item)">
|
||||
<view class="left text-gray-5">
|
||||
<view class="cu-avatar lg mr-4" v-if="imageField" :style="[{ backgroundImage: 'url(' + getImage(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="item.id"></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)">
|
||||
<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="item.id"></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, isString } from '@/utils/is'
|
||||
import { getFileAccessHttpUrl } from '@/common/uitls'
|
||||
|
||||
defineOptions({
|
||||
name: 'popupReportModal',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
selected: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
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((id, index) => {
|
||||
const findIndex = dataList.value.findIndex((item) => item.id === id)
|
||||
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 getImage(src) {
|
||||
return src?src:'';
|
||||
}
|
||||
// 清除搜索条件
|
||||
function handleClear() {
|
||||
search.keyword = ''
|
||||
handleSearch()
|
||||
}
|
||||
const hanldeCheck = (index, item) => {
|
||||
if (props.multi) {
|
||||
if (Array.isArray(checkboxRef.value)) {
|
||||
checkboxRef.value[index].toggle()
|
||||
}
|
||||
} else {
|
||||
checkedValue.value = item.id
|
||||
}
|
||||
}
|
||||
|
||||
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) => {})
|
||||
}
|
||||
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)) {
|
||||
// @ts-ignore
|
||||
checkedValue.value = props.selected.join(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
init()
|
||||
defineExpose({
|
||||
beforeOpen,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wrap {
|
||||
height: 100%;
|
||||
.mr-4 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
: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>
|
||||
@@ -0,0 +1,145 @@
|
||||
<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,
|
||||
},
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['change', 'update:modelValue'])
|
||||
|
||||
// 定义响应式数据
|
||||
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 {
|
||||
let code = props.dict;
|
||||
if (code.indexOf(',') > 0 && code.indexOf(' ') > 0) {
|
||||
// 编码后类似sys_user%20where%20username%20like%20xxx' 是不包含空格的,这里判断如果有空格和逗号说明需要编码处理
|
||||
code = encodeURI(code);
|
||||
}
|
||||
const res = await http.get('/sys/dict/getDictItems/' + code)
|
||||
if (res.success) {
|
||||
options.value = res.result
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求数据出错:', error)
|
||||
}
|
||||
} else {
|
||||
props.dict.forEach((item) => {
|
||||
options.value.push(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选择器改变事件处理函数
|
||||
const handleChange = ({value}) => {
|
||||
let valueStr='';
|
||||
if (value && isArray(value)) {
|
||||
valueStr = value.join(',')
|
||||
}
|
||||
emit('update:modelValue', valueStr);
|
||||
emit('change', valueStr);
|
||||
}
|
||||
|
||||
// 监听 dict 和 value 的变化
|
||||
watch(() => props.dict, () => {
|
||||
initSelections();
|
||||
});
|
||||
// 监听value 的变化
|
||||
watchEffect(()=>{
|
||||
props.modelValue && initVal()
|
||||
})
|
||||
//初始化数值
|
||||
function initVal(){
|
||||
selected.value = isString(props.modelValue)? props.modelValue.split(',') :[];
|
||||
}
|
||||
// 组件挂载时初始化选项
|
||||
onMounted(() => {
|
||||
initSelections()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.wd-checkbox__shape){
|
||||
border-radius:0 !important;
|
||||
}
|
||||
</style>
|
||||
178
JeecgUniapp-master/src/components/online/view/online-date.vue
Normal file
178
JeecgUniapp-master/src/components/online/view/online-date.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<view class="pickerArea" :class="{ clear: !!currentTime }">
|
||||
<!-- 周视图 -->
|
||||
<wd-calendar
|
||||
v-if="type == 'week'"
|
||||
v-model="currentTime"
|
||||
type="week"
|
||||
:disabled="disabled"
|
||||
:default-value="defaultValue"
|
||||
:labelWidth="labelWidth"
|
||||
:label="label"
|
||||
@confirm="handleConfirm" />
|
||||
<!-- 季度视图 -->
|
||||
<wd-picker
|
||||
v-else-if="type == 'quarter'"
|
||||
v-model="currentQuarter"
|
||||
:disabled="disabled"
|
||||
:labelWidth="labelWidth"
|
||||
:label="label"
|
||||
:columns="quarterColumns"
|
||||
@open="quarterOpen"
|
||||
@confirm="quarterConfirm"/>
|
||||
<!-- 其他视图 -->
|
||||
<wd-datetime-picker
|
||||
v-else
|
||||
:disabled="disabled"
|
||||
:minDate="-662716800000"
|
||||
:maxDate="4102416000000"
|
||||
:default-value="defaultValue"
|
||||
:type="type"
|
||||
:labelWidth="labelWidth"
|
||||
v-model="currentTime"
|
||||
:label="label"
|
||||
@confirm="handleConfirm"
|
||||
/>
|
||||
<view v-if="!!currentTime && !disabled" class="u-iconfont u-icon-close" @click.stop="handleClear"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 定义 props
|
||||
import { isString } from '@/utils/is'
|
||||
import dayjs from 'dayjs'
|
||||
import {dateToQuarterStart} from "@/common/uitls";
|
||||
|
||||
defineOptions({
|
||||
name: 'online-date',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
|
||||
//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: 'date',
|
||||
required: false,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Date],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['input', 'change', 'update:value'])
|
||||
// 定义响应式数据;
|
||||
const currentQuarter = ref('');
|
||||
// 定义响应式数据;
|
||||
const currentTime = ref(props.type == 'week' ? null : '');
|
||||
// 默認
|
||||
const defaultValue = ref(Date.now())
|
||||
// 季度选择器列
|
||||
const quarterColumns = ref([])
|
||||
|
||||
// 监听 value 的变化
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
if (props.type == 'quarter' && quarterColumns.value.length == 0) {
|
||||
generateQuarterOptions()
|
||||
}
|
||||
initVal(val)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 初始化值
|
||||
function initVal(val){
|
||||
if (props.type == 'quarter') {
|
||||
currentQuarter.value = dateToQuarterStart(val);
|
||||
}else{
|
||||
if (typeof val == 'object') {
|
||||
currentTime.value = dayjs(val).valueOf();
|
||||
} else {
|
||||
currentTime.value = isString(val) ? new Date(val).getTime() : val;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 选择器改变事件处理函数
|
||||
const handleConfirm = (e) => {
|
||||
emit('update:value', currentTime.value)
|
||||
emit('change', currentTime.value)
|
||||
}
|
||||
// 选择器改变事件处理函数
|
||||
const quarterOpen = () => {
|
||||
!quarterColumns.value.length && generateQuarterOptions();
|
||||
if(!currentQuarter.value) {
|
||||
currentQuarter.value = dateToQuarterStart(new Date());
|
||||
}
|
||||
}
|
||||
// 选择器改变事件处理函数
|
||||
const quarterConfirm = (e) => {
|
||||
emit('update:value', currentQuarter.value)
|
||||
emit('change', currentQuarter.value)
|
||||
}
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
currentTime.value = null
|
||||
handleConfirm(null)
|
||||
}
|
||||
// 生成季度选择器列
|
||||
function generateQuarterOptions(){
|
||||
const options = [];
|
||||
const currentYear = new Date().getFullYear();
|
||||
const startYear = currentYear - 40;
|
||||
const endYear = currentYear + 40;
|
||||
const years = Array.from({ length: endYear - startYear + 1 }, (_, i) => endYear - i).reverse();
|
||||
years.forEach(year => {
|
||||
options.push({label: `${year}年 Q1`, value: `${year}-01-01`})
|
||||
options.push({label: `${year}年 Q2`, value: `${year}-04-01`})
|
||||
options.push({label: `${year}年 Q3`, value: `${year}-07-01`})
|
||||
options.push({label: `${year}年 Q4`, value: `${year}-10-01`})
|
||||
})
|
||||
quarterColumns.value = [...options];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pickerArea {
|
||||
position: relative;
|
||||
.u-icon-close {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: calc(10px + 4px);
|
||||
color: #585858;
|
||||
font-size: 15px;
|
||||
}
|
||||
&.clear {
|
||||
:deep(.wd-picker__arrow) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<view>
|
||||
<template v-if="fileList && fileList.length > 0">
|
||||
<view class="cu-list menu-avatar ol-file-list">
|
||||
<view
|
||||
class="cu-item"
|
||||
:class="modalName == 'move-box-' + index ? 'move-cur' : ''"
|
||||
v-for="(item, index) in fileList"
|
||||
:key="index"
|
||||
@touchstart="ListTouchStart"
|
||||
@touchmove="ListTouchMove"
|
||||
@touchend="ListTouchEnd"
|
||||
:data-target="'move-box-' + index"
|
||||
>
|
||||
<view class="flex align-center">
|
||||
<text
|
||||
class="mr-4px text-blue"
|
||||
style="font-size: 18px"
|
||||
:class="fileType(item.name) ? 'cuIcon-text' : 'cuIcon-pic'"
|
||||
></text>
|
||||
{{ item.name }}
|
||||
</view>
|
||||
<view class="move">
|
||||
<view @tap="DelFile" class="bg-red" :data-index="index">删除</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<view class="cu-bar bg-white">
|
||||
<wd-button @tap="ChooseFile" size="small" :disabled="disabled" plain type="primary">
|
||||
<text class="cuIcon-upload"></text>
|
||||
上传
|
||||
</wd-button>
|
||||
</view>
|
||||
<LFile ref="customFile" @up-success="onSuccess"></LFile>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
import LFile from '@/components/LFile/LFile.vue';
|
||||
import { useUserStore } from '@/store';
|
||||
import { getEnvBaseUrl } from '@/utils';
|
||||
import { useToast } from 'wot-design-uni';
|
||||
import { getFileAccessHttpUrl } from '@/common/uitls';
|
||||
//图片类型
|
||||
const imgTypeArr = ['png', 'jpg', 'jpeg', 'bmp']
|
||||
const userStore = useUserStore()
|
||||
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,
|
||||
},
|
||||
maxNum: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
// 事件
|
||||
const emits = defineEmits(['change', 'input', 'update:value'])
|
||||
// 提示
|
||||
const toast = useToast()
|
||||
// 组件
|
||||
const customFile = ref(null)
|
||||
// 文件数据
|
||||
const fileList = ref([])
|
||||
// token
|
||||
const token = ref(userStore.userInfo.token)
|
||||
const modalName = ref(null)
|
||||
const listTouchStart = ref(0)
|
||||
const listTouchDirection = ref(null)
|
||||
|
||||
// ListTouch触摸开始
|
||||
const ListTouchStart = (e) => {
|
||||
listTouchStart.value = e.touches[0].pageX
|
||||
}
|
||||
|
||||
// ListTouch计算方向
|
||||
const ListTouchMove = (e) => {
|
||||
listTouchDirection.value = e.touches[0].pageX - listTouchStart.value > 0 ? 'right' : 'left'
|
||||
}
|
||||
|
||||
// ListTouch计算滚动
|
||||
const ListTouchEnd = (e) => {
|
||||
if (listTouchDirection.value == 'left') {
|
||||
modalName.value = e.currentTarget.dataset.target
|
||||
} else {
|
||||
modalName.value = null
|
||||
}
|
||||
listTouchDirection.value = null
|
||||
}
|
||||
/**
|
||||
* 选择文件
|
||||
* @constructor
|
||||
*/
|
||||
const ChooseFile = () => {
|
||||
if (props.maxNum && fileList.value.length >= props.maxNum) {
|
||||
toast.warning('最多只能上传' + props.maxNum + '个文件')
|
||||
return
|
||||
}
|
||||
customFile.value.upload({
|
||||
// #ifdef APP-PLUS
|
||||
currentWebview: getCurrentPages()[getCurrentPages().length - 1].$getAppWebview(),
|
||||
// #endif
|
||||
header: {
|
||||
'X-Access-Token': token.value,
|
||||
},
|
||||
url: `${getEnvBaseUrl()}/sys/common/upload`,
|
||||
})
|
||||
}
|
||||
|
||||
const onSuccess = (res) => {
|
||||
console.log('这是上传成功返回数值res:', res)
|
||||
let fileObj = res.data
|
||||
// #ifdef APP-PLUS
|
||||
fileObj = JSON.parse(res.data.id)
|
||||
// #endif
|
||||
if (fileObj.success) {
|
||||
let file = {
|
||||
name: res.fileName,
|
||||
path: fileObj.message,
|
||||
url: getFileAccessHttpUrl(fileObj.message),
|
||||
}
|
||||
fileList.value.unshift(file)
|
||||
changeOnlineFormValue()
|
||||
}
|
||||
}
|
||||
// 获取文件路径
|
||||
const pathArr = (arg) => {
|
||||
return fileList.value.map((item) => item[arg])
|
||||
}
|
||||
// 改变表单值
|
||||
const changeOnlineFormValue = () => {
|
||||
let arr = pathArr('path')
|
||||
let str = arr.join(',')
|
||||
emits('change', str)
|
||||
emits('input', str)
|
||||
emits('update:value', str)
|
||||
}
|
||||
|
||||
const DelFile = (e) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除吗?',
|
||||
cancelText: '取消',
|
||||
confirmText: '确定',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
fileList.value.splice(e.currentTarget.dataset.index, 1)
|
||||
changeOnlineFormValue()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const loadFile = () => {
|
||||
if (!props.value || props.value.length == 0) {
|
||||
return
|
||||
}
|
||||
let pathArr = props.value.split(',')
|
||||
let fileArray = []
|
||||
pathArr.map((path) => {
|
||||
let seg = path.lastIndexOf('/')
|
||||
if (seg < 0) {
|
||||
seg = 0
|
||||
}
|
||||
fileArray.push({
|
||||
name: formatPath(seg == 0 ? path : path.substr(seg + 1)),
|
||||
path: path,
|
||||
url: getFileAccessHttpUrl(path),
|
||||
})
|
||||
})
|
||||
fileList.value = [...fileArray]
|
||||
}
|
||||
|
||||
const formatPath = (path) => {
|
||||
let seg = path.lastIndexOf('.')
|
||||
if (seg < 0) {
|
||||
seg = 0
|
||||
}
|
||||
let pathSuffix = path.substr(seg)
|
||||
if (path.length > 20) {
|
||||
return path.substring(0, 15) + '(..)' + pathSuffix
|
||||
} else {
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
const fileType = (name) => {
|
||||
let seg = name.lastIndexOf('.')
|
||||
if (seg < 0) {
|
||||
seg = 0
|
||||
}
|
||||
let pathSuffix = name.substr(seg + 1)
|
||||
return imgTypeArr.indexOf(pathSuffix) == -1
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
loadFile()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ol-file-block {
|
||||
display: block;
|
||||
}
|
||||
.ol-file-block .action {
|
||||
-webkit-justify-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.ol-file-list > .cu-item {
|
||||
-webkit-justify-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
height: 45px;
|
||||
}
|
||||
</style>
|
||||
171
JeecgUniapp-master/src/components/online/view/online-file.vue
Normal file
171
JeecgUniapp-master/src/components/online/view/online-file.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<wd-upload
|
||||
v-model:file-list="fileList"
|
||||
accept="all"
|
||||
:upload-method="customUpload"
|
||||
:disabled="disabled"
|
||||
:limit="maxNum"
|
||||
:before-remove="delFile"
|
||||
:multiple="maxNum && maxNum > 1"
|
||||
></wd-upload>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { UploadMethod } from 'wot-design-uni/components/wd-upload/types'
|
||||
import { getEnvBaseUploadUrl,getEnvBaseUrl } 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 = `${getEnvBaseUrl()}/sys/common/upload`
|
||||
// 接收 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,
|
||||
},
|
||||
maxNum: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
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>
|
||||
171
JeecgUniapp-master/src/components/online/view/online-image.vue
Normal file
171
JeecgUniapp-master/src/components/online/view/online-image.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<wd-upload
|
||||
v-model:file-list="fileList"
|
||||
accept="image"
|
||||
:upload-method="customUpload"
|
||||
:disabled="disabled"
|
||||
:limit="maxNum"
|
||||
:before-remove="delFile"
|
||||
:multiple="maxNum && maxNum > 1"
|
||||
></wd-upload>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { UploadMethod } from 'wot-design-uni/components/wd-upload/types'
|
||||
import { getEnvBaseUploadUrl, getEnvBaseUrl } 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 = `${getEnvBaseUrl()}/sys/common/upload`
|
||||
// 接收 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,
|
||||
},
|
||||
maxNum: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
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>
|
||||
150
JeecgUniapp-master/src/components/online/view/online-multi.vue
Normal file
150
JeecgUniapp-master/src/components/online/view/online-multi.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<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,
|
||||
},
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits([ 'change', 'update:modelValue'])
|
||||
|
||||
// 定义响应式数据
|
||||
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 {
|
||||
let code = props.dict;
|
||||
if (code.indexOf(',') > 0 && code.indexOf(' ') > 0) {
|
||||
// 编码后类似sys_user%20where%20username%20like%20xxx' 是不包含空格的,这里判断如果有空格和逗号说明需要编码处理
|
||||
code = encodeURI(code);
|
||||
}
|
||||
const res = await http.get('/sys/dict/getDictItems/' + code)
|
||||
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 = ({value}) => {
|
||||
console.log("下拉多选handleChange",value);
|
||||
let valueStr = "";
|
||||
if (value && isArray(value)) {
|
||||
valueStr = value.join(',');
|
||||
}
|
||||
emit('update:modelValue', valueStr);
|
||||
emit('change', valueStr);
|
||||
}
|
||||
|
||||
// 监听 dict 和 value 的变化
|
||||
watch(() => props.dict, () => {
|
||||
initSelections();
|
||||
});
|
||||
// 监听 value 的变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
if(props?.modelValue){
|
||||
selected.value = isString(props.modelValue)? props.modelValue.split(','):[];
|
||||
}else{
|
||||
selected.value = []
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
// 组件挂载时初始化选项
|
||||
onMounted(() => {
|
||||
initSelections()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.wd-checkbox__shape){
|
||||
border-radius:0 !important;
|
||||
}
|
||||
</style>
|
||||
108
JeecgUniapp-master/src/components/online/view/online-pca.vue
Normal file
108
JeecgUniapp-master/src/components/online/view/online-pca.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<view>
|
||||
<wd-picker
|
||||
:columns="columns"
|
||||
:label-width="labelWidth"
|
||||
:label="label"
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
v-model="selected"
|
||||
:column-change="onChangeDistrict"
|
||||
@confirm="handleConfirm"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getAreaArrByCode,getPcaOptionData } from '@/common/areaData/Area'
|
||||
import {isString} from "@/utils/is";
|
||||
// 接收 props
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: '130px',
|
||||
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,
|
||||
},
|
||||
backAll: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
// 定义 emits
|
||||
const emits = defineEmits(['input', 'change', 'update:value'])
|
||||
|
||||
// 定义响应式数据
|
||||
const selected = ref([])
|
||||
const district = { ...getPcaOptionData() }
|
||||
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}) => {
|
||||
if(value){
|
||||
emits('update:value', props.backAll?value:value[2]);
|
||||
}
|
||||
}
|
||||
// 监听 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>
|
||||
@@ -0,0 +1,175 @@
|
||||
<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"
|
||||
:selected="value"
|
||||
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',
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
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:any)=>{
|
||||
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 (!props.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>
|
||||
157
JeecgUniapp-master/src/components/online/view/online-radio.vue
Normal file
157
JeecgUniapp-master/src/components/online/view/online-radio.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<wd-picker
|
||||
:label-width="labelWidth"
|
||||
:label="label"
|
||||
clearable
|
||||
filterable
|
||||
v-model="selected"
|
||||
:columns="options"
|
||||
:disabled="disabled"
|
||||
placeholder="请选择"
|
||||
@confirm="handleChange"
|
||||
></wd-picker>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<wd-select-picker
|
||||
:label-width="labelWidth"
|
||||
:show-confirm="false"
|
||||
:label="label"
|
||||
v-model="selected"
|
||||
type="radio"
|
||||
filterable
|
||||
clearable
|
||||
:columns="options"
|
||||
:disabled="disabled"
|
||||
placeholder="请选择"
|
||||
@change="handleChange"
|
||||
></wd-select-picker>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { isArray, isString } from 'lodash'
|
||||
import { http } from "@/utils/http";
|
||||
import { isNullOrUnDef } from "@/utils/is"; // 假设使用 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,
|
||||
},
|
||||
modelValue: {
|
||||
type: [Array, String],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['input', 'change', 'update:modelValue'])
|
||||
|
||||
// 定义响应式数据
|
||||
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 {
|
||||
let code = props.dict;
|
||||
if (code.indexOf(',') > 0 && code.indexOf(' ') > 0) {
|
||||
// 编码后类似sys_user%20where%20username%20like%20xxx' 是不包含空格的,这里判断如果有空格和逗号说明需要编码处理
|
||||
code = encodeURI(code);
|
||||
}
|
||||
const res = await http.get('/sys/dict/getDictItems/' + code)
|
||||
if (res.success) {
|
||||
options.value = res.result;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求数据出错:', error)
|
||||
}
|
||||
} else {
|
||||
props.dict.forEach((item) => {
|
||||
options.value.push(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选择器改变事件处理函数
|
||||
const handleChange = ({value}) => {
|
||||
emit('update:modelValue', value);
|
||||
emit('change', value);
|
||||
}
|
||||
|
||||
// 监听 dict 和 value 的变化
|
||||
watch(() => props.dict, () => {
|
||||
initSelections();
|
||||
});
|
||||
// 监听 value 的变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
selected.value = isNullOrUnDef(val)?'':val;
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 组件挂载时初始化选项
|
||||
onMounted(() => {
|
||||
initSelections()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
163
JeecgUniapp-master/src/components/online/view/online-select.vue
Normal file
163
JeecgUniapp-master/src/components/online/view/online-select.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<!-- 带搜索 -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<wd-select-picker
|
||||
:label-width="labelWidth"
|
||||
:label="label"
|
||||
type="radio"
|
||||
filterable
|
||||
v-model="selected"
|
||||
:columns="options"
|
||||
:disabled="disabled"
|
||||
:show-confirm="false"
|
||||
placeholder="请选择"
|
||||
@change="handleChange"
|
||||
></wd-select-picker>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<!-- 常规下拉 -->
|
||||
<wd-picker
|
||||
:label-width="labelWidth"
|
||||
:label="label"
|
||||
filterable
|
||||
v-model="selected"
|
||||
:columns="options"
|
||||
:disabled="disabled"
|
||||
placeholder="请选择"
|
||||
@confirm="handleChange"
|
||||
></wd-picker>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { isArray, isString } from 'lodash'
|
||||
import { http } from '@/utils/http'
|
||||
import { isNullOrUnDef } from '@/utils/is'
|
||||
|
||||
// 定义 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,
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 定义 emits
|
||||
const emit = defineEmits(['input', 'change', 'update:modelValue'])
|
||||
|
||||
// 定义响应式数据
|
||||
const selected = ref<any>('')
|
||||
const options = ref<any>([])
|
||||
|
||||
// 初始化选项
|
||||
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: any = await http.get('/sys/dict/getDictItems/' + temp)
|
||||
if (res.success) {
|
||||
options.value = (res.result ?? []).filter((item) => item !== null)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求数据出错:', error)
|
||||
}
|
||||
} else {
|
||||
if (!props.dict || props.dict.length === 0) {
|
||||
return
|
||||
}
|
||||
if (isString(props.dict)) {
|
||||
try {
|
||||
let code = props.dict;
|
||||
if (code.indexOf(',') > 0 && code.indexOf(' ') > 0) {
|
||||
// 编码后类似sys_user%20where%20username%20like%20xxx' 是不包含空格的,这里判断如果有空格和逗号说明需要编码处理
|
||||
code = encodeURI(code);
|
||||
}
|
||||
const res: any = await http.get('/sys/dict/getDictItems/' + code)
|
||||
if (res.success) {
|
||||
options.value = res.result
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('请求数据出错:', error)
|
||||
}
|
||||
} else {
|
||||
props.dict.forEach((item) => {
|
||||
options.value.push(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 选择器改变事件处理函数
|
||||
const handleChange = ({value}) => {
|
||||
emit('update:modelValue', value)
|
||||
emit('change', value)
|
||||
}
|
||||
|
||||
// 监听 dict 和 value 的变化
|
||||
watch(
|
||||
() => props.dict,
|
||||
() => {
|
||||
initSelections()
|
||||
},
|
||||
)
|
||||
// 监听 value 的变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
selected.value = isNullOrUnDef(props.modelValue) ? '' : val
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 组件挂载时初始化选项
|
||||
onMounted(() => {
|
||||
initSelections()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.wd-checkbox__shape) {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<wd-datetime-picker :disabled="disabled" type="time" :labelWidth="labelWidth" v-model="currentTime" :label="label" @confirm="handleConfirm" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 定义 props
|
||||
import dayjs from "dayjs";
|
||||
|
||||
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,Date],
|
||||
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 = initData(val);
|
||||
},
|
||||
{
|
||||
immediate:true
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param val
|
||||
* @returns {*|string}
|
||||
*/
|
||||
function initData(time){
|
||||
try {
|
||||
if(typeof time == 'object' ){
|
||||
time = dayjs(time).format('HH:mm:ss');
|
||||
}
|
||||
}catch (e) {
|
||||
console.log("initData>>e",e);
|
||||
}
|
||||
return time?time:'';
|
||||
}
|
||||
// 选择器改变事件处理函数
|
||||
const handleConfirm = (e) => {
|
||||
emit('update:value', currentTime.value+':00')
|
||||
emit('change', currentTime.value+':00')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
109
JeecgUniapp-master/src/components/registerGlobComp.ts
Normal file
109
JeecgUniapp-master/src/components/registerGlobComp.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
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';
|
||||
import JTotalFlyLineMap from '@/pages-work/components/echarts/map/TotalFlyLineMap/index.vue';
|
||||
import JTotalBarMap from '@/pages-work/components/echarts/map/TotalBarMap/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';
|
||||
import JForm from '@/pages-work/components/drag/form/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)
|
||||
app.component('JTotalFlyLineMap', JTotalFlyLineMap)
|
||||
app.component('JTotalBarMap', JTotalBarMap)
|
||||
|
||||
//非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)
|
||||
app.component('JForm', JForm)
|
||||
|
||||
}
|
||||
33
JeecgUniapp-master/src/env.d.ts
vendored
Normal file
33
JeecgUniapp-master/src/env.d.ts
vendored
Normal 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
|
||||
}
|
||||
295
JeecgUniapp-master/src/hooks/useGeoPosition.ts
Normal file
295
JeecgUniapp-master/src/hooks/useGeoPosition.ts
Normal file
@@ -0,0 +1,295 @@
|
||||
import { geoDistance } from '@/common/uitls'
|
||||
// #ifdef MP-WEIXIN || APP-PLUS
|
||||
import amap from '@/common/js-sdk/js-amap/amap-wx'
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
import H5Map from '@/common/js-sdk/js-amap/amap-h5'
|
||||
// #endif
|
||||
|
||||
export default function useGeo<T>({ compLatitude, compLongitude }, emit?) {
|
||||
// 定义响应式数据
|
||||
const amapPlugin = ref(null)
|
||||
//高德地图微信key
|
||||
const wxMapKey = import.meta.env.VITE_GD_MAP_KEY__WEIXIN
|
||||
console.log('wxMapKey', wxMapKey)
|
||||
//打卡距离
|
||||
const distance = ref(0)
|
||||
//地址
|
||||
const address = ref('')
|
||||
//纬度
|
||||
const latitude = ref(compLatitude)
|
||||
//经度
|
||||
const longitude = ref(compLongitude)
|
||||
//地图缩放程度
|
||||
const scale = ref(16)
|
||||
//地图标记
|
||||
const marker = ref([])
|
||||
//在地图上显示圆
|
||||
const circles = ref([
|
||||
{
|
||||
latitude: compLatitude,
|
||||
longitude: compLongitude,
|
||||
radius: 80,
|
||||
fillColor: '#ffffffAA',
|
||||
color: '#55aaffAA',
|
||||
strokeWidth: 1,
|
||||
},
|
||||
])
|
||||
//地图配置
|
||||
const resAmap = ref(null)
|
||||
|
||||
// 在打卡范围内
|
||||
const inCircle = () => {
|
||||
return distance.value <= 80
|
||||
}
|
||||
|
||||
//刷新定位
|
||||
const refreshLocation = () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
getAuthorizeInfo()
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
getLocationInfo()
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
initH5Map()
|
||||
// #endif
|
||||
}
|
||||
/**
|
||||
* 权限提示
|
||||
*/
|
||||
const getAuthorizeInfo = () => {
|
||||
//1. uniapp弹窗弹出获取授权(地理,个人微信信息等授权信息)弹窗
|
||||
uni.authorize({
|
||||
scope: 'scope.userLocation',
|
||||
success() {
|
||||
//1.1 允许授权
|
||||
getLocationInfo()
|
||||
},
|
||||
fail() {
|
||||
console.log('你拒绝了授权,无法获得周边信息')
|
||||
openConfirm()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const getLocationInfo = () => {
|
||||
uni.showLoading({
|
||||
title: '定位中...',
|
||||
mask: true,
|
||||
})
|
||||
amapPlugin.value.getRegeo({
|
||||
type: 'gcj02',
|
||||
success: function (res) {
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
}, 2000)
|
||||
latitude.value = res[0].latitude
|
||||
longitude.value = res[0].longitude
|
||||
address.value = res[0].name + res[0].desc
|
||||
distance.value = geoDistance(longitude.value, latitude.value, compLongitude, compLatitude)
|
||||
console.log('getLocationInfoWx***distance>>>', distance.value)
|
||||
let tipTextVal = (distance.value > 80 ? '不在' : '已在') + '打卡范围'
|
||||
let bgColorVal = distance.value > 80 ? '#ff0000' : '#00c16f'
|
||||
let markerVal = {
|
||||
id: 0,
|
||||
latitude: latitude.value,
|
||||
longitude: longitude.value,
|
||||
iconPath: '/static/location.png',
|
||||
width: 35,
|
||||
height: 35,
|
||||
// #ifdef MP-WEIXIN
|
||||
//为标记点旁边增加标签
|
||||
label: {
|
||||
content: tipTextVal, //文本
|
||||
color: '#ffffff', //文本颜色
|
||||
fontSize: 14, //文字大小
|
||||
borderWidth: 2, //边框宽度
|
||||
borderColor: bgColorVal, //边框颜色
|
||||
bgColor: bgColorVal, //背景颜色
|
||||
borderRadius: 2, //边框圆角
|
||||
padding: 5, //文本边缘留白
|
||||
textAlign: 'center', //文本对齐方式
|
||||
x: 0, //label的坐标,原点是 marker 对应的经纬度
|
||||
y: 0, //label的坐标,原点是 marker 对应的经纬度
|
||||
},
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
//自定义标记点上方的气泡窗口 点击有效
|
||||
callout: {
|
||||
content: tipTextVal, //文本
|
||||
color: '#ffffff', //文字颜色
|
||||
fontSize: 14, //文本大小
|
||||
bgColor: bgColorVal, //背景颜色
|
||||
display: 'ALWAYS', //常显
|
||||
textAlign: 'center',
|
||||
},
|
||||
// #endif
|
||||
}
|
||||
marker.value = [markerVal]
|
||||
emit && emit('success', {})
|
||||
},
|
||||
fail: (res) => {
|
||||
emit && emit('fail', res)
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
}, 2000)
|
||||
},
|
||||
})
|
||||
//TODO h5端可能因为非https域名会导致一直处在定位中的状态,因此设置10s后清除加载状态
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
}, 10000)
|
||||
}
|
||||
/**
|
||||
* 获取地理授权
|
||||
*/
|
||||
const openConfirm = () => {
|
||||
uni.showModal({
|
||||
title: '请求授权当前位置',
|
||||
content: '需要获取您的地理位置,请确认授权',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.openSetting()
|
||||
} else if (res.cancel) {
|
||||
uni.showToast({
|
||||
title: '你拒绝了授权,无法获得位置信息',
|
||||
icon: 'none',
|
||||
duration: 1000,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 获取地理位置信息
|
||||
*/
|
||||
const getMyAddress = computed(()=>{
|
||||
return address.value
|
||||
})
|
||||
/**
|
||||
* 根据坐标返回地址(逆地理编码)
|
||||
* @param points
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const getAddress = async (points) => {
|
||||
try {
|
||||
resAmap.value = await H5Map()
|
||||
resAmap.value.plugin('AMap.Geocoder', () => {
|
||||
const geocoder = new resAmap.value.Geocoder({
|
||||
radius: 1000,
|
||||
})
|
||||
geocoder.getAddress(points, (status, result) => {
|
||||
if (status === 'complete' && result.regeocode.formattedAddress) {
|
||||
address.value = result.regeocode.formattedAddress
|
||||
}
|
||||
})
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
const initH5Map = async () => {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '定位中...',
|
||||
mask: true,
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
}, 10000)
|
||||
resAmap.value = await H5Map()
|
||||
resAmap.value.plugin('AMap.Geolocation', () => {
|
||||
const geolocation = new resAmap.value.Geolocation({
|
||||
enableHighAccuracy: true,
|
||||
timeout: 10000,
|
||||
buttonPosition: 'RB',
|
||||
zoomToAccuracy: true,
|
||||
})
|
||||
geolocation.getCurrentPosition(function (status, result) {
|
||||
if (status == 'complete') {
|
||||
onComplete(result)
|
||||
} else {
|
||||
onError(result)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const onComplete = (data) => {
|
||||
console.log('H5高德定位', data)
|
||||
console.log('当前位置的经度:' + data.position.lat)
|
||||
console.log('当前位置的纬度:' + data.position.lng)
|
||||
distance.value = geoDistance(
|
||||
data.position.lng,
|
||||
data.position.lat,
|
||||
compLongitude,
|
||||
compLatitude,
|
||||
)
|
||||
console.log('打卡距离distance.value:' + distance.value)
|
||||
let tipTextVal = (distance.value > 80 ? '未在' : '已在') + '打卡范围内'
|
||||
let bgColorVal = distance.value > 80 ? '#ff0000' : '#00c16f'
|
||||
longitude.value = data.position.lng
|
||||
latitude.value = data.position.lat
|
||||
address.value = data?.formattedAddress
|
||||
let markerVal = {
|
||||
latitude: latitude.value,
|
||||
longitude: longitude.value,
|
||||
iconPath: '/static/location.png',
|
||||
callout: {
|
||||
content: tipTextVal,
|
||||
color: '#ffffff',
|
||||
fontSize: 14,
|
||||
borderRadius: 2,
|
||||
bgColor: bgColorVal,
|
||||
display: 'BYCLICK',
|
||||
},
|
||||
}
|
||||
marker.value = [markerVal]
|
||||
uni.hideLoading()
|
||||
getAddress([longitude.value, latitude.value])
|
||||
emit && emit('success', {})
|
||||
}
|
||||
|
||||
const onError = (data) => {
|
||||
console.log(data)
|
||||
emit && emit('fail', data)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
onBeforeMount(() => {
|
||||
// #ifdef MP-WEIXIN || APP-PLUS
|
||||
amapPlugin.value = new amap.AMapWX({
|
||||
key: wxMapKey,
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
// 生命周期钩子函数
|
||||
onMounted(() => {
|
||||
// #ifdef MP-WEIXIN
|
||||
getAuthorizeInfo()
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
initH5Map()
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
getLocationInfo()
|
||||
// #endif
|
||||
})
|
||||
|
||||
return [
|
||||
{ inCircle, distance, getAddress,getMyAddress,latitude,longitude,marker,scale,circles },
|
||||
{ getAuthorizeInfo, getLocationInfo,refreshLocation },
|
||||
]
|
||||
}
|
||||
45
JeecgUniapp-master/src/hooks/usePageList.ts
Normal file
45
JeecgUniapp-master/src/hooks/usePageList.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import {useToast} from "wot-design-uni";
|
||||
import {useRouter} from "@/plugin/uni-mini-router";
|
||||
import {useParamsStore} from "@/store/page-params";
|
||||
import {ref} from "vue";
|
||||
import {http} from "@/utils/http";
|
||||
|
||||
|
||||
export default function usePageList<T = string>(list,params={}) {
|
||||
const toast = useToast()
|
||||
const router = useRouter()
|
||||
const paramsStore = useParamsStore()
|
||||
const paging = ref(null)
|
||||
const pageNo = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const pageTotal = ref(1)
|
||||
const dataList = ref([])
|
||||
const queryParams = () => {
|
||||
return {
|
||||
pageNo: pageNo.value,
|
||||
pageSize: pageSize.value,
|
||||
order: 'desc',
|
||||
column: 'createTime',
|
||||
hasQuery: true,
|
||||
}
|
||||
}
|
||||
|
||||
const queryList = (_pageNo, _pageSize) => {
|
||||
pageNo.value = _pageNo
|
||||
pageSize.value = _pageSize
|
||||
let allParams = { ...queryParams(),...params };
|
||||
http
|
||||
.get(list, allParams)
|
||||
.then((res: any) => {
|
||||
if (res.success) {
|
||||
paging.value.complete(res.result?.records ?? [])
|
||||
} else {
|
||||
toast.warning(res.message)
|
||||
}
|
||||
})
|
||||
.catch((res) => {
|
||||
toast.error('加载数据失败~')
|
||||
})
|
||||
}
|
||||
return { toast, router, paging,paramsStore, dataList, queryParams, queryList }
|
||||
}
|
||||
44
JeecgUniapp-master/src/hooks/useRequest.ts
Normal file
44
JeecgUniapp-master/src/hooks/useRequest.ts
Normal 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
JeecgUniapp-master/src/hooks/useUpload.ts
Normal file
86
JeecgUniapp-master/src/hooks/useUpload.ts
Normal 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 代替。
|
||||
// 微信小程序在2023年10月17日之后,使用本API需要配置隐私协议
|
||||
uni.chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sourceType,
|
||||
sizeType, //可以指定是原图还是压缩图,默认二者都有
|
||||
success: (res) => {
|
||||
loading.value = true
|
||||
const tempFilePath = res.tempFiles[0].tempFilePath
|
||||
const fileName = res.tempFiles[0].name || res.tempFiles[0].tempFilePath.substring(tempFilePath.lastIndexOf('/') + 1)
|
||||
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 || tempFilePath.substring(tempFilePath.lastIndexOf('/') + 1)
|
||||
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: formData.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
|
||||
},
|
||||
})
|
||||
}
|
||||
44
JeecgUniapp-master/src/hybrid/html/iframe_index.html
Normal file
44
JeecgUniapp-master/src/hybrid/html/iframe_index.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>JEECG BOOT</title>
|
||||
<style type="text/css">
|
||||
|
||||
#iframe {
|
||||
width: 100%;
|
||||
z-index: 99;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="iframe" frameborder="0" ></iframe>
|
||||
|
||||
<!-- uni 的 SDK -->
|
||||
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
let mId;
|
||||
document.addEventListener('UniAppJSBridgeReady', () => {
|
||||
let {src,mid,height} = plus.webview.currentWebview();
|
||||
let iframe = document.getElementById("iframe");
|
||||
iframe.height = height;
|
||||
document.getElementById("iframe").src=src;
|
||||
mId=mid;
|
||||
});
|
||||
|
||||
window.addEventListener('message', function(event) {
|
||||
|
||||
let { messageId, type, data } = event.data
|
||||
if ( mId !== messageId) {
|
||||
return
|
||||
}
|
||||
let dataString=JSON.stringify(data);
|
||||
|
||||
location.href = `callback?data=${escape(dataString)}&type=${escape(type)}`;
|
||||
}, false)
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user