zhichao lei преди 4 години
родител
ревизия
8bcadcd39f
променени са 100 файла, в които са добавени 15761 реда и са изтрити 0 реда
  1. +1
    -0
      Learun.Framework.Ultimate V7/Learun.Application.WebApi/Modules/Index.cs
  2. +75
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/.eslintrc.js
  3. +97
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/.gitignore
  4. +49
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/App.vue
  5. +170
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/README.md
  6. +65
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/config.default.js
  7. +24
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/css/customlist.less
  8. +75
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/css/sidepage.less
  9. +296
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/customform.js
  10. +313
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/custompage.js
  11. +683
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/md5.js
  12. +804
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/mixins.js
  13. +63
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/store.js
  14. +5463
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/u-charts.js
  15. +170
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customform-table.vue
  16. +312
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customform.vue
  17. +62
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customlist-action.vue
  18. +40
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customlist-add.vue
  19. +62
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customlist-banner.vue
  20. +194
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customlist-sidepage-datefilter.vue
  21. +17
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customlist.vue
  22. +46
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/demo-description.vue
  23. +85
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/layer-picker.vue
  24. +75
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/organize-picker.vue
  25. +90
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/organize-single-item.vue
  26. +231
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/organize-tree.vue
  27. +86
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/scroll-list.vue
  28. +155
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/upload-file.vue
  29. +131
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/workflow-action.vue
  30. +44
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/workflow-timeline.vue
  31. +20
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/colors.js
  32. +302
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/icons.js
  33. +77
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/index.js
  34. +33
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/avatar.vue
  35. +123
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/banner.vue
  36. +14
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/bar-item-button.vue
  37. +39
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/bar-item.vue
  38. +14
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/bar.vue
  39. +62
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/button.vue
  40. +82
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/card.vue
  41. +72
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/chat-input.vue
  42. +101
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/chat-msg.vue
  43. +20
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/chat.vue
  44. +111
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/checkbox-picker.vue
  45. +50
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/checkbox.vue
  46. +91
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/date-picker.vue
  47. +621
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/datetime-panel.vue
  48. +143
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/datetime-picker.vue
  49. +28
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/icon.vue
  50. +59
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/input.vue
  51. +45
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/label.vue
  52. +90
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/list-item.vue
  53. +26
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/list.vue
  54. +24
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/loading.vue
  55. +221
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/modal.vue
  56. +70
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/nav.vue
  57. +41
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/progress.vue
  58. +46
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/radio.vue
  59. +52
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/region-picker.vue
  60. +157
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/select.vue
  61. +29
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/step.vue
  62. +33
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/switch.vue
  63. +55
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/tag.vue
  64. +80
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/textarea.vue
  65. +99
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/time-picker.vue
  66. +23
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/timeline-item.vue
  67. +18
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/timeline.vue
  68. +59
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/title.vue
  69. +59
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/upload.vue
  70. +27
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/avatar.vue
  71. +114
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/banner.vue
  72. +14
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/bar-item-button.vue
  73. +37
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/bar-item.vue
  74. +14
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/bar.vue
  75. +52
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/button.vue
  76. +82
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/card.vue
  77. +63
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/chat-input.vue
  78. +80
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/chat-msg.vue
  79. +20
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/chat.vue
  80. +114
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/checkbox-picker.vue
  81. +49
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/checkbox.vue
  82. +60
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/date-picker.vue
  83. +621
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/datetime-panel.vue
  84. +82
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/datetime-picker.vue
  85. +23
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/icon.vue
  86. +58
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/input.vue
  87. +45
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/label.vue
  88. +79
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/list-item.vue
  89. +22
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/list.vue
  90. +20
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/loading.vue
  91. +216
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/modal.vue
  92. +75
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/nav.vue
  93. +39
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/progress.vue
  94. +44
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/radio.vue
  95. +54
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/region-picker.vue
  96. +164
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/select.vue
  97. +29
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/step.vue
  98. +33
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/switch.vue
  99. +48
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/tag.vue
  100. +116
    -0
      Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/textarea.vue

+ 1
- 0
Learun.Framework.Ultimate V7/Learun.Application.WebApi/Modules/Index.cs Целия файл

@@ -15,6 +15,7 @@ namespace Learun.Application.WebApi
: base()
{
Get["/"] = MainIndex;
Get["/index.html"] = MainIndex
Get["/bgimg"] = BgImg;
}
/// <summary>


+ 75
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/.eslintrc.js Целия файл

@@ -0,0 +1,75 @@
// 本文件是 ESLint 的配置文件
// ESLint 用于在您二次开发时,每次保存文件后,对代码进行校验,并对可能出错的地方给予提示
// 一般来说不需要修改此文件,除非您有额外的需求

//更详细的配置文档请参考:https://github.com/vuejs/eslint-plugin-vue#gear-configs
module.exports = {
"extends": "plugin:vue/base",
parserOptions: {
ecmaVersion: 2017,
sourceType: 'module'
},
"rules": {
//在computed properties中禁用异步actions
'vue/no-async-in-computed-properties': 'error',
//不允许重复的keys
'vue/no-dupe-keys': 'error',
//不允许重复的attributes
'vue/no-duplicate-attributes': 'warn',
//在 <template> 标签下不允许解析错误
'vue/no-parsing-error': ['error', {
'x-invalid-end-tag': false,
}],
//不允许覆盖保留关键字
'vue/no-reserved-keys': 'error',
//强制data必须是一个带返回值的函数
// 'vue/no-shared-component-data': 'error',
//不允许在computed properties中出现副作用。
'vue/no-side-effects-in-computed-properties': 'error',
//<template>不允许key属性
'vue/no-template-key': 'warn',
//在 <textarea> 中不允许mustaches
'vue/no-textarea-mustache': 'error',
//不允许在v-for或者范围内的属性出现未使用的变量定义
'vue/no-unused-vars': 'warn',
//<component>标签需要v-bind:is属性
'vue/require-component-is': 'error',
// render 函数必须有一个返回值
'vue/require-render-return': 'error',
//保证 v-bind:key 和 v-for 指令成对出现
'vue/require-v-for-key': 'error',
// 检查默认的prop值是否有效
'vue/require-valid-default-prop': 'error',
// 保证computed属性中有return语句
'vue/return-in-computed-property': 'error',
// 强制校验 template 根节点
'vue/valid-template-root': 'error',
// 强制校验 v-bind 指令
'vue/valid-v-bind': 'error',
// 强制校验 v-cloak 指令
'vue/valid-v-cloak': 'error',
// 强制校验 v-else-if 指令
'vue/valid-v-else-if': 'error',
// 强制校验 v-else 指令
'vue/valid-v-else': 'error',
// 强制校验 v-for 指令
'vue/valid-v-for': 'error',
// 强制校验 v-html 指令
'vue/valid-v-html': 'error',
// 强制校验 v-if 指令
'vue/valid-v-if': 'error',
// 强制校验 v-model 指令
'vue/valid-v-model': 'error',
// 强制校验 v-on 指令
'vue/valid-v-on': 'error',
// 强制校验 v-once 指令
'vue/valid-v-once': 'error',
// 强制校验 v-pre 指令
'vue/valid-v-pre': 'error',
// 强制校验 v-show 指令
'vue/valid-v-show': 'error',
// 强制校验 v-text 指令
'vue/valid-v-text': 'error',
'vue/comment-directive': 0
}
}

+ 97
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/.gitignore Целия файл

@@ -0,0 +1,97 @@

# Created by https://www.gitignore.io/api/node
# Edit at https://www.gitignore.io/?templates=node

### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
# node_modules/
# jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# End of https://www.gitignore.io/api/node

unpackage

+ 49
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/App.vue Целия файл

@@ -0,0 +1,49 @@
<script>
export default {
// 小程序:onLaunch 仅启动时调用一次
// H5/App:onLaunch 打开网页/用户点刷新/代码热更新时均会调用;
// 考虑到用户刷新网页时会丢失全局数据、页面栈、页面数据等,因此直接跳回首页即可

async onLaunch(param) {
// #ifdef H5 || APP-VUE
// H5 刷新时获取当前页面路径
const pagePath = '/' + param.path

// 如果 H5 刷新后访问的不是首页/登录页/注册页,直接跳转回首页
if (!['/pages/login', '/pages/home', '/pages/signup'].includes(pagePath)) {
this.$nextTick(() => {
this.TAB_TO('/pages/home')
return
})
}
// #endif

// #ifdef MP-WEIXIN
// 小程序端,处理更新 (支付宝/钉钉暂不支持)
const updateManager = uni.getUpdateManager()
updateManager.onUpdateReady(() => {
this.HIDE_LOADING()
uni.showModal({
title: '更新提示',
content: '小程序新版本已准备好,是否更新应用?',
success: ({ confirm }) => {
if (confirm) {
updateManager.applyUpdate()
}
}
})
})
// #endif
}
}
</script>

<style lang="less">
// 现在 CSS 必须在 <style> 中引入,参考:https://ask.dcloud.net.cn/question/86907
// 后续 uni-app 升级后可能会支持直接 import 样式,到时候可以省略下行代码
@import '~@/components/learun-mpui/styles/index.css';

page {
background-color: #f3f3f3;
}
</style>

+ 170
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/README.md Целия файл

@@ -0,0 +1,170 @@
# 力软框架移动端 · 二次开发手册
修订日期:2020年 7月 20日
App 版本号:2.2.0
HBuilderX 版本不低于:2.7.9

(本文档只是对初次使用框架做简单的介绍,对于框架 API、组件、平台差异等,您可查看 **/docs** 目录下的详细说明文档)
(本文档格式为 markdown,您可以使用浏览器等工具来打开,获取更佳的阅读体验)
# 文档目录
- **/README.md** : 二次开发手册(本文档);
- **/docs/api.md** : 框架 API 文档;
- **/docs/platform.md**: 各个平台差异介绍;
- **/docs/component.md**: 移动端组件文档。
# 开发前的准备
力软框架移动端使用 uni-app 技术来开发。
使用 uni-app,您只需编写一套 Vue 代码,就能编译输出成手机 App、各小程序平台、H5 网页端等多个平台的成品。
力软框架移动端支持 H5、安卓 App/iOS App、微信/支付宝/钉钉小程序。
需要下载安装以下工具:
- 【必装工具】:[HBuilderX](https://www.dcloud.io/hbuilderx.html) ,uni-app 的官方开发工具,**注意请下载App开发版**
- 开发微信小程序:[微信小程序开发工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html)
- 开发支付宝/钉钉小程序:[支付宝小程序开发工具](https://opendocs.alipay.com/mini/ide/download)
- 开发 iOS /安卓 App / H5:HBuilderX 内置网页版调试工具,也可以连接手机真机调试,无需安装其他软件。

**注意:**请使用最新版 HBuilderX。如果您已安装,可以点击菜单栏的「帮助」>「检查更新」来升级到最新版。请确保您的 HBuilderX 版本不低于本文档开头所标注的HBuilderX 开发工具版本。
HBuilderX 下载后是个压缩包,将它解压到硬盘上,运行 HBuilderX.exe 即可启动;
小程序开发工具则按照指引,依次点击下一步即可完成安装。
**本文档只负责提示您开发过程中易错点,以及对力软框架提供的 API、工具类等给出说明,不是使用 uni-app 开发移动应用的教程。**
学习 uni-app 移动端开发,或是在以上工具的安装、使用过程中遇到问题时,可以参考以下文档:
- [uni-app 介绍与文档](https://uniapp.dcloud.io/)
- [微信小程序官方开发指南](https://developers.weixin.qq.com/miniprogram/dev/framework/)
- [支付宝小程序官方开发指南](https://opendocs.alipay.com/mini/developer)
- [钉钉小程序官方开发指南](https://ding-doc.dingtalk.com/doc#/dev/)
- [Vue.js 官网文档](https://cn.vuejs.org/index.html)
# 起步
启动 HBuilderX,点击左上角菜单:「文件」>「打开目录」,然后选择本项目的文件夹,即可打开本项目。
安装后**首次**运行 HBuilderX 时,需要的操作:
- 【必做】 点击左上角菜单>「工具」>「插件安装」,在打开的插件列表中安装「less 编译」;
- 【必做】 找到 /config.js 文件,修改公司名称、后台接口地址等相关配置;
- 【微信小程序开发】 点击左上角菜单>「工具」>「设置」>点击「运行配置」页,找到「微信开发者工具路径」,在这里请正确设置微信开发者工具的安装路径;
- 【支付宝/钉钉小程序开发】 点击左上角菜单>「工具」>「设置」>点击「运行配置」页,找到「支付宝开发者工具路径」,在这里请正确设置支付宝开发者工具的安装路径,注意支付宝小程序的路径要设置到 .exe 文件,不能直接选择目录,支付宝和钉钉共用同一个工具路径。
## 小程序主体注册
如果是开发小程序,您需要在对应的平台来申请注册自己的小程序主体账号,然后提交小程序名称、图标、类目,并申请小程序 AppID;
后续提交审核、发布小程序,均需要在小程序公众平台上进行操作。
请先申请小程序 AppID:
[微信小程序后台](https://mp.weixin.qq.com/)
[钉钉小程序后台](https://open-dev.dingtalk.com/)
[支付宝小程序后台](https://openhome.alipay.com/)

申请了小程序 AppID,打开项目根目录下的 manifest.json,请在其中「微信/支付宝小程序配置」页面填入 AppID。
## 项目目录介绍
打开本项目后,可以在左侧的浏览项目目录和文件,点击文件即可进行编辑。
本项目的目录文件介绍:
(目录):
- /docs: 【重要】 详细文档
- /pages: 【重要】 所有页面均放在此目录下
- /components: 使用的组件(力软自研组件、其他第三方组件等都在此)
- /static: 图片、音视频等静态资源目录
- /common: 多个页面通用的 CSS、JS 代码
- /res: 编译安卓/iOS 手机 App 用到的图标和启动封面图
- /node_modules: 使用到的 npm 库
- /unpackage (从未运行过的项目没有此文件夹): 运行项目时会自动把编译生成的代码放在此目录下
(文件):
- App.vue: 【重要】 运行时的全局 Vue 对象,可以用来设置应用生命周期钩子、导入全局样式等
- config.js: 【重要】 多个页面的配置文件,可以控制页面显示的内容和方式
- main.js: 【重要】 项目启动时的入口 JS 文件
- pages.json: 【重要】 所有页面的路径、样式的配置、引入的组件和目录等配置
- manifest.json:【重要】 uni-app 相关的设置,在这里设置应用名称、微信 AppID 等
- package.json: 用到的 npm 库的名称和版本、uni-app 编译到钉钉小程序的编译配置等
- uni.scss: uni-app 内置的样式
- index.html: 编译到 H5 端时的页面模板
## uni-app 页面开发
uni-app 中,每一个页面都是一个 .vue 文件,它包含了模板、JS、CSS 代码;
文件中可以引入其他 JS、CSS,引入安装好的 npm 库,也可以引入图片视频音乐等媒体。
**需要注意的是,所有页面必须放置于 /pages 目录中, 且每个页面都必须在 /pages.json 中添加一条对应的记录,否则页面无法导航。**
**当您向力软框架中添加新的页面时,请及时修改 /pages.json 文件,确保所有页面都登记在该文件中。**
例如:
某个小程序页面,它的目录为:
`/pages/mypage/index.vue`
那么在 /pages.json 里,需要在 pages 列表中添加一条记录:
`{ "path": "pages/mypage/index", "style": { "navigationBarTitleText": "我是页面的标题" } }`
# 使用力软代码生成器
请先确保代码生成器的版本和 App 版本相同,不同版本可能会引发错误。
以下是使用力软代码生成器创建页面的流程:
- 【自动】 使用力软代码生成器发布功能时,会在 /pages 目录下生成相应的页面文件;
- 【手动】 您需要在 /pages.json 页面配置文件中,手动把这些页面添加到 pages 字段中。
例如:
假设您使用代码生成器时,设置的输出目录名为 LR_Codedemo,功能类名为 wxtest,那么代码生成器会自动生成两个文件:
`/pages/LR_Codedemo/wxtest/list.vue` (移动页面主页面);
`/pages/LR_Codedemo/wxtest/single.vue` (移动表单页)。
然后,需要配置 /pages.json 文件:
在 pages 数组里添加以下两条(navigationStyle 是页面的标题,可以自行更改):
```
{
"pages": [
{ "path": "pages/LR_Codedemo/wxtest/list", "style": { "navigationStyle": "移动页面主页面标题" } },
{ "path": "pages/LR_Codedemo/wxtest/single", "style": { "navigationStyle": "移动表单页标题" } },
// ...其他页面
]
}
```
这样就完成了新自定义页面的生成。
按照小程序和 uni-app 的规范, 每个页面均需要单独建立一个文件夹;但是这样将使得工程目录过于复杂,因此力软框架中,每个页面使用单个 .vue 文件,和传统 Web 开发相同,请知悉。
也因为以上原因,新建页面时,请不要勾选「创建同名目录」这一项。
-----
# 二次开发指南
**注意:** 小程序端不同于常规 Web 端,它没有浏览器上的 DOM 、BOM,也就是说没有 document、window 对象,因此也无法使用 jQuery 等工具;但是有同类的工具和 API 可以替代。
**注意:** 本框架中的每个页面为单个 .vue 文件,无需按照小程序或 uni-app 的要求,为每个页面单独建一个文件夹,因为这样会导致目录结构过于复杂。
每个 .vue 文件的格式如下:
``` html
<template>
<!-- 页面模板内容 -->
</template>

<script>
// 页面 JS 代码 ...
export default {
// ...
}
</script>

<style lang="less" scoped>
/* 页面样式 */
</style>
```
## 页面模板相关注意事项:
- 模板遵循 Vue 模板写法,[uni-app 页面模板文档](https://uniapp.dcloud.io/component/README);
- 网页中的 `<div>`、`<span>`、`<img>` 等标签,在 uni-app 中需要分别写成 `<view>`、`<text>`、`<image>` 等;
- 监听事件时,`@click` 需要改为 `@tap` 等对应的移动端事件 ;
- (推荐)您也可以维持 HTML 中的写法不做修改,依然写成 `<div>` 或是 `@click`,uni-app 框架会在编译时自动处理,包括 CSS 选择器名也会自动处理;
- 图片、视频、音频等媒体资源必须放置于 /static 目录下,引入示例:`src="/static/.../a.png"`;
- 为了跨端兼容性,请避免使用 .sync 绑定属性。
## 页面 JS 代码注意事项:
- JS 代码遵循 Vue 开发写法,[uni-app 使用 Vue.js 时的注意事项](https://uniapp.dcloud.io/use);
- 生命周期和常规 Vue 网页不同,uni-app 中存在「页面生命周期」、「应用生命周期」两个概念,请阅读文档以了解;
- 注意对空数据的处理,空数据在 uni-app 的不同平台中可能显示为 null 字符串或 undefined 字符串,而不是为空,这一点不同于网页版 Vue 开发,请注意处理;
- 在使用 `import` 引入其他 JS 文件时,路径中可用 `@` 来代表项目根目录,例如 `import xxx from '@/xxx/xxx.js'`,但是引入资源文件(图片、样式表)时,需要使用 `~@` 来代表根目录。
## 页面样式注意事项:
- uni-app 中存在一种特殊的 CSS 单位 rpx,也叫 upx(upx 已经废弃,不要使用);具体来讲,750rpx 即等于设备屏幕宽度,例如 iPhone 6 手机屏幕宽度为 375px,因此在 iPhone 6 上的 750rpx = 375px,比例为 2:1;设备屏幕越大则 rpx 对 px 的比例越接近 1:1;
- 不同平台上 rem、em 尺寸可能表现不同(尤其是 rem),因此请慎用 rem、em 单位;
- pages.json 中无法设置页面背景色,需要使用 `page { }` 样式来设置(等同于 H5 中的 `body { }`);pages.json 中的 `backgroundColor` 相关的指的是 iOS 系统上屏幕上下滚动露出的回弹区域背景色,并不是直接设置页面内容的背景色;
- 使用 LESS 样式时候写成 `<style lang="less" scoped>`,注意多了 `lang="less"` 的属性;样式标签的 `scoped` 属性表示这些样式仅对当前页面,绝不会影响到其他页面;使用 LESS 开发时,您需要在 HBuilder X 中安装对应的插件;(也可以使用 SASS 等,方法同)
- 复用的样式代码可以放置在 /common/css 中,引入时的路径: `~@/common/css/.../a.css` 需要写成这种形式;
- 在 /App.vue 中的样式,适用于项目全局;
- 部分页面样式(例如导航栏样式、标题样式等不属于页面内容的样式),需要在 /pages.json 中配置,参考[uni-app pages.json 配置文档](https://uniapp.dcloud.io/collocation/pages),uni 也可能提供了 API 用来动态改变;
- 对于屏幕类型特殊的手机,如 iPhone X,由于底部两边圆角过大且底部有虚拟 Home 键,页面可能会被虚拟 Home 键挡住,使用 CSS 变量 `env(safe-area-inset-bottom)` 可以获取屏幕显示安全区距离屏幕底部的距离。

+ 65
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/config.default.js Целия файл

@@ -0,0 +1,65 @@
// 本页是框架的默认配置页
// 请不要修改本页的任何内容

// 如需定制相关配置项,请修改项目根目录下的 config.js 文件内的相应条目

export default {
// 登录页显示的公司名称
"company": "(未设置公司名)",
// App 版本号
"appVersion": "0.0.1",
// 是否允许用户注册
"enableSignUp": true,
// 请求数据的接口地址;可以配置多个,开发环境下登录页会出现选择菜单供您选择
"apiHost": [
"https://www.learun.cn/admsapi/learun/adms",
"http://localhost:31173/learun/adms"
],
// 开发环境下自动填充登录账号密码,与接口地址一一对应,只在开发环境下显示
"devAccount": [
{ username: "18702193424", password: "123456" },
{ username: "System", password: "0000" },
],
// 开发环境使用的接口地址(数组索引)
"devApiHostIndex": 0,
// 生产环境使用的接口地址(数组索引)
"prodApiHostIndex": 0,
// 额外添加的全局变量,必须在此处注册才能使用
"globalVariables": [],

// 小程序绑定登录等配置(login=登录,bind=绑定,unbind=解绑)
"miniProgramAccount": {
// 微信小程序
"weixin": ["login", "bind", "unbind"],
// 支付宝小程序
"alipay": ["login", "bind", "unbind"],
// 钉钉小程序
"dingtalk": ["login", "bind", "unbind"]
},

// 页面相关配置
"pageConfig": {
// 全局设置是否使用圆形头像
"roundAvatar": false,

// 「消息」页
"msg": {
// 周期轮询消息的时间间隔,单位是毫秒
"fetchMsg": 3000
},

// 「通讯录」页
"contact": {
// 是否显示(分)公司、部门、职员标签
"tag": true,
// 是否在职员这一级也显示标签
"staffTag": false
},

// 「聊天消息」页
"chat": {
// 周期轮询消息的时间间隔,单位是毫秒
"fetchMsg": 1500
}
}
}

+ 24
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/css/customlist.less Целия файл

@@ -0,0 +1,24 @@
.customlist-item {
padding: 20rpx 20rpx;
border-bottom: 1rpx solid #ddd;
background: #ffffff;
position: relative;
color: #8f8f94;

&:first-child {
border-top: 1rpx solid #ddd;
}

.customlist-item-field {
padding-top: 4px;

&:first-child {
padding-top: 0;
}

.customlist-item-field-title {
white-space: nowrap;
color: #333333;
}
}
}

+ 75
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/css/sidepage.less Целия файл

@@ -0,0 +1,75 @@
page {
width: 100vw;
overflow: hidden;
}

.mainpage {
position: fixed;
width: 100vw;
height: calc(100% - var(--window-top));
left: 0;
background-color: #f1f1f1;
transition: all 0.4s;

&.show {
left: 85vw;
box-shadow: 0 0 60upx rgba(0, 0, 0, 0.2);
transform: scale(0.9, 0.9);
transform-origin: 0;
}
}

.sidepage {
position: absolute;
width: 85vw;
height: calc(100% - var(--window-top));
left: 0;
top: 0;
transform: scale(0.9, 0.9) translateX(-100%);
opacity: 0;
pointer-events: none;
transition: all 0.4s;
padding: 50upx 0;

.side-title {
font-size: 1.2em;
margin-bottom: 20rpx;
margin-top: 40rpx;

&:first-of-type {
margin-top: 0;
}
}

&.show {
transform: scale(1, 1) translateX(0%);
opacity: 1;
pointer-events: all;
}
}

.sideclose {
position: absolute;
width: 40vw;
height: calc(100% - var(--window-top));
right: 0;
top: 0;
color: transparent;
padding-bottom: 30upx;
display: flex;
align-items: flex-end;
justify-content: center;
background-image: linear-gradient(90deg, rgba(0, 0, 0, 0.01), rgba(0, 0, 0, 0.6));
letter-spacing: 5px;
font-size: 50upx;
opacity: 0;
pointer-events: none;
transition: all 0.4s;

&.show {
opacity: 1;
pointer-events: all;
width: 15vw;
color: #fff;
}
}

+ 296
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/customform.js Целия файл

@@ -0,0 +1,296 @@
import get from 'lodash/get'
import moment from 'moment'

/**
* 表单数据处理相关方法
* (配合 <l-customform> 使用,注意本工具类不包含拉取表单数据的代码)
*
* 提供以下工具方法:
*
* 【新建表单时使用】:
* async getDefaultData(schemeItem, { processId })
* 获取单个表单项的默认数据
*
* 【打开一个表单时使用】:
* async getSourceData(schemeItem)
* 获取表单中的选单数据 / 获取单条表单项的选单数据
*
* async convertToFormValue(schemeItem, val)
* 将从 API 拉取的表单数据格式化规范化
*
* 【提交表单时使用】:
* async convertToPostData(schemeItem, val, formValue, scheme)
* 将本地表单数据格式化为提交时的格式
*/

export default {
methods: {
/**
* 获取一个 scheme 表单项的源数据 (加载表单时使用)
* 参数: 单个 schemeItem
*
* radio、select、checkbox、layer 这四种表单项,需要加载额外的选单数据
* 选单数据有两种获取方式:
* 1、来自数据字典:
* 数据字典在 this.GET_GLOBAL('dataDictionary')
* 表单使用的字段在 schemeItem.itemCode
* 选单数据中的 text 字段作为显示, value 字段作为值
*
* 2、来自数据源:
* 将 schemeItem.dataSourceId 按符号「,」逗号分割为数组,分割为: [code, displayField, valueField]
* 数据源需要请求 API 来获取,请求需要带上数据源的编号 code
* displayField、valueField 分别为展示字段和值绑定字段
*
* 选单数据有两种格式:
* 1、对于 radio、select、checkbox 来说:
* 只需要一个数组,数组形如: [{ text: '选项文字', value: '选项值' }, ...]
* 将获取的数据绑定到组件的 range 属性上即可
* 全局数据中默认是对象形式,使用 Object.values() 转化即可
*
* 2、对于 layer 来说:
* 返回一个对象,形如 { source, layerData, selfField }
* source: 为弹层中列出的数据,是一个数组
* layerData: 需要在弹层窗口中展示的字段及标题文字,形如: [{ name:'要展示的字段名', label:'标题文字' }]
* selfField: 该表单值绑定哪个字段,默认为绑定到自身的字段
*/
async getSourceData(schemeItem) {
if (['radio', 'select', 'checkbox'].includes(schemeItem.type)) {
// radio select checkbox 三种情况
if (!schemeItem.dataSource || Number(schemeItem.dataSource) === 0) {
// dataSource 为 0,使用 clientData
return Object
.values(this.GET_GLOBAL('dataDictionary')[schemeItem.itemCode])
.map(t => ({ value: t.value, text: t.text }))

} else {
// dataSource 不为 0,使用数据源,需要请求接口,并且提取出显示字段和实际字段
const [code, displayField = schemeItem.showField, valueField = schemeItem.saveField] = schemeItem.dataSourceId
.split(',')
const sourceData = await this.FETCH_DATASOURCE(code)
if (!sourceData) { return [] }

return sourceData.data.map(t => ({ text: t[displayField], value: t[valueField] }))
}

} else if (['layer'].includes(schemeItem.type)) {
// layer 需要更多属性
if (!schemeItem.dataSource || Number(schemeItem.dataSource) === 0) {
// dataSource 为 0,使用 clientData
// clientData 对象转数组后,隐含 key:item.text 和 value:item.value 的关系
const [keyItem, valueItem] = schemeItem.layerData
const source = Object
.values(this.GET_GLOBAL('dataDictionary')[schemeItem.itemCode])
.map(t => ({ value: t.value, text: t.text }))

return {
source,
layerData: [
{ name: 'text', label: keyItem.label || '', value: keyItem.value || '' },
{ name: 'value', label: valueItem.label || '', value: valueItem.value || '' }
]
}
} else {
// dataSource 不为 0,使用数据源,需要请求接口,并且提取出显示字段和实际字段
const [code] = schemeItem.dataSourceId.split(',')
const sourceData = await this.FETCH_DATASOURCE(code)
if (!sourceData) { return [] }

const source = sourceData.data

return { source, layerData: schemeItem.layerData.filter(t => (!t.hide) && (t.value || t.label)) }
}
}

return []
},

/**
* 获取一个 scheme 表单项的默认值 (用户新建表单时使用,或是编辑草稿)
* 参数: 单个 schemeItem , { processId }
*
* 每种类别的表单项分别获取的默认值:
*
* currentInfo: 根据类别取当前用户/部门/公司/时间日期
* datetime: 根据 dfValue 字段表示昨天/今天/明天,格式化为字符串
* radio、select: 有 dfValue 则使用,否则取第一条
* checkbox: 有 dfValue 则使用,否则为空数组
* encode: 根据 rulecode 请求表单编码
* upload: 空数组
* guid: 赋值第二个参数中的 processId,但是如果在子表格中,赋空字符串
* girdtable: 递归所有表格项 scheme 依次为它们生成默认值
* datetimerange: 字符串 0
*/
async getDefaultData(item, prop) {
const { processId } = prop
switch (item.type) {
case 'currentInfo':
switch (item.dataType) {
case 'user':
return this.GET_GLOBAL('loginUser').userId
case 'department':
return this.GET_GLOBAL('loginUser').departmentId
case 'company':
return this.GET_GLOBAL('loginUser').companyId
case 'time':
return moment().format('YYYY-MM-DD HH:mm:ss')
default:
return ''
}

case 'datetime':
const datetimeFormat = item.table ?
(Number(item.dateformat) === 0 ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss') :
(item.datetime === 'datetime' ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD')
const today = moment()
const dfDatetime = [
today.subtract(1, 'day'),
today,
today.add(1, 'day')
][Number(item.dfvalue)] || today

return dfDatetime.format(datetimeFormat) || ''

case 'radio':
case 'select':
const radioItem = item.__sourceData__.find(t => t.value === item.dfvalue) || item.__sourceData__[0]
return item.type === 'radio' ? radioItem.value : ''

case 'checkbox':
if (!item.dfvalue) {
return []
}
return item.dfvalue.split(',').filter(t => item.__sourceData__.find(s => s.value === t))

case 'encode':
const result = await this.FETCH_ENCODE(item.rulecode)
return result

case 'upload':
return []

case 'guid':
return item.table ? processId : ''

case 'girdtable':
const tableItemObj = {}
for (const fieldItem of item.fieldsData) {
tableItemObj[fieldItem.field] = await this.getDefaultData(fieldItem, prop)
}
return this.COPY(tableItemObj)

case 'datetimerange':
return '0'

default:
return item.dfvalue || ''
}
},

/**
* 将单条 formData 值转化为 formValue 值 (拉取表单数据时使用)
* 参数: 单个 schemeItem , 数据值
*
* 具体执行逻辑:
* radio、select: 剔除无效值
* checkbox: 分割成数组并剔除无效值
* upload: 分割成数组,拉取其中所有文件的信息
* datetime: 按照时间日期格式进行格式化字符串
* 其他类型: 保留原值
*/
async convertToFormValue(item, val) {
switch (item.type) {
case 'upload':
if (!val) { return [] }
const uidList = val.split(',')
const fileList = []

for (const uid of uidList || []) {
const fileInfo = await this.FETCH_FILEINFO(uid)
if (!fileInfo) { continue }

const fileType = fileInfo.F_FileType
const fileSize = fileInfo.F_FileSize

const path = this.API + '/annexes/wxdown?' + this.URL_QUERY(uid, true)

fileList.push({ path, type: fileType, uid, size: fileSize })
}
return fileList

case 'select':
case 'radio':
if (!val || !item.__sourceData__.map(t => t.value).includes(val)) {
return ''
}
return val

case 'checkbox':
const validValue = item.__sourceData__.map(t => t.value)
const checkboxVal = val.split(',') || []
return checkboxVal.filter(t => validValue.includes(t))

case 'datetime':
if (!val) {
return ''
}
return moment(val).format(
Number(item.dateformat) === 0 || item.datetime === 'date' ?
'YYYY-MM-DD' :
'YYYY-MM-DD HH:mm:ss'
)

default:
return val || ''
}
},

/**
* 将一个 formValue 值转化为 post 提交值(提交表单数据时使用)
* 参数: 单个 schemeItem , 表单项值 , 所有 formValue , scheme
*
* 具体执行逻辑:
* checkbox: 将数组使用符号「,」逗号拼接成字符串
* datetimerange: 获取开始日期、结束日期,计算差值天数并保留整数
* datetime: 格式化为完整时间日期字符串
* upload: 依次上传文件,将返回的文件 ID 使用符号「,」逗号拼接成字符串
* 其他类型: 保留原值
*/
async convertToPostData(item, val, formValue, scheme) {
switch (item.type) {
case 'checkbox':
return val ? val.join(',') : ''

case 'datetimerange':
const startTime = get(formValue, scheme.find(t => t.id === item.startTime).__valuePath__, null)
const endTime = get(formValue, scheme.find(t => t.id === item.endTime).__valuePath__, null)
if (!startTime || !endTime || moment(endTime).isBefore(startTime)) {
return ''
} else {
return moment.duration(moment(endTime).diff(moment(startTime))).asDays().toFixed(0)
}

case 'datetime':
return val ? moment(val).format('YYYY-MM-DD HH:mm:ss') : ''

case 'upload':
const uploadUid = []
for (const { path, uid } of val) {
if (uid) {
uploadUid.push(uid)
continue
}

const fileId = await this.HTTP_UPLOAD(path)
if (fileId) {
uploadUid.push(fileId)
}
}

return uploadUid.join(',')

default:
return val || ''
}
}
}
}

+ 313
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/custompage.js Целия файл

@@ -0,0 +1,313 @@
import get from 'lodash/get'
import omit from 'lodash/omit'
import moment from 'moment'

/**
* 用于代码生成器页面,加载表单数据使用
*
* 提供以下工具方法:
*
* 【新建表单时使用】:
* async getDefaultForm()
* async getDefaultValue(path, scheme)
* 根据表单 scheme 获取初始化的空表单数据 / 获取单条表单项的初始数据
*
* 【提交表单时使用】:
* async getPostData()
* async convertToPostData()
* 生成 POST 提交用的表单数据 / 获取单条表单项的提交数据
*
* verifyForm()
* 验证表单数据,返回一个输入错误信息的数组
*
* 【打开一个表单时使用】:
* async formatFormData(formData)
* async convertToFormValue(scheme, val, dataSource)
* 将拉取的表单值转化为表单数据 / 将单条表单项的表单值转化为表单数据
*
*/
export default {
methods: {
// 获取表单默认值
async getDefaultForm() {
const result = {}
for (const [tableName, tableItem] of Object.entries(this.scheme)) {
const itemData = {}
for (const [fieldName, scheme] of Object.entries(tableItem)) {
if (fieldName !== '__GIRDTABLE__') {
itemData[fieldName] = await this.getDefaultValue(`${tableName}.${fieldName}`, scheme)
}
}
result[tableName] = '__GIRDTABLE__' in tableItem ? [itemData] : itemData
}

return result
},

// 获取单条表单项的默认值
async getDefaultValue(path, schemeItem) {
switch (schemeItem.type) {
case 'keyValue':
return this.processId

case 'currentInfo':
switch (schemeItem.dataType) {
case 'user':
return this.GET_GLOBAL('loginUser').userId
case 'department':
return this.GET_GLOBAL('loginUser').departmentId
case 'company':
return this.GET_GLOBAL('loginUser').companyId
case 'time':
return moment().format('YYYY-MM-DD HH:mm:ss')
default:
return ''
}

case 'datetime':
const datetimeFormat = (Number(schemeItem.dateformat) === 0 ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss')
const today = moment()
const dfDatetime = [
today.subtract(1, 'day'),
today,
today.add(1, 'day')
][Number(schemeItem.dfvalue)] || today

return dfDatetime.format(datetimeFormat) || ''

case 'radio':
case 'select':
const radioItem = get(this.dataSource, path).find(t => t.value === schemeItem.dfvalue) ||
get(this.dataSource, path)[0]
return schemeItem.type === 'radio' ? radioItem.value : ''

case 'checkbox':
if (!schemeItem.dfvalue) { return [] }
return schemeItem.dfvalue.split(',').filter(t => get(this.dataSource, path, []).find(s => s.value === t))

case 'encode':
if (!schemeItem.rulecode) { return '' }
const result = await this.FETCH_ENCODE(schemeItem.rulecode)
return result || ''

case 'upload':
return []

case 'guid':
return this.GUID('-')

default:
return schemeItem.dfvalue || ''
}
},

// 验证表单项输入是否正确,返回一个包含所有错误信息的数组
verifyForm() {
const result = []
Object.entries(this.scheme).forEach(([tableName, tableItem]) => {
if ('__GIRDTABLE__' in tableItem) {
this.getValue(tableName).forEach((tableValue, index) => {
Object.entries(tableItem).forEach(([fieldName, scheme]) => {
if (fieldName === '__GIRDTABLE__' || !scheme.verify) { return }

const val = tableValue[fieldName]
const verifyResult = this.verify[scheme.verify](val)

if (verifyResult !== true) {
result.push(`[表格${tableItem.__GIRDTABLE__}第${index}行${scheme.title}列]: ${verifyResult}`)
}
})
})
} else {
Object.entries(tableItem).forEach(([fieldName, scheme]) => {
if (!scheme.verify) { return }

const val = this.getValue(`${tableName}.${fieldName}`)
const verifyResult = this.verify[scheme.verify](val)

if (verifyResult !== true) {
result.push(`[${scheme.title}]: ${verifyResult}`)
}
})
}
})

return result
},

// 获取要提交的表单数据(提交时使用)
async getPostData(keyValue) {
const result = {}

for (const [tableName, tableItem] of Object.entries(this.scheme)) {
if ('__GIRDTABLE__' in tableItem) {
// 从表
const tableArray = []
const tableData = this.current[tableName]
for (let index = 0; index < tableData.length; ++index) {
const tableValue = tableData[index]
const tableObj = {}
for (const [fieldName, scheme] of Object.entries(tableItem)) {
if (fieldName === '__GIRDTABLE__') { continue }
tableObj[fieldName] = await this.convertToPostData(scheme, tableValue[fieldName])
}
tableArray.push(tableObj)
}
result[`str${tableName}Entity`] = JSON.stringify(tableArray)

} else {
// 主表
const strEntity = {}
for (const [fieldName, scheme] of Object.entries(tableItem)) {
strEntity[fieldName] = await this.convertToPostData(scheme, this.current[tableName][fieldName])
}
result['strEntity'] = JSON.stringify(strEntity)
}
}

if (keyValue) {
result.keyValue = keyValue
}

return result
},

// 将单项表单数据转为 post 数据(提交时使用)
async convertToPostData(scheme, val) {
switch (scheme.type) {
case 'checkbox':
return val ? val.join(',') : ''

case 'datetimerange':
const startTime = this.getValue(scheme.startTime)
const endTime = this.getValue(scheme.endTime)
if (!startTime || !endTime || moment(endTime).isBefore(startTime)) {
return ''
} else {
return moment.duration(moment(endTime).diff(moment(startTime))).asDays().toFixed(0)
}

case 'datetime':
return val ? moment(val).format('YYYY-MM-DD HH:mm') : ''

case 'upload':
const uploadUid = []
for (const { path, uid } of val) {
if (uid) {
uploadUid.push(uid)
continue
}

const fileId = await this.HTTP_UPLOAD(path)
if (fileId) {
uploadUid.push(fileId)
}
}

return uploadUid.join(',')

default:
return val || ''
}
},

// 格式化处理表单数据(拉取时使用)
async formatFormData(formData) {
const data = omit(formData, 'keyValue')
for (const [tableName, schemeItem] of Object.entries(this.scheme)) {
if ('__GIRDTABLE__' in schemeItem) {
if (!data[tableName] || data[tableName].length <= 0) { data[tableName] = [{}] }
const tableData = data[tableName]

for (let index = 0; index < tableData.length; ++index) {
const tableValue = tableData[index]

for (const [fieldName, scheme] of Object.entries(schemeItem)) {
if (fieldName === '__GIRDTABLE__') { continue }
const dataSource = get(this.dataSource, `${tableName}.${fieldName}`)
tableValue[fieldName] = await this.convertToFormValue(scheme, tableValue[fieldName], dataSource)
}
}
} else {
for (const [fieldName, scheme] of Object.entries(schemeItem)) {
const dataSource = get(this.dataSource, `${tableName}.${fieldName}`)
data[tableName][fieldName] = await this.convertToFormValue(scheme, data[tableName][fieldName], dataSource)
}
}
}

return data
},

// 将单项表单数据格式化(拉取时使用)
async convertToFormValue(scheme, val, dataSource) {
switch (scheme.type) {
case 'upload':
if (!val) { return [] }
const uidList = val.split(',')
const fileList = []

for (const uid of uidList || []) {
const fileInfo = await this.FETCH_FILEINFO(uid)
if (!fileInfo) { continue }

const fileType = fileInfo.F_FileType
const fileSize = fileInfo.F_FileSize

const path = this.API + '/annexes/wxdown?' + this.URL_QUERY(uid, true)

fileList.push({ path, type: fileType, uid, size: fileSize })
}
return fileList

case 'radio':
case 'select':
if (!val || !dataSource.map(t => t.value).includes(String(val))) { return '' }
return String(val)

case 'checkbox':
if (!val) { return [] }
const validValue = dataSource.map(t => t.value)
const checkboxVal = val.split(',') || []
return checkboxVal.filter(t => validValue.includes(t))

case 'datetime':
if (!val) { return '' }
return moment(val).format(
Number(scheme.dateformat) === 0 || scheme.datetime === 'date' ?
'YYYY-MM-DD' :
'YYYY-MM-DD HH:mm:ss'
)

default:
return val === null || val === undefined ? '' : val
}
}
},

computed: {
// 验证函数
verify() {
return {
NotNull: t => t.length > 0 || '不能为空',
Num: t => !isNaN(t) || '须输入数值',
NumOrNull: t => t.length <= 0 || !isNaN(t) || '须留空或输入数值',
Email: t => /^[a-zA-Z0-9-_.]+@[a-zA-Z0-9-_]+.[a-zA-Z0-9]+$/.test(t) || '须符合Email格式',
EmailOrNull: t => t.length <= 0 || /^[a-zA-Z0-9-_.]+@[a-zA-Z0-9-_]+.[a-zA-Z0-9]+$/.test(t) ||
'须留空或符合Email格式',
EnglishStr: t => /^[a-zA-Z]*$/.test(t) || '须由英文字母组成',
EnglishStrOrNull: t => t.length <= 0 || /^[a-zA-Z]*$/.test(t) || '须留空或由英文字母组成',
Phone: t => /^[+0-9- ]*$/.test(t) || '须符合电话号码格式',
PhoneOrNull: t => t.length <= 0 || /^[+0-9- ]*$/.test(t) || '须留空或符合电话号码格式',
Fax: t => /^[+0-9- ]*$/.test(t) || '须符合传真号码格式',
Mobile: t => /^1[0-9]{10}$/.test(t) || '须符合手机号码格式',
MobileOrPhone: t => /^[+0-9- ]*$/.test(t) || /^1[0-9]{10}$/.test(t) || '须符合电话或手机号码格式',
MobileOrNull: t => t.length <= 0 || /^1[0-9]{10}$/.test(t) || '须留空或符合手机号码格式',
MobileOrPhoneOrNull: t => t.length <= 0 || /^1[0-9]{10}$/.test(t) || /^[+0-9- ]*$/.test(t) ||
'须留空或符合手机/电话号码格式',
Uri: t => /^[a-zA-z]+:\/\/[^\s]*$/.test(t) || '须符合网址Url格式',
UriOrNull: t => t.length <= 0 || /^[a-zA-z]+:\/\/[^\s]*$/.test(t) || '须留空或符合网址Url格式'
}
}
}
}

+ 683
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/md5.js Целия файл

@@ -0,0 +1,683 @@
/**
* [js-md5]{@link https://github.com/emn178/js-md5}
*
* @namespace md5
* @version 0.7.3
* @author Chen, Yi-Cyuan [emn178@gmail.com]
* @copyright Chen, Yi-Cyuan 2014-2017
* @license MIT
*/
(function () {
'use strict';

var ERROR = 'input is invalid type';
var WINDOW = typeof window === 'object';
var root = WINDOW ? window : {};
if (root.JS_MD5_NO_WINDOW) {
WINDOW = false;
}
var WEB_WORKER = !WINDOW && typeof self === 'object';
var NODE_JS = !root.JS_MD5_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node;
if (NODE_JS) {
root = global;
} else if (WEB_WORKER) {
root = self;
}
var COMMON_JS = !root.JS_MD5_NO_COMMON_JS && typeof module === 'object' && module.exports;
var AMD = typeof define === 'function' && define.amd;
var ARRAY_BUFFER = !root.JS_MD5_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined';
var HEX_CHARS = '0123456789abcdef'.split('');
var EXTRA = [128, 32768, 8388608, -2147483648];
var SHIFT = [0, 8, 16, 24];
var OUTPUT_TYPES = ['hex', 'array', 'digest', 'buffer', 'arrayBuffer', 'base64'];
var BASE64_ENCODE_CHAR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');

var blocks = [], buffer8;
if (ARRAY_BUFFER) {
var buffer = new ArrayBuffer(68);
buffer8 = new Uint8Array(buffer);
blocks = new Uint32Array(buffer);
}

if (root.JS_MD5_NO_NODE_JS || !Array.isArray) {
Array.isArray = function (obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
}

if (ARRAY_BUFFER && (root.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) {
ArrayBuffer.isView = function (obj) {
return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer;
};
}

/**
* @method hex
* @memberof md5
* @description Output hash as hex string
* @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
* @returns {String} Hex string
* @example
* md5.hex('The quick brown fox jumps over the lazy dog');
* // equal to
* md5('The quick brown fox jumps over the lazy dog');
*/
/**
* @method digest
* @memberof md5
* @description Output hash as bytes array
* @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
* @returns {Array} Bytes array
* @example
* md5.digest('The quick brown fox jumps over the lazy dog');
*/
/**
* @method array
* @memberof md5
* @description Output hash as bytes array
* @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
* @returns {Array} Bytes array
* @example
* md5.array('The quick brown fox jumps over the lazy dog');
*/
/**
* @method arrayBuffer
* @memberof md5
* @description Output hash as ArrayBuffer
* @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
* @returns {ArrayBuffer} ArrayBuffer
* @example
* md5.arrayBuffer('The quick brown fox jumps over the lazy dog');
*/
/**
* @method buffer
* @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead.
* @memberof md5
* @description Output hash as ArrayBuffer
* @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
* @returns {ArrayBuffer} ArrayBuffer
* @example
* md5.buffer('The quick brown fox jumps over the lazy dog');
*/
/**
* @method base64
* @memberof md5
* @description Output hash as base64 string
* @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
* @returns {String} base64 string
* @example
* md5.base64('The quick brown fox jumps over the lazy dog');
*/
var createOutputMethod = function (outputType) {
return function (message) {
return new Md5(true).update(message)[outputType]();
};
};

/**
* @method create
* @memberof md5
* @description Create Md5 object
* @returns {Md5} Md5 object.
* @example
* var hash = md5.create();
*/
/**
* @method update
* @memberof md5
* @description Create and update Md5 object
* @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
* @returns {Md5} Md5 object.
* @example
* var hash = md5.update('The quick brown fox jumps over the lazy dog');
* // equal to
* var hash = md5.create();
* hash.update('The quick brown fox jumps over the lazy dog');
*/
var createMethod = function () {
var method = createOutputMethod('hex');
if (NODE_JS) {
method = nodeWrap(method);
}
method.create = function () {
return new Md5();
};
method.update = function (message) {
return method.create().update(message);
};
for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
var type = OUTPUT_TYPES[i];
method[type] = createOutputMethod(type);
}
return method;
};

var nodeWrap = function (method) {
var crypto = eval("require('crypto')");
var Buffer = eval("require('buffer').Buffer");
var nodeMethod = function (message) {
if (typeof message === 'string') {
return crypto.createHash('md5').update(message, 'utf8').digest('hex');
} else {
if (message === null || message === undefined) {
throw ERROR;
} else if (message.constructor === ArrayBuffer) {
message = new Uint8Array(message);
}
}
if (Array.isArray(message) || ArrayBuffer.isView(message) ||
message.constructor === Buffer) {
return crypto.createHash('md5').update(new Buffer(message)).digest('hex');
} else {
return method(message);
}
};
return nodeMethod;
};

/**
* Md5 class
* @class Md5
* @description This is internal class.
* @see {@link md5.create}
*/
function Md5(sharedMemory) {
if (sharedMemory) {
blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] =
blocks[4] = blocks[5] = blocks[6] = blocks[7] =
blocks[8] = blocks[9] = blocks[10] = blocks[11] =
blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
this.blocks = blocks;
this.buffer8 = buffer8;
} else {
if (ARRAY_BUFFER) {
var buffer = new ArrayBuffer(68);
this.buffer8 = new Uint8Array(buffer);
this.blocks = new Uint32Array(buffer);
} else {
this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
}
}
this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0;
this.finalized = this.hashed = false;
this.first = true;
}

/**
* @method update
* @memberof Md5
* @instance
* @description Update hash
* @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
* @returns {Md5} Md5 object.
* @see {@link md5.update}
*/
Md5.prototype.update = function (message) {
if (this.finalized) {
return;
}

var notString, type = typeof message;
if (type !== 'string') {
if (type === 'object') {
if (message === null) {
throw ERROR;
} else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) {
message = new Uint8Array(message);
} else if (!Array.isArray(message)) {
if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) {
throw ERROR;
}
}
} else {
throw ERROR;
}
notString = true;
}
var code, index = 0, i, length = message.length, blocks = this.blocks;
var buffer8 = this.buffer8;

while (index < length) {
if (this.hashed) {
this.hashed = false;
blocks[0] = blocks[16];
blocks[16] = blocks[1] = blocks[2] = blocks[3] =
blocks[4] = blocks[5] = blocks[6] = blocks[7] =
blocks[8] = blocks[9] = blocks[10] = blocks[11] =
blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
}

if (notString) {
if (ARRAY_BUFFER) {
for (i = this.start; index < length && i < 64; ++index) {
buffer8[i++] = message[index];
}
} else {
for (i = this.start; index < length && i < 64; ++index) {
blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
}
}
} else {
if (ARRAY_BUFFER) {
for (i = this.start; index < length && i < 64; ++index) {
code = message.charCodeAt(index);
if (code < 0x80) {
buffer8[i++] = code;
} else if (code < 0x800) {
buffer8[i++] = 0xc0 | (code >> 6);
buffer8[i++] = 0x80 | (code & 0x3f);
} else if (code < 0xd800 || code >= 0xe000) {
buffer8[i++] = 0xe0 | (code >> 12);
buffer8[i++] = 0x80 | ((code >> 6) & 0x3f);
buffer8[i++] = 0x80 | (code & 0x3f);
} else {
code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
buffer8[i++] = 0xf0 | (code >> 18);
buffer8[i++] = 0x80 | ((code >> 12) & 0x3f);
buffer8[i++] = 0x80 | ((code >> 6) & 0x3f);
buffer8[i++] = 0x80 | (code & 0x3f);
}
}
} else {
for (i = this.start; index < length && i < 64; ++index) {
code = message.charCodeAt(index);
if (code < 0x80) {
blocks[i >> 2] |= code << SHIFT[i++ & 3];
} else if (code < 0x800) {
blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
} else if (code < 0xd800 || code >= 0xe000) {
blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
} else {
code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
}
}
}
}
this.lastByteIndex = i;
this.bytes += i - this.start;
if (i >= 64) {
this.start = i - 64;
this.hash();
this.hashed = true;
} else {
this.start = i;
}
}
if (this.bytes > 4294967295) {
this.hBytes += this.bytes / 4294967296 << 0;
this.bytes = this.bytes % 4294967296;
}
return this;
};

Md5.prototype.finalize = function () {
if (this.finalized) {
return;
}
this.finalized = true;
var blocks = this.blocks, i = this.lastByteIndex;
blocks[i >> 2] |= EXTRA[i & 3];
if (i >= 56) {
if (!this.hashed) {
this.hash();
}
blocks[0] = blocks[16];
blocks[16] = blocks[1] = blocks[2] = blocks[3] =
blocks[4] = blocks[5] = blocks[6] = blocks[7] =
blocks[8] = blocks[9] = blocks[10] = blocks[11] =
blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
}
blocks[14] = this.bytes << 3;
blocks[15] = this.hBytes << 3 | this.bytes >>> 29;
this.hash();
};

Md5.prototype.hash = function () {
var a, b, c, d, bc, da, blocks = this.blocks;

if (this.first) {
a = blocks[0] - 680876937;
a = (a << 7 | a >>> 25) - 271733879 << 0;
d = (-1732584194 ^ a & 2004318071) + blocks[1] - 117830708;
d = (d << 12 | d >>> 20) + a << 0;
c = (-271733879 ^ (d & (a ^ -271733879))) + blocks[2] - 1126478375;
c = (c << 17 | c >>> 15) + d << 0;
b = (a ^ (c & (d ^ a))) + blocks[3] - 1316259209;
b = (b << 22 | b >>> 10) + c << 0;
} else {
a = this.h0;
b = this.h1;
c = this.h2;
d = this.h3;
a += (d ^ (b & (c ^ d))) + blocks[0] - 680876936;
a = (a << 7 | a >>> 25) + b << 0;
d += (c ^ (a & (b ^ c))) + blocks[1] - 389564586;
d = (d << 12 | d >>> 20) + a << 0;
c += (b ^ (d & (a ^ b))) + blocks[2] + 606105819;
c = (c << 17 | c >>> 15) + d << 0;
b += (a ^ (c & (d ^ a))) + blocks[3] - 1044525330;
b = (b << 22 | b >>> 10) + c << 0;
}

a += (d ^ (b & (c ^ d))) + blocks[4] - 176418897;
a = (a << 7 | a >>> 25) + b << 0;
d += (c ^ (a & (b ^ c))) + blocks[5] + 1200080426;
d = (d << 12 | d >>> 20) + a << 0;
c += (b ^ (d & (a ^ b))) + blocks[6] - 1473231341;
c = (c << 17 | c >>> 15) + d << 0;
b += (a ^ (c & (d ^ a))) + blocks[7] - 45705983;
b = (b << 22 | b >>> 10) + c << 0;
a += (d ^ (b & (c ^ d))) + blocks[8] + 1770035416;
a = (a << 7 | a >>> 25) + b << 0;
d += (c ^ (a & (b ^ c))) + blocks[9] - 1958414417;
d = (d << 12 | d >>> 20) + a << 0;
c += (b ^ (d & (a ^ b))) + blocks[10] - 42063;
c = (c << 17 | c >>> 15) + d << 0;
b += (a ^ (c & (d ^ a))) + blocks[11] - 1990404162;
b = (b << 22 | b >>> 10) + c << 0;
a += (d ^ (b & (c ^ d))) + blocks[12] + 1804603682;
a = (a << 7 | a >>> 25) + b << 0;
d += (c ^ (a & (b ^ c))) + blocks[13] - 40341101;
d = (d << 12 | d >>> 20) + a << 0;
c += (b ^ (d & (a ^ b))) + blocks[14] - 1502002290;
c = (c << 17 | c >>> 15) + d << 0;
b += (a ^ (c & (d ^ a))) + blocks[15] + 1236535329;
b = (b << 22 | b >>> 10) + c << 0;
a += (c ^ (d & (b ^ c))) + blocks[1] - 165796510;
a = (a << 5 | a >>> 27) + b << 0;
d += (b ^ (c & (a ^ b))) + blocks[6] - 1069501632;
d = (d << 9 | d >>> 23) + a << 0;
c += (a ^ (b & (d ^ a))) + blocks[11] + 643717713;
c = (c << 14 | c >>> 18) + d << 0;
b += (d ^ (a & (c ^ d))) + blocks[0] - 373897302;
b = (b << 20 | b >>> 12) + c << 0;
a += (c ^ (d & (b ^ c))) + blocks[5] - 701558691;
a = (a << 5 | a >>> 27) + b << 0;
d += (b ^ (c & (a ^ b))) + blocks[10] + 38016083;
d = (d << 9 | d >>> 23) + a << 0;
c += (a ^ (b & (d ^ a))) + blocks[15] - 660478335;
c = (c << 14 | c >>> 18) + d << 0;
b += (d ^ (a & (c ^ d))) + blocks[4] - 405537848;
b = (b << 20 | b >>> 12) + c << 0;
a += (c ^ (d & (b ^ c))) + blocks[9] + 568446438;
a = (a << 5 | a >>> 27) + b << 0;
d += (b ^ (c & (a ^ b))) + blocks[14] - 1019803690;
d = (d << 9 | d >>> 23) + a << 0;
c += (a ^ (b & (d ^ a))) + blocks[3] - 187363961;
c = (c << 14 | c >>> 18) + d << 0;
b += (d ^ (a & (c ^ d))) + blocks[8] + 1163531501;
b = (b << 20 | b >>> 12) + c << 0;
a += (c ^ (d & (b ^ c))) + blocks[13] - 1444681467;
a = (a << 5 | a >>> 27) + b << 0;
d += (b ^ (c & (a ^ b))) + blocks[2] - 51403784;
d = (d << 9 | d >>> 23) + a << 0;
c += (a ^ (b & (d ^ a))) + blocks[7] + 1735328473;
c = (c << 14 | c >>> 18) + d << 0;
b += (d ^ (a & (c ^ d))) + blocks[12] - 1926607734;
b = (b << 20 | b >>> 12) + c << 0;
bc = b ^ c;
a += (bc ^ d) + blocks[5] - 378558;
a = (a << 4 | a >>> 28) + b << 0;
d += (bc ^ a) + blocks[8] - 2022574463;
d = (d << 11 | d >>> 21) + a << 0;
da = d ^ a;
c += (da ^ b) + blocks[11] + 1839030562;
c = (c << 16 | c >>> 16) + d << 0;
b += (da ^ c) + blocks[14] - 35309556;
b = (b << 23 | b >>> 9) + c << 0;
bc = b ^ c;
a += (bc ^ d) + blocks[1] - 1530992060;
a = (a << 4 | a >>> 28) + b << 0;
d += (bc ^ a) + blocks[4] + 1272893353;
d = (d << 11 | d >>> 21) + a << 0;
da = d ^ a;
c += (da ^ b) + blocks[7] - 155497632;
c = (c << 16 | c >>> 16) + d << 0;
b += (da ^ c) + blocks[10] - 1094730640;
b = (b << 23 | b >>> 9) + c << 0;
bc = b ^ c;
a += (bc ^ d) + blocks[13] + 681279174;
a = (a << 4 | a >>> 28) + b << 0;
d += (bc ^ a) + blocks[0] - 358537222;
d = (d << 11 | d >>> 21) + a << 0;
da = d ^ a;
c += (da ^ b) + blocks[3] - 722521979;
c = (c << 16 | c >>> 16) + d << 0;
b += (da ^ c) + blocks[6] + 76029189;
b = (b << 23 | b >>> 9) + c << 0;
bc = b ^ c;
a += (bc ^ d) + blocks[9] - 640364487;
a = (a << 4 | a >>> 28) + b << 0;
d += (bc ^ a) + blocks[12] - 421815835;
d = (d << 11 | d >>> 21) + a << 0;
da = d ^ a;
c += (da ^ b) + blocks[15] + 530742520;
c = (c << 16 | c >>> 16) + d << 0;
b += (da ^ c) + blocks[2] - 995338651;
b = (b << 23 | b >>> 9) + c << 0;
a += (c ^ (b | ~d)) + blocks[0] - 198630844;
a = (a << 6 | a >>> 26) + b << 0;
d += (b ^ (a | ~c)) + blocks[7] + 1126891415;
d = (d << 10 | d >>> 22) + a << 0;
c += (a ^ (d | ~b)) + blocks[14] - 1416354905;
c = (c << 15 | c >>> 17) + d << 0;
b += (d ^ (c | ~a)) + blocks[5] - 57434055;
b = (b << 21 | b >>> 11) + c << 0;
a += (c ^ (b | ~d)) + blocks[12] + 1700485571;
a = (a << 6 | a >>> 26) + b << 0;
d += (b ^ (a | ~c)) + blocks[3] - 1894986606;
d = (d << 10 | d >>> 22) + a << 0;
c += (a ^ (d | ~b)) + blocks[10] - 1051523;
c = (c << 15 | c >>> 17) + d << 0;
b += (d ^ (c | ~a)) + blocks[1] - 2054922799;
b = (b << 21 | b >>> 11) + c << 0;
a += (c ^ (b | ~d)) + blocks[8] + 1873313359;
a = (a << 6 | a >>> 26) + b << 0;
d += (b ^ (a | ~c)) + blocks[15] - 30611744;
d = (d << 10 | d >>> 22) + a << 0;
c += (a ^ (d | ~b)) + blocks[6] - 1560198380;
c = (c << 15 | c >>> 17) + d << 0;
b += (d ^ (c | ~a)) + blocks[13] + 1309151649;
b = (b << 21 | b >>> 11) + c << 0;
a += (c ^ (b | ~d)) + blocks[4] - 145523070;
a = (a << 6 | a >>> 26) + b << 0;
d += (b ^ (a | ~c)) + blocks[11] - 1120210379;
d = (d << 10 | d >>> 22) + a << 0;
c += (a ^ (d | ~b)) + blocks[2] + 718787259;
c = (c << 15 | c >>> 17) + d << 0;
b += (d ^ (c | ~a)) + blocks[9] - 343485551;
b = (b << 21 | b >>> 11) + c << 0;

if (this.first) {
this.h0 = a + 1732584193 << 0;
this.h1 = b - 271733879 << 0;
this.h2 = c - 1732584194 << 0;
this.h3 = d + 271733878 << 0;
this.first = false;
} else {
this.h0 = this.h0 + a << 0;
this.h1 = this.h1 + b << 0;
this.h2 = this.h2 + c << 0;
this.h3 = this.h3 + d << 0;
}
};

/**
* @method hex
* @memberof Md5
* @instance
* @description Output hash as hex string
* @returns {String} Hex string
* @see {@link md5.hex}
* @example
* hash.hex();
*/
Md5.prototype.hex = function () {
this.finalize();

var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3;

return HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] +
HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] +
HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] +
HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] +
HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] +
HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] +
HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] +
HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] +
HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] +
HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] +
HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] +
HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] +
HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] +
HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] +
HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] +
HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F];
};

/**
* @method toString
* @memberof Md5
* @instance
* @description Output hash as hex string
* @returns {String} Hex string
* @see {@link md5.hex}
* @example
* hash.toString();
*/
Md5.prototype.toString = Md5.prototype.hex;

/**
* @method digest
* @memberof Md5
* @instance
* @description Output hash as bytes array
* @returns {Array} Bytes array
* @see {@link md5.digest}
* @example
* hash.digest();
*/
Md5.prototype.digest = function () {
this.finalize();

var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3;
return [
h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 24) & 0xFF,
h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 24) & 0xFF,
h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 24) & 0xFF,
h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 24) & 0xFF
];
};

/**
* @method array
* @memberof Md5
* @instance
* @description Output hash as bytes array
* @returns {Array} Bytes array
* @see {@link md5.array}
* @example
* hash.array();
*/
Md5.prototype.array = Md5.prototype.digest;

/**
* @method arrayBuffer
* @memberof Md5
* @instance
* @description Output hash as ArrayBuffer
* @returns {ArrayBuffer} ArrayBuffer
* @see {@link md5.arrayBuffer}
* @example
* hash.arrayBuffer();
*/
Md5.prototype.arrayBuffer = function () {
this.finalize();

var buffer = new ArrayBuffer(16);
var blocks = new Uint32Array(buffer);
blocks[0] = this.h0;
blocks[1] = this.h1;
blocks[2] = this.h2;
blocks[3] = this.h3;
return buffer;
};

/**
* @method buffer
* @deprecated This maybe confuse with Buffer in node.js. Please use arrayBuffer instead.
* @memberof Md5
* @instance
* @description Output hash as ArrayBuffer
* @returns {ArrayBuffer} ArrayBuffer
* @see {@link md5.buffer}
* @example
* hash.buffer();
*/
Md5.prototype.buffer = Md5.prototype.arrayBuffer;

/**
* @method base64
* @memberof Md5
* @instance
* @description Output hash as base64 string
* @returns {String} base64 string
* @see {@link md5.base64}
* @example
* hash.base64();
*/
Md5.prototype.base64 = function () {
var v1, v2, v3, base64Str = '', bytes = this.array();
for (var i = 0; i < 15;) {
v1 = bytes[i++];
v2 = bytes[i++];
v3 = bytes[i++];
base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] +
BASE64_ENCODE_CHAR[(v1 << 4 | v2 >>> 4) & 63] +
BASE64_ENCODE_CHAR[(v2 << 2 | v3 >>> 6) & 63] +
BASE64_ENCODE_CHAR[v3 & 63];
}
v1 = bytes[i];
base64Str += BASE64_ENCODE_CHAR[v1 >>> 2] +
BASE64_ENCODE_CHAR[(v1 << 4) & 63] +
'==';
return base64Str;
};

var exports = createMethod();

if (COMMON_JS) {
module.exports = exports;
} else {
/**
* @method md5
* @description Md5 hash function, export to global in browsers.
* @param {String|Array|Uint8Array|ArrayBuffer} message message to hash
* @returns {String} md5 hashes
* @example
* md5(''); // d41d8cd98f00b204e9800998ecf8427e
* md5('The quick brown fox jumps over the lazy dog'); // 9e107d9d372bb6826bd81d3542a419d6
* md5('The quick brown fox jumps over the lazy dog.'); // e4d909c290d0fb1ca068ffaddf22cbd0
*
* // It also supports UTF-8 encoding
* md5('中文'); // a7bac2239fcdcb3a067903d8077c4a07
*
* // It also supports byte `Array`, `Uint8Array`, `ArrayBuffer`
* md5([]); // d41d8cd98f00b204e9800998ecf8427e
* md5(new Uint8Array([])); // d41d8cd98f00b204e9800998ecf8427e
*/
root.md5 = exports;
if (AMD) {
define(function () {
return exports;
});
}
}
})();

+ 804
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/mixins.js Целия файл

@@ -0,0 +1,804 @@
import moment from 'moment'
import get from 'lodash/get'
import mapValues from 'lodash/mapValues'

import defaultConfig from './config.default.js'
import globalConfig from '@/config.js'

import md5 from '@/common/md5.js'

/**
* 通用工具方法、全局数据
* 以下定义的所有方法、计算属性,均会被挂载到所有 .vue 页面的 this 上
*
* 注意:小程序端,计算属性会以数据对象的形式挂载到所有页面的 Page 对象中
* 因此,数据量较大的情况下,为优化性能请使用调用方法动态获取的形式,避免占用太多内存
* (例如,GLOBAL 全局对象现在已经移除,改为使用 GET_GLOBAL(key) 来获取全局对象)
*/
export default {
// 【App 平台】点击返回键后移除 loading,防止某个页面出错按返回键后还有 loading
// #ifdef APP-VUE
onBackPress() {
uni.hideLoading()
},
// #endif

methods: {
// 暂存一个跨页面变量 (与 this.GET_PARAM 成对使用)
SET_PARAM(val) {
this.SET_GLOBAL('pageParam', val)
},

// 获取之前暂存的跨页面变量 (与 this.SET_PARAM 成对使用)
GET_PARAM() {
return this.GET_GLOBAL('pageParam')
},

// 进入某个页面
NAV_TO(url, param, usePageParam) {
uni.navigateTo({
url: this.handleNav(url, param, usePageParam)
})
},

// 返回上个页面
// delta 参数为返回的次数
NAV_BACK(delta = 1) {
uni.navigateBack({
delta
})
},

// 跳转到某个页面,跳转后无法返回
JUMP_TO(url, param, usePageParam) {
uni.redirectTo({
url: this.handleNav(url, param, usePageParam)
})
},

// 转到某个 Tab 页
TAB_TO(url, param) {
uni.switchTab({
url: this.handleNav(url, param, true)
})
},

// 重启应用,跳转到指定页面
RELAUNCH_TO(url) {
uni.reLaunch({
url
})
},

// 获取一个全局变量
// key 为键名
GET_GLOBAL(key) {
return this.$store.state[key]
},

// 设置一个全局变量
// key 为键名
// val 为值
SET_GLOBAL(key, val) {
this.$store.commit(key, val)
},

// 清空全局变量
CLEAR_GLOBAL() {
this.$store.commit('clear')
uni.removeStorageSync('token')
},

// 将数据写入本地缓存
SET_STORAGE(key, data) {
if (data === null) {
uni.removeStorageSync(key)
} else {
uni.setStorageSync(key, data)
}
},

// 获取之前写入本地缓存的数据
GET_STORAGE(key) {
return uni.getStorageSync(key)
},

// 清空本地缓存
CLEAR_STORAGE() {
uni.clearStorageSync()
},

// 展示提示框
// title 为提示文字
// mask 为是否禁止点击
// icon 为显示图标,可以改为 'success'
TOAST(title, icon = 'none', mask = false) {
uni.showToast({
title,
icon,
mask
})
},

// 停止展示 toast 提示框
HIDE_TOAST() {
uni.hideToast()
},

// 显示 loading 提示框
// title 为提示文字
// mask 为是否禁止点击
LOADING(title, mask = true) {
uni.showLoading({
title,
mask
})
},

// 停止展示 loading 提示框
HIDE_LOADING() {
uni.hideLoading()
},

// 展示确认提示框
// title 为标题
// content 为提示框文字内容
// showCancel 为是否显示取消按钮
// 返回一个结果,true 或 false,表示用户是否确认
async CONFIRM(title, content, showCancel = false) {
return new Promise(res => {
uni.showModal({
title,
content,
showCancel,
success: ({
confirm
}) => {
res(confirm)
},
fail: () => {
res(false)
}
})
})
},

// 设置页面标题
SET_TITLE(title) {
uni.setNavigationBarTitle({
title
})
},

// 从 /config.js 中获取某条配置项
// 如果在 /config.js 中获取不到,则前往 /common/config.default.js 中获取默认值
// path 为配置项的访问路径
// 举例:
// 以下语句获取是否全局开启圆形头像
// this.CONFIG('pageConfig.roundAvatar')
CONFIG(path) {
return get(globalConfig, path, get(defaultConfig, path))
},

// 监听一个全局事件
ON(name, func) {
uni.$on(name, func)
},

// 仅单次监听一个全局事件
ONCE(name, func) {
uni.$once(name, func)
},

// 触发一个全局事件
EMIT(name, data) {
uni.$emit(name, data)
},

// 移除全局事件监听器
OFF(name, func) {
uni.$off(name, func)
},

// 拉取指定 code 值的数据源数据
async FETCH_DATASOURCE(code) {
if (!code) {
return []
}

return await this.HTTP_GET('/datasource/map', {
code,
ver: ''
})
},

// 拉取指定规则编号的表单编码数据
async FETCH_ENCODE(rulecode) {
if (!rulecode) {
return ''
}

return await this.HTTP_GET('/coderule/code', rulecode)
},

// 拉取指定 id 的文件信息
async FETCH_FILEINFO(fileId) {
if (!fileId) {
return null
}

return await this.HTTP_GET('/annexes/wxfileinfo', fileId)
},

// 封装的 GET 请求,集成了验证信息
// 返回请求结果或 null
// 对网络错误、返回错误码、登录状态失效等情况做了相应处理
// url 为请求地址
// data 为请求附带的提交数据
async HTTP_GET(url, data, showTips) {
const [err, res] = await this.requestBase(url, data, null, 'GET')

return this.handleResult(err, res, showTips)
},

// 封装的 POST 请求,集成了验证信息
// 返回请求结果或 null
// 对网络错误、返回错误码、登录状态失效等情况做了相应处理
// url 为请求地址
// data 为请求附带的提交数据
async HTTP_POST(url, data, showTips) {
const [err, res] = await this.requestBase(url, data, null, 'POST')

return this.handleResult(err, res, showTips)
},

// 封装的文件上传,集成了验证信息
// 返回接口返回值或 null
// 对网络错误、返回错误码、登录状态失效等情况做了相应处理
// url 为请求地址
// filePath 为临时文件的路径
// formData 为请求附带的提交数据
async HTTP_UPLOAD(filePath, formData) {
const [err, res] = await this.UPLOAD('/annexes/wxupload', filePath, formData)

return this.handleResult(err, res)
},

// 封装的文件下载,集成了验证信息
// 返回临时文件路径或 null
// 对网络错误、返回错误码、登录状态失效等情况做了相应处理
// url 为请求地址
// formData 为请求附带的提交数据
async HTTP_DOWNLOAD(formData) {
const [err, res] = await this.DOWNLOAD('/annexes/wxdown', formData)

return this.handleResult(err, res)
},

// 发起一个 GET 请求,封装了身份验证
// url 为请求地址
// data 为请求附带的提交数据
// 返回结果是一个数组: [error, result]
// error 表示错误,一般是网络错误,请求很可能根本没有发出
// result 包含 { statusCode, headers, data } 分别表示状态码、响应头、数据
async GET(url, data, header) {
return await this.requestBase(url, data, header, 'GET')
},

// 发起一个 POST 请求,封装了身份验证
// url 为请求地址
// data 为请求附带的提交数据
// 返回结果是一个数组: [error, result]
// error 表示错误,一般是网络错误,请求很可能根本没有发出
// result 包含 { statusCode, headers, data } 分别表示状态码、响应头、数据
async POST(url, data, header) {
return await this.requestBase(url, data, header, 'POST')
},

// 上传一个文件 (本地临时文件),封装了身份验证
// url 为提交地址
// filePath 为本地临时文件路径
// formData 为上传时附带的参数
// 返回结果是一个数组: [error, result]
// error 表示错误,一般是网络错误,请求很可能根本没有发出
// result 包含 { statusCode, data } 分别表示状态码、接口返回的数据
async UPLOAD(url, filePath, formData) {
const uploadUrl = this.handleUrl(url)
const query = {
loginMark: this.getLoginMark(),
token: this.GET_GLOBAL('token')
}

if (formData && typeof formData === 'object') {
Object.assign(query, formData)
} else if (typeof formData === 'string') {
Object.assign(query, {
data: formData
})
}

// #ifdef MP-DINGTALK
return new Promise((res, rej) => {
dd.uploadFile({
url: uploadUrl,
filePath,
fileName: 'file',
fileType: 'image',
formData: query,
success: dt => {
dt.data = JSON.parse(dt.data)
res([null, dt])
},
fail: rs => {
rej([rs, null])
}
})
})
// #endif

// #ifndef MP-DINGTALK
return uni.uploadFile({
url: uploadUrl,
filePath,
name: 'file',
fileType: 'image',
formData: query
}).then(([err, result]) => {
if (!err) {
result.data = JSON.parse(result.data)
return [null, result]
} else {
return [err, null]
}

})
// #endif
},

// 下载一个文件(下载后为临时文件),封装了身份验证
// url 为请求的地址
// formData 为请求时附带的参数
// 返回结果是一个数组: [error, result]
// error 表示错误,一般是网络错误,请求很可能根本没有发出
// result 包含 { statusCode, tempFilePath } 分别表示状态码、下载后的临时文件路径
async DOWNLOAD(url, formData) {
let downloadUrl = this.handleUrl(url)

const query = {}
if (formData && typeof formData === 'object') {
Object.assign(query, formData)
} else if (typeof formData === 'string') {
Object.assign(query, {
data: formData
})
}

downloadUrl = downloadUrl + '?' + this.URL_QUERY(query, true)

return uni.downloadFile({
url: downloadUrl
}).then(([err, result]) => {
if (!err) {
result.data = {
data: result.tempFilePath
}
result.statusCode = 200
}

return [err, result]
})
},

// 拉取客户端全局数据,直接写入全局变量
// 目前包括了:公司、部门、人员、数据字典
async FETCH_CLIENTDATA() {
await Promise.all([
this.HTTP_GET('/company/map').then(res => this.SET_GLOBAL('company', res.data || {})),
this.HTTP_GET('/department/map').then(res => this.SET_GLOBAL('department', res.data || {})),
this.HTTP_GET('/user/map').then(res => this.SET_GLOBAL('user', res.data || {})),
this.HTTP_GET('/dataitem/map').then(res => this.SET_GLOBAL('dataDictionary', res.data || {}))
])
},

// 使用 JSON 序列化的方式克隆一个对象或数组
COPY(val) {
return JSON.parse(JSON.stringify(val))
},

// 生成一个32位 GUID 随机字符串
// joinChar 为分割符,默认为下划线
GUID(joinChar = '_') {
return `xxxxxxxx${joinChar}xxxx${joinChar}4xxx${joinChar}yxxx${joinChar}xxxxxxxxxxxx`.replace(/[xy]/g, c => {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);

return v.toString(16);
})
},

// 获取指定字符串的 MD5 码
MD5(val = '') {
return md5(val)
},

// 将时间日期转化为特定格式 (一般用于 <l-list-item>)
// datetimeString 为要格式化的日期时间字符串
// 返回值是一个数组,它也可以当做字符串直接使用
// 举例:
// 如果日期是当天,则返回 ['今天 17:32'] 或者 '今天 17:32'
// 如果日期是今年,则返回 ['6月8日', '17:32'] 或者 '6月8日 17:32'
// 如果日期不是今年,返回 ['2018-06-08'] 或者 '2018-06-08'
TABLEITEM_DATEFORMAT(datetimeString) {
const dt = moment(datetimeString)
let result = []

if (!dt.isValid()) {
result.toString = () => ''
return result
}

const now = moment()

if (dt.isSame(now, 'day')) {
result = [`今天 ${dt.format('HH:mm')}`]
result.toString = () => `今天 ${dt.format('HH:mm')}`

} else if (dt.isSame(now, 'year')) {
result = [dt.format('M月D日'), dt.format('HH:mm')]
result.toString = () => dt.format('M月D日') + ' ' + dt.format('HH:mm')

} else {
result = [dt.format('YYYY-MM-DD')]
result.toString = () => dt.format('YYYY-MM-DD')
}

return result
},

// 将一个对象编码并转化为 url 查询字符串
// obj 为要转换的对象,值为空则会被忽略,值为对象会被转为 JSON 字符串
// auth 为是否编入身份验证信息
URL_QUERY(obj, auth = false) {
let queryObject = obj || {}
if (typeof obj === 'string') {
queryObject = {
data: obj
}
}

if (auth) {
Object.assign(queryObject, {
loginMark: this.getLoginMark(),
token: this.GET_GLOBAL('token')
})
}

return Object.entries(queryObject)
.filter(([k, v]) => k && v)
.map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(typeof v === 'object' ? JSON.stringify(v) : v))
.join('&')
},

// 将字符串转化为 HTML 格式 (处理表单页面的 HTML)
CONVERT_HTML(str) {
if (!str) {
return ''
}

return str
.replace(/{@zuojian@}|{@youjian@}|{@and@}/g, tag => ({
'{@zuojian@}': '<',
'{@youjian@}': '>',
'{@and@}': '&'
})[tag] || tag)
.replace(/&amp;|&lt;|&gt;|&#39;|&quot;/g, tag => ({
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&#39;': "'",
'&quot;': '"'
})[tag] || tag)
},

// 用于编码小程序分享消息的 url 查询字符串 (开发环境下还会打印出来)
// pageParam 为点击分享消息跳转时携带的 pageParam
// query 为点击分享消息跳转时携带的 query
// pagePath 点击分享消息跳转到小程序的页面 (默认为当前页)
// 返回编码好的查询字符串
MP_SHARE_ENCODE(pageParam, query, pagePath) {
const shareObj = {
fromUser: this.GET_GLOBAL('loginUser').userId,
fromPlatform: this.PLATFORM,
timestamp: new Date().valueOf(),
pagePath: pagePath || this.pagePath,
query: query,
pageParam,
learun: this.APP_VERSION
}
const result = this.URL_QUERY(shareObj)

if (this.DEV) {
console.log('【您正在分享力软小程序页面】')
console.log('====分享对象:====')
console.log(shareObj)
console.log('====启动路径:====')
console.log('/pages/home')
console.log('====启动参数:====')
console.log(result)
console.log('====(以上消息仅开发模式可见)====')
}

return result
},

// 解析小程序分享字符串 (会自动适配微信小程序的 url 编码)
MP_SHARE_DECODE(info) {
// 微信小程序中获取的分享信息是被 uri 编码过的,需要解码
// 支付宝/钉钉小程序不需要解码
// #ifdef MP-WEIXIN
const shareInfo = mapValues(info, decodeURIComponent)
// #endif

shareInfo.pageParam = shareInfo.pageParam ? JSON.parse(shareInfo.pageParam) : undefined
shareInfo.query = shareInfo.query ? this.URL_QUERY(JSON.parse(shareInfo.query)) : undefined

if (this.DEV) {
console.log('【您通过小程序消息分享启动了力软小程序】')
console.log('====小程序分享对象:====')
console.log(shareInfo)
console.log('====即将转入页面:====')
console.log(shareInfo.pagePath)
console.log('====设置的 url query:====')
console.log(shareInfo.query)
console.log('====设置的 pageParam:====')
console.log(shareInfo.pageParam)
console.log('====(以上消息仅开发模式可见)====')
}

this.SET_GLOBAL('pageParam', shareInfo.pageParam)
uni.navigateTo({
url: `${shareInfo.pagePath}?${this.URL_QUERY(shareInfo.query)}`
})
},

// 【内部方法】处理页面跳转 url 和参数
handleNav(url, param, usePageParam) {
let query = ''
if (param && usePageParam) {
this.SET_PARAM(param)
} else if (param && !usePageParam) {
query += '?' + Object.entries(param).filter(([k, v]) => k && v).map(([k, v]) => k + '=' + v).join('&')
}

return url + query
},

// 【内部方法】从全局变量和缓存中获取 loginMark 设备标识,获取不到则会重新生成一个
getLoginMark() {
if (this.GET_GLOBAL('loginMark')) {
return this.GET_GLOBAL('loginMark')
}

const storageData = uni.getStorageSync('loginMark')
if (storageData && storageData !== 'null' && storageData !== 'undefined') {
this.SET_GLOBAL('loginMark', storageData)

return storageData
}

const newLoginMark = this.GUID()
this.SET_GLOBAL('loginMark', newLoginMark)
uni.setStorageSync('loginMark', newLoginMark)

return newLoginMark
},

// 【内部方法】处理 url,判断是否需要添加后台地址前缀
handleUrl(url) {
let result = url
if (result.startsWith('http://') || result.startsWith('https://')) {
return result
}
if (!result.startsWith(this.API)) {
result = this.API + result
}

return result
},

// 【内部方法】HTTP 请求基础方法
async requestBase(url, data, header, method = 'GET') {
const requestUrl = this.handleUrl(url)
const requestHeader = header || {}

let requestData = {
loginMark: this.getLoginMark(),
token: this.GET_GLOBAL('token') || ''
}
if (data && typeof data === 'object') {
requestData.data = JSON.stringify(data)
} else if (data) {
Object.assign(requestData, {
data
})
}

return uni.request({
url: requestUrl,
method,
header: {
'content-type': 'application/x-www-form-urlencoded',
...requestHeader
},
data: requestData
})
},

// 【内部方法】处理网络请求方法的返回结果
handleResult(err, result, tips) {
// 出现错误,一般是网络连接错误
if (err || !result) {
uni.hideLoading()
uni.showToast({
title: '网络请求失败,请检查您的网络连接',
icon: 'none'
})

return null
}

// 状态码为 410,登录状态失效
if (result.statusCode === 410 || (result.data && result.data.code === 410)) {
uni.hideLoading()
uni.showToast({
title: '登录状态无效,正在跳转到登录页…',
icon: 'none'
})
this.CLEAR_GLOBAL()
uni.reLaunch({
url: '/pages/login'
})

return null

uni.hideLoading()
if (tips) {
const errInfo = (result.data && result.data.info) || '(未知原因)'
const errTips = typeof tips === 'string' ? tips : '请求数据时发生错误'
uni.showToast({
title: `${errTips}: ${errInfo}`,
icon: 'none'
})
}

return null
}

return result.data.data
},

// 【即将废弃】请使用 this.CONFIG() 来替代
config(path) {
return get(globalConfig, path, get(defaultConfig, path))
},

// 【即将废弃】请使用 this.SET_PARAM() 来代替
setPageParam(val) {
this.$store.commit('pageParam', val)
},

// 【即将废弃】请使用 this.GET_PARAM() 来代替
getPageParam() {
return this.$store.state.pageParam
}
},

computed: {
// 请求后台接口的地址
API() {
return this.$store.state.apiRoot ||
this.CONFIG('apiHost')[this.DEV ? this.CONFIG('devApiHostIndex') : this.CONFIG('prodApiHostIndex')]
},

// 当前页面的路径
// 举例:登录页为 '/pages/login'
PATH() {
if (!getCurrentPages) {
return ''
}
const pages = getCurrentPages()

return pages ? '/' + pages.slice(-1)[0].route : ''
},

// 当前是否为开发环境
DEV() {
return process.env.NODE_ENV === 'development'
},

// 【仅开发模式】获取当前全局变量
// 生产环境、正式发行时无效,因为小程序端全局变量会挂载到每个页面,影响性能
DEV_ONLY_GLOBAL() {
return process.env.NODE_ENV === 'development' && this.$store.state
},

// 获取当前移动端版本号 (定义在 config.js)
APP_VERSION() {
return this.CONFIG('appVersion')
},

// 当前运行平台
// 取值 'alipay'/'weixin'/'dingtalk'/'h5'/'app'/'unknow'
PLATFORM() {
let result = 'unknow'

// #ifdef MP-ALIPAY
// #ifndef MP-DINGTALK
result = 'alipay'
// #endif
// #ifdef MP-DINGTALK
result = 'dingtalk'
// #endif
// #endif

// #ifdef MP-WEIXIN
result = 'weixin'
// #endif

// #ifdef H5
result = 'h5'
// #endif

// #ifdef APP-VUE
result = 'app'
// #endif

return result
},

// 获取当前运行平台的中文全称
// 取值 '支付宝小程序'/'微信小程序'/'钉钉小程序'/'移动 H5 '/'手机 App '/'(未知)'
PLATFORM_TEXT() {
let result = '(未知)'

// #ifdef MP-ALIPAY
// #ifndef MP-DINGTALK
result = '支付宝小程序'
// #endif
// #ifdef MP-DINGTALK
result = '钉钉小程序'
// #endif
// #endif

// #ifdef MP-WEIXIN
result = '微信小程序'
// #endif

// #ifdef H5
result = '移动 H5 '
// #endif

// #ifdef APP-VUE
result = '手机 App '
// #endif

return result
},

// 【即将废弃】请使用 this.PATH 来代替
pagePath() {
if (!getCurrentPages) {
return ''
}
const pages = getCurrentPages()

return pages ? '/' + pages.slice(-1)[0].route : ''
},

// 【即将废弃】请使用 this.API 来代替
apiRoot() {
return this.$store.state.apiRoot ||
this.CONFIG('apiHost')[this.DEV ? this.CONFIG('devApiHostIndex') : this.CONFIG('prodApiHostIndex')]
},
}
}

+ 63
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/store.js Целия файл

@@ -0,0 +1,63 @@
import Vue from 'vue'
import Vuex, { Store } from 'vuex'

import config from '@/config.js'
import defaultConfig from '@/common/config.default.js'

const state = {
apiRoot: null,

loginUser: null,
loginMark: null,
token: null,

company: null,
department: null,
user: null,

dataDictionary: null,

pageParam: null,
jumpParam: null
}

const mutations = {
apiRoot(state, val) { state.apiRoot = val },

loginUser(state, val) { state.loginUser = val },
loginMark(state, val) { state.loginMark = val },
token(state, val) { state.token = val },

company(state, val) { state.company = val },
department(state, val) { state.department = val },
user(state, val) { state.user = val },

dataDictionary(state, val) { state.dataDictionary = val },

pageParam(state, val) { state.pageParam = val },
jumpParam(state, val) { state.jumpParam = val }
}

// 获取用户定义的全局变量,依次注册它们
const globalVariables = config['globalVariables'] || defaultConfig['globalVariables']
globalVariables.forEach(t => state[t] = null)
globalVariables.forEach(t => mutations[t] = (state, val) => { state[t] = val })

mutations.clear = (state) => {
state.loginUser = null
state.token = null
state.company = null
state.department = null
state.user = null
state.dataDictionary = null
state.pageParam = null
state.jumpParam = null

globalVariables.forEach(t => {
state[t] = null
})
}

Vue.use(Vuex)

export default new Store({ state, mutations })

+ 5463
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/common/u-charts.js
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 170
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customform-table.vue Целия файл

@@ -0,0 +1,170 @@
<template>
<view>
<!-- 循环按照 formValue 里的行来渲染表格块 -->
<view v-for="(valueItem, valueIndex) in value" :key="valueIndex">
<!-- 表格内部的组件 只有几种 (layer 类似 select,label 类似 title) -->
<!-- 表格标题 除了第一个,后面的都有删除按钮 -->
<view class="table-item padding-lr">
<view class="table-item-title">{{ item.title }} (第{{ valueIndex + 1 }}行)</view>
<view v-if="valueIndex !== 0 && edit" @click="tableDelete(valueIndex)" class="table-item-delete text-blue">
删除
</view>
</view>

<!-- 循环按照 fieldsData 里面定义的列来渲染组件 -->
<view v-for="tableItem of item.fieldsData" :key="tableItem.id">
<!-- 标题文字 label -->
<l-input
v-if="tableItem.type === 'label'"
:value="getTableValue(`${valueIndex}.${tableItem.field}`)"
:title="tableItem.name"
disabled
/>

<!-- 文字输入框 input (text) -->
<l-input
v-else-if="tableItem.type === 'input'"
@input="setTableValue(`${valueIndex}.${tableItem.field}`, $event)"
:value="getTableValue(`${valueIndex}.${tableItem.field}`)"
:disabled="!edit"
:required="Boolean(tableItem.verify)"
:title="tableItem.name"
/>

<!-- 单选和选择 radio select -->
<l-select
v-else-if="tableItem.type === 'radio' || tableItem.type === 'select'"
@input="setTableValue(`${valueIndex}.${tableItem.field}`, $event)"
:value="getTableValue(`${valueIndex}.${tableItem.field}`)"
:required="Boolean(tableItem.verify)"
:range="tableItem.__sourceData__"
:title="tableItem.name"
:disabled="!edit"
/>

<!-- 弹层选择器 layer -->
<l-layer-picker
v-else-if="tableItem.type === 'layer'"
@input="layerSetTableValue(valueIndex, $event, tableItem.__sourceData__)"
:value="getTableValue(`${valueIndex}.${tableItem.field}`)"
:required="Boolean(tableItem.verify)"
:readonly="!edit"
:title="tableItem.name"
:source="tableItem.__sourceData__.source"
:layerData="tableItem.__sourceData__.layerData"
/>

<!-- 时间 datetime / time -->
<l-datetime-picker
v-else-if="tableItem.type === 'datetime' && tableItem.datetime === 'datetime'"
@input="setTableValue(`${valueIndex}.${tableItem.field}`, $event)"
:value="getTableValue(`${valueIndex}.${tableItem.field}`)"
:required="Boolean(tableItem.verify)"
:disabled="!edit"
:title="tableItem.name"
/>

<!-- 日期 datetime / date -->
<l-date-picker
v-else-if="tableItem.type === 'datetime' && tableItem.datetime !== 'datetime'"
@input="setTableValue(`${valueIndex}.${tableItem.field}`, $event)"
:value="getTableValue(`${valueIndex}.${tableItem.field}`)"
:required="Boolean(tableItem.verify)"
:disabled="!edit"
:title="tableItem.name"
/>

<!-- 多选 checkbox -->
<l-checkbox-picker
v-else-if="tableItem.type === 'checkbox'"
@input="setTableValue(`${valueIndex}.${tableItem.field}`, $event)"
:value="getTableValue(`${valueIndex}.${tableItem.field}`)"
:readonly="!edit"
:range="tableItem.__sourceData__"
:required="Boolean(tableItem.verify)"
:title="tableItem.name"
/>
</view>
</view>

<!-- 添加表格按钮 -->
<view
v-if="edit"
@click="tableAdd()"
class="bg-white flex flex-wrap justify-center align-center solid-bottom margin-bottom"
>
<view class="add-btn text-blue padding">+ 添加一行表格</view>
</view>
</view>
</template>

<script>
import get from 'lodash/get'
import set from 'lodash/set'

export default {
name: 'l-customform-table',
props: {
item: { type: Object, required: true },
value: { type: Array, required: true },
edit: { type: Boolean, default: true }
},

methods: {
// 设置表单数据的方法
setTableValue(path, value) {
const newVal = this.COPY(this.value)
set(newVal, path, value)
this.$emit('input', newVal)
},

// 获取表单数据的方法
getTableValue(path) {
return get(this.value, path)
},

// 弹层,可能一次性设置多个字段
layerSetTableValue(tableIndex, layerValue, layerInfo) {
const newVal = this.COPY(this.value)
const layerData = layerInfo.layerData || []

layerData.forEach(({ name, value }) => {
set(newVal, `${tableIndex}.${value}`, layerValue[name] || '')
})
this.$emit('input', newVal)
},

// 删除表格行
tableDelete(tableIndex) {
const newVal = this.value.filter((t, i) => i !== tableIndex)
this.$emit('input', newVal)
},

// 添加表格行
tableAdd() {
const newVal = [...this.value, this.COPY(this.item.__defaultItem__)]
this.$emit('input', newVal)
}
}
}
</script>

<style lang="less" scoped>
.table-item {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
height: 30px;

.table-item-action {
cursor: pointer;
}
}

.add-btn {
text-align: center;
line-height: 1em;
}
</style>

+ 312
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customform.vue Целия файл

@@ -0,0 +1,312 @@
<template>
<view>
<view
:key="item.id"
v-for="(item, index) of scheme"
:style="{ marginTop: index !== 0 && item.type === 'label' ? '15px' : '0' }"
>
<!-- 标题文字 label -->
<l-title v-if="item.type === 'label'" border>{{ item.title }}</l-title>

<!-- 文字输入框 text -->
<l-input
v-else-if="item.type === 'text'"
@input="setValue(item.__valuePath__, $event)"
:value="getValue(item.__valuePath__)"
:disabled="!isEdit(item)"
:required="Boolean(item.verify)"
:title="item.title"
/>

<!-- 单选和选择 radio和select -->
<l-select
v-else-if="item.type === 'radio' || item.type === 'select'"
@input="setValue(item.__valuePath__, $event)"
:value="getValue(item.__valuePath__)"
:disabled="!isEdit(item)"
:required="Boolean(item.verify)"
:range="item.__sourceData__"
:title="item.title"
/>

<!-- 多选 checkbox -->
<l-checkbox-picker
v-else-if="item.type === 'checkbox'"
@input="setValue(item.__valuePath__, $event)"
:value="getValue(item.__valuePath__)"
:readonly="!isEdit(item)"
:required="Boolean(item.verify)"
:range="item.__sourceData__"
:title="item.title"
/>

<!-- 多行文本 textarea -->
<l-textarea
v-else-if="item.type === 'textarea' || item.type === 'texteditor'"
@input="setValue(item.__valuePath__, $event)"
:value="getValue(item.__valuePath__)"
:readonly="!isEdit(item)"
:required="Boolean(item.verify)"
:title="item.title"
/>

<!-- 时间日期 datetime -->
<l-datetime-picker
v-else-if="item.type === 'datetime' && Number(item.dateformat) === 1"
@input="setValue(item.__valuePath__, $event)"
@change="relChange(item.id, $event)"
:value="getValue(item.__valuePath__)"
:disabled="!isEdit(item)"
:required="Boolean(item.verify)"
:title="item.title"
/>

<!-- 日期 date -->
<l-date-picker
v-else-if="item.type === 'datetime' && Number(item.dateformat) !== 1"
@input="setValue(item.__valuePath__, $event)"
@change="relChange(item.id, $event)"
:value="getValue(item.__valuePath__)"
:required="Boolean(item.verify)"
:disabled="!isEdit(item)"
:title="item.title"
/>

<!-- 日期区间 datetimerange -->
<l-input
v-else-if="item.type === 'datetimerange'"
@input="setValue(item.__valuePath__, $event)"
:value="getValue(item.__valuePath__)"
:disabled="!isEdit(item)"
:required="Boolean(item.verify)"
:title="item.title"
placeholder=""
/>

<!-- 当前状态 currentInfo:组织结构 organize -->
<l-organize-picker
v-else-if="item.type === 'currentInfo' && item.dataType !== 'time'"
:value="getValue(item.__valuePath__)"
:type="item.dataType"
:required="Boolean(item.verify)"
:title="item.title"
readonly
/>

<!-- 当前状态 currentInfo:时间 time -->
<l-input
v-else-if="item.type === 'currentInfo' && item.dataType === 'time'"
:value="getValue(item.__valuePath__)"
:disabled="!isEdit(item)"
:required="Boolean(item.verify)"
:title="item.title"
disabled
/>

<!-- 编码 encode -->
<l-input
v-else-if="item.type === 'encode'"
:value="getValue(item.__valuePath__)"
:required="Boolean(item.verify)"
:title="item.title"
disabled
/>

<!-- 公司人员结构选单 organize -->
<l-organize-picker
v-else-if="item.type === 'organize'"
@input="setValue(item.__valuePath__, $event)"
@change="relChange(item.id, $event)"
:value="getValue(item.__valuePath__)"
:rootId="getValue(item.__relationPath__)"
:type="item.dataType"
:required="Boolean(item.verify)"
:readonly="item.__relationPath__ ? !isEdit(item) || !getValue(item.__relationPath__) : !isEdit(item)"
:title="item.title"
:placeholder="displayPlaceHolder(item)"
/>

<!-- 文件上传 upload -->
<l-upload-file
v-else-if="item.type === 'upload'"
@input="setValue(item.__valuePath__, $event)"
:value="getValue(item.__valuePath__)"
:required="Boolean(item.verify)"
:readonly="!isEdit(item)"
:title="item.title"
:number="9"
/>

<!-- HTML内容 html -->
<view v-else-if="item.type === 'html'" class="cu-form-group">
<view class="bg-white">
<u-parse :imageProp="{ domain: apiRoot }" :content="CONVERT_HTML(item.title)"></u-parse>
</view>
</view>

<!-- 表格 girdtable -->
<l-customform-table
v-else-if="item.type === 'girdtable'"
@input="setValue(item.__valuePath__, $event)"
:value="getValue(item.__valuePath__)"
:item="item"
:edit="isEdit(item)"
/>
</view>
</view>
</template>

<script>
import get from 'lodash/get'
import set from 'lodash/set'
import moment from 'moment'

export default {
name: 'l-customform',

props: {
scheme: { default: () => [] },
editMode: { default: true },
initFormValue: { default: () => ({}) },
rel: { default: () => ({}) }
},

data() {
return {
formValue: this.initFormValue
}
},

methods: {
// 本方法由调用方使用 $refs 的方式调用
// 给表单设置值
setFormValue(formValue) {
this.formValue = formValue
},

// 本方法由调用方使用 $refs 的方式调用
// 获取表单值
getFormValue() {
return this.formValue
},

// 本方法由调用方使用 $refs 的方式调用
// 依次验证表单项,返回一个所有错误提示的数组,如果为空数组则表示无错误
verifyValue() {
const errorList = []

this.scheme
.filter(t => t.verify)
.forEach(schemeItem => {
if (schemeItem.table && schemeItem.field) {
const verifyFunc = this.verify[schemeItem.verify]
const verifyResult = verifyFunc(this.getValue(schemeItem.__valuePath__))
if (verifyResult !== true) {
errorList.push(`[${schemeItem.title}]: ${verifyResult}`)
}
} else if (schemeItem.fieldsData) {
this.getValue(schemeItem.__valuePath__).forEach((valueItem, valueIndex) => {
schemeItem.fieldsData.forEach(fieldItem => {
const verifyFunc = this.verify[fieldItem.verify]
const verifyResult = verifyFunc(
this.getValue(`${schemeItem.__valuePath__}.${valueIndex}.${fieldItem.field}`)
)
if (verifyResult !== true) {
errorList.push(`[表格${schemeItem.title}第${tableIndex}行${fieldItem.name}列]: ${verifyResult}`)
}
})
})
}
})

return errorList
},

// 设置表单数据的方法
setValue(path, value) {
set(this.formValue, path, value)
},

// 获取表单数据的方法
getValue(path) {
return get(this.formValue, path)
},

// 处理互相关联的表单项(organize 级联,datetimerange 日期区间)
relChange(id, val) {
const relList = this.rel[id] || []
relList.forEach(relItem => {
if (relItem.type === 'organize') {
// 组织结构,重设级联过来的组件
this.setValue(relItem.path, '')

// 向下递归
if (this.rel[relItem.id]) {
this.relChange(relItem.id)
}
} else if (relItem.type === 'datetimerange') {
// 时间日期区间,重新计算
const startTime = this.getValue(relItem.startPath)
const endTime = this.getValue(relItem.endPath)

if (!startTime || !endTime || moment(endTime).isBefore(startTime)) {
return
}

const newVal = moment
.duration(moment(endTime).diff(moment(startTime)))
.asDays()
.toFixed(0)

this.setValue(relItem.path, newVal)
}
})
},

// 获取表单项的 placeholder ,在无法编辑时隐藏
displayPlaceHolder(item) {
if (!this.editMode || item.type !== 'organize') {
return ' '
}

if (!item.relation) {
return `请选择${item.title || item.name}`
}

const parentType = { user: '部门', department: '公司' }[item.dataType]
const parentValue = this.getValue(item.__relationPath__)
return parentValue ? `请选择${item.title || item.name}` : `请先选择上级${parentType}`
},

// 获取表单项可否编辑
isEdit(item) {
return this.editMode && item.edit !== 0
}
},

computed: {
verify() {
return {
NotNull: t => (t !== null && t !== undefined && t !== '' && (!Array.isArray(t) || t.length > 0)) || '不能为空',
Num: t => !isNaN(t) || '须输入数值',
NumOrNull: t => t.length <= 0 || !isNaN(t) || '须留空或输入数值',
Email: t => /^[a-zA-Z0-9-_.]+@[a-zA-Z0-9-_]+.[a-zA-Z0-9]+$/.test(t) || '须符合Email格式',
EmailOrNull: t =>
t.length <= 0 || /^[a-zA-Z0-9-_.]+@[a-zA-Z0-9-_]+.[a-zA-Z0-9]+$/.test(t) || '须留空或符合Email格式',
EnglishStr: t => /^[a-zA-Z]*$/.test(t) || '须由英文字母组成',
EnglishStrOrNull: t => t.length <= 0 || /^[a-zA-Z]*$/.test(t) || '须留空或由英文字母组成',
Phone: t => /^[+0-9- ]*$/.test(t) || '须符合电话号码格式',
PhoneOrNull: t => t.length <= 0 || /^[+0-9- ]*$/.test(t) || '须留空或符合电话号码格式',
Fax: t => /^[+0-9- ]*$/.test(t) || '须符合传真号码格式',
Mobile: t => /^1[0-9]{10}$/.test(t) || '须符合手机号码格式',
MobileOrPhone: t => /^[+0-9- ]*$/.test(t) || /^1[0-9]{10}$/.test(t) || '须符合电话或手机号码格式',
MobileOrNull: t => t.length <= 0 || /^1[0-9]{10}$/.test(t) || '须留空或符合手机号码格式',
MobileOrPhoneOrNull: t =>
t.length <= 0 || /^1[0-9]{10}$/.test(t) || /^[+0-9- ]*$/.test(t) || '须留空或符合手机/电话号码格式',
Uri: t => /^[a-zA-z]+:\/\/[^\s]*$/.test(t) || '须符合网址Url格式',
UriOrNull: t => t.length <= 0 || /^[a-zA-z]+:\/\/[^\s]*$/.test(t) || '须留空或符合网址Url格式'
}
}
}
}
</script>

+ 62
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customlist-action.vue Целия файл

@@ -0,0 +1,62 @@
<template>
<view class="customlist-banner-action">
<view
v-if="showDelete"
@click="$emit('delete')"
class="customlist-banner-action-btn line-red text-sm"
style="border: currentColor 1px solid"
>
<l-icon type="delete" />
删除
</view>
<view
v-if="showEdit"
@click="$emit('edit')"
class="customlist-banner-action-btn line-blue text-sm"
style="border: currentColor 1px solid"
>
<l-icon type="edit" />
编辑
</view>
<view
@click="$emit('view')"
class="customlist-banner-action-btn line-blue text-sm"
style="border: currentColor 1px solid;min-width: 160rpx;"
>
查看
<l-icon type="right" />
</view>
</view>
</template>

<script>
export default {
name: 'l-customlist-banner',

props: {
showDelete: {},
showEdit: {}
}
}
</script>

<style lang="less" scoped>
.customlist-banner-action {
margin-top: 20rpx;
text-align: right;

.customlist-banner-action-btn {
display: inline-block;
padding: 4px 6px;
margin: 0 3px;
border-radius: 3px;
text-align: center;
}
}
</style>

<style lang="less">
:host {
display: block;
}
</style>

+ 40
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customlist-add.vue Целия файл

@@ -0,0 +1,40 @@
<template>
<!-- #ifdef MP-ALIPAY -->
<view @click="$emit('click', $event)" :class="className" :style="style" class="custom-add">
<l-icon :type="icon" />
</view>
<!-- #endif -->

<!-- #ifndef MP-ALIPAY -->
<view @click="$emit('click', $event)" class="custom-add"><l-icon :type="icon" /></view>
<!-- #endif -->
</template>

<script>
export default {
name: 'l-custom-add',

props: {
icon: { default: 'add' }
}
}
</script>

<style lang="less">
.custom-add {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
bottom: 20px;
bottom: calc(20rpx + constant(safe-area-inset-bottom));
bottom: calc(20rpx + env(safe-area-inset-bottom));
right: 20px;
height: 40px;
width: 40px;
border-radius: 50%;
background-color: #488aff;
color: #fff;
}
</style>

+ 62
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customlist-banner.vue Целия файл

@@ -0,0 +1,62 @@
<template>
<!-- #ifndef MP-ALIPAY -->
<view class="customlist-banner-header">
<view class="customlist-banner-content">
<slot></slot>
<view v-if="buttonShow" class="customlist-banner-action">
<l-icon @click="$emit('buttonClick', $event)" :type="buttonIcon" color="blue" class="text-xxl" />
</view>
</view>
</view>
<!-- #endif -->

<!-- #ifdef MP-ALIPAY -->
<view :class="className" :style="style" class="customlist-banner-header">
<view class="customlist-banner-content">
<slot></slot>
<view v-if="buttonShow" class="customlist-banner-action">
<l-icon @click="$emit('buttonClick', $event)" :type="buttonIcon" color="blue" class="text-xxl" />
</view>
</view>
</view>
<!-- #endif -->
</template>

<script>
export default {
name: 'l-customlist-banner',

props: {
buttonShow: { default: true },
buttonIcon: { default: 'searchlist' }
}
}
</script>

<style lang="less" scoped>
.customlist-banner-header {
position: fixed;
top: var(--window-top);
z-index: 1024;
border-bottom: 1rpx solid #ddd;
height: 80rpx;
width: 100%;
box-shadow: 0 0.5px 3px rgba(0, 0, 0, 0.1);
background: #f1f1f1;

.customlist-banner-content {
color: #8f8f94;
background: #ffffff;
padding: 20rpx 25rpx;
width: 100%;
vertical-align: middle;
border-bottom: 1rpx solid #ddd;

.customlist-banner-action {
position: absolute;
right: 20rpx;
top: 20rpx;
}
}
}
</style>

+ 194
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customlist-sidepage-datefilter.vue Целия файл

@@ -0,0 +1,194 @@
<template>
<view class="sidepage-datefilter padding-bottom">
<view class="title">{{ title }}</view>
<l-button
v-if="isDisplay('all')"
@click="changeDateRange('all')"
:line="dateRange !== 'all' ? 'green' : ''"
:color="dateRange === 'all' ? 'green' : ''"
class="block margin-top-sm"
block
>
全部
</l-button>
<l-button
v-if="isDisplay('today')"
@click="changeDateRange('today')"
:line="dateRange !== 'today' ? 'blue' : ''"
:color="dateRange === 'today' ? 'blue' : ''"
class="block margin-top-sm"
block
>
今天
</l-button>
<l-button
v-if="isDisplay('7d')"
@click="changeDateRange('7d')"
:line="dateRange !== '7d' ? 'blue' : ''"
:color="dateRange === '7d' ? 'blue' : ''"
class="block margin-top-sm"
block
>
最近7天内
</l-button>
<l-button
v-if="isDisplay('1m')"
@click="changeDateRange('1m')"
:line="dateRange !== '1m' ? 'blue' : ''"
:color="dateRange === '1m' ? 'blue' : ''"
class="block margin-top-sm"
block
>
最近1个月内
</l-button>
<l-button
v-if="isDisplay('3m')"
@click="changeDateRange('3m')"
:line="dateRange !== '3m' ? 'blue' : ''"
:color="dateRange === '3m' ? 'blue' : ''"
class="block margin-top-sm"
block
>
最近3个月内
</l-button>
<l-button
v-if="isDisplay('custom')"
@click="changeDateRange('custom')"
:line="dateRange !== 'custom' ? 'cyan' : ''"
:color="dateRange === 'custom' ? 'cyan' : ''"
class="block margin-top-sm"
block
>
自定义
</l-button>

<view v-if="dateRange === 'custom'" class="title">自定义时间区间:</view>
<l-date-picker
v-if="dateRange === 'custom'"
v-model="startDate"
@change="customChange"
title="起始时间"
placeholder="点击来选取"
/>
<l-date-picker
v-if="dateRange === 'custom'"
v-model="endDate"
@change="customChange"
title="结束时间"
placeholder="点击来选取"
/>
</view>
</template>

<script>
import moment from 'moment'

export default {
name: 'l-customlist-sidepage-datefilter',

props: {
title: { default: '按时间日期筛选:' },
ranges: { default: () => ['all', 'today', '7d', '1m', '3m', 'custom'] },
value: {}
},

data() {
return {
dateRange: 'all',
startDate: null,
endDate: null
}
},

methods: {
// 点击按钮修改快捷选择区间
changeDateRange(type) {
this.dateRange = type
const { startDate, endDate } = this
const todayEnd = moment().format('YYYY-MM-DD 23:59:59')

if (type === 'all') {
this.$emit('input', null)
this.$emit('change', null)
} else if (type === 'today') {
const e = {
start: moment()
.subtract(1, 'day')
.format('YYYY-MM-DD 00:00:00'),
end: todayEnd
}
this.$emit('input', e)
this.$emit('change', e)
} else if (type === '7d') {
const e = {
start: moment()
.subtract(7, 'days')
.format('YYYY-MM-DD 00:00:00'),
end: todayEnd
}
this.$emit('input', e)
this.$emit('change', e)
} else if (type === '1m') {
const e = {
start: moment()
.subtract(1, 'month')
.format('YYYY-MM-DD 00:00:00'),
end: todayEnd
}
this.$emit('input', e)
this.$emit('change', e)
} else if (type === '3m') {
const e = {
start: moment()
.subtract(3, 'months')
.format('YYYY-MM-DD 00:00:00'),
end: todayEnd
}
this.$emit('input', e)
this.$emit('change', e)
} else if (type === 'custom' && (startDate || startDate)) {
this.customChange()
}
},

// 自定义区间修改起止
customChange() {
if (this.dateRange !== 'custom') {
return
}

const { startDate, endDate } = this
const todayEnd = moment().format('YYYY-MM-DD 23:59:59')

if (!(startDate && startDate && moment(startDate).isAfter(endDate))) {
const e = {
start: startDate ? moment(startDate).format('YYYY-MM-DD 00:00:00') : '1900-01-01 00:00:00',
end: endDate ? moment(endDate).format('YYYY-MM-DD 23:59:59') : todayEnd
}
this.$emit('input', e)
this.$emit('change', e)
} else {
this.$emit('input', null)
this.$emit('change', null)
}
},

// 是否渲染该区间的快捷选择按钮
isDisplay(type) {
return this.ranges.includes(type)
}
}
}
</script>

<style lang="less" scoped>
.sidepage-datefilter {
display: block;

.title {
font-size: 1.2em;
margin-bottom: 20rpx;
margin-top: 40rpx;
}
}
</style>

+ 17
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/customlist.vue Целия файл

@@ -0,0 +1,17 @@
<template>
<view class="customlist">
<slot></slot>
<view v-if="showTips" class="padding text-gray text-center">{{ tips }}</view>
</view>
</template>

<script>
export default {
name: 'l-customlist',

props: {
showTips: {},
tips: { default: '已加载全部项目' }
}
}
</script>

+ 46
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/demo-description.vue Целия файл

@@ -0,0 +1,46 @@
<template>
<view :class="displayDesc ? 'demo-description-display' : ''" class="demo-description margin-sm padding-sm radius">
<view v-if="displayDesc" class="demo-description-text text-center">
<text class="demo-description-content text-left"><slot></slot></text>
</view>
<view v-else @click="displayDesc = true" class="demo-description-tip text-center">{{ tips }}</view>
</view>
</template>

<script>
export default {
name: 'demo-description',

props: {
expand: { default: false },
tips: { default: '显示说明' }
},

data() {
return {
displayDesc: this.expand
}
}
}
</script>

<style scoped>
.demo-description-display {
background: #eeeeee;
}

.demo-description-content {
display: inline-block;
font-size: 1em;
}

.demo-description-tip {
font-size: 1em;
}
</style>

<style>
:host {
display: block;
}
</style>

+ 85
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/layer-picker.vue Целия файл

@@ -0,0 +1,85 @@
<template>
<!-- #ifndef MP-DINGTALK -->
<l-label @click="click" :arrow="!readonly" :required="required" :title="title">
{{ value || displayPlaceholder }}
</l-label>
<!-- #endif -->

<!-- #ifdef MP-DINGTALK -->
<l-label :arrow="!readonly" :required="required" :title="title">
<text @click="click">{{ value || displayPlaceholder }}</text>
</l-label>
<!-- #endif -->
</template>

<script>
/**
* 弹层选择器
*
* 接受两个参数:
* source: 弹层选单源数据
* layerData: 弹层绑定定义
*
* 举例:
* // 数据源,这里以数据字典里的公司类别来举例
* source: this.GET_GLOBAL('dataDictionary').CompanyType
*
* // 需要展示在弹层页面中的字段
* layerData: [
* {
* name: 'text', // 对应数据源中的字段
* label: '公司类别', // 标题文本
* value: 'F_CompanyName' // 对应要绑定到的字段
* }, {
* name: 'value',
* label: '对应值',
* value: 'F_CompanyId'
* }
* ]
*/

export default {
name: 'l-layer-picker',

props: {
value: { default: null },
title: {},
required: {},
placeholder: {},
readonly: {},
source: { default: () => [] },
layerData: { default: () => [] }
},

methods: {
click() {
if (this.readonly) {
return
}

const { source, layerData } = this
this.ONCE(`select-layer`, data => {
this.$emit('input', data)
this.$emit('change', data)
})

this.SET_PARAM({ source, layerData })
this.NAV_TO('/pages/common/select-layer')
}
},

computed: {
displayPlaceholder() {
if (this.readonly) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return this.title ? `请选择${this.title}` : '请选择…'
}
}
}
</script>

+ 75
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/organize-picker.vue Целия файл

@@ -0,0 +1,75 @@
<template>
<!-- #ifdef MP-DINGTALK -->
<l-label :arrow="!readonly" :required="required" :title="title">
<text @click="click">{{ display || displayPlaceholder }}</text>
</l-label>
<!-- #endif -->

<!-- #ifndef MP-DINGTALK -->
<l-label @click="click" :arrow="!readonly" :required="required" :title="title">
{{ display || displayPlaceholder }}
</l-label>
<!-- #endif -->
</template>

<script>
export default {
name: 'l-organize-picker',

props: {
value: { default: '' },
type: {},
title: {},
required: {},
placeholder: {},
readonly: {},
rootId: {},
rootType: {}
},

methods: {
click() {
if (this.readonly) {
return
}

this.ONCE('select-organize', data => {
this.$emit('input', data.id)
this.$emit('change', data.id)
})

const rootType = this.rootType || { user: 'department', department: 'company', company: 'company' }[this.type]
const rootId = this.rootId

const url = rootId
? `/pages/common/select-organize?type=${this.type}&rootId=${rootId}&rootType=${rootType}`
: `/pages/common/select-organize?type=${this.type}`

this.NAV_TO(url)
}
},

computed: {
display() {
if (!this.value) {
return this.placeholder
}
const orgItem = this.GET_GLOBAL(this.type)[this.value]

return orgItem ? orgItem.name : this.placeholder
},

displayPlaceholder() {
if (this.readonly) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return this.title ? `请选择${this.title}` : '请选择…'
}
}
}
</script>

+ 90
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/organize-single-item.vue Целия файл

@@ -0,0 +1,90 @@
<template>
<view @click="click" class="tree-item">
<!-- 用户头像 -->
<image
v-if="item.type === 'user'"
class="tree-item-avatar"
:src="avatar"
:style="{ borderRadius: roundAvatar ? '50%' : '3px' }"
mode="aspectFill"
></image>

<!-- 名称 -->
<text class="tree-item-title">{{ item.name }}</text>

<!-- 用户标签 -->
<l-tag :line="tagColor" size="sm" class="margin-left-sm">{{ tagName }}</l-tag>

<!-- 如果开启选择按钮显示,并且级别符合,则显示按钮 -->
<view v-if="button" class="tree-item-action">
<l-button @click="$emit('buttonClick', item)" line="green" size="sm">选择</l-button>
</view>
</view>
</template>

<script>
export default {
name: 'l-organize-single-item',

props: {
item: {},
button: {}
},

methods: {
// 点击事件
click(e) {
this.$emit('click', e)
}
},

computed: {
// 获取用户头像
avatar() {
if (!Number.isNaN(this.item.img)) {
return Number(this.item.img) === 1 ? '/static/img-avatar/chat-boy.jpg' : '/static/img-avatar/chat-girl.jpg'
}

return this.item.img
},

// 获取树形列表每一项后面 tag 的显示
tagName() {
return { user: '职员', department: '部门', company: '公司' }[this.item.type]
},

// 获取 tag 的颜色
tagColor(item) {
return { company: 'red', department: 'blue', user: 'green' }[this.item.type]
},

// 头像圆形/方形显示参数
roundAvatar() {
return this.CONFIG('pageConfig.roundAvatar')
}
}
}
</script>

<style lang="less" scoped>
.tree-item {
background-color: #fff;
display: flex;
min-height: 65rpx;
align-items: center;

.tree-item-avatar {
width: 60rpx;
height: 60rpx;
margin-top: 8rpx;
margin-bottom: 8rpx;
margin-left: 30rpx;
margin-right: 16rpx;
}

.tree-item-action {
position: absolute;
right: 30rpx;
}
}
</style>

+ 231
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/organize-tree.vue Целия файл

@@ -0,0 +1,231 @@
<template>
<view class="tree-item">
<!-- 全局根元素时隐藏 -->
<view v-if="root.id !== '0'" @click="selfClick" class="tree-item-self">
<!-- 已确认子列表为空则显示横杠图标;加载中则显示 loading 图标;否则显示展开/收起图标 -->
<l-icon v-if="isEmpty" type="move" class="tree-item-icon" />
<l-icon
v-else-if="root.type !== 'user' && !isLoading"
:type="isOpen ? 'unfold' : 'right'"
class="tree-item-icon"
/>
<view v-else-if="root.type !== 'user' && isLoading" class="loading-fix cu-load loading tree-item-icon"></view>

<!-- 是职员,则显示头像 -->
<image
v-else
class="tree-item-avatar"
:src="avatar"
:style="{ borderRadius: roundAvatar ? '50%' : '3px' }"
mode="aspectFill"
></image>

<!-- 名称 -->
<text class="tree-item-title">{{ name }}</text>

<!-- 非用户,显示后置标题 -->
<l-tag v-if="root.type !== 'user' || staffTag" :line="tagColor" size="sm" class="margin-left-sm">
{{ tagName }}
</l-tag>

<!-- 开启按钮显示且级别对应,则显示按钮 -->
<view v-if="button && root.type === level" class="tree-item-action">
<l-button @click="$emit('buttonClick', root)" line="green" size="sm">选择</l-button>
</view>
</view>

<!-- 子节点列表;非用户节点且已加载完毕时显示 -->
<view
v-if="root.type !== 'user' && isLoadOk"
v-show="isOpen"
:style="{ paddingLeft: root.id === '0' ? '0' : '25rpx' }"
class="tree-item-children"
>
<l-organize-tree
v-for="child of children"
@userClick="$emit('userClick', $event)"
@buttonClick="$emit('buttonClick', $event)"
:key="child.id"
:open="root.id === '0'"
:root="child"
:button="button"
:level="level"
/>
</view>
</view>
</template>

<script>
export default {
name: 'l-organize-tree',

props: {
root: { default: () => ({ type: 'company', id: '0' }) },
level: { default: 'user' },
button: {},
open: { default: true }
},

data() {
return {
isOpen: this.open,
isLoading: false,
isLoadOk: false,
isEmpty: false,
children: []
}
},

async created() {
await this.init()
},

methods: {
// 组件被创建
async init() {
// 如果自己是根节点,则创建时直接加载子节点
if (this.open) {
await this.loadChildren()
}
},

// 自身被点击,如果不是用户节点则收起/展开 (如果没有加载子节点则还需加载),否则触发点击用户的事件
selfClick() {
if (this.isLoading) {
return
}

if (this.root.type === 'user') {
this.$emit('userClick', this.root)
return
}

if (this.isLoadOk) {
this.isOpen = !this.isOpen
return
}

this.loadChildren()
},

// 加载子节点
async loadChildren() {
this.isLoading = true

let children = []
// 只有公司/根节点能加载子公司
if (this.root.type === 'company') {
children = children.concat(
Object.entries(this.GET_GLOBAL('company'))
.filter(([id, item]) => item.parentId === this.root.id)
.map(([id, item]) => ({ ...item, id, type: 'company' }))
)
}

// 加载子部门
if (this.level === 'department' || this.level === 'user') {
children = children.concat(
Object.entries(this.GET_GLOBAL('department'))
.filter(([id, item]) => item.companyId === this.root.id && Number(item.parentId) !== -1)
.map(([id, item]) => ({ ...item, id, type: 'department' }))
)
}

// 加载子职员
if (this.level === 'user') {
children = children.concat(
Object.entries(this.GET_GLOBAL('user'))
.filter(
([id, item]) =>
this.root.id ===
(item.departmentId && Number(item.departmentId) !== 0 ? item.departmentId : item.companyId)
)
.map(([id, item]) => ({ ...item, id, type: 'user' }))
)
}

this.children = children

this.isLoadOk = true
this.isOpen = true
this.isLoading = false
}
},

computed: {
// 获取用户头像
avatar() {
if (!Number.isNaN(this.root.img)) {
return Number(this.root.img) === 1 ? '/static/img-avatar/chat-boy.jpg' : '/static/img-avatar/chat-girl.jpg'
}

return this.root.img
},

// 是否在职员一级显示标签
staffTag() {
return this.CONFIG('pageConfig.contact.staffTag')
},

// 获取树形列表每一项后面 tag 的显示
tagName() {
return { user: '职员', department: '部门', company: '公司' }[this.root.type]
},

// 获取 tag 的颜色
tagColor() {
return { company: 'red', department: 'blue', user: 'green' }[this.root.type]
},

// 节点名称
name() {
if (this.root.name) {
return this.root.name
}

const rootItem = this.GET_GLOBAL(this.root.type)[this.root.id]
return rootItem ? rootItem.name : '(根节点)'
},

// 头像圆形/方形显示参数
roundAvatar() {
return this.CONFIG('pageConfig.roundAvatar')
}
}
}
</script>

<style lang="less" scoped>
:host {
display: block;
}

.tree-item {
background-color: #fff;

.tree-item-self {
display: flex;
min-height: 65rpx;
align-items: center;

.tree-item-icon {
margin: 0 30rpx;
line-height: 1em;
}

.tree-item-avatar {
width: 60rpx;
height: 60rpx;
margin-top: 8rpx;
margin-bottom: 8rpx;
margin-left: 30rpx;
margin-right: 16rpx;
}

.tree-item-action {
position: absolute;
right: 30rpx;
}
}
}
</style>

+ 86
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/scroll-list.vue Целия файл

@@ -0,0 +1,86 @@
<template>
<scroll-view
@scrolltolower="$emit('toBottom')"
@refresherrefresh="onRefresh"
@refresherrestore="onRefreshRestore"
@scroll="scroll"
:scroll-top="scrollTop"
:refresher-triggered="pullDown"
:refresher-enabled="enablePullDown"
:refresher-background="refresherBackground"
:refresher-threshold="80"
class="lr-scroll-list"
style="height: 100%; display: block;"
scroll-anchoring
scroll-y
>
<slot></slot>
</scroll-view>
</template>

<script>
export default {
name: 'l-scroll-list',

props: {
refresherBackground: { default: '#f3f3f3' }
},

data() {
return {
scrollTop: 0,
pullDown: true,
enablePullDown: true
}
},

methods: {
// 【外部调用的方法】,滚动到某个高度
scrollTo(num) {
this.$nextTick(() => {
this.scrollTop = num
})
},

// 【外部调用的方法】,结束下拉刷新
stopPullDown() {
this.pullDown = false
this.isPullDown = false
},

// 下拉刷新触发
onRefresh() {
if (this.isPullDown) {
return
}

this.isPullDown = true
this.pullDown = true
this.$emit('pullDown')
},

// 下拉恢复可用
onRefreshRestore() {
this.pullDown = false
this.isPullDown = false
},

// 列表滚动时
scroll(e) {
// 修复 App 和 H5 端的滚动过快
// #ifdef APP-VUE || H5
this.enablePullDown = e.detail.scrollTop <= 0
// #endif

this.$emit('scroll', e)
}
}
}
</script>

<style lang="less">
:host {
display: block;
height: 100%;
}
</style>

+ 155
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/upload-file.vue Целия файл

@@ -0,0 +1,155 @@
<template>
<view>
<view class="cu-form-group" style="border-bottom: none; padding-bottom: 0;">
<view class="title">
<text v-if="required" class="lr-required">*</text>
{{ title || '' }}
</view>
</view>

<view class="cu-form-group" style="border-top: none;">
<view class="grid col-4 grid-square flex-sub">
<view v-for="(file, index) in value" :key="index" class="bg-img">
<image
v-if="isImgFile(file.type)"
@click="fileClick(index)"
:src="file.path"
:webp="file.type === 'webp'"
mode="aspectFill"
></image>
<view v-else-if="isDocFile(file.type)" @click="fileClick(index)" class="file-icon solids">
<l-icon type="text" />
</view>
<view v-else class="file-icon solids"><l-icon type="text" /></view>

<view v-if="!readonly" @click.stop="delFile(index)" class="cu-tag bg-red" style="height: 24px; width: 24px;">
<l-icon type="close" color="white" style="width: 18px; height: 24px; font-size: 24px;" />
</view>
</view>

<view v-if="!readonly && value.length < Number(number)" @click="chooseFile" class="solids">
<l-icon type="file" />
</view>
</view>
</view>
</view>
</template>

<script>
export default {
name: 'l-upload-file',

props: {
number: { default: 1 },
readonly: {},
value: { default: () => [] },
title: {},
required: {}
},

methods: {
getFileExt(path) {
return /\.(\w{2,5})$/.exec(path)[1] || null
},

isImgFile(type) {
const typeString = (type || '').toLowerCase()
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'image'].includes(typeString)
},

isDocFile(type) {
const typeString = (type || '').toLowerCase()
return ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf'].includes(typeString)
},

delFile(index) {
const newList = JSON.parse(JSON.stringify(this.value))
newList.splice(index, 1)
this.$emit('input', newList)
this.$emit('change')
this.$emit('del')
},

chooseFile() {
// #ifdef MP-DINGTALK
dd.chooseImage({
count: Number(this.number),
success: ({ filePaths }) => {
if (filePaths) {
const newList = JSON.parse(JSON.stringify(this.value || [])).concat(
filePaths.map(t => ({ path: t, type: this.getFileExt(t) }))
)
this.$emit('input', newList)
this.$emit('change', newList)
this.$emit('add')
}
}
})
// #endif

// #ifndef MP-DINGTALK
uni.chooseImage({
count: Number(this.number),
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: ({ tempFilePaths }) => {
const newList = JSON.parse(JSON.stringify(this.value || [])).concat(
tempFilePaths.map(t => ({ path: t, type: this.getFileExt(t) }))
)
this.$emit('input', newList)
this.$emit('change', newList)
this.$emit('add')
}
})
// #endif
},

async fileClick(index) {
const { path, type, uid, size = 0 } = this.value[index]
if (this.isImgFile(type)) {
uni.previewImage({ urls: [path], current: path })
} else if (this.isDocFile(type)) {
// #ifndef H5 || MP-DINGTALK
if (size >= 50 * 1024 * 1024) {
this.TOAST('小程序端无法下载超过50MB的文件,此文件大小为${size}KB,超过限制')
return
}
// #endif

// #ifndef MP-DINGTALK
const tempFilePath = await this.HTTP_DOWNLOAD(uid)
uni.openDocument({ filePath: tempFilePath, fileType: type })
// #endif

// #ifdef MP-DINGTALK
this.TOAST('钉钉小程序只支持查看图片文件')
// #endif
} else {
// #ifndef MP-DINGTALK
this.TOAST('小程序端只支持打开图片和文档(word、pdf等)文件')
// #endif

// #ifdef MP-DINGTALK
this.TOAST('钉钉小程序只支持查看图片文件')
// #endif

// #ifdef APP-VUE
const tempFilePath = await this.HTTP_DOWNLOAD(uid)
uni.openDocument({ filePath: tempFilePath, fileType: type })
// #endif

// #ifdef H5
await this.HTTP_DOWNLOAD(uid)
// #endif
}
}
}
}
</script>

<style lang="less" scoped>
.file-icon {
line-height: 100%;
position: static;
}
</style>

+ 131
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/workflow-action.vue Целия файл

@@ -0,0 +1,131 @@
<template>
<view class="wf-action lr-hidden">
<!-- 催办/撤销,适用于打开我的任务 -->
<view v-if="canUrge || canRevoke" class="form-action padding bg-white margin-top lr-hidden" style="padding-top: 0;">
<l-button v-if="canUrge" @click="$emit('action', 'urge')" size="lg" color="orange" class="block margin-top" block>
催办审核
</l-button>
<l-button
v-if="canRevoke"
@click="$emit('action', 'revoke')"
size="lg"
color="red"
class="block margin-top"
block
>
撤销流程
</l-button>
</view>

<!-- 审批按钮列表,适用于打开待办任务 -->
<view
v-if="buttonList.length > 0"
class="form-action padding bg-white margin-top lr-hidden"
style="padding-top: 0;"
>
<l-button
v-for="button of buttonList"
@click="taskAction(button)"
:key="button.id"
:color="button.color"
class="block margin-top"
size="lg"
block
>
{{ button.name }}
</l-button>
</view>

<!-- 子流程草稿/提交按钮 -->
<view
v-if="['child', 'create', 'again'].includes(type)"
class="form-action padding bg-white margin-top lr-hidden"
style="padding-top: 0;"
>
<l-button @click="$emit('action', 'draft')" size="lg" color="orange" class="block margin-top" block>
保存草稿
</l-button>
<l-button @click="$emit('action', 'submit')" size="lg" color="green" class="block margin-top" block>
提交流程
</l-button>
</view>

<!-- 已阅按钮,传阅模式使用 -->
<view v-if="type === 'refer'" class="form-action padding bg-white margin-top lr-hidden" style="padding-top: 0;">
<l-button @click="$emit('action', 'refer')" color="blue" class="block margin-top" size="lg" block>
标记已阅
</l-button>
</view>
</view>
</template>

<script>
export default {
name: 'l-workflow-action',

props: {
currentNode: { default: () => ({}) },
currentTask: { default: () => ({}) },
type: {}
},

computed: {
// 按钮列表(通过 isSign 判断可否加签;如果可以加签,则额外添加一个加签按钮)
buttonList() {
if (this.openFrom !== 'pre') {
return []
}

const btnList = (this.currentNode.btnList || []).filter(t => Number(t.isHide) !== 1)
if (this.canMultipleSign) {
btnList.push({
id: '__sign__',
code: '__sign__',
name: '加签'
})
}

return btnList.map(t => ({ ...t, color: { agree: 'green', disagree: 'red', end: 'red' }[t.code] || 'blue' }))
},

// 获取表单页是从任务列表哪一栏打开,my=我的,pre=待办,maked=已办
openFrom() {
return this.currentTask.mark
},

// 是否显示催办按钮(仅限我的任务,满足以下条件则可以催办:已开始、未结束、未作废)
canUrge() {
if (this.openFrom !== 'my') {
return false
}

return !this.currentTask.F_IsFinished && this.currentTask.F_EnabledMark !== 3
},

// 是否显示撤销按钮(仅限我的任务,满足以下条件则可以撤销:未开始)
canRevoke() {
if (this.openFrom !== 'my') {
return false
}

return !this.currentTask.F_IsStart
},

// 是否允许加签(仅限待办任务,流程中配置允许加签则)
canMultipleSign() {
if (this.openFrom !== 'pre') {
return false
}

return Number(this.currentNode.isSign) === 1
}
},

methods: {
// 点击审批按钮
taskAction(buttonItem) {
this.$emit('audit', buttonItem)
}
}
}
</script>

+ 44
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-app/workflow-timeline.vue Целия файл

@@ -0,0 +1,44 @@
<template>
<view class="progress">
<l-timeline title="当前">
<l-timeline-item v-if="currentTask && currentTask.F_IsFinished" contentStyle="padding:10px" color="blue">
<text class="text-blod">结束</text>
</l-timeline-item>

<l-timeline-item
v-for="processItem of processList"
:key="processItem.F_Id"
contentStyle="padding:10px 13px"
color="grey"
>
<view
v-if="processItem.F_NodeName"
style="font-size: 1.2em;padding-bottom:2px;margin-bottom: 6px;border-bottom: solid 1px #fff;"
>
{{ processItem.F_NodeName }}
</view>
<view>
<text class="text-bold">{{ processItem.F_CreateUserName || '「系统」' }}</text>
: {{ processItem.F_OperationName }}
</view>
<view v-if="processItem.F_Des">
<text class="text-bold">审批意见</text>
: {{ processItem.F_Des }}
</view>
<view style="font-size: 0.8em;margin-top:3px">{{ processItem.F_CreateDate }}</view>
</l-timeline-item>

<l-timeline-item contentStyle="padding:10px" color="green"><text class="text-blod">起步</text></l-timeline-item>
</l-timeline>
</view>
</template>

<script>
export default {
name: 'l-workflow-timeline',

props: {
processList: { default: () => [] }
}
}
</script>

+ 20
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/colors.js Целия файл

@@ -0,0 +1,20 @@
const colors = [
{ title: '嫣红', name: 'red', color: '#e54d42' },
{ title: '桔橙', name: 'orange', color: '#f37b1d' },
{ title: '明黄', name: 'yellow', color: '#fbbd08' },
{ title: '橄榄', name: 'olive', color: '#8dc63f' },
{ title: '森绿', name: 'green', color: '#39b54a' },
{ title: '天青', name: 'cyan', color: '#1cbbb4' },
{ title: '海蓝', name: 'blue', color: '#0081ff' },
{ title: '姹紫', name: 'purple', color: '#6739b6' },
{ title: '木槿', name: 'mauve', color: '#9c26b0' },
{ title: '桃粉', name: 'pink', color: '#e03997' },
{ title: '棕褐', name: 'brown', color: '#a5673f' },
{ title: '玄灰', name: 'grey', color: '#8799a3' },
{ title: '草灰', name: 'gray', color: '#aaaaaa' },
{ title: '墨黑', name: 'black', color: '#333333' },
{ title: '雅白', name: 'white', color: '#ffffff' }
]

export default colors
export { colors }

+ 302
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/icons.js Целия файл

@@ -0,0 +1,302 @@
const icons = [
'favor_light',
'appreciate',
'check',
'close',
'edit',
'emoji',
'favorfill',
'favor',
'loading',
'locationfill',
'location',
'phone',
'roundcheckfill',
'roundcheck',
'roundclosefill',
'roundclose',
'roundrightfill',
'roundright',
'search',
'taxi',
'timefill',
'time',
'unfold',
'warnfill',
'warn',
'camerafill',
'camera',
'commentfill',
'comment',
'likefill',
'like',
'notificationfill',
'notification',
'order',
'samefill',
'same',
'deliver',
'evaluate',
'pay',
'send',
'shop',
'ticket',
'back',
'cascades',
'discover',
'list',
'more',
'scan',
'settings',
'questionfill',
'question',
'shopfill',
'form',
'pic',
'filter',
'footprint',
'top',
'pulldown',
'pullup',
'right',
'refresh',
'moreandroid',
'deletefill',
'refund',
'cart',
'qrcode',
'remind',
'delete',
'profile',
'home',
'cartfill',
'discoverfill',
'homefill',
'message',
'addressbook',
'link',
'lock',
'unlock',
'vip',
'weibo',
'activity',
'friendaddfill',
'friendadd',
'friendfamous',
'friend',
'goods',
'selection',
'explore',
'present',
'squarecheckfill',
'square',
'squarecheck',
'round',
'roundaddfill',
'roundadd',
'add',
'notificationforbidfill',
'explorefill',
'fold',
'game',
'redpacket',
'selectionfill',
'similar',
'appreciatefill',
'infofill',
'info',
'forwardfill',
'forward',
'rechargefill',
'recharge',
'vipcard',
'voice',
'voicefill',
'friendfavor',
'wifi',
'share',
'wefill',
'we',
'lightauto',
'lightforbid',
'lightfill',
'camerarotate',
'light',
'barcode',
'flashlightclose',
'flashlightopen',
'searchlist',
'service',
'sort',
'down',
'mobile',
'mobilefill',
'copy',
'countdownfill',
'countdown',
'noticefill',
'notice',
'upstagefill',
'upstage',
'babyfill',
'baby',
'brandfill',
'brand',
'choicenessfill',
'choiceness',
'clothesfill',
'clothes',
'creativefill',
'creative',
'female',
'keyboard',
'male',
'newfill',
'new',
'pullleft',
'pullright',
'rankfill',
'rank',
'bad',
'cameraadd',
'focus',
'friendfill',
'cameraaddfill',
'apps',
'paintfill',
'paint',
'picfill',
'refresharrow',
'colorlens',
'markfill',
'mark',
'presentfill',
'repeal',
'album',
'peoplefill',
'people',
'servicefill',
'repair',
'file',
'repairfill',
'taoxiaopu',
'weixin',
'attentionfill',
'attention',
'commandfill',
'command',
'communityfill',
'community',
'read',
'calendar',
'cut',
'magic',
'backwardfill',
'playfill',
'stop',
'tagfill',
'tag',
'group',
'all',
'backdelete',
'hotfill',
'hot',
'post',
'radiobox',
'rounddown',
'upload',
'writefill',
'write',
'radioboxfill',
'punch',
'shake',
'move',
'safe',
'activityfill',
'crownfill',
'crown',
'goodsfill',
'messagefill',
'profilefill',
'sound',
'sponsorfill',
'sponsor',
'upblock',
'weblock',
'weunblock',
'my',
'myfill',
'emojifill',
'emojiflashfill',
'flashbuyfill',
'text',
'goodsfavor',
'musicfill',
'musicforbidfill',
'card',
'triangledownfill',
'triangleupfill',
'roundleftfill-copy',
'font',
'title',
'recordfill',
'record',
'cardboardfill',
'cardboard',
'formfill',
'coin',
'cardboardforbid',
'circlefill',
'circle',
'attentionforbid',
'attentionforbidfill',
'attentionfavorfill',
'attentionfavor',
'titles',
'icloading',
'full',
'mail',
'peoplelist',
'goodsnewfill',
'goodsnew',
'medalfill',
'medal',
'newsfill',
'newshotfill',
'newshot',
'news',
'videofill',
'video',
'exit',
'skinfill',
'skin',
'moneybagfill',
'usefullfill',
'usefull',
'moneybag',
'redpacket_fill',
'subscription',
'loading1',
'github',
'global',
'settingsfill',
'back_android',
'expressman',
'evaluate_fill',
'group_fill',
'play_forward_fill',
'deliver_fill',
'notice_forbid_fill',
'fork',
'pick',
'wenzi',
'ellipse',
'qr_code',
'dianhua',
'cuIcon',
'loading2',
'btn'
]

export default icons
export { icons }

+ 77
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/index.js Целия файл

@@ -0,0 +1,77 @@
/*
Learun-MPUI
力软框架小程序 UI 库 (uni-app 专用)
在项目中使用本 UI 库,需要以下三个步骤:
1、将本项目文件夹放置在 /components/ 内;
2、在 /main.js 中添加以下内容,以导入 mixins 和 CSS:
import '@/components/learun-mpui'
3、在 /pages.json 中添加以下内容,以引入组件:
"easycom": {
"custom": {
// #ifdef MP-ALIPAY
"l-(.+)": "@/components/learun-ui-ali/$1.vue",
// #endif
// #ifndef MP-ALIPAY
"l-(.*)": "@/components/learun-ui-wx/$1.vue",
// #endif
"-": "-"
}
}
*/

import Vue from 'vue'

import uiIcons from './icons.js'
import uiColors from './colors.js'

// 现在 CSS 必须在 <style> 中引入,参考:https://ask.dcloud.net.cn/question/86907
// 等待 uni-app 后续升级后可以解除下行代码的注释
// import './styles/index.css'

const mixins = {
props: {
// #ifdef MP-ALIPAY
style: { default: '' },
className: { default: '' },
// #endif
},

methods: {
getStyle(style) {
const styleHelper = t => {
if (!t) { return '' }

return typeof t === 'object' ?
Object.entries(t).map(([k, v]) => v ? `${k.replace(/([A-Z])/g,"-$1").toLowerCase()}: ${v}; ` : '') :
'' + t
}

return styleHelper(this.style) + styleHelper(style)
},

getColor(colorStr, defaultValue = null) {
if (colorStr.startsWith('#')) {
return colorStr
}

const result = this.colors.find(t => t.color === colorStr)
return result ? result.name : defaultValue
},

getUiIcons() {
return uiIcons
},

getUiColors() {
return uiColors
}
}
}

Vue.mixin(mixins)

+ 33
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/avatar.vue Целия файл

@@ -0,0 +1,33 @@
<template>
<view
:class="[size, round ? 'round' : '', radius ? 'radius' : '', color ? 'bg-' + color : '', className]"
:style="rootStyle"
class="cu-avatar"
>
<slot></slot>
<slot name="badge"></slot>
</view>
</template>

<script>
export default {
name: 'l-avatar',

props: {
src: {},
size: { default: 'df' },
round: {},
radius: {},
color: {}
},

computed: {
rootStyle() {
return this.getStyle({
backgroundImage: this.src ? 'url(' + this.src + ')' : null,
backgroundColor: this.color || this.src ? null : '#ccc'
})
}
}
}
</script>

+ 123
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/banner.vue Целия файл

@@ -0,0 +1,123 @@
<template>
<view
v-if="type === 'default'"
:class="[hexColor ? '' : 'bg-' + color, fixed ? 'banner-fixed' : '', className]"
:style="rootStyle"
class="cu-bar cu-banner"
>
<view @click="clickLeft" class="action"><slot name="left"></slot></view>

<view @click="clickCenter" class="content">
<slot></slot>
{{ title }}
</view>

<view @click="clickRight" class="action"><slot name="right"></slot></view>
</view>

<view
v-else-if="type === 'search'"
:class="[hexColor ? '' : 'bg-' + color, fixed ? 'banner-fixed' : '', className]"
:style="rootStyle"
class="cu-bar search cu-banner"
>
<slot name="left"></slot>
<view class="search-form" :class="[fill ? 'radius' : 'round']" :style="inputStyle">
<slot name="searchInput">
<text class="cuIcon-search"></text>
<input
@input="searchTextChange"
@focus="$emit('inputFocus', $event)"
:placeholder-style="placeholderStyle"
:adjust-position="false"
:placeholder="placeholder"
:value="value"
:focus="focus"
confirm-type="search"
type="text"
ref="bannerInput"
/>
</slot>
</view>
<view v-if="!noSearchButton" class="action">
<slot name="searchButton">
<template v-if="fill">
<view @click="searchClick">搜索</view>
</template>
<button v-else @click="searchClick" class="cu-btn bg-green shadow-blur round">搜索</button>
</slot>
</view>
</view>
</template>

<script>
export default {
name: 'l-banner',

props: {
type: { default: 'default' },
color: { default: 'white' },
hexColor: {},
inputStyle: {},
fill: {},
title: {},
value: {},
placeholder: { default: '搜索图片、文章、视频' },
noSearchButton: {},
placeholderStyle: {},
fixed: {},
noshadow: {},
focus: {}
},

methods: {
searchTextChange(e) {
this.$emit('input', e.detail.value)
this.$emit('change', e.detail.value)
},

clickLeft(e) {
this.$emit('clickLeft', e)
},

clickRight(e) {
this.$emit('clickRight', e)
},

clickCenter(e) {
this.$emit('clickCenter', e)
},

searchClick(F) {
this.$emit('searchClick')
},

inputFocus() {
this.$refs.bannerInput.focus()
}
},

computed: {
rootStyle() {
return this.type === 'default'
? this.getStyle({
backgroundColor: this.hexColor,
boxShadow: this.noshadow ? null : '0 1rpx 6rpx rgba(0, 0, 0, 0.1)'
})
: this.getStyle({
backgroundColor: this.hexColor,
boxShadow: this.noshadow ? null : '0 1rpx 6rpx rgba(0, 0, 0, 0.1)'
})
}
}
}
</script>

<style scoped>
.banner-fixed {
position: fixed;
width: 100%;
top: var(--window-top);
z-index: 1024;
}
</style>

+ 14
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/bar-item-button.vue Целия файл

@@ -0,0 +1,14 @@
<template>
<view :class="['bg-' + color, className]" :style="style" class="submit cu-bar-item-button">{{ title }}</view>
</template>

<script>
export default {
name: 'l-bar-item-button',

props: {
title: {},
color: { default: 'gray' }
}
}
</script>

+ 39
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/bar-item.vue Целия файл

@@ -0,0 +1,39 @@
<template>
<view v-if="type === 'default'" @click="click" :class="className" :style="style" class="action cu-bar-item">
<slot v-if="$slots.$default"></slot>
<template v-else>
<l-icon :type="icon" :color="color" size="lg" class="cuIcon-cu-image" />
<slot name="badge"></slot>
<view :class="'text-' + color">
<slot name="title">{{ title }}</slot>
</view>
</template>
</view>

<view v-else-if="type === 'round'" :class="className" :style="style" class="action text-gray add-action cu-bar-item">
<slot v-if="$slots.$default"></slot>
<template v-else>
<button :class="['bg-' + color, 'cuIcon-' + icon]" class="cu-btn shadow"></button>
<slot name="title">{{ title }}</slot>
</template>
</view>
</template>

<script>
export default {
name: 'l-bar-item',

props: {
type: { default: 'default' },
title: {},
icon: {},
color: { default: 'gray' }
},

methods: {
click(e) {
this.$emit('click', e)
}
}
}
</script>

+ 14
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/bar.vue Целия файл

@@ -0,0 +1,14 @@
<template>
<view :class="[dark ? 'bg-black' : 'bg-white', className]" :style="style" class="cu-bar tabbar"><slot></slot></view>
</template>

<script>
export default {
name: 'l-bar',

props: {
msginput: {},
dark: {}
}
}
</script>

+ 62
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/button.vue Целия файл

@@ -0,0 +1,62 @@
<template>
<button
@click="click"
:class="[
size,
round ? 'round' : '',
shadow ? 'shadow' : '',
buttonColor,
buttonBorder,
block ? 'block' : '',
disabled ? 'disabled' : '',
className
]"
:disabled="disabled ? 'disabled' : null"
:type="disabled && line ? 'default' : null"
:style="style"
hover-class="button-hover"
class="cu-btn"
>
<slot></slot>
</button>
</template>

<script>
export default {
name: 'l-button',

props: {
size: { default: 'df' },
color: {},
round: {},
shadow: {},
line: {},
blod: {},
icon: {},
disabled: {},
block: {}
},

computed: {
buttonBorder() {
return this.line ? `line${this.blod ? 's' : ''}-${this.line || 'gray'}` : ''
},

buttonColor() {
if (this.color) {
return `bg-${this.color}`
} else if (!this.line) {
return `bg-gray`
}

return ''
}
},

methods: {
click(e) {
this.$emit('click', e)
}
}
}
</script>

+ 82
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/card.vue Целия файл

@@ -0,0 +1,82 @@
<template>
<view :class="[outline ? '' : 'no-card', type !== 'base' ? type : '', className]" :style="style" class="cu-card">
<view class="cu-item shadow">
<template v-if="type === 'case'">
<view class="image">
<slot name="img"><image :src="info.img" mode="widthFix"></image></slot>
<slot name="badge">
<l-tag :color="info.badgeColor">{{ info.badge }}</l-tag>
</slot>
<view class="cu-bar bg-shadeBottom">
<slot name="title">
<text class="text-cut">{{ info.title }}</text>
</slot>
</view>
</view>
<view class="cu-list menu-avatar">
<view class="cu-item">
<slot name="avatar">
<l-avatar :src="info.avatar" size="lg" style="margin: 0 30rpx; position: absolute; left: 0;" round />
</slot>
<view class="content flex-sub">
<slot name="user">
<view class="text-grey">{{ info.user }}</view>
</slot>
<view class="text-gray text-sm flex justify-between">
<slot name="tips">{{ info.tips }}</slot>
<slot name="footer"></slot>
</view>
</view>
</view>
</view>
</template>

<template v-else-if="type === 'dynamic'">
<view class="cu-list menu-avatar">
<view class="cu-item">
<slot name="avatar">
<l-avatar :src="info.avatar" size="lg" style="margin: 0 30rpx; position: absolute; left: 0;" round />
</slot>
<view class="content flex-sub">
<slot name="user">
<view>{{ info.user }}</view>
</slot>
<slot name="tips">
<view class="text-gray text-sm flex justify-between">{{ info.tips }}</view>
</slot>
</view>
</view>
</view>
<slot></slot>
<slot name="footer"></slot>
</template>

<template v-else-if="type === 'article'">
<view class="title">
<slot name="title">
<view class="text-cut">{{ info.title }}</view>
</slot>
</view>
<view class="content">
<slot name="img"><image :src="info.img" mode="aspectFill"></image></slot>
<view class="desc">
<view class="text-content"><slot></slot></view>
<slot name="footer"></slot>
</view>
</view>
</template>
</view>
</view>
</template>

<script>
export default {
name: 'l-card',

props: {
type: { default: 'case' },
outline: {},
info: { default: () => ({}) }
}
}
</script>

+ 72
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/chat-input.vue Целия файл

@@ -0,0 +1,72 @@
<template>
<view :style="rootStyle" :class="className" class="cu-bar foot input">
<input
@focus="inputFocus"
@blur="inputBlur"
@input="input"
@confirm="sendClick"
:value="value"
:adjust-position="false"
:enableNative="false"
:focus="false"
:placeholder="placeholder"
:disabled="inputDisabled"
:confirm-hold="confirmHold"
cursor-spacing="10"
class="solid-bottom"
confirm-type="send"
type="text"
/>
<slot name="action">
<button @click="sendClick" :disabled="buttonDisabled" class="cu-btn bg-green shadow">发送</button>
</slot>
</view>
</template>

<script>
export default {
name: 'l-chat-input',

props: {
value: {},
placeholder: {},
inputDisabled: {},
buttonDisabled: {},
confirmHold: {}
},

data() {
return { inputBottom: 0 }
},

methods: {
input(e) {
this.$emit('input', e.detail.value)
this.$emit('change', e.detail.value)
},

inputFocus(e) {
if (e && e.detail && e.detail.height) {
this.inputBottom = e.detail.height
}
this.$emit('focus')
},

inputBlur(e) {
this.inputBottom = 0
this.$emit('blur')
},

sendClick(e) {
this.$emit('sendMsg', this.value)
}
},

computed: {
rootStyle() {
return this.getStyle({ bottom: this.inputBottom + 'px' })
// return this.getStyle({ bottom: '0px' })
}
}
}
</script>

+ 101
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/chat-msg.vue Целия файл

@@ -0,0 +1,101 @@
<template>
<view :class="[type === 'left' ? '' : 'self', className]" :style="style" class="cu-item">
<slot v-if="type === 'left'" name="avatar">
<view
v-if="type === 'left' && avatar"
:style="{ backgroundImage: `url(${avatar})`, borderRadius: roundAvatar ? '50%' : '3px' }"
class="cu-avatar radius"
></view>
<image
v-else-if="type === 'left' && imgAvatar"
:src="imgAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'};` + imgStyle"
mode="aspectFill"
class="chat-msg-imgAvatar cu-avatar radius"
></image>
<text
v-else-if="type === 'left' && iconAvatar"
:class="'cuIcon-' + iconAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'}; ` + iconStyle"
class="chat-msg-iconAvatar flex justify-center align-center"
></text>

<!-- 支付宝小程序,使用 l-icon 报错 -->
<!--
<l-icon
v-else-if="type === 'left' && iconAvatar"
:type="iconAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'}; ` + iconStyle"
class="chat-msg-iconAvatar flex justify-center align-center"
/>
-->
</slot>

<view class="main">
<view
:class="[type === 'left' ? (leftColor ? 'bg-' + leftColor : '') : rightColor ? 'bg-' + rightColor : '']"
class="content shadow"
>
<text style="word-break: break-all;">
<slot></slot>
{{ content }}
</text>
</view>
</view>

<slot v-if="type !== 'left'" name="avatar">
<view
v-if="type !== 'left' && avatar"
:style="{ backgroundImage: `url(${avatar})`, borderRadius: roundAvatar ? '50%' : '3px' }"
class="cu-avatar radius"
></view>
<image
v-else-if="type !== 'left' && imgAvatar"
:src="imgAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'};` + imgStyle"
mode="aspectFill"
class="chat-msg-imgAvatar cu-avatar radius"
></image>
<text
v-else-if="type !== 'left' && iconAvatar"
:class="'cuIcon-' + iconAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'}; ` + iconStyle"
class="chat-msg-iconAvatar flex justify-center align-center"
></text>

<!-- 支付宝小程序,使用 l-icon 报错 -->
<!--
<l-icon
v-else-if="type !== 'left' && iconAvatar"
:type="iconAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'}; ` + iconStyle"
class="chat-msg-iconAvatar flex justify-center align-center"
/>
-->
</slot>

<view class="date">
<slot name="date">{{ date }}</slot>
</view>
</view>
</template>

<script>
export default {
name: 'l-chat-msg',

props: {
content: { default: '' },
type: { default: 'left' },
roundAvatar: {},
date: {},
leftColor: {},
rightColor: { default: 'green' },
avatar: {},
iconAvatar: {},
iconStyle: { default: '' },
imgAvatar: {},
imgStyle: { default: '' }
}
}
</script>

+ 20
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/chat.vue Целия файл

@@ -0,0 +1,20 @@
<template>
<view :class="className" :style="style" class="cu-chat">
<slot></slot>
<view v-if="empty" class="padding text-gray text-center">{{ emptyTips }}</view>
<view v-if="!empty && nomore" class="padding text-gray text-center">{{ nomoreTips }}</view>
</view>
</template>

<script>
export default {
name: 'l-chat',

props: {
empty: {},
emptyTips: { default: '暂无消息' },
nomore: { default: true },
nomoreTips: { default: '没有更多消息了' }
}
}
</script>

+ 111
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/checkbox-picker.vue Целия файл

@@ -0,0 +1,111 @@
<template>
<l-label @click="click" :arrow="!readonly" :required="required" :title="title" :class="className" :style="style">
{{ display || displayPlaceholder }}
<l-modal
v-if="modal"
@ok="confirm"
@cancel="cancel"
@checkboxChange="checkboxChange"
:checkbox="innerChecked"
:value="modalOpen"
:range="range"
:readonly="readonly"
type="checkbox"
/>
</l-label>
</template>

<script>
export default {
name: 'l-checkbox-picker',

props: {
value: {},
range: { default: () => [] },
title: {},
required: {},
placeholder: {},
readonly: {}
},

data() {
return {
modal: false,
modalOpen: false,
innerChecked: this.value || []
}
},

methods: {
click() {
if (this.readonly || this.modal || this.modalOpen) {
return
}

this.innerChecked = this.value
this.modal = true
setTimeout(() => {
this.modalOpen = true
this.$emit('open')
}, 300)
},

checkboxChange(newVal) {
this.innerChecked = newVal
},

confirm() {
this.modalOpen = false
setTimeout(() => {
this.modal = false
this.$emit('input', this.innerChecked)
this.$emit('change', this.innerChecked)
this.$emit('close')
}, 300)
},

cancel() {
this.modalOpen = false
setTimeout(() => {
this.modal = false
this.$emit('close')
}, 300)
}
},

computed: {
display() {
if (this.value.length <= 0) {
return null
}

if (!this.objMode) {
return this.value.join(',')
}

return this.value
.reduce((a, b) => {
const selected = this.range.find(t => t.value === b)
return selected ? a.concat(selected.text) : a
}, [])
.join(',')
},

displayPlaceholder() {
if (this.readonly) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return this.title ? `请选择${this.title}` : '请选择…'
},

objMode() {
return typeof this.range[0] === 'object'
}
}
}
</script>

+ 50
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/checkbox.vue Целия файл

@@ -0,0 +1,50 @@
<template>
<view :class="className" :style="style" class="cu-form-group">
<view class="title">{{ title }}</view>
<checkbox
@change="change"
:class="[isCheck ? 'checked' : '', color ? color : '', round ? 'round' : '']"
:checked="isCheck"
:value="checkboxValue"
:disabled="disabled"
></checkbox>
</view>
</template>

<script>
export default {
name: 'l-checkbox',

props: {
title: {},
disabled: {},
round: {},
color: {},
checkboxValue: {},
checked: {},
value: { default: () => [] }
},

methods: {
change(e) {
let newVal = this.value
const isCurrentCheck = e.detail.value

if (isCurrentCheck) {
newVal = [...this.value, this.checkboxValue]
} else {
newVal = this.value.filter(t => t !== this.checkboxValue)
}

this.$emit('input', newVal)
this.$emit('change', newVal)
}
},

computed: {
isCheck() {
return this.value.includes(this.checkboxValue)
}
}
}
</script>

+ 91
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/date-picker.vue Целия файл

@@ -0,0 +1,91 @@
<template>
<l-label :class="className" :style="style" :arrow="!disabled" :required="required" :title="title">
<!-- #ifndef MP-DINGTALK -->
<picker @change="change" :fields="fields" :disabled="disabled" :value="value" :start="start" :end="end" mode="date">
<view class="picker">{{ value || displayPlaceholder }}</view>
</picker>
<!-- #endif -->

<!-- #ifdef MP-DINGTALK -->
<text @click="click">{{ value || displayPlaceholder }}</text>
<!-- #endif -->
</l-label>
</template>

<script>
export default {
name: 'l-date-picker',

props: {
title: {},
start: { default: '1900-01-01' },
end: { default: '2100-01-01' },
fields: { default: 'day' },
disabled: {},
placeholder: {},
required: {},
value: {}
},

methods: {
click(e) {
if (this.disabled) {
return
}

this.$emit('click')

// #ifdef MP-DINGTALK
const getCurrent = () => {
const now = new Date()

const year = now.getFullYear().toString()
const month = (now.getMonth() + 1).toString().padStart(2, '0')
const date = now
.getDate()
.toString()
.padStart(2, '0')

return `${year}-${month}-${date}`
}

const currentDate = this.value ? this.value : getCurrent()
this.$emit('open')

dd.datePicker({
currentDate,
format: 'yyyy-MM-dd',
success: ({ date }) => {
if (date) {
this.$emit('input', date)
this.$emit('change', date)
}
},
complete: () => {
this.$emit('close')
}
})
// #endif
},

change(e) {
this.$emit('input', e.detail.value)
this.$emit('change', e.detail.value)
}
},

computed: {
displayPlaceholder() {
if (this.disabled) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return this.title ? `请选择${this.title}` : '请选择日期…'
}
}
}
</script>

+ 621
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/datetime-panel.vue Целия файл

@@ -0,0 +1,621 @@
<template>
<view :class="className" :style="style" class="lr-datetime-picker">
<view
@tap.stop="maskClick"
@touchmove.stop.prevent
:class="{ show: showPicker }"
class="lr-datetime-mask"
catchtouchmove="true"
></view>
<view :class="{ show: showPicker }" class="lr-datetime-content">
<view @touchmove.stop.prevent catchtouchmove="true" class="lr-datetime-hd">
<view @tap.stop="pickerCancel" class="lr-datetime-btn">取消</view>
<view @tap.stop="pickerConfirm" :style="{ color: color }" class="lr-datetime-btn">确定</view>
</view>
<view class="lr-datetime-view">
<picker-view
v-if="isAll"
@change="bindChange"
:indicator-style="itemHeight"
:value="values"
style="height: 100%;"
>
<picker-view-column>
<view v-for="(item, index) in dateObj.years" :key="index" class="lr-datetime-item">{{ item }}年</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.months" :key="index" class="lr-datetime-item">{{ item }}月</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.days" :key="index" class="lr-datetime-item">{{ item }}日</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.hours" :key="index" class="lr-datetime-item">{{ item }}时</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.minutes" :key="index" class="lr-datetime-item">{{ item }}分</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.seconds" :key="index" class="lr-datetime-item">{{ item }}秒</view>
</picker-view-column>
</picker-view>
<picker-view
v-else
@change="bindDateChange"
:indicator-style="itemHeight"
:value="dateValues"
style="height: 100%;"
>
<picker-view-column class="yu-picker-column">
<view v-for="(item, index) in dateObj.dates" :key="index" class="lr-datetime-item">{{ item }}</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.hours" :key="index" class="lr-datetime-item">{{ item }}时</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.minutes" :key="index" class="lr-datetime-item">{{ item }}分</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.seconds" :key="index" class="lr-datetime-item">{{ item }}秒</view>
</picker-view-column>
</picker-view>
</view>
</view>
</view>
</template>

<script>
export default {
name: 'l-datetime-panel',

props: {
//全部日期有效可选
isAll: { default: true },
//颜色
color: { default: '#1aad19' },
//开始年份
startYear: { default: '1900' },
//结束年份
endYear: { default: '2100' },
//设置默认日期时间
val: { default: '' }
},

data() {
let date = new Date()
let year = date.getFullYear()
let month = date.getMonth() + 1
let day = date.getDate()
let hour = date.getHours()
let minute = date.getMinutes()
let second = date.getSeconds()
let dates = []
let months = []
let years = []
let days = []
let hours = []
let minutes = []
let seconds = []
for (let i = month; i <= month + 2; i++) {
//获取包括当前月份在内的3个月内的日期
let localMonth = i
let localYear = year
if (i == 13) {
localYear += 1
}
if (i >= 13) {
localMonth -= 12
}
let total = new Date(localYear, localMonth, 0).getDate()
if (i == month) {
for (let j = day; j <= total; j++) {
let m = localMonth
let d = j
if (localMonth < 10) {
m = '0' + m
}
if (j < 10) {
d = '0' + d
}
let str = year + '-' + m + '-' + d
dates.push(str)
}
} else {
for (let j = 1; j <= total; j++) {
let m = localMonth
let d = j
if (localMonth < 10) {
m = '0' + m
}
if (j < 10) {
d = '0' + d
}
let str = year + '-' + m + '-' + d
dates.push(str)
}
}
}
for (let i = parseInt(this.startYear); i <= this.endYear; i++) {
years.push(i)
}
for (let i = 1; i <= 12; i++) {
let str = i
if (i < 10) {
str = '0' + str
} else {
str = '' + str
}
months.push(str)
}

if (this.val) {
let valueArr = this.val.split(' ')
let valueDateArr = valueArr[0].split('-')
let totalCurrent = new Date(valueDateArr[0], valueDateArr[1], 0).getDate()
for (let i = 1; i <= totalCurrent; i++) {
let str = i
if (i < 10) {
str = '0' + str
} else {
str = '' + str
}
days.push(str)
}
} else {
let totalCurrent = new Date(year, month, 0).getDate()
for (let i = 1; i <= totalCurrent; i++) {
let str = i
if (i < 10) {
str = '0' + str
} else {
str = '' + str
}
days.push(str)
}
}
for (let i = 0; i < 24; i++) {
let str = i
if (i < 10) {
str = '0' + str
} else {
str = '' + str
}
hours.push(str)
}
for (let i = 0; i < 60; i++) {
let str = i
if (i < 10) {
str = '0' + str
} else {
str = '' + str
}
minutes.push(str)
}
for (let i = 0; i < 60; i++) {
let str = i
if (i < 10) {
str = '0' + str
} else {
str = '' + str
}
seconds.push(str)
}
let dateObj = {
dates,
years,
months,
days,
hours,
minutes,
seconds
}
return {
value: this.val,
year,
month,
day,
hour,
minute,
second,
dateObj,
itemHeight: `height: ${uni.upx2px(88)}px;`,
values: [0, 0, 0, 0, 0, 0],
selectArr: [],
selectRes: '',
showPicker: false,
dateValues: [0, 0, 0, 0]
}
},

mounted() {
this.setDate()
},

methods: {
setDate(dt) {
if (dt) {
this.value = dt
}

this.initDate()

if (!this.value) {
this.showCurrent()
}
},

initDate() {
let _this = this
//解析默认显示的日期时间
if (_this.value) {
let values = [0, 0, 0, 0, 0, 0]
let dateValues = [0, 0, 0, 0]
let valueStr = _this.value
let valueArr = valueStr.split(' ')
let valueDateArr = valueArr[0].split('-')
let valueTimeArr = valueArr[1].split(':')
if (_this.isAll) {
values[0] = valueDateArr[0] - _this.startYear > 0 ? valueDateArr[0] - _this.startYear : 0
values[1] = parseInt(valueDateArr[1]) - 1
values[2] = parseInt(valueDateArr[2]) - 1
values[3] = parseInt(valueTimeArr[0])
values[4] = parseInt(valueTimeArr[1])
values[5] = parseInt(valueTimeArr[2])
_this.$nextTick(() => {
_this.values = values
})
if (valueDateArr[0] - _this.startYear >= 0) {
_this.selectArr = [
valueDateArr[0],
valueDateArr[1],
valueDateArr[2],
valueTimeArr[0],
valueTimeArr[1],
valueTimeArr[2]
]
_this.selectRes = _this.value
} else {
_this.selectArr = [
_this.formatNum(_this.startYear),
valueDateArr[1],
valueDateArr[2],
valueTimeArr[0],
valueTimeArr[1],
valueTimeArr[2]
]
_this.selectRes = `${this.formatNum(_this.startYear) +
'-' +
valueDateArr[1] +
'-' +
valueDateArr[2] +
' ' +
valueTimeArr[0] +
':' +
valueTimeArr[1] +
':' +
valueTimeArr[2]}`
}
} else {
let str = valueDateArr.join('')
let localStr = _this.formatNum(_this.year) + _this.formatNum(_this.month) + _this.formatNum(_this.day) + ''
if (str < localStr) {
dateValues[0] = 0
_this.selectArr = [
_this.formatNum(_this.year),
_this.formatNum(_this.month),
_this.formatNum(_this.day),
valueTimeArr[0],
valueTimeArr[1],
valueTimeArr[2]
]
_this.selectRes = `${_this.formatNum(_this.year) +
'-' +
_this.formatNum(_this.month) +
'-' +
_this.formatNum(_this.day) +
' ' +
valueTimeArr[0] +
':' +
valueTimeArr[1] +
':' +
valueTimeArr[2]}`
} else {
let num = 0 //计算默认日期和当前日期相隔天数,计算下标
let start =
_this.formatNum(_this.year) + '-' + _this.formatNum(_this.month) + '-' + _this.formatNum(_this.day)
let res = _this.getBetweenDateStr(start, valueArr[0])
dateValues[0] = res.length - 1
_this.selectArr = [
valueDateArr[0],
valueDateArr[1],
valueDateArr[2],
valueTimeArr[0],
valueTimeArr[1],
valueTimeArr[2]
]
_this.selectRes = _this.value
}
dateValues[1] = parseInt(valueTimeArr[0])
dateValues[2] = parseInt(valueTimeArr[1])
dateValues[3] = parseInt(valueTimeArr[2])
_this.$nextTick(() => {
_this.dateValues = dateValues
})
}
return
}
if (_this.isAll) {
_this.selectArr = [_this.formatNum(_this.startYear), '01', '01', '00', '00', '00']
_this.selectRes = `${_this.formatNum(_this.startYear) + '-01-01 00:00:00'}`
} else {
_this.selectArr = [_this.formatNum(_this.year), _this.formatNum(_this.month), '01', '00', '00', '00']
_this.selectRes = `${_this.formatNum(_this.year) + '-' + _this.formatNum(_this.month) + '-01 00:00:00'}`
}
},
showCurrent() {
//显示当前的日期时间
let arr = [0, 0, 0, 0, 0, 0]
let dateArr = [0, 0, 0, 0]
this.selectArr = [
this.formatNum(this.year),
this.formatNum(this.month),
this.formatNum(this.day),
this.formatNum(this.hour),
this.formatNum(this.minute),
this.formatNum(this.second)
]
this.selectRes = `${this.formatNum(this.year) +
'-' +
this.formatNum(this.month) +
'-' +
this.formatNum(this.day) +
' ' +
this.formatNum(this.hour) +
':' +
this.formatNum(this.minute) +
':' +
this.formatNum(this.second)}`
if (this.isAll) {
arr[0] = this.year - this.startYear
arr[1] = this.month - 1
arr[2] = this.day - 1
arr[3] = this.hour
arr[4] = this.minute
arr[5] = this.second
this.$nextTick(() => {
this.values = arr
})
} else {
dateArr[1] = this.hour
dateArr[2] = this.minute
dateArr[3] = this.second
this.$nextTick(() => {
this.dateValues = dateArr
})
}
},
initDayArr: (year, month) => {
//初始化月份天数
let totalDay = new Date(year, month, 0).getDate()
let dayArr = []
for (let i = 1; i <= totalDay; i++) {
if (i < 10) {
i = '0' + i
} else {
i = i + ''
}
dayArr.push(i)
}
return dayArr
},
formatNum(num) {
//日期时间的初始化
return num < 10 ? '0' + num : num + ''
},
maskClick() {
//日期时间的遮罩
this.showPicker = false
this.$emit('cancel')
},
show() {
//日期时间的显示
this.showPicker = true
},
hide() {
//日期时间的隐藏
this.showPicker = false
this.$emit('cancel')
},
pickerCancel() {
//日期时间取消
this.showPicker = false
this.$emit('cancel', {
selectArr: this.selectArr
})
},
pickerConfirm(e) {
//日期时间确定
this.showPicker = false
this.$emit('confirm', {
selectArr: this.selectArr,
selectRes: this.selectRes
})
},
bindChange(e) {
//默认滚动日期时间方法
let valueArr = e.detail.value
let year = '',
month = '',
day = '',
hour = '',
minute = '',
second = ''
let selectArr = this.selectArr
let dayArr = []
year = this.dateObj.years[valueArr[0]]
month = this.dateObj.months[valueArr[1]]
day = this.dateObj.days[valueArr[2]]
hour = this.dateObj.hours[valueArr[3]]
minute = this.dateObj.minutes[valueArr[4]]
second = this.dateObj.seconds[valueArr[5]]
if (year != selectArr[0]) {
dayArr = this.initDayArr(year, month)
this.dateObj.days = dayArr
}
if (month != selectArr[1]) {
dayArr = this.initDayArr(year, month)
this.dateObj.days = dayArr
}
this.selectArr = [year, month, day, hour, minute, second]
this.selectRes = `${year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second}`
this.$nextTick(() => {
this.values = valueArr
})
},
bindDateChange(e) {
//有效日期的滚动日期时间方法
let valueArr = e.detail.value
let dateStr = '',
dateArr = [],
hour = '',
minute = '',
second = ''
let selectArr = this.selectArr
let dayArr = []
dateStr = this.dateObj.dates[valueArr[0]]
dateArr = dateStr.split('-')
hour = this.dateObj.hours[valueArr[1]]
minute = this.dateObj.minutes[valueArr[2]]
second = this.dateObj.seconds[valueArr[3]]
this.selectArr = [dateArr[0], dateArr[1], dateArr[2], hour, minute, second]
this.selectRes = `${dateArr[0] + '-' + dateArr[1] + '-' + dateArr[2] + ' ' + hour + ':' + minute + ':' + second}`
this.$nextTick(() => {
this.dateValues = valueArr
})
},
//遍历两个日期间的所有日期
getBetweenDateStr(start, end) {
let result = []
let beginDay = start.split('-')
let endDay = end.split('-')
let diffDay = new Date()
let dateList = new Array()
let i = 0
diffDay.setDate(beginDay[2])
diffDay.setMonth(beginDay[1] - 1)
diffDay.setFullYear(beginDay[0])
result.push(start)
while (i == 0) {
let countDay = diffDay.getTime() + 24 * 60 * 60 * 1000
diffDay.setTime(countDay)
dateList[2] = diffDay.getDate()
dateList[1] = diffDay.getMonth() + 1
dateList[0] = diffDay.getFullYear()
if (String(dateList[1]).length == 1) {
dateList[1] = '0' + dateList[1]
}
if (String(dateList[2]).length == 1) {
dateList[2] = '0' + dateList[2]
}
result.push(dateList[0] + '-' + dateList[1] + '-' + dateList[2])
if (dateList[0] == endDay[0] && dateList[1] == endDay[1] && dateList[2] == endDay[2]) {
i = 1
}
}
return result
}
}
}
</script>

<style>
.lr-datetime-picker {
position: relative;
z-index: 888;
}

.lr-datetime-mask {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
visibility: hidden;
opacity: 0;
transition: all 0.3s ease;
}

.lr-datetime-mask.show {
visibility: visible;
opacity: 1;
}

.lr-datetime-content {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
transition: all 0.3s ease;
transform: translateY(100%);
z-index: 3000;
}

.lr-datetime-content.show {
transform: translateY(0);
}

.lr-datetime-hd {
display: flex;
align-items: center;
padding: 0 30upx;
height: 44px;
background-color: #fff;
position: relative;
text-align: center;
font-size: 15px;
justify-content: space-between;
}

.lr-datetime-btn {
font-size: 14px;
}

.lr-datetime-hd:after {
content: ' ';
position: absolute;
left: 0;
bottom: 0;
right: 0;
height: 1px;
border-bottom: 1px solid #e5e5e5;
color: #e5e5e5;
transform-origin: 0 100%;
transform: scaleY(0.5);
}

.lr-datetime-view {
width: 100%;
height: 200px;
overflow: hidden;
background-color: rgba(255, 255, 255, 1);
z-index: 666;
}

.yu-picker-column {
-webkit-flex: 2;
-webkit-box-flex: 2;
flex: 2;
}

.lr-datetime-item {
text-align: center;
width: 100%;
height: 88upx;
line-height: 88upx;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 30upx;
}
</style>

+ 143
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/datetime-picker.vue Целия файл

@@ -0,0 +1,143 @@
<template>
<!-- #ifndef MP-DINGTALK -->
<l-label @click="click" :arrow="!disabled" :required="required" :title="title" :class="className" :style="style">
{{ value || displayPlaceholder }}
<l-datetime-panel
v-if="datetimeModal"
@confirm="confirm"
@cancel="cancel"
:val="value"
:startYear="1900"
ref="datetime"
allShow
isAll
/>
</l-label>
<!-- #endif -->

<!-- #ifdef MP-DINGTALK -->
<l-label :arrow="arrow" :required="required" :title="title" :class="className" :style="style">
<text @click="click">{{ value || displayPlaceholder }}</text>
</l-label>
<!-- #endif -->
</template>

<script>
export default {
name: 'l-datetime-picker',

props: {
title: {},
disabled: {},
placeholder: {},
required: {},
value: {}
},

// #ifndef MP-DINGTALK
data() {
return {
datetimeModal: false
}
},
// #endif

methods: {
click(e) {
if (this.disabled) {
return
}

// #ifndef MP-DINGTALK
// 支付宝
if (this.datetimeModal) {
return
}

const datetimePanel = this.$refs.datetime
this.datetimeModal = true

this.$nextTick(() => {
datetimePanel.setDate(this.value)
datetimePanel.show()
this.$emit('open')
})
// #endif

// #ifdef MP-DINGTALK
// 钉钉
const getCurrent = () => {
const now = new Date()

const year = now.getFullYear().toString()
const month = (now.getMonth() + 1).toString().padStart(2, '0')
const date = now
.getDate()
.toString()
.padStart(2, '0')

const hours = now
.getHours()
.toString()
.padStart(2, '0')
const minutes = now
.getMinutes()
.toString()
.padStart(2, '0')

return `${year}-${month}-${date} ${hours}:${minutes}`
}

const currentDate = this.value ? this.value : getCurrent()
this.$emit('open')

dd.datePicker({
currentDate,
format: 'yyyy-MM-dd HH:mm',
success: ({ date }) => {
if (date) {
this.$emit('input', date + ':00')
this.$emit('change', date + ':00')
}
},
complete: () => {
this.$emit('close')
}
})
// #endif
},

// #ifndef MP-DINGTALK
confirm({ selectRes }) {
setTimeout(() => {
this.datetimeModal = false
this.$emit('input', selectRes)
this.$emit('change', selectRes)
this.$emit('close')
}, 300)
},

cancel() {
setTimeout(() => {
this.datetimeModal = false
}, 300)
this.$emit('close')
}
// #endif
},

computed: {
displayPlaceholder() {
if (this.disabled) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return this.title ? `请选择${this.title}` : '请选择…'
}
}
}
</script>

+ 28
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/icon.vue Целия файл

@@ -0,0 +1,28 @@
<template>
<text
@click="click"
:class="['cuIcon-' + type, color ? 'text-' + color : '', shadow ? 'text-shadow' : '', className]"
:style="style"
class="cuIcon"
>
<slot></slot>
</text>
</template>

<script>
export default {
name: 'l-icon',

props: {
color: {},
type: { require: true },
shadow: {}
},

methods: {
click(e) {
this.$emit('click', e)
}
}
}
</script>

+ 59
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/input.vue Целия файл

@@ -0,0 +1,59 @@
<template>
<view :class="className" :style="style" class="cu-form-group">
<view class="title">
<text v-if="required" class="lr-required">*</text>
<slot name="title">{{ title }}</slot>
</view>
<input
@input="input"
@change="input"
:value="value"
:type="type"
:disabled="disabled"
:password="password"
:maxlength="maxlength"
:placeholder="displayPlaceholder"
:style="{ textAlign: left ? 'left' : 'right' }"
/>
<slot name="suffix"></slot>
</view>
</template>

<script>
export default {
name: 'l-input',

props: {
type: { default: 'text' },
title: {},
placeholder: {},
maxlength: { default: -1 },
disabled: {},
password: {},
left: {},
required: {},
value: {}
},

methods: {
input(e) {
this.$emit('input', e.detail.value)
this.$emit('change', e.detail.value)
}
},

computed: {
displayPlaceholder() {
if (this.disabled) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return this.title ? `请输入${this.title}` : `请输入…`
}
}
}
</script>

+ 45
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/label.vue Целия файл

@@ -0,0 +1,45 @@
<template>
<view :class="className" :style="style" class="cu-form-group">
<view class="title">
<slot name="title">
<text v-if="required" class="lr-required">*</text>
{{ title }}
</slot>
</view>
<view @click="itemClick" :class="[arrow ? 'form-item-arrow' : 'form-item-noarrow']" class="action form-item-action">
<view class="picker"><slot></slot></view>
</view>
</view>
</template>

<script>
export default {
name: 'l-label',

props: {
title: {},
arrow: {},
required: {}
},

methods: {
itemClick(e) {
this.$emit('click', e)
}
}
}
</script>

<style>
.form-item-action {
flex: 1;
padding-right: 40rpx;
overflow: hidden;
position: relative;
text-align: right;
}

.form-item-noarrow {
padding-right: 0;
}
</style>

+ 90
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/list-item.vue Целия файл

@@ -0,0 +1,90 @@
<template>
<view @click="$emit('click', $event)" :class="[arrow ? 'arrow' : '', className]" :style="style" class="cu-item">
<slot name="avatar">
<l-avatar
v-if="avatar"
:src="avatar"
:radius="!roundAvatar"
:round="roundAvatar"
:style="avatarStyle"
size="lg"
/>
<image
v-else-if="imgAvatar"
:src="imgAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'}; ` + imgStyle"
mode="aspectFill"
class="list-item-imgAvatar"
/>
<text
v-else-if="iconAvatar"
:class="'cuIcon-' + iconAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'}; ` + iconStyle"
class="list-item-iconAvatar flex justify-center align-center"
></text>

<!-- 支付宝小程序,使用 l-icon 报错 -->
<!--
<l-icon
v-else-if="iconAvatar"
:type="iconAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'}; ` + iconStyle"
class="flex justify-center align-center"
/>
-->
</slot>

<view class="content">
<view class="text-grey">
<slot></slot>
{{ title || '' }}
</view>

<view v-if="extra || $slots.extra" class="text-gray text-sm flex">
<view class="text-cut text-grey">
<slot name="extra">{{ extra }}</slot>
</view>
</view>
</view>

<view class="action">
<view v-if="times" class="text-grey text-sm">
<view v-if="times" style="display: flex; flex-direction: column; align-items: flex-end;">
<view class="text-right margin-bottom-xs">{{ times[0] }}</view>
<view v-if="times[1]" class="text-right">{{ times[1] }}</view>
</view>
</view>

<view v-else-if="time || $slots.time || $slots.tips" class="text-grey text-sm">
<slot name="time">{{ time || '' }}</slot>
<slot name="tips"></slot>
</view>

<template v-else>
<slot name="action">{{ action }}</slot>
</template>
</view>
</view>
</template>

<script>
export default {
name: 'l-list-item',

props: {
title: {},
action: {},
arrow: {},
extra: {},
time: { default: '' },
times: {},
roundAvatar: {},
avatar: {},
avatarStyle: { default: '' },
iconAvatar: {},
iconStyle: { default: '' },
imgAvatar: { default: '' },
imgStyle: { default: '' }
}
}
</script>

+ 26
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/list.vue Целия файл

@@ -0,0 +1,26 @@
<template>
<view
class="cu-list"
:class="[card ? 'card-menu margin-tb' : '', message ? 'menu-avatar' : 'menu', className]"
:style="style"
>
<slot></slot>
<view v-if="empty" class="padding text-gray text-center">{{ emptyTips }}</view>
<view v-if="!empty && nomore" class="padding text-gray text-center">{{ nomoreTips }}</view>
</view>
</template>

<script>
export default {
name: 'l-list',

props: {
card: {},
message: {},
empty: {},
emptyTips: { default: '列表为空' },
nomore: { default: false },
nomoreTips: { default: '没有更多了' }
}
}
</script>

+ 24
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/loading.vue Целия файл

@@ -0,0 +1,24 @@
<template>
<view
:class="['bg-' + color, error ? 'erro' : completed ? 'over' : 'loading', className]"
:style="style"
class="cu-load"
>
{{ error ? errorText : completed ? completeText : loadingText }}
</view>
</template>

<script>
export default {
name: 'l-loading',

props: {
completed: {},
error: {},
color: { default: 'gray' },
loadingText: { default: '加载中…' },
completeText: { default: '加载完成' },
errorText: { default: '加载失败' }
}
}
</script>

+ 221
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/modal.vue Целия файл

@@ -0,0 +1,221 @@
<template>
<view
@click="modalClick"
:class="[value ? 'show' : '', type === 'bottom' ? 'bottom-modal' : '', className]"
:style="style"
class="cu-modal"
>
<!-- 图片弹出窗口 -->
<view v-if="type === 'img'" class="cu-dialog">
<view
:style="{ backgroundImage: 'url(' + img + ')', height: '200px' }"
class="bg-img"
style="position: relative;"
>
<view @tap="close" class="action text-white" style="position: absolute;top: 10px;right: 10px;">
<l-icon type="close" />
</view>
</view>
<view class="cu-bar bg-white"><view @tap="close" class="action margin-0 flex-sub solid-left">关闭</view></view>
</view>

<!-- 单选弹出窗口 -->
<view v-else-if="type === 'radio'" @tap.stop class="cu-dialog">
<radio-group @change="radioChange" class="block">
<view class="cu-list menu text-left">
<view v-for="(item, index) of range" :key="index" class="cu-item">
<label class="flex justify-between align-center flex-sub">
<view class="flex-sub">{{ objectMode ? item[textField] : item }}</view>
<radio
:checked="radioIndex === index ? true : false"
:value="objectMode ? item[valueField] : item"
:class="radioIndex === index ? 'checked' : ''"
class="round"
></radio>
</label>
</view>
</view>
</radio-group>
</view>

<!-- 多选弹出窗口 -->
<view v-else-if="type === 'checkbox'" @tap.stop class="cu-dialog">
<view class="cu-bar bg-white">
<view @tap="close(0)" class="action text-blue">取消</view>
<view v-if="!readonly" @tap="close(1)" class="action text-green">确定</view>
</view>
<view class="grid col-3 padding-sm">
<view v-for="(item, index) in range" :key="index" class="padding-xs">
<button
@tap="chooseCheckbox(index)"
:class="checkboxIndex.includes(index) ? 'bg-orange' : 'line-orange'"
class="cu-btn orange lg block"
>
{{ objectMode ? item[textField] : item }}
</button>
</view>
</view>
</view>

<!-- 普通/对话/底部弹出窗口 -->
<view v-else class="cu-dialog">
<view :class="[type === 'bottom' ? '' : 'justify-end pading-left']" class="cu-bar bg-white">
<!-- 普通/对话弹出窗口 -->
<template v-if="type === 'default' || type === 'confirm'">
<view class="content">
<slot name="title">{{ title }}</slot>
</view>
<view @tap="close" class="action"><l-icon type="close" color="red" /></view>
</template>

<!-- 底部弹出窗口 -->
<template v-if="type === 'bottom'">
<slot name="action">
<l-button @click="close" class="action padding-lr" line="red">取消</l-button>
<view class="action padding-lr">{{ title }}</view>
</slot>
</template>
</view>

<view class="padding">
<slot>{{ content }}</slot>
</view>

<!-- 对话弹出窗口的操作区 -->
<template v-if="type === 'confirm'">
<view class="cu-bar bg-white justify-end padding-right">
<view class="action">
<button @tap="close(0)" class="cu-btn line-green text-green">取消</button>
<button @tap="close(1)" class="cu-btn bg-green margin-left">确定</button>
</view>
</view>
</template>
</view>
</view>
</template>

<script>
export default {
name: 'l-modal',

props: {
title: {},
content: {},
value: {},
type: { default: 'default' },
img: {},
range: { default: () => [] },
radio: {},
checkbox: {},
readonly: {},
textField: { default: 'text' },
valueField: { default: 'value' }
},

data() {
return {
radioIndex: undefined,
checkboxIndex: []
}
},

created() {
this.init()
},

methods: {
init() {
if (this.type === 'radio') {
this.radioIndex = this.objectMode
? this.range.findIndex(t => t[this.valueField] === this.radio)
: this.range.indexOf(this.radio)
} else if (this.type === 'checkbox' && Array.isArray(this.checkbox)) {
this.checkboxIndex = this.checkbox.reduce(
(a, b) =>
a.concat(this.objectMode ? this.range.findIndex(t => t[this.valueField] === b) : this.range.indexOf(b)),
[]
)
}
},

close(arg) {
this.$emit('input', false)
this.$emit('close', false)

if (typeof arg === 'number') {
this.$emit(['cancel', 'ok'][arg])
}

if (this.type === 'checkbox') {
if (arg === 0) {
this.checkboxIndex = this.checkbox
} else {
const checkboxValue = this.checkboxIndex.reduce(
(a, b) => a.concat(this.objectMode ? this.range[b][this.valueField] : this.range[b]),
[]
)

this.$emit('checkboxChange', checkboxValue)
this.$emit('checkboxIndex', this.checkboxIndex)
}
}

if (typeof arg === 'number') {
this.$emit(['cancel', 'ok'][arg])
}
},

modalClick() {
if (this.type === 'radio' || this.type === 'checkbox') {
this.close()
}
},

radioChange(e) {
if (this.readonly) {
return
}

this.radioIndex = this.objectMode
? this.range.findIndex(t => t[this.valueField] === e.detail.value)
: this.range.indexOf(e.detail.value)
this.$emit(
'radioChange',
this.objectMode ? this.range[this.radioIndex][this.valueField] : this.range[this.radioIndex]
)
this.$emit('radioIndex', this.radioIndex)
},

chooseCheckbox(index) {
if (this.readonly) {
return
}

if (this.checkboxIndex.includes(index)) {
this.checkboxIndex = this.checkboxIndex.filter(t => t !== index)
} else {
this.checkboxIndex.push(index)
}

const checkboxValue = this.checkboxIndex.reduce(
(a, b) => a.concat(this.objectMode ? this.range[b][this.valueField] : this.range[b]),
[]
)
}
},

watch: {
value(newVal) {
if (newVal) {
this.init()
}
}
},

computed: {
objectMode() {
return typeof this.range[0] === 'object'
}
}
}
</script>

+ 70
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/nav.vue Целия файл

@@ -0,0 +1,70 @@
<template>
<scroll-view
:scroll-left="scrollLeft"
:class="['bg-' + color, type === 'center' ? 'text-center' : '', shadow ? 'nav-shadow' : '', className]"
:style="style"
class="nav"
style="z-index: 10;"
scroll-with-animation
scroll-x
>
<template v-if="type !== 'flex'">
<view
v-for="(item, idx) in items"
@tap="tabSelect(idx)"
:class="value === idx ? 'text-green cur' : ''"
:key="idx"
class="cu-item"
>
{{ item }}
</view>
</template>

<view v-else class="flex text-center">
<view
@tap="tabSelect(idx)"
v-for="(item, idx) in items"
:key="idx"
:class="[value === idx ? (color ? 'bg-white cur' : 'bg-green cur') : '']"
class="cu-item flex-sub"
>
{{ item }}
</view>
</view>
</scroll-view>
</template>

<script>
export default {
name: 'l-nav',

props: {
color: { default: 'white' },
items: { default: () => [] },
value: {},
type: { default: 'default' },
shadow: {}
},

data() {
return {
scrollLeft: 0
}
},

methods: {
tabSelect(idx) {
this.scrollLeft = (idx - 1) * 60

this.$emit('input', idx)
this.$emit('change', idx)
}
}
}
</script>

<style scoped>
.nav-shodow {
box-shadow: 0px 0px 7px 0px #666;
}
</style>

+ 41
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/progress.vue Целия файл

@@ -0,0 +1,41 @@
<template>
<view
:class="[
size,
radius ? 'radius' : '',
round ? 'round' : '',
striped ? 'striped' : '',
active && striped ? 'active' : '',
className
]"
:style="style"
class="cu-progress"
>
<view :class="[color ? 'bg-' + color : '']" :style="{ width: displayPercent + '%' }">
{{ noText ? '' : displayPercent + '%' }}
</view>
</view>
</template>

<script>
export default {
name: 'l-progress',

props: {
percent: {},
noText: {},
color: {},
radius: {},
round: {},
size: { default: 'df' },
striped: {},
active: {}
},

computed: {
displayPercent() {
return Number(this.percent) || 0
}
}
}
</script>

+ 46
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/radio.vue Целия файл

@@ -0,0 +1,46 @@
<template>
<view :class="className" :style="style" class="cu-form-group">
<view class="title">
<text v-if="required" class="lr-required">*</text>
{{ title }}
</view>
<radio
@change="change"
:class="[isCheck ? 'checked' : '', color ? color : '', point ? 'radio' : '']"
:checked="isCheck"
:value="radioValue"
:disabled="disabled"
></radio>
</view>
</template>

<script>
export default {
name: 'l-radio',

props: {
title: {},
disabled: {},
point: {},
color: {},
radioValue: {},
value: {},
required: {}
},

methods: {
change(e) {
if (e.detail.value) {
this.$emit('input', this.radioValue)
this.$emit('change', this.radioValue)
}
}
},

computed: {
isCheck() {
return this.value && this.value == this.radioValue
}
}
}
</script>

+ 52
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/region-picker.vue Целия файл

@@ -0,0 +1,52 @@
<template>
<view :class="className" :style="style" class="cu-form-group">
<view class="title">
<text v-if="required" class="lr-required">*</text>
{{ title }}
</view>
<picker
@change="change"
:customitem="customitem"
:value="value"
:disabled="disabled"
:class="[arrow ? 'picker-arrow' : '']"
mode="region"
>
<view class="picker">{{ display || placeholder }}</view>
</picker>
</view>
</template>

<script>
export default {
name: 'l-region-picker',

props: {
title: {},
disabled: {},
required: {},
placeholder: { default: '请选择地区…' },
multiple: {},
customitem: {},
arrow: { default: true },
value: {}
},

methods: {
change(e) {
this.$emit('change', e.detail.value)
this.$emit('input', e.detail.value)
}
},

computed: {
display() {
if (!this.value || !this.value.length) {
return this.placeholder
}

return this.value.join(' ')
}
}
}
</script>

+ 157
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/select.vue Целия файл

@@ -0,0 +1,157 @@
<template>
<l-label :class="className" :style="style" :arrow="!disabled" :required="required" :title="title">
<picker
@change="change"
:mode="multiple ? 'multiSelector' : 'selector'"
:range="innerRange"
:value="index"
:disabled="disabled"
>
<view class="picker">{{ display }}</view>
</picker>
</l-label>
</template>

<script>
export default {
name: 'l-select',

props: {
title: {},
disabled: {},
range: {},
placeholder: {},
multiple: {},
value: {},
emptyText: { default: '(未选择)' },
splitText: { default: ' ' },
required: {},
textField: { default: 'text' },
valueField: { default: 'value' }
},

data() {
return {
index: -1
}
},

model: {
prop: 'value',
event: 'input'
},

mounted() {
this.calcIndex()
},

methods: {
change(e) {
this.index = e.detail.value
this.$emit('input', this.currentModel)
this.$emit('change', this.currentModel)
},

calcIndex() {
if (this.multiple) {
if (this.objectMode) {
this.index = this.range.map((group, idx) =>
group.findIndex(t => this.arrayCompare(t[this.valueField], this.value[idx]))
)
} else {
this.index = this.range.map((group, idx) => group.findIndex(t => this.arrayCompare(t, this.value[idx])))
}
} else {
if (this.objectMode) {
this.index = this.range.findIndex(t => t[this.valueField] === this.value)
} else {
this.index = this.range.indexOf(this.value)
}
}
},

arrayCompare(a, b) {
if (!a || !b || a.length !== b.length) {
return false
}

let isOk = true
a.forEach((t, i) => {
if (b[i] !== t) {
isOk = false
}
})

return isOk
}
},

computed: {
objectMode() {
return typeof (this.multiple ? this.range[0][0] : this.range[0]) === 'object'
},

innerRange() {
if (!this.objectMode) {
return this.range
}

if (this.multiple) {
return this.range.map(item => item.map(t => t[this.textField]))
}

return this.range.map(t => t[this.textField])
},

currentModel() {
const { multiple, range, index, objectMode } = this
if (!multiple) {
return index === -1 ? undefined : objectMode ? range[index][this.valueField] : range[index]
}

if (!objectMode) {
return range.map((group, idx) => group[index[idx]])
}

range.map((group, idx) => (index[idx] === -1 ? undefined : group[index[idx]][this.valueField]))
},

display() {
const { multiple, range, value, index, objectMode, emptyText, displayPlaceholder, splitText } = this
if (!multiple) {
return index === -1 ? displayPlaceholder : objectMode ? range[index][this.textField] : range[index]
}

if (!Array.isArray(index) || index.every(t => t === -1)) {
return displayPlaceholder
}

if (!objectMode) {
return range.map((group, idx) => group[index[idx]] || emptyText).join(splitText)
}

return range
.map((group, idx) => (index[idx] === -1 ? emptyText : group[index[idx]][this.textField]))
.join(splitText)
},

displayPlaceholder() {
if (this.disabled) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return this.title ? `请选择${this.title}` : '请选择…'
}
},

watch: {
value() {
this.calcIndex()
}
}
}
</script>

+ 29
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/step.vue Целия файл

@@ -0,0 +1,29 @@
<template>
<view :class="[arrow ? 'steps-arrow' : '', className]" :style="style" class="cu-steps">
<view
v-for="(item, index) in steps"
:key="index"
:class="index < step ? 'text-' + activeColor : ''"
class="cu-item"
>
<text v-if="number" :data-index="index + 1" class="num"></text>
<text v-else :class="index < step || !point ? 'cuIcon-' + item.icon : 'cuIcon-title'"></text>
{{ item.title }}
</view>
</view>
</template>

<script>
export default {
name: 'l-step',

props: {
step: { default: 0 },
steps: { default: () => [] },
activeColor: { default: 'blue' },
point: {},
arrow: {},
number: {}
}
}
</script>

+ 33
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/switch.vue Целия файл

@@ -0,0 +1,33 @@
<template>
<view :class="className" :style="style" class="cu-form-group">
<view class="title" style="flex-shrink: 0;">{{ title }}</view>
<switch
@change="change"
:checked="value"
:disabled="disabled"
:class="[value ? 'checked' : '', icon ? 'switch-' + icon : '', color ? color : '', radius ? 'radius' : '']"
></switch>
</view>
</template>

<script>
export default {
name: 'l-switch',

props: {
title: {},
color: {},
icon: {},
radius: {},
disabled: {},
value: {}
},

methods: {
change(e) {
this.$emit('change', e.detail.value)
this.$emit('input', e.detail.value)
}
}
}
</script>

+ 55
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/tag.vue Целия файл

@@ -0,0 +1,55 @@
<template>
<view
@click="click"
:class="[
round ? 'round' : '',
radius ? 'radius' : '',
size && !capsule ? size : '',
color && !capsule ? 'bg-' + color : '',
line && !capsule ? 'line-' + line : '',
light && !capsule ? 'light' : '',
capsule ? 'cu-capsule' : 'cu-tag',
badge ? 'badge' : '',
className
]"
:style="rootStyle"
>
<template v-if="capsule">
<view :class="[size, color ? 'bg-' + color : '']" class="cu-tag tag-icon"><slot name="icon"></slot></view>
<view :class="[size, color ? 'line-' + color : '']" class="cu-tag"><slot></slot></view>
</template>

<template v-else>
<slot></slot>
</template>
</view>
</template>

<script>
export default {
name: 'l-tag',

props: {
round: {},
radius: {},
size: { default: 'df' },
color: {},
line: {},
light: {},
capsule: {},
badge: {}
},

methods: {
click(e) {
this.$emit('click', e)
}
},

computed: {
rootStyle() {
return this.getStyle({ zIndex: this.badge ? 2 : 1 })
}
}
}
</script>

+ 80
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/textarea.vue Целия файл

@@ -0,0 +1,80 @@
<template>
<view v-if="!simpleMode" :class="className" :style="style">
<view class="cu-form-group" style="border-bottom: none; padding-bottom: 0;">
<view class="title">
<text v-if="required" style="color: red; font-size: 1.2em;">*</text>
{{ title || '' }}
</view>
</view>

<view class="cu-form-group" style="position: relative;border-top: none">
<textarea
@input="textareaInput"
:maxlength="maxlength"
:placeholder="displayPlaceHolder"
:value="value"
:auto-height="autoHeight"
:disabled="readonly"
:enableNative="false"
style="margin-top: 0; border-top: none"
controlled
></textarea>
</view>
</view>

<view v-else :class="className" :style="style" class="cu-form-group" style="position: relative;">
<textarea
@input="textareaInput"
:maxlength="maxlength"
:placeholder="displayPlaceHolder"
:fixed="fixed"
:value="value"
:style="textareaStyle"
:auto-height="autoHeight"
:disabled="readonly"
:enableNative="false"
controlled
></textarea>
</view>
</template>

<script>
export default {
name: 'l-textarea',

props: {
maxlength: { default: -1 },
readonly: {},
placeholder: {},
fixed: {},
value: {},
textareaStyle: {},
autoHeight: {},
simpleMode: {},
required: {},
title: {},
disabled: {}
},

methods: {
textareaInput(e) {
this.$emit('change', e.detail.value)
this.$emit('input', e.detail.value)
}
},

computed: {
displayPlaceHolder() {
if (this.readonly) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return !this.simpleMode && this.title ? `请输入${this.title}` : '请输入…'
}
}
}
</script>

+ 99
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/time-picker.vue Целия файл

@@ -0,0 +1,99 @@
<template>
<l-label @click="click" :class="className" :style="style" :arrow="!disabled" :required="required" :title="title">
<!-- #ifndef MP-DINGTALK -->
<picker
@change="change"
:value="value"
:start="start"
:end="end"
:disabled="disabled"
:class="[disabled ? '' : 'picker-arrow']"
mode="time"
>
<view class="picker">{{ value || displayPlaceholder }}</view>
</picker>
<!-- #endif -->

<!-- #ifdef MP-DINGTALK -->
<text @click="click">{{ value || displayPlaceholder }}</text>
<!-- #endif -->
</l-label>
</template>

<script>
export default {
name: 'l-time-picker',

props: {
title: {},
start: { default: '00:00' },
end: { default: '23:59' },
disabled: {},
placeholder: {},
value: {}
},

methods: {
click(e) {
if (this.disabled) {
return
}

this.$emit('click', e)

// #ifdef MP-DINGTALK
const getCurrent = () => {
const now = new Date()

const hours = now
.getHours()
.toString()
.padStart(2, '0')
const minutes = now
.getMinutes()
.toString()
.padStart(2, '0')

return hours + ':' + minutes
}

const currentDate = this.value ? this.value : getCurrent()
this.$emit('open')

dd.datePicker({
currentDate,
format: 'HH:mm',
success: ({ date }) => {
if (date) {
this.$emit('input', date + ':00')
this.$emit('change', date + ':00')
}
},
complete: () => {
this.$emit('close')
}
})
// #endif
},

change(e) {
this.$emit('input', e.detail.value)
this.$emit('change', e.detail.value)
}
},

computed: {
displayPlaceholder() {
if (this.disabled) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return this.title ? `请选择${this.title}` : '请选择…'
}
}
}
</script>

+ 23
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/timeline-item.vue Целия файл

@@ -0,0 +1,23 @@
<template>
<view :class="[icon ? 'cuIcon-' + icon : '', className]" :style="style" class="cu-item cu-timeline-item">
<view :class="['bg-' + color]" :style="contentStyle" class="content shadow-blur">
<slot name="time">
<text v-if="time" class="margin-right-sm">{{ time }}</text>
</slot>
<slot></slot>
</view>
</view>
</template>

<script>
export default {
name: 'l-timeline-item',

props: {
time: {},
icon: {},
color: { default: 'blue' },
contentStyle: { default: '' }
}
}
</script>

+ 18
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/timeline.vue Целия файл

@@ -0,0 +1,18 @@
<template>
<view :class="className" :style="style" class="cu-timeline">
<view class="cu-time">
<slot name="title">{{ title }}</slot>
</view>
<slot></slot>
</view>
</template>

<script>
export default {
name: 'l-timeline',

props: {
title: {}
}
}
</script>

+ 59
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/title.vue Целия файл

@@ -0,0 +1,59 @@
<template>
<view v-if="type !== 'default'" :class="className" :style="style" class="cu-bar bg-white cu-title">
<!-- 下划线标题(普通/变色) -->
<view v-if="type === 'underline' || type === 'colorfulUnderline'" class="action border-title">
<text :class="['text-' + color, blod ? 'text-blod' : '']" class="text-xl"><slot></slot></text>
<text v-if="type === 'underline'" :class="'bg-' + color" style="width:2rem"></text>
<text v-else-if="type === 'colorfulUnderline'" class="bg-gradual-blue" style="width:3rem"></text>
</view>

<!-- 扩散型下划线标题 -->
<view v-else-if="type === 'blurUnderline'" class="action sub-title">
<text :class="['text-' + color, blod ? 'text-blod' : '']" class="text-xl"><slot></slot></text>
<text :class="'bg-' + color"></text>
</view>

<!-- 扩散型副标题 -->
<view v-else-if="type === 'blurSubtitle'" class="action sub-title">
<text :class="[' text-' + color, blod ? 'text-blod' : '']" class="text-xl"><slot></slot></text>
<text :class="[' text-' + color]" class="text-ABC">
<slot name="subtitle">{{ subtitle }}</slot>
</text>
</view>

<!-- 后缀型副标题 -->
<view v-else-if="type === 'nextSubtitle'" class="action title-style-3">
<text :class="['text-' + color, blod ? 'text-blod' : '']" class="text-xl"><slot></slot></text>
<text :class="'text-' + color" class="text-Abc self-end margin-left-sm light">
<slot name="subtitle">{{ subtitle }}</slot>
</text>
</view>
</view>

<!-- 普通标题(圆点/长圆点) -->
<view v-else :class="[border ? 'solid-bottom' : '', className]" :style="style" class="cu-bar bg-white cu-title">
<view class="action" style="vertical-align:middle;">
<text
:class="[subColor ? 'text-' + subColor : '', long ? 'cuIcon-titles' : 'cuIcon-title']"
class="margin-right-xs"
></text>
<text :class="[blod ? 'text-blod' : '']" class="text-xl"><slot></slot></text>
</view>
</view>
</template>

<script>
export default {
name: 'l-title',

props: {
type: { default: 'default' },
color: { default: 'black' },
subColor: { default: 'blue' },
long: {},
subtitle: {},
blod: {},
border: {}
}
}
</script>

+ 59
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-ali/upload.vue Целия файл

@@ -0,0 +1,59 @@
<template>
<view :class="className" :style="style" class="cu-form-group">
<view class="grid col-4 grid-square flex-sub">
<view v-for="(path, index) in value" @tap="viewImg" :key="index" class="bg-img">
<image :src="path" mode="aspectFill"></image>
<view v-if="!readonly" @tap.stop="delImg(index)" class="cu-tag bg-red" style="height: 24px; width: 24px;">
<l-icon type="close" style="width: 18px;height: 24px;font-size: 24px;" />
</view>
</view>

<view v-if="!readonly && value.length < Number(number)" @tap="chooseImg" class="solids">
<l-icon type="cameraadd" />
</view>
</view>
</view>
</template>

<script>
export default {
name: 'l-upload',

props: {
number: { default: 1 },
readonly: {},
value: { default: () => [] }
},

methods: {
delImg(index) {
const newList = JSON.parse(JSON.stringify(this.value))
newList.splice(index, 1)
this.$emit('input', newList)
this.$emit('change')
this.$emit('del')
},

chooseImg() {
uni.chooseImage({
count: Number(this.number),
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: ({ tempFilePaths }) => {
const newList = JSON.parse(JSON.stringify(this.value || [])).concat(tempFilePaths)
this.$emit('input', newList)
this.$emit('change')
this.$emit('add')
}
})
},

viewImg(index) {
uni.previewImage({
urls: this.value,
current: this.value[index]
})
}
}
}
</script>

+ 27
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/avatar.vue Целия файл

@@ -0,0 +1,27 @@
<template>
<view
:class="[size, round ? 'round' : '', radius ? 'radius' : '', color ? 'bg-' + color : '']"
:style="{
backgroundImage: src ? `url(${src})` : 'none',
backgroundColor: color || src ? 'none' : '#ccc'
}"
class="cu-avatar"
>
<slot></slot>
<slot name="badge"></slot>
</view>
</template>

<script>
export default {
name: 'l-avatar',

props: {
src: {},
size: { default: 'df' },
round: {},
radius: {},
color: {}
}
}
</script>

+ 114
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/banner.vue Целия файл

@@ -0,0 +1,114 @@
<template>
<view
v-if="type === 'default'"
:class="[hexColor ? '' : 'bg-' + color, fixed ? 'banner-fixed' : '']"
:style="{
backgroundColor: hexColor,
boxShadow: noshadow ? 'none' : '0 1rpx 6rpx rgba(0, 0, 0, 0.1)'
}"
class="cu-bar cu-banner"
>
<view @click="clickLeft" class="action"><slot name="left"></slot></view>

<view @click="clickCenter" class="content">
<slot>{{ title }}</slot>
</view>

<view @click="clickRight" class="action"><slot name="right"></slot></view>
</view>

<view
v-else-if="type === 'search'"
:class="[hexColor ? '' : 'bg-' + color, fixed ? 'banner-fixed' : '']"
:style="{
backgroundColor: hexColor,
boxShadow: noshadow ? 'none' : '0 1rpx 6rpx rgba(0, 0, 0, 0.1)'
}"
class="cu-bar search cu-banner"
>
<slot name="left"></slot>
<view class="search-form" :class="[fill ? 'radius' : 'round']" :style="inputStyle">
<slot name="searchInput">
<text class="cuIcon-search"></text>
<input
@input="searchTextChange"
@focus="$emit('inputFocus', $event)"
:placeholder-style="placeholderStyle"
:adjust-position="false"
:placeholder="placeholder"
:value="value"
:focus="focus"
confirm-type="search"
type="text"
ref="bannerInput"
/>
</slot>
</view>
<view v-if="!noSearchButton" class="action">
<slot name="searchButton">
<template v-if="fill">
<view @click="searchClick">搜索</view>
</template>
<button v-else @click="searchClick" class="cu-btn bg-green shadow-blur round">搜索</button>
</slot>
</view>
</view>
</template>

<script>
export default {
name: 'l-banner',

props: {
type: { default: 'default' },
color: { default: 'white' },
hexColor: {},
inputStyle: {},
fill: {},
title: {},
value: {},
placeholder: { default: '搜索图片、文章、视频' },
noSearchButton: {},
placeholderStyle: {},
fixed: {},
noshadow: {},
focus: {}
},

methods: {
searchTextChange(e) {
this.$emit('input', e.detail.value)
this.$emit('change', e.detail.value)
},

clickLeft(e) {
this.$emit('clickLeft', e)
},

clickRight(e) {
this.$emit('clickRight', e)
},

clickCenter(e) {
this.$emit('clickCenter', e)
},

searchClick(F) {
this.$emit('searchClick')
},

inputFocus() {
this.$refs.bannerInput.focus()
}
}
}
</script>

<style scoped>
.banner-fixed {
position: fixed;
width: 100%;
top: var(--window-top);
z-index: 1024;
}
</style>

+ 14
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/bar-item-button.vue Целия файл

@@ -0,0 +1,14 @@
<template>
<view :class="['bg-' + color]" class="submit cu-bar-item-button">{{ title }}</view>
</template>

<script>
export default {
name: 'l-bar-item-button',

props: {
title: {},
color: { default: 'gray' }
}
}
</script>

+ 37
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/bar-item.vue Целия файл

@@ -0,0 +1,37 @@
<template>
<view v-if="type === 'default'" @click="click" class="action cu-bar-item">
<slot>
<l-icon :type="icon" :color="color" size="lg" class="cuIcon-cu-image" />
<slot name="badge"></slot>
<view :class="'text-' + color">
<slot name="title">{{ title }}</slot>
</view>
</slot>
</view>

<view v-else-if="type === 'round'" class="action text-gray add-action cu-bar-item">
<slot>
<button :class="['bg-' + color, 'cuIcon-' + icon]" class="cu-btn shadow"></button>
<slot name="title">{{ title }}</slot>
</slot>
</view>
</template>

<script>
export default {
name: 'l-bar-item',

props: {
type: { default: 'default' },
title: {},
icon: {},
color: { default: 'gray' }
},

methods: {
click(e) {
this.$emit('click', e)
}
}
}
</script>

+ 14
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/bar.vue Целия файл

@@ -0,0 +1,14 @@
<template>
<view :class="[dark ? 'bg-black' : 'bg-white']" class="cu-bar tabbar"><slot></slot></view>
</template>

<script>
export default {
name: 'l-bar',

props: {
msginput: {},
dark: {}
}
}
</script>

+ 52
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/button.vue Целия файл

@@ -0,0 +1,52 @@
<template>
<button
@click="click"
:disabled="disabled ? 'disabled' : null"
:type="disabled && line ? 'default' : null"
:class="[size, round ? 'round' : '', shadow ? 'shadow' : '', buttonColor, buttonBorder, block ? 'block' : '']"
hover-class="button-hover"
class="cu-btn"
>
<slot></slot>
</button>
</template>

<script>
export default {
name: 'l-button',

props: {
size: { default: 'df' },
color: {},
round: {},
shadow: {},
line: {},
blod: {},
icon: {},
disabled: {},
block: {}
},

computed: {
buttonBorder() {
return this.line ? `line${this.blod ? 's' : ''}-${this.line || 'gray'}` : ''
},

buttonColor() {
if (this.color) {
return `bg-${this.color}`
} else if (!this.line) {
return `bg-gray`
}

return ''
}
},

methods: {
click(e) {
this.$emit('click', e)
}
}
}
</script>

+ 82
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/card.vue Целия файл

@@ -0,0 +1,82 @@
<template>
<view :class="[outline ? '' : 'no-card', type !== 'base' ? type : '']" class="cu-card">
<view class="cu-item shadow">
<template v-if="type === 'case'">
<view class="image">
<slot name="img"><image :src="info.img" mode="widthFix"></image></slot>
<slot name="badge">
<l-tag :color="info.badgeColor">{{ info.badge }}</l-tag>
</slot>
<view class="cu-bar bg-shadeBottom">
<slot name="title">
<text class="text-cut">{{ info.title }}</text>
</slot>
</view>
</view>
<view class="cu-list menu-avatar">
<view class="cu-item">
<slot name="avatar">
<l-avatar :src="info.avatar" size="lg" style="margin: 0 30rpx; position: absolute; left: 0;" round />
</slot>
<view class="content flex-sub">
<slot name="user">
<view class="text-grey">{{ info.user }}</view>
</slot>
<view class="text-gray text-sm flex justify-between">
<slot name="tips">{{ info.tips }}</slot>
<slot name="footer"></slot>
</view>
</view>
</view>
</view>
</template>

<template v-else-if="type === 'dynamic'">
<view class="cu-list menu-avatar">
<view class="cu-item">
<slot name="avatar">
<l-avatar :src="info.avatar" size="lg" style="margin: 0 30rpx; position: absolute; left: 0;" round />
</slot>
<view class="content flex-sub">
<slot name="user">
<view>{{ info.user }}</view>
</slot>
<slot name="tips">
<view class="text-gray text-sm flex justify-between">{{ info.tips }}</view>
</slot>
</view>
</view>
</view>
<slot></slot>
<slot name="footer"></slot>
</template>

<template v-else-if="type === 'article'">
<view class="title">
<slot name="title">
<view class="text-cut">{{ info.title }}</view>
</slot>
</view>
<view class="content">
<slot name="img"><image :src="info.img" mode="aspectFill"></image></slot>
<view class="desc">
<view class="text-content"><slot></slot></view>
<slot name="footer"></slot>
</view>
</view>
</template>
</view>
</view>
</template>

<script>
export default {
name: 'l-card',

props: {
type: { default: 'case' },
outline: {},
info: { default: () => ({}) }
}
}
</script>

+ 63
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/chat-input.vue Целия файл

@@ -0,0 +1,63 @@
<template>
<view :style="[{ bottom: inputBottom + 'px' }]" class="cu-bar foot input">
<input
@focus="inputFocus"
@blur="inputBlur"
@input="input"
@confirm="sendClick"
:value="value"
:adjust-position="false"
:enableNative="false"
:focus="false"
:placeholder="placeholder"
:disabled="inputDisabled"
:confirm-hold="confirmHold"
cursor-spacing="10"
class="solid-bottom"
confirm-type="send"
type="text"
/>
<slot name="action">
<button @click="sendClick" :disabled="buttonDisabled" class="cu-btn bg-green shadow">发送</button>
</slot>
</view>
</template>

<script>
export default {
name: 'l-chat-input',

props: {
value: {},
placeholder: {},
inputDisabled: {},
buttonDisabled: {},
confirmHold: {}
},

data() {
return { inputBottom: 0 }
},

methods: {
input(e) {
this.$emit('input', e.detail.value)
this.$emit('change', e.detail.value)
},

inputFocus(e) {
this.inputBottom = e.detail.height
this.$emit('focus')
},

inputBlur(e) {
this.inputBottom = 0
this.$emit('blur')
},

sendClick(e) {
this.$emit('sendMsg', this.value)
}
}
}
</script>

+ 80
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/chat-msg.vue Целия файл

@@ -0,0 +1,80 @@
<template>
<view :class="[type === 'left' ? '' : 'self']" class="cu-item">
<slot v-if="type === 'left'" name="avatar">
<view
v-if="type === 'left' && avatar"
:style="{ backgroundImage: `url(${avatar})`, borderRadius: roundAvatar ? '50%' : '3px' }"
class="cu-avatar radius"
></view>
<image
v-else-if="type === 'left' && imgAvatar"
:src="imgAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'};` + imgStyle"
mode="aspectFill"
class="chat-msg-imgAvatar cu-avatar radius"
></image>
<l-icon
v-else-if="type === 'left' && iconAvatar"
:type="iconAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'}; ` + iconStyle"
class="chat-msg-iconAvatar flex justify-center align-center"
/>
</slot>

<view class="main">
<view
:class="[type === 'left' ? (leftColor ? 'bg-' + leftColor : '') : rightColor ? 'bg-' + rightColor : '']"
class="content shadow"
>
<text style="word-break: break-all;">
<slot>{{ content }}</slot>
</text>
</view>
</view>

<slot v-if="type !== 'left'" name="avatar">
<view
v-if="type !== 'left' && avatar"
:style="{ backgroundImage: `url(${avatar})`, borderRadius: roundAvatar ? '50%' : '3px' }"
class="cu-avatar radius"
></view>
<image
v-else-if="type !== 'left' && imgAvatar"
:src="imgAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'};` + imgStyle"
mode="aspectFill"
class="chat-msg-imgAvatar cu-avatar radius"
></image>
<l-icon
v-else-if="type !== 'left' && iconAvatar"
:type="iconAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'}; ` + iconStyle"
class="chat-msg-iconAvatar flex justify-center align-center"
/>
</slot>

<view class="date">
<slot name="date">{{ date }}</slot>
</view>
</view>
</template>

<script>
export default {
name: 'l-chat-msg',

props: {
content: {},
type: { default: 'left' },
roundAvatar: {},
date: {},
leftColor: {},
rightColor: { default: 'green' },
avatar: {},
iconAvatar: {},
iconStyle: { default: '' },
imgAvatar: {},
imgStyle: { default: '' }
}
}
</script>

+ 20
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/chat.vue Целия файл

@@ -0,0 +1,20 @@
<template>
<view class="cu-chat">
<slot></slot>
<view v-if="empty" class="padding text-gray text-center">{{ emptyTips }}</view>
<view v-if="!empty && nomore" class="padding text-gray text-center">{{ nomoreTips }}</view>
</view>
</template>

<script>
export default {
name: 'l-chat',

props: {
empty: {},
emptyTips: { default: '暂无消息' },
nomore: { default: true },
nomoreTips: { default: '没有更多消息了' }
}
}
</script>

+ 114
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/checkbox-picker.vue Целия файл

@@ -0,0 +1,114 @@
<template>
<l-label @click="click" :arrow="!readonly" :required="required" :title="title">
{{ display || displayPlaceholder }}
<l-modal
v-if="modal"
@ok="confirm"
@cancel="cancel"
@checkboxChange="checkboxChange"
:checkbox="innerChecked"
:value="modalOpen"
:range="range"
:readonly="readonly"
type="checkbox"
/>
</l-label>
</template>

<script>
export default {
name: 'l-checkbox-picker',

props: {
value: {},
range: { default: () => [] },
title: {},
required: {},
placeholder: {},
readonly: {}
},

data() {
return {
modal: false,
modalOpen: false,
innerChecked: this.value || []
}
},

methods: {
click() {
if (this.readonly || this.modal || this.modalOpen) {
return
}

this.innerChecked = this.value
this.modal = true
setTimeout(() => {
this.modalOpen = true
uni.$emit('modal', true)
this.$emit('open')
}, 300)
},

checkboxChange(newVal) {
this.innerChecked = newVal
},

confirm() {
this.modalOpen = false
setTimeout(() => {
this.modal = false
this.$emit('input', this.innerChecked)
this.$emit('change', this.innerChecked)
uni.$emit('modal', false)
this.$emit('close')
}, 300)
},

cancel() {
this.modalOpen = false
setTimeout(() => {
this.modal = false
uni.$emit('modal', false)
this.$emit('close')
}, 300)
}
},

computed: {
display() {
if (this.value.length <= 0) {
return null
}

if (!this.objMode) {
return this.value.join(',')
}

return this.value
.reduce((a, b) => {
const selected = this.range.find(t => t.value === b)
return selected ? a.concat(selected.text) : a
}, [])
.join(',')
},

displayPlaceholder() {
if (this.readonly) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return this.title ? `请选择${this.title}` : '请选择…'
},

objMode() {
return typeof this.range[0] === 'object'
}
}
}
</script>

+ 49
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/checkbox.vue Целия файл

@@ -0,0 +1,49 @@
<template>
<view class="cu-form-group">
<view class="title">{{ title }}</view>
<checkbox
@click="change"
:checked="isCheck"
:value="checkboxValue"
:disabled="disabled"
:class="[isCheck ? 'checked' : '', color ? color : '', round ? 'round' : '']"
></checkbox>
</view>
</template>

<script>
export default {
name: 'l-checkbox',

props: {
title: {},
disabled: {},
round: {},
color: {},
checkboxValue: {},
value: { default: () => [] }
},

methods: {
change(e) {
let arr = this.value
const isCurrentCheck = this.value.includes(this.checkboxValue)

if (isCurrentCheck) {
arr = arr.filter(t => t !== this.checkboxValue)
} else {
arr.push(this.checkboxValue)
}

this.$emit('input', arr)
this.$emit('change', arr)
}
},

computed: {
isCheck() {
return this.value.includes(this.checkboxValue)
}
}
}
</script>

+ 60
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/date-picker.vue Целия файл

@@ -0,0 +1,60 @@
<template>
<view class="cu-form-group">
<view class="title">
<text v-if="required" class="lr-required">*</text>
{{ title }}
</view>
<picker
@change="change"
:fields="fields"
:disabled="disabled"
:value="value"
:start="start"
:end="end"
:class="[disabled ? '' : 'picker-arrow']"
mode="date"
>
<view class="picker">
<slot>{{ value || displayPlaceholder }}</slot>
</view>
</picker>
</view>
</template>

<script>
export default {
name: 'l-date-picker',

props: {
title: {},
start: { default: '1900-01-01' },
end: { default: '2100-01-01' },
fields: { default: 'day' },
disabled: {},
placeholder: {},
required: {},
value: {}
},

methods: {
change(e) {
this.$emit('input', e.detail.value)
this.$emit('change', e.detail.value)
}
},

computed: {
displayPlaceholder() {
if (this.disabled) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return this.title ? `请选择${this.title}` : '请选择日期…'
}
}
}
</script>

+ 621
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/datetime-panel.vue Целия файл

@@ -0,0 +1,621 @@
<template>
<view class="lr-datetime-picker">
<view
@tap.stop="maskClick"
@touchmove.stop.prevent
:class="{ show: showPicker }"
class="lr-datetime-mask"
catchtouchmove="true"
></view>
<view :class="{ show: showPicker }" class="lr-datetime-content">
<view @touchmove.stop.prevent catchtouchmove="true" class="lr-datetime-hd">
<view @tap.stop="pickerCancel" class="lr-datetime-btn">取消</view>
<view @tap.stop="pickerConfirm" :style="{ color: color }" class="lr-datetime-btn">确定</view>
</view>
<view class="lr-datetime-view">
<picker-view
v-if="isAll"
@change="bindChange"
:indicator-style="itemHeight"
:value="values"
style="height: 100%;"
>
<picker-view-column>
<view v-for="(item, index) in dateObj.years" :key="index" class="lr-datetime-item">{{ item }}年</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.months" :key="index" class="lr-datetime-item">{{ item }}月</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.days" :key="index" class="lr-datetime-item">{{ item }}日</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.hours" :key="index" class="lr-datetime-item">{{ item }}时</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.minutes" :key="index" class="lr-datetime-item">{{ item }}分</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.seconds" :key="index" class="lr-datetime-item">{{ item }}秒</view>
</picker-view-column>
</picker-view>
<picker-view
v-else
@change="bindDateChange"
:indicator-style="itemHeight"
:value="dateValues"
style="height: 100%;"
>
<picker-view-column class="yu-picker-column">
<view v-for="(item, index) in dateObj.dates" :key="index" class="lr-datetime-item">{{ item }}</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.hours" :key="index" class="lr-datetime-item">{{ item }}时</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.minutes" :key="index" class="lr-datetime-item">{{ item }}分</view>
</picker-view-column>
<picker-view-column>
<view v-for="(item, index) in dateObj.seconds" :key="index" class="lr-datetime-item">{{ item }}秒</view>
</picker-view-column>
</picker-view>
</view>
</view>
</view>
</template>

<script>
export default {
name: 'l-datetime-panel',

props: {
//全部日期有效可选
isAll: { default: true },
//颜色
color: { default: '#1aad19' },
//开始年份
startYear: { default: '1900' },
//结束年份
endYear: { default: '2100' },
//设置默认日期时间
val: { default: '' }
},

data() {
let date = new Date()
let year = date.getFullYear()
let month = date.getMonth() + 1
let day = date.getDate()
let hour = date.getHours()
let minute = date.getMinutes()
let second = date.getSeconds()
let dates = []
let months = []
let years = []
let days = []
let hours = []
let minutes = []
let seconds = []
for (let i = month; i <= month + 2; i++) {
//获取包括当前月份在内的3个月内的日期
let localMonth = i
let localYear = year
if (i == 13) {
localYear += 1
}
if (i >= 13) {
localMonth -= 12
}
let total = new Date(localYear, localMonth, 0).getDate()
if (i == month) {
for (let j = day; j <= total; j++) {
let m = localMonth
let d = j
if (localMonth < 10) {
m = '0' + m
}
if (j < 10) {
d = '0' + d
}
let str = year + '-' + m + '-' + d
dates.push(str)
}
} else {
for (let j = 1; j <= total; j++) {
let m = localMonth
let d = j
if (localMonth < 10) {
m = '0' + m
}
if (j < 10) {
d = '0' + d
}
let str = year + '-' + m + '-' + d
dates.push(str)
}
}
}
for (let i = parseInt(this.startYear); i <= this.endYear; i++) {
years.push(i)
}
for (let i = 1; i <= 12; i++) {
let str = i
if (i < 10) {
str = '0' + str
} else {
str = '' + str
}
months.push(str)
}

if (this.val) {
let valueArr = this.val.split(' ')
let valueDateArr = valueArr[0].split('-')
let totalCurrent = new Date(valueDateArr[0], valueDateArr[1], 0).getDate()
for (let i = 1; i <= totalCurrent; i++) {
let str = i
if (i < 10) {
str = '0' + str
} else {
str = '' + str
}
days.push(str)
}
} else {
let totalCurrent = new Date(year, month, 0).getDate()
for (let i = 1; i <= totalCurrent; i++) {
let str = i
if (i < 10) {
str = '0' + str
} else {
str = '' + str
}
days.push(str)
}
}
for (let i = 0; i < 24; i++) {
let str = i
if (i < 10) {
str = '0' + str
} else {
str = '' + str
}
hours.push(str)
}
for (let i = 0; i < 60; i++) {
let str = i
if (i < 10) {
str = '0' + str
} else {
str = '' + str
}
minutes.push(str)
}
for (let i = 0; i < 60; i++) {
let str = i
if (i < 10) {
str = '0' + str
} else {
str = '' + str
}
seconds.push(str)
}
let dateObj = {
dates,
years,
months,
days,
hours,
minutes,
seconds
}
return {
value: this.val,
year,
month,
day,
hour,
minute,
second,
dateObj,
itemHeight: `height: ${uni.upx2px(88)}px;`,
values: [0, 0, 0, 0, 0, 0],
selectArr: [],
selectRes: '',
showPicker: false,
dateValues: [0, 0, 0, 0]
}
},

mounted() {
this.setDate()
},

methods: {
setDate(dt) {
if (dt) {
this.value = dt
}

this.initDate()

if (!this.value) {
this.showCurrent()
}
},

initDate() {
let _this = this
//解析默认显示的日期时间
if (_this.value) {
let values = [0, 0, 0, 0, 0, 0]
let dateValues = [0, 0, 0, 0]
let valueStr = _this.value
let valueArr = valueStr.split(' ')
let valueDateArr = valueArr[0].split('-')
let valueTimeArr = valueArr[1].split(':')
if (_this.isAll) {
values[0] = valueDateArr[0] - _this.startYear > 0 ? valueDateArr[0] - _this.startYear : 0
values[1] = parseInt(valueDateArr[1]) - 1
values[2] = parseInt(valueDateArr[2]) - 1
values[3] = parseInt(valueTimeArr[0])
values[4] = parseInt(valueTimeArr[1])
values[5] = parseInt(valueTimeArr[2])
_this.$nextTick(() => {
_this.values = values
})
if (valueDateArr[0] - _this.startYear >= 0) {
_this.selectArr = [
valueDateArr[0],
valueDateArr[1],
valueDateArr[2],
valueTimeArr[0],
valueTimeArr[1],
valueTimeArr[2]
]
_this.selectRes = _this.value
} else {
_this.selectArr = [
_this.formatNum(_this.startYear),
valueDateArr[1],
valueDateArr[2],
valueTimeArr[0],
valueTimeArr[1],
valueTimeArr[2]
]
_this.selectRes = `${this.formatNum(_this.startYear) +
'-' +
valueDateArr[1] +
'-' +
valueDateArr[2] +
' ' +
valueTimeArr[0] +
':' +
valueTimeArr[1] +
':' +
valueTimeArr[2]}`
}
} else {
let str = valueDateArr.join('')
let localStr = _this.formatNum(_this.year) + _this.formatNum(_this.month) + _this.formatNum(_this.day) + ''
if (str < localStr) {
dateValues[0] = 0
_this.selectArr = [
_this.formatNum(_this.year),
_this.formatNum(_this.month),
_this.formatNum(_this.day),
valueTimeArr[0],
valueTimeArr[1],
valueTimeArr[2]
]
_this.selectRes = `${_this.formatNum(_this.year) +
'-' +
_this.formatNum(_this.month) +
'-' +
_this.formatNum(_this.day) +
' ' +
valueTimeArr[0] +
':' +
valueTimeArr[1] +
':' +
valueTimeArr[2]}`
} else {
let num = 0 //计算默认日期和当前日期相隔天数,计算下标
let start =
_this.formatNum(_this.year) + '-' + _this.formatNum(_this.month) + '-' + _this.formatNum(_this.day)
let res = _this.getBetweenDateStr(start, valueArr[0])
dateValues[0] = res.length - 1
_this.selectArr = [
valueDateArr[0],
valueDateArr[1],
valueDateArr[2],
valueTimeArr[0],
valueTimeArr[1],
valueTimeArr[2]
]
_this.selectRes = _this.value
}
dateValues[1] = parseInt(valueTimeArr[0])
dateValues[2] = parseInt(valueTimeArr[1])
dateValues[3] = parseInt(valueTimeArr[2])
_this.$nextTick(() => {
_this.dateValues = dateValues
})
}
return
}
if (_this.isAll) {
_this.selectArr = [_this.formatNum(_this.startYear), '01', '01', '00', '00', '00']
_this.selectRes = `${_this.formatNum(_this.startYear) + '-01-01 00:00:00'}`
} else {
_this.selectArr = [_this.formatNum(_this.year), _this.formatNum(_this.month), '01', '00', '00', '00']
_this.selectRes = `${_this.formatNum(_this.year) + '-' + _this.formatNum(_this.month) + '-01 00:00:00'}`
}
},
showCurrent() {
//显示当前的日期时间
let arr = [0, 0, 0, 0, 0, 0]
let dateArr = [0, 0, 0, 0]
this.selectArr = [
this.formatNum(this.year),
this.formatNum(this.month),
this.formatNum(this.day),
this.formatNum(this.hour),
this.formatNum(this.minute),
this.formatNum(this.second)
]
this.selectRes = `${this.formatNum(this.year) +
'-' +
this.formatNum(this.month) +
'-' +
this.formatNum(this.day) +
' ' +
this.formatNum(this.hour) +
':' +
this.formatNum(this.minute) +
':' +
this.formatNum(this.second)}`
if (this.isAll) {
arr[0] = this.year - this.startYear
arr[1] = this.month - 1
arr[2] = this.day - 1
arr[3] = this.hour
arr[4] = this.minute
arr[5] = this.second
this.$nextTick(() => {
this.values = arr
})
} else {
dateArr[1] = this.hour
dateArr[2] = this.minute
dateArr[3] = this.second
this.$nextTick(() => {
this.dateValues = dateArr
})
}
},
initDayArr: (year, month) => {
//初始化月份天数
let totalDay = new Date(year, month, 0).getDate()
let dayArr = []
for (let i = 1; i <= totalDay; i++) {
if (i < 10) {
i = '0' + i
} else {
i = i + ''
}
dayArr.push(i)
}
return dayArr
},
formatNum(num) {
//日期时间的初始化
return num < 10 ? '0' + num : num + ''
},
maskClick() {
//日期时间的遮罩
this.showPicker = false
this.$emit('cancel')
},
show() {
//日期时间的显示
this.showPicker = true
},
hide() {
//日期时间的隐藏
this.showPicker = false
this.$emit('cancel')
},
pickerCancel() {
//日期时间取消
this.showPicker = false
this.$emit('cancel', {
selectArr: this.selectArr
})
},
pickerConfirm(e) {
//日期时间确定
this.showPicker = false
this.$emit('confirm', {
selectArr: this.selectArr,
selectRes: this.selectRes
})
},
bindChange(e) {
//默认滚动日期时间方法
let valueArr = e.detail.value
let year = '',
month = '',
day = '',
hour = '',
minute = '',
second = ''
let selectArr = this.selectArr
let dayArr = []
year = this.dateObj.years[valueArr[0]]
month = this.dateObj.months[valueArr[1]]
day = this.dateObj.days[valueArr[2]]
hour = this.dateObj.hours[valueArr[3]]
minute = this.dateObj.minutes[valueArr[4]]
second = this.dateObj.seconds[valueArr[5]]
if (year != selectArr[0]) {
dayArr = this.initDayArr(year, month)
this.dateObj.days = dayArr
}
if (month != selectArr[1]) {
dayArr = this.initDayArr(year, month)
this.dateObj.days = dayArr
}
this.selectArr = [year, month, day, hour, minute, second]
this.selectRes = `${year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second}`
this.$nextTick(() => {
this.values = valueArr
})
},
bindDateChange(e) {
//有效日期的滚动日期时间方法
let valueArr = e.detail.value
let dateStr = '',
dateArr = [],
hour = '',
minute = '',
second = ''
let selectArr = this.selectArr
let dayArr = []
dateStr = this.dateObj.dates[valueArr[0]]
dateArr = dateStr.split('-')
hour = this.dateObj.hours[valueArr[1]]
minute = this.dateObj.minutes[valueArr[2]]
second = this.dateObj.seconds[valueArr[3]]
this.selectArr = [dateArr[0], dateArr[1], dateArr[2], hour, minute, second]
this.selectRes = `${dateArr[0] + '-' + dateArr[1] + '-' + dateArr[2] + ' ' + hour + ':' + minute + ':' + second}`
this.$nextTick(() => {
this.dateValues = valueArr
})
},
//遍历两个日期间的所有日期
getBetweenDateStr(start, end) {
let result = []
let beginDay = start.split('-')
let endDay = end.split('-')
let diffDay = new Date()
let dateList = new Array()
let i = 0
diffDay.setDate(beginDay[2])
diffDay.setMonth(beginDay[1] - 1)
diffDay.setFullYear(beginDay[0])
result.push(start)
while (i == 0) {
let countDay = diffDay.getTime() + 24 * 60 * 60 * 1000
diffDay.setTime(countDay)
dateList[2] = diffDay.getDate()
dateList[1] = diffDay.getMonth() + 1
dateList[0] = diffDay.getFullYear()
if (String(dateList[1]).length == 1) {
dateList[1] = '0' + dateList[1]
}
if (String(dateList[2]).length == 1) {
dateList[2] = '0' + dateList[2]
}
result.push(dateList[0] + '-' + dateList[1] + '-' + dateList[2])
if (dateList[0] == endDay[0] && dateList[1] == endDay[1] && dateList[2] == endDay[2]) {
i = 1
}
}
return result
}
}
}
</script>

<style>
.lr-datetime-picker {
position: relative;
z-index: 888;
}

.lr-datetime-mask {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
visibility: hidden;
opacity: 0;
transition: all 0.3s ease;
}

.lr-datetime-mask.show {
visibility: visible;
opacity: 1;
}

.lr-datetime-content {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
transition: all 0.3s ease;
transform: translateY(100%);
z-index: 3000;
}

.lr-datetime-content.show {
transform: translateY(0);
}

.lr-datetime-hd {
display: flex;
align-items: center;
padding: 0 30upx;
height: 44px;
background-color: #fff;
position: relative;
text-align: center;
font-size: 15px;
justify-content: space-between;
}

.lr-datetime-btn {
font-size: 14px;
}

.lr-datetime-hd:after {
content: ' ';
position: absolute;
left: 0;
bottom: 0;
right: 0;
height: 1px;
border-bottom: 1px solid #e5e5e5;
color: #e5e5e5;
transform-origin: 0 100%;
transform: scaleY(0.5);
}

.lr-datetime-view {
width: 100%;
height: 200px;
overflow: hidden;
background-color: rgba(255, 255, 255, 1);
z-index: 666;
}

.yu-picker-column {
-webkit-flex: 2;
-webkit-box-flex: 2;
flex: 2;
}

.lr-datetime-item {
text-align: center;
width: 100%;
height: 88upx;
line-height: 88upx;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 30upx;
}
</style>

+ 82
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/datetime-picker.vue Целия файл

@@ -0,0 +1,82 @@
<template>
<l-label @click="click" :arrow="!disabled" :required="required" :title="title">
{{ value || displayPlaceholder }}
<l-datetime-panel
v-if="datetimeModal"
@confirm="confirm"
@cancel="cancel"
:val="value"
:startYear="1900"
ref="datetime"
isAll
/>
</l-label>
</template>

<script>
export default {
name: 'l-datetime-picker',

props: {
title: {},
disabled: {},
placeholder: {},
required: {},
value: {}
},

data() {
return {
datetimeModal: false
}
},

methods: {
click(e) {
if (this.disabled || this.datetimeModal) {
return
}

this.datetimeModal = true
this.$nextTick(() => {
this.$refs.datetime.setDate(this.value)
this.$refs.datetime.show()
uni.$emit('modal', true)
this.$emit('open')
})
},

confirm({ selectRes }) {
setTimeout(() => {
this.datetimeModal = false
this.$emit('input', selectRes)
this.$emit('change', selectRes)
uni.$emit('modal', false)
this.$emit('close')
}, 300)
},

cancel() {
setTimeout(() => {
this.datetimeModal = false
}, 300)
uni.$emit('modal', false)
this.$emit('close')
}
},

computed: {
displayPlaceholder() {
if (this.disabled) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return this.title ? `请选择${this.title}` : '请选择…'
}
}
}
</script>

+ 23
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/icon.vue Целия файл

@@ -0,0 +1,23 @@
<template>
<text @click="click" :class="['cuIcon', 'cuIcon-' + type, color ? 'text-' + color : '', shadow ? 'text-shadow' : '']">
<slot></slot>
</text>
</template>

<script>
export default {
name: 'l-icon',

props: {
color: {},
type: { require: true },
shadow: {}
},

methods: {
click(e) {
this.$emit('click', e)
}
}
}
</script>

+ 58
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/input.vue Целия файл

@@ -0,0 +1,58 @@
<template>
<view class="cu-form-group">
<view class="title">
<text v-if="required" class="lr-required">*</text>
<slot name="title">{{ title }}</slot>
</view>
<input
@input="input"
:value="value"
:type="type"
:disabled="disabled"
:password="password"
:maxlength="maxlength"
:placeholder="displayPlaceholder"
:style="{ textAlign: left ? 'left' : 'right' }"
/>
<slot name="suffix"></slot>
</view>
</template>

<script>
export default {
name: 'l-input',

props: {
type: { default: 'text' },
title: {},
placeholder: {},
maxlength: { default: -1 },
disabled: {},
password: {},
left: {},
value: {},
required: {}
},

methods: {
input(e) {
this.$emit('input', e.detail.value)
this.$emit('change', e.detail.value)
}
},

computed: {
displayPlaceholder() {
if (this.disabled) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return this.title ? `请输入${this.title}` : `请输入…`
}
}
}
</script>

+ 45
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/label.vue Целия файл

@@ -0,0 +1,45 @@
<template>
<view class="cu-form-group">
<view class="title">
<slot name="title">
<text v-if="required" class="lr-required">*</text>
{{ title }}
</slot>
</view>
<view @click="itemClick" :class="[arrow ? 'form-item-arrow' : 'form-item-noarrow']" class="action form-item-action">
<view class="picker"><slot></slot></view>
</view>
</view>
</template>

<script>
export default {
name: 'l-label',

props: {
title: {},
arrow: {},
required: {}
},

methods: {
itemClick(e) {
this.$emit('click', e)
}
}
}
</script>

<style>
.form-item-action {
flex: 1;
padding-right: 40rpx;
overflow: hidden;
position: relative;
text-align: right;
}

.form-item-noarrow {
padding-right: 0;
}
</style>

+ 79
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/list-item.vue Целия файл

@@ -0,0 +1,79 @@
<template>
<view @click="$emit('click', $event)" :class="arrow ? 'arrow' : ''" class="cu-item">
<slot name="avatar">
<l-avatar
v-if="avatar"
:src="avatar"
:radius="!roundAvatar"
:round="roundAvatar"
:style="avatarStyle"
size="lg"
/>
<image
v-else-if="imgAvatar"
:src="imgAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'}; ` + imgStyle"
mode="aspectFill"
class="list-item-imgAvatar"
/>
<l-icon
v-else-if="iconAvatar"
:type="iconAvatar"
:style="`border-radius: ${roundAvatar ? '50%' : '3px'}; ` + iconStyle"
class="list-item-iconAvatar flex justify-center align-center"
/>
</slot>

<view class="content">
<view class="text-grey">
<slot>{{ title }}</slot>
</view>

<view v-if="extra || $slots.extra" class="text-gray text-sm flex">
<view class="text-cut text-grey">
<slot name="extra">{{ extra }}</slot>
</view>
</view>
</view>

<view class="action">
<view v-if="times" class="text-grey text-sm">
<view v-if="times" style="display: flex; flex-direction: column; align-items: flex-end;">
<view class="text-right margin-bottom-xs">{{ times[0] }}</view>
<view v-if="times[1]" class="text-right">{{ times[1] }}</view>
</view>
</view>

<view v-else-if="time || $slots.time || $slots.tips" class="text-grey text-sm">
<slot name="time">{{ time }}</slot>
<slot name="tips"></slot>
</view>

<template v-else>
<slot name="action">{{ action }}</slot>
</template>
</view>
</view>
</template>

<script>
export default {
name: 'l-list-item',

props: {
title: { default: '' },
action: { default: '' },
arrow: {},
extra: { default: '' },
time: { default: '' },
times: {},
roundAvatar: {},
avatar: {},
avatarStyle: { default: '' },
iconAvatar: {},
iconStyle: { default: '' },
imgAvatar: { default: '' },
imgStyle: { default: '' }
}
}
</script>

+ 22
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/list.vue Целия файл

@@ -0,0 +1,22 @@
<template>
<view :class="[card ? 'card-menu margin-tb' : '', message ? 'menu-avatar' : 'menu']" class="cu-list">
<slot></slot>
<view v-if="empty" class="padding text-gray text-center">{{ emptyTips }}</view>
<view v-if="!empty && nomore" class="padding text-gray text-center">{{ nomoreTips }}</view>
</view>
</template>

<script>
export default {
name: 'l-list',

props: {
card: {},
message: {},
empty: {},
emptyTips: { default: '列表为空' },
nomore: { default: false },
nomoreTips: { default: '没有更多了' }
}
}
</script>

+ 20
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/loading.vue Целия файл

@@ -0,0 +1,20 @@
<template>
<view :class="['bg-' + color, error ? 'erro' : completed ? 'over' : 'loading']" class="cu-load">
{{ error ? errorText : completed ? completeText : loadingText }}
</view>
</template>

<script>
export default {
name: 'l-loading',

props: {
completed: {},
error: {},
color: { default: 'gray' },
loadingText: { default: '加载中…' },
completeText: { default: '加载完成' },
errorText: { default: '加载失败' }
}
}
</script>

+ 216
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/modal.vue Целия файл

@@ -0,0 +1,216 @@
<template>
<view @click="modalClick" :class="[value ? 'show' : '', type === 'bottom' ? 'bottom-modal' : '']" class="cu-modal">
<!-- 图片弹出窗口 -->
<view v-if="type === 'img'" class="cu-dialog">
<view
:style="{ backgroundImage: 'url(' + img + ')', height: '200px' }"
class="bg-img"
style="position: relative;"
>
<view @tap="close" class="action text-white" style="position: absolute;top: 10px;right: 10px;">
<l-icon type="close" />
</view>
</view>
<view class="cu-bar bg-white"><view @tap="close" class="action margin-0 flex-sub solid-left">关闭</view></view>
</view>

<!-- 单选弹出窗口 -->
<view v-else-if="type === 'radio'" @tap.stop class="cu-dialog">
<radio-group @change="radioChange" class="block">
<view class="cu-list menu text-left">
<view v-for="(item, index) of range" :key="index" class="cu-item">
<label class="flex justify-between align-center flex-sub">
<view class="flex-sub">{{ objectMode ? item[textField] : item }}</view>
<radio
:checked="radioIndex === index ? true : false"
:value="objectMode ? item[valueField] : item"
:class="radioIndex === index ? 'checked' : ''"
class="round"
></radio>
</label>
</view>
</view>
</radio-group>
</view>

<!-- 多选弹出窗口 -->
<view v-else-if="type === 'checkbox'" @tap.stop class="cu-dialog">
<view class="cu-bar bg-white">
<view @tap="close(0)" class="action text-blue">取消</view>
<view v-if="!readonly" @tap="close(1)" class="action text-green">确定</view>
</view>
<view class="grid col-3 padding-sm">
<view v-for="(item, index) in range" :key="index" class="padding-xs">
<button
@tap="chooseCheckbox(index)"
:class="checkboxIndex.includes(index) ? 'bg-orange' : 'line-orange'"
class="cu-btn orange lg block"
>
{{ objectMode ? item[textField] : item }}
</button>
</view>
</view>
</view>

<!-- 普通/对话/底部弹出窗口 -->
<view v-else class="cu-dialog">
<view :class="[type === 'bottom' ? '' : 'justify-end pading-left']" class="cu-bar bg-white">
<!-- 普通/对话弹出窗口 -->
<template v-if="type === 'default' || type === 'confirm'">
<view class="content">
<slot name="title">{{ title }}</slot>
</view>
<view @tap="close" class="action"><l-icon type="close" color="red" /></view>
</template>

<!-- 底部弹出窗口 -->
<template v-if="type === 'bottom'">
<slot name="action">
<l-button @click="close" class="action padding-lr" line="red">取消</l-button>
<view class="action padding-lr">{{ title }}</view>
</slot>
</template>
</view>

<view class="padding">
<slot>{{ content }}</slot>
</view>

<!-- 对话弹出窗口的操作区 -->
<template v-if="type === 'confirm'">
<view class="cu-bar bg-white justify-end padding-right">
<view class="action">
<button @tap="close(0)" class="cu-btn line-green text-green">取消</button>
<button @tap="close(1)" class="cu-btn bg-green margin-left">确定</button>
</view>
</view>
</template>
</view>
</view>
</template>

<script>
export default {
name: 'l-modal',

props: {
title: {},
content: {},
value: {},
type: { default: 'default' },
img: {},
range: { default: () => [] },
radio: {},
checkbox: {},
readonly: {},
textField: { default: 'text' },
valueField: { default: 'value' }
},

data() {
return {
radioIndex: undefined,
checkboxIndex: []
}
},

created() {
this.init()
},

methods: {
init() {
if (this.type === 'radio') {
this.radioIndex = this.objectMode
? this.range.findIndex(t => t[this.valueField] === this.radio)
: this.range.indexOf(this.radio)
} else if (this.type === 'checkbox' && Array.isArray(this.checkbox)) {
this.checkboxIndex = this.checkbox.reduce(
(a, b) =>
a.concat(this.objectMode ? this.range.findIndex(t => t[this.valueField] === b) : this.range.indexOf(b)),
[]
)
}
},

close(arg) {
this.$emit('input', false)
this.$emit('close', false)

if (typeof arg === 'number') {
this.$emit(['cancel', 'ok'][arg])
}

if (this.type === 'checkbox') {
if (arg === 0) {
this.checkboxIndex = this.checkbox
} else {
const checkboxValue = this.checkboxIndex.reduce(
(a, b) => a.concat(this.objectMode ? this.range[b][this.valueField] : this.range[b]),
[]
)

this.$emit('checkboxChange', checkboxValue)
this.$emit('checkboxIndex', this.checkboxIndex)
}
}

if (typeof arg === 'number') {
this.$emit(['cancel', 'ok'][arg])
}
},

modalClick() {
if (this.type === 'radio' || this.type === 'checkbox') {
this.close()
}
},

radioChange(e) {
if (this.readonly) {
return
}

this.radioIndex = this.objectMode
? this.range.findIndex(t => t[this.valueField] === e.detail.value)
: this.range.indexOf(e.detail.value)
this.$emit(
'radioChange',
this.objectMode ? this.range[this.radioIndex][this.valueField] : this.range[this.radioIndex]
)
this.$emit('radioIndex', this.radioIndex)
},

chooseCheckbox(index) {
if (this.readonly) {
return
}

if (this.checkboxIndex.includes(index)) {
this.checkboxIndex = this.checkboxIndex.filter(t => t !== index)
} else {
this.checkboxIndex.push(index)
}

const checkboxValue = this.checkboxIndex.reduce(
(a, b) => a.concat(this.objectMode ? this.range[b][this.valueField] : this.range[b]),
[]
)
}
},

watch: {
value(newVal) {
if (newVal) {
this.init()
}
}
},

computed: {
objectMode() {
return typeof this.range[0] === 'object'
}
}
}
</script>

+ 75
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/nav.vue Целия файл

@@ -0,0 +1,75 @@
<template>
<scroll-view
:scroll-left="scrollLeft"
:class="['bg-' + color, type === 'center' ? 'text-center' : '', shadow ? 'nav-shadow' : '']"
class="nav"
style="z-index: 10;"
scroll-with-animation
scroll-x
>
<template v-if="type !== 'flex'">
<view
v-for="(item, idx) in items"
@tap="tabSelect(idx)"
:key="idx"
:class="value === idx ? 'text-green cur' : ''"
class="cu-item"
>
{{ item }}
</view>
</template>

<view v-else class="flex text-center">
<view
@tap="tabSelect(idx)"
v-for="(item, idx) in items"
:key="idx"
:class="[value === idx ? (color ? 'bg-white cur' : 'bg-green cur') : '']"
class="cu-item flex-sub"
>
{{ item }}
</view>
</view>
</scroll-view>
</template>

<script>
export default {
name: 'l-nav',

props: {
color: { default: 'white' },
items: { default: () => [] },
value: {},
type: { default: 'default' },
shadow: {}
},

data() {
return {
scrollLeft: 0
}
},

methods: {
tabSelect(idx) {
this.scrollLeft = (idx - 1) * 60

this.$emit('input', idx)
this.$emit('change', idx)
}
}
}
</script>

<style scoped>
.nav-shadow {
box-shadow: 0px 0px 7px 0px #666;
}
</style>

<style>
:host {
z-index: 10;
}
</style>

+ 39
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/progress.vue Целия файл

@@ -0,0 +1,39 @@
<template>
<view
:class="[
size,
radius ? 'radius' : '',
round ? 'round' : '',
striped ? 'striped' : '',
active && striped ? 'active' : ''
]"
class="cu-progress"
>
<view :class="[color ? 'bg-' + color : '']" :style="{ width: displayPercent + '%' }">
{{ noText ? '' : displayPercent + '%' }}
</view>
</view>
</template>

<script>
export default {
name: 'l-progress',

props: {
percent: {},
noText: {},
color: {},
radius: {},
round: {},
size: { default: 'df' },
striped: {},
active: {}
},

computed: {
displayPercent() {
return Number(this.percent) || 0
}
}
}
</script>

+ 44
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/radio.vue Целия файл

@@ -0,0 +1,44 @@
<template>
<view class="cu-form-group">
<view class="title">
<text v-if="required" class="lr-required">*</text>
{{ title }}
</view>
<radio
@click="change"
:checked="isCheck"
:value="radioValue"
:disabled="disabled"
:class="[isCheck ? 'checked' : '', color ? color : '', point ? 'radio' : '']"
></radio>
</view>
</template>

<script>
export default {
name: 'l-radio',

props: {
title: {},
disabled: {},
point: {},
color: {},
radioValue: {},
value: {},
required: {}
},

computed: {
isCheck() {
return this.value && this.value == this.radioValue
}
},

methods: {
change(e) {
this.$emit('input', this.radioValue)
this.$emit('change', this.radioValue)
}
}
}
</script>

+ 54
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/region-picker.vue Целия файл

@@ -0,0 +1,54 @@
<template>
<view class="cu-form-group">
<view class="title">
<text v-if="required" class="lr-required">*</text>
{{ title }}
</view>
<picker
@change="change"
:customitem="customitem"
:value="value"
:disabled="disabled"
:class="[arrow ? 'picker-arrow' : '']"
mode="region"
>
<view class="picker">
<slot>{{ display || placeholder }}</slot>
</view>
</picker>
</view>
</template>

<script>
export default {
name: 'l-region-picker',

props: {
title: {},
disabled: {},
required: {},
placeholder: { default: '请选择地区…' },
multiple: {},
customitem: {},
value: {},
arrow: { default: true }
},

methods: {
change(e) {
this.$emit('change', e.detail.value)
this.$emit('input', e.detail.value)
}
},

computed: {
display() {
if (!this.value || !this.value.length) {
return this.placeholder || ''
}

return this.value.join(' ')
}
}
}
</script>

+ 164
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/select.vue Целия файл

@@ -0,0 +1,164 @@
<template>
<view class="cu-form-group">
<view class="title">
<text v-if="required" class="lr-required">*</text>
{{ title }}
</view>
<picker
@change="change"
:mode="multiple ? 'multiSelector' : 'selector'"
:range="innerRange"
:value="index"
:disabled="disabled"
:class="[disabled ? '' : 'picker-arrow']"
>
<view class="picker">
<slot>{{ display }}</slot>
</view>
</picker>
</view>
</template>

<script>
export default {
name: 'l-select',

props: {
title: {},
disabled: {},
range: {},
placeholder: {},
multiple: {},
value: {},
emptyText: { default: '(未选择)' },
splitText: { default: ' ' },
required: {},
textField: { default: 'text' },
valueField: { default: 'value' }
},

data() {
return {
index: -1
}
},

model: {
prop: 'value',
event: 'input'
},

mounted() {
this.calcIndex()
},

methods: {
change(e) {
this.index = e.detail.value
this.$emit('input', this.currentModel)
this.$emit('change', this.currentModel)
},

calcIndex() {
if (this.multiple) {
if (this.objectMode) {
this.index = this.range.map((group, idx) =>
group.findIndex(t => this.arrayCompare(t[this.valueField], this.value[idx]))
)
} else {
this.index = this.range.map((group, idx) => group.findIndex(t => this.arrayCompare(t, this.value[idx])))
}
} else {
if (this.objectMode) {
this.index = this.range.findIndex(t => t[this.valueField] === this.value)
} else {
this.index = this.range.indexOf(this.value)
}
}
},

arrayCompare(a, b) {
if (!a || !b || a.length !== b.length) {
return false
}

let isOk = true
a.forEach((t, i) => {
if (b[i] !== t) {
isOk = false
}
})

return isOk
}
},

computed: {
objectMode() {
return typeof (this.multiple ? this.range[0][0] : this.range[0]) === 'object'
},

innerRange() {
if (!this.objectMode) {
return this.range
}

if (this.multiple) {
return this.range.map(item => item.map(t => t[this.textField]))
}

return this.range.map(t => t[this.textField])
},

currentModel() {
const { multiple, range, index, objectMode } = this
if (!multiple) {
return index === -1 ? undefined : objectMode ? range[index][this.valueField] : range[index]
}

if (!objectMode) {
return range.map((group, idx) => group[index[idx]])
}

range.map((group, idx) => (index[idx] === -1 ? undefined : group[index[idx]][this.valueField]))
},

display() {
const { multiple, range, value, index, objectMode, emptyText, displayPlaceholder, splitText } = this
if (!multiple) {
return index === -1 ? displayPlaceholder : objectMode ? range[index][this.textField] : range[index]
}

if (!Array.isArray(index) || index.every(t => t === -1)) {
return displayPlaceholder
}

if (!objectMode) {
return range.map((group, idx) => group[index[idx]] || emptyText).join(splitText)
}

return range
.map((group, idx) => (index[idx] === -1 ? emptyText : group[index[idx]][this.textField]))
.join(splitText)
},

displayPlaceholder() {
if (this.disabled) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return this.title ? `请选择${this.title}` : '请选择…'
}
},

watch: {
value() {
this.calcIndex()
}
}
}
</script>

+ 29
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/step.vue Целия файл

@@ -0,0 +1,29 @@
<template>
<view :class="[arrow ? 'steps-arrow' : '']" class="cu-steps">
<view
v-for="(item, index) in steps"
:key="index"
:class="index < step ? 'text-' + activeColor : ''"
class="cu-item"
>
<text v-if="number" :data-index="index + 1" class="num"></text>
<text v-else :class="index < step || !point ? 'cuIcon-' + item.icon : 'cuIcon-title'"></text>
{{ item.title }}
</view>
</view>
</template>

<script>
export default {
name: 'l-step',

props: {
step: { default: 0 },
steps: { default: () => [] },
activeColor: { default: 'blue' },
point: {},
arrow: {},
number: {}
}
}
</script>

+ 33
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/switch.vue Целия файл

@@ -0,0 +1,33 @@
<template>
<view class="cu-form-group">
<view class="title">{{ title }}</view>
<switch
@change="change"
:checked="value"
:disabled="disabled"
:class="[value ? 'checked' : '', icon ? 'switch-' + icon : '', color ? color : '', radius ? 'radius' : '']"
></switch>
</view>
</template>

<script>
export default {
name: 'l-switch',

props: {
title: {},
color: {},
icon: {},
radius: {},
disabled: {},
value: {}
},

methods: {
change(e) {
this.$emit('change', e.detail.value)
this.$emit('input', e.detail.value)
}
}
}
</script>

+ 48
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/tag.vue Целия файл

@@ -0,0 +1,48 @@
<template>
<view
@click="click"
:class="[
round ? 'round' : '',
radius ? 'radius' : '',
size && !capsule ? size : '',
color && !capsule ? 'bg-' + color : '',
line && !capsule ? 'line-' + line : '',
light && !capsule ? 'light' : '',
capsule ? 'cu-capsule' : 'cu-tag',
badge ? 'badge' : ''
]"
:style="{ zIndex: badge ? 2 : 1 }"
>
<template v-if="capsule">
<view :class="[size, color ? 'bg-' + color : '']" class="cu-tag tag-icon"><slot name="icon"></slot></view>
<view :class="[size, color ? 'line-' + color : '']" class="cu-tag"><slot></slot></view>
</template>

<template v-else>
<slot></slot>
</template>
</view>
</template>

<script>
export default {
name: 'l-tag',

props: {
round: {},
radius: {},
size: { default: 'df' },
color: {},
line: {},
light: {},
capsule: {},
badge: {}
},

methods: {
click(e) {
this.$emit('click', e)
}
}
}
</script>

+ 116
- 0
Learun.Framework.Ultimate V7/LearunApp-2.2.0/components/learun-mpui/learun-ui-mp/textarea.vue Целия файл

@@ -0,0 +1,116 @@
<template>
<view v-if="!simpleMode">
<view class="cu-form-group" style="border-bottom: none">
<view class="title">
<text v-if="required" class="lr-required">*</text>
{{ title }}
</view>
</view>
<view class="cu-form-group" style="position: relative;border-top: none">
<textarea
@input="textareaInput"
@focus.stop.prevent
:maxlength="maxlength"
:placeholder="displayPlaceHolder"
:fixed="fixed"
:value="display"
:auto-height="autoHeight"
:readonly="readonly"
style="margin-top:0;"
></textarea>
<cover-view v-if="readonly" @click.stop.prevent class="textarea-mask"></cover-view>
</view>
</view>

<view v-else class="cu-form-group" style="position: relative;">
<textarea
@input="textareaInput"
@focus.stop.prevent
:maxlength="maxlength"
:placeholder="displayPlaceHolder"
:fixed="fixed"
:value="display"
:auto-height="autoHeight"
:readonly="readonly"
:style="textareaStyle"
></textarea>
<cover-view v-if="readonly" @click.stop.prevent class="textarea-mask"></cover-view>
</view>
</template>

<script>
export default {
name: 'l-textarea',

props: {
maxlength: { default: -1 },
readonly: {},
placeholder: {},
fixed: {},
value: {},
textareaStyle: {},
autoHeight: {},
simpleMode: {},
required: {},
title: {}
},

data() {
return {
hide: false
}
},

// #ifdef MP-WEIXIN
beforeMount() {
uni.$on('modal', this.handelModal)
},

beforeDestroy() {
uni.$off('modal', this.handelModal)
},
// #endif

methods: {
textareaInput(e) {
this.$emit('change', e.detail.value)
this.$emit('input', e.detail.value)
},

// #ifdef MP-WEIXIN
handelModal(modal) {
this.hide = !!modal
}
// #endif
},

computed: {
display() {
return this.hide ? '' : this.value
},

displayPlaceHolder() {
if (this.readonly || this.hide) {
return ''
}

if (this.placeholder) {
return this.placeholder
}

return !this.simpleMode && this.title ? `请输入${this.title}` : '请输入…'
}
}
}
</script>

<style lang="less" scoped>
.textarea-mask {
z-index: 999;
position: absolute;
bottom: 0;
top: 0;
left: 0;
right: 0;
}
</style>

Някои файлове не бяха показани, защото твърде много файлове са промени

Зареждане…
Отказ
Запис