Browse Source

Merge pull request 'feat' (#2) from feat into develop

Reviewed-on: http://101.201.226.163:3000/TALL/TALL-MUI-4/pulls/2
develop
wally 4 years ago
parent
commit
b32599987c
  1. 148
      .drone.yml
  2. 4
      .editorconfig
  3. 56
      .eslintrc.js
  4. 4
      .hbuilderx/launch.json
  5. 195
      App.vue
  6. 66
      CHANGELOG.md
  7. 17
      apis/project.js
  8. 12
      apis/role.js
  9. 33
      apis/tall.js
  10. 20
      apis/task.js
  11. 7
      apis/wbs.js
  12. BIN
      common/img/weixinIcon.png
  13. 15
      common/js/config.js
  14. 48
      common/js/dc-clipboard/clipboard.js
  15. 4484
      common/styles/tailwind.scss
  16. 8
      common/styles/theme/default.scss
  17. 5
      common/styles/theme/index.scss
  18. 8
      common/styles/theme/test.scss
  19. 451
      components/Calendar/Calendar.vue
  20. 136
      components/Calendar/generateDates.js
  21. 82
      components/Globals/Globals.vue
  22. 139
      components/Plugin/Plugin.vue
  23. 480
      components/PrettyExchange/PrettyExchange.vue
  24. 134
      components/Projects/ProjectItem.vue
  25. 57
      components/Projects/Projects.vue
  26. 232
      components/Roles/Roles.vue
  27. 173
      components/Skeleton/Skeleton.vue
  28. 13
      components/Theme/Theme.vue
  29. 126
      components/TimeLine/TimeLine.vue
  30. 38
      components/TimeLine/component/Barrier.vue
  31. 177
      components/TimeLine/component/TaskTools.vue
  32. 132
      components/TimeLine/component/TimeBox.vue
  33. 317
      components/TimeLine/component/TimeStatus.vue
  34. 0
      components/TimeLine/component/Title.vue
  35. 81
      components/Tips/Tips.vue
  36. 221
      components/Title/Title.vue
  37. 466
      components/Title/components/CreateTask copy.vue
  38. 583
      components/Title/components/CreateTask.vue
  39. 210
      components/Title/components/ShareProject.vue
  40. 43
      components/Upload/Upload.vue
  41. 2
      config/task.js
  42. 17
      config/time.js
  43. 194
      hooks/project/useGetTasks.js
  44. 122
      hooks/project/useInit.js
  45. 7
      hooks/theme/useTheme.js
  46. 37
      hooks/user/useGetToken.js
  47. 10
      hooks/user/useGetUserIdFromLocal.js
  48. 285
      hooks/user/userMixin - 副本.js
  49. 123
      hooks/user/userMixin.js
  50. 71
      main.js
  51. 115
      package.json
  52. 26
      pages.json
  53. 171
      pages/index/index.vue
  54. 142
      pages/project/project.vue
  55. 96
      pages/user/accountLogin.vue
  56. 8
      pages/user/login.vue
  57. 266
      pages/user/mixin.js
  58. 9
      plugins/p-deliver/p-deliver.vue
  59. 10
      plugins/p-task-title/p-task-title.vue
  60. 3
      store/db/actions.js
  61. 3
      store/db/getters.js
  62. 12
      store/db/index.js
  63. 3
      store/db/mutations.js
  64. 7
      store/db/state.js
  65. 42
      store/index.js
  66. 3
      store/messages/getters.js
  67. 4
      store/messages/index.js
  68. 85
      store/messages/mutations.js
  69. 8
      store/messages/state.js
  70. 1
      store/project/getters.js
  71. 4
      store/project/index.js
  72. 19
      store/project/mutations.js
  73. 2
      store/role/actions.js
  74. 363
      store/socket/actions.js
  75. 15
      store/socket/index.js
  76. 50
      store/socket/mutations.js
  77. 12
      store/socket/state.js
  78. 4
      store/task/actions.js
  79. 4
      store/task/getters.js
  80. 17
      store/user/actions.js
  81. 7
      store/user/getters.js
  82. 22
      store/user/index.js
  83. 15
      store/user/mutations.js
  84. 3
      store/user/state.js
  85. 2
      uni.scss
  86. 12
      uni_modules/uni-calendar/changelog.md
  87. 546
      uni_modules/uni-calendar/components/uni-calendar/calendar.js
  88. 12
      uni_modules/uni-calendar/components/uni-calendar/i18n/en.json
  89. 8
      uni_modules/uni-calendar/components/uni-calendar/i18n/index.js
  90. 12
      uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hans.json
  91. 12
      uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hant.json
  92. 181
      uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue
  93. 551
      uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue
  94. 354
      uni_modules/uni-calendar/components/uni-calendar/util.js
  95. 88
      uni_modules/uni-calendar/package.json
  96. 103
      uni_modules/uni-calendar/readme.md
  97. 16
      uni_modules/uni-icons/changelog.md
  98. 1115
      uni_modules/uni-icons/components/uni-icons/icons.js
  99. 89
      uni_modules/uni-icons/components/uni-icons/uni-icons.vue
  100. 663
      uni_modules/uni-icons/components/uni-icons/uniicons.css

148
.drone.yml

@ -0,0 +1,148 @@
---
kind: pipeline
type: docker
name: dev
# 挂载的主机卷,可以映射到docker容器中
volumes:
# maven构建缓存(宿主机目录)
- name: ssh_key
host:
path: /root/.ssh/
- name: cache
host:
path: /var/lib/cache
- name: data
host:
path: /var/lib/data
steps:
- name: restore-cache
image: drillster/drone-volume-cache
volumes:
- name: cache
path: /cache
settings:
restore: true
mount:
- ./node_modules
- name: build
image: node:latest
pull: if-not-exists # default always
volumes:
- name: cache
path: /root/.m2
commands:
- npm i
- npm run build:dev
- name: rebuild-cache
image: drillster/drone-volume-cache
volumes:
- name: cache
path: /cache
settings:
rebuild: true
mount:
- ./node_modules
- name: deploy-scp
image: appleboy/drone-scp
pull: if-not-exists
volumes:
- name: ssh_key
path: /root/.ssh/
settings:
host: test.tall.wiki
port: 22
username: root
key_path: /root/.ssh/id_rsa
rm: true # true则会删除目标目录重建
target: /home/tall/v4.0.0
source: dist/**/*
strip_components: 1 # 去除的目录层数,如果没有该选项,则拷贝过去是 target/xxx.jar,1代表去除target
# - name: run-ssh
# image: appleboy/drone-ssh
# pull: if-not-exists
# volumes:
# - name: ssh_key
# path: /root/.ssh/
# settings:
# settings:
# host: test.tall.wiki
# port: 22
# username: root
# key_path: /root/.ssh/id_rsa
# script_stop: true # stop script after first failure
# #command_timeout: 30s # 30seconds, the maximum amount of time for the execute commands, default is 10 minutes.
# script:
# - cd /home/iacd-platform-drone
# - ./re.sh > /dev/null 2> /dev/null &
- name: notify-email
image: drillster/drone-email
pull: if-not-exists
settings:
host: smtp.qiye.aliyun.com #例如 smtp.qq.com
port: 465 #例如QQ邮箱端口465
username: devops@ccsens.com #邮箱用户名
password: #邮箱密码
from_secret: orgsecret_password_mail_devops
from: devops@ccsens.com
recipients: weizezhao@ccsens.com #收件人,多个用,隔开
when: #执行条件
status:
- success
- changed
- failure
- name: notify-wechatwork
image: fifsky/drone-wechat-work
pull: if-not-exists
settings:
url: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=b2b93e9a-128b-41d4-8dce-12004e3f48b9
msgtype: markdown
content: |
{{if eq .Status "success" }}
#### 🎉 ${DRONE_REPO} 构建成功
> Commit: [${DRONE_COMMIT_MESSAGE}](${DRONE_COMMIT_LINK})
> Author: ${DRONE_COMMIT_AUTHOR}
> [点击查看](${DRONE_BUILD_LINK})
{{else}}
#### ❌ ${DRONE_REPO} 构建失败
> Commit: [${DRONE_COMMIT_MESSAGE}](${DRONE_COMMIT_LINK})
> Author: ${DRONE_COMMIT_AUTHOR}
> 请立即修复!!!
> [点击查看](${DRONE_BUILD_LINK})
{{end}}
when:
status:
- failure
- success
trigger:
branch: develop
# - name: notify-dingtalk
# image: lddsb/drone-dingtalk-message
# environment:
# PASSWORD:
# from_secret: password_mail_devops
# settings:
# token: your-dingtalk-robot-access-token
# type: markdown
# message_color: true
# message_pic: true
# sha_link: true
# -name: notify-slack
# image: plugins/slack
# webhook: https://hooks.slack.com/ www.dijiuyy.com services/xxx/xxx/xxx
# channel: dev
# template: >
# {{#success build.status}}
# build {{build.number}} succeeded. Good job.
# {{else}}
# build {{build.number}} failed. Fix me please.
# {{/success}}

4
.editorconfig

@ -1,8 +1,8 @@
root = true
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 140
root = true

56
.eslintrc.js

@ -1,21 +1,41 @@
module.exports = {
"env": {
"browser": true,
"es2021": true
},
"extends": [
"plugin:vue/essential",
"airbnb-base"
env: {
browser: true,
es2021: true,
},
extends: [
'plugin:vue/essential',
'airbnb-base',
],
parserOptions: {
ecmaVersion: 13,
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: [
'vue',
'@typescript-eslint',
],
rules: {
'vue/html-self-closing': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-param-reassign': 'off',
'max-len': ['error', { code: 140, tabWidth: 2 }],
'object-curly-newline': ['error', { multiline: true }],
'arrow-parens': ['error', 'as-needed'],
'linebreak-style': 'off',
'vue/attributes-order': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/html-indent': 'off',
'vue/html-closing-bracket-newline': [
'error',
{
singleline: 'never',
multiline: 'always',
},
],
"parserOptions": {
"ecmaVersion": 13,
"parser": "@typescript-eslint/parser",
"sourceType": "module"
},
"plugins": [
"vue",
"@typescript-eslint"
],
"rules": {
}
},
};

4
.hbuilderx/launch.json

@ -2,6 +2,10 @@
// launchtypelocalremote, localremote
"version": "0.0",
"configurations": [{
"app-plus" :
{
"launchtype" : "local"
},
"default" :
{
"launchtype" : "local"

195
App.vue

@ -1,36 +1,169 @@
<script>
// import {
// ref,
// computed
// } from 'vue';
// import {
// useStore
// } from 'vuex';
// const store = useStore();
// onLaunch(() => {
// // checkNetwork(); //
// });
// store
// 2g 3g ;
// function checkNetwork() {
// uni.getNetworkType({
// success: ({ networkType }) => {
// this.setNetworkConnected(!(networkType === 'none' || networkType === '2g' || networkType === '3g'));
// },
// });
// //
// uni.onNetworkStatusChange(({ isConnected, networkType }) => {
// this.setNetworkConnected(isConnected && !(networkType === '2g' || networkType === '3g'));
// });
// }
// import { mapState } from 'vuex';
export default {
// computed: {
// ...mapState(['theme']),
// },
// watch: {
// theme(newTheme) {
// console.log('newTheme: ', newTheme);
// if (!newTheme) return;
// this.loadTheme();
// },
// },
async onLaunch(options) {
// this.loadTheme();
console.log('onLaunch options: ', options);
this.checkNetwork(); //
this.getSystemInfo(); //
await this.syncLocalDataToStore(options.query.u); // localStoragestore
const token = await this.getToken();
if (!token) {
this.$ui.showToast('获取用户信息失败, 请登录');
// TODO:
return;
}
this.noPhone(this.$store.state.user.phone);
this.$store.dispatch('socket/initSocket');
},
methods: {
// loadTheme() {
// const path = this.theme.replace('-', '/');
// import(`./common/styles/${path}.scss`);
// },
async getToken() {
const { token } = this.$store.state.user;
const tokenIsAvailable = this.$store.getters['user/tokenIsAvailable'];
const userId = this.$store.getters['user/userId'];
if (token && tokenIsAvailable) {
// 1.1 storetoken 使storetoken
return token;
} else {
// 2. userIdtoken
if (userId) {
try {
const { token } = await this.$store.dispatch('user/getTokenByUserId', userId);
return token;
} catch (error) {
console.error('error: ', error);
return null;
}
} else {
return null;
}
}
},
/**
* 将localStorage里的数据同步到store里
* user, token, tokenExpiredTime
*/
syncLocalDataToStore(urlUserId) {
return new Promise((resolve, reject) => {
try {
const localUser = uni.$storage.getStorageSync('user');
const localToken = uni.$storage.getStorageSync('anyringToken');
const tokenExpiredTime = uni.$storage.getStorageSync('tokenExpiredTime');
if (!this.$store.state.user.user) {
if (localUser) {
// user
const user = JSON.parse(localUser);
if (!urlUserId || user.id === urlUserId) {
this.$store.commit('user/setUser', user);
} else {
this.$store.commit('user/setUser', { id: urlUserId });
}
} else {
this.$store.commit('user/setUser', { id: urlUserId });
}
}
if (this.$store.state.user.token && localToken) {
// token
this.$store.commit('user/setToken', localToken);
}
if (this.$store.state.user.tokenExpiredTime && tokenExpiredTime) {
// tokenExpiredTime
this.$store.commit('user/setTokenExpiredTime', +tokenExpiredTime);
}
resolve();
} catch (error) {
reject(error);
}
});
},
// store
// 2g 3g ;
checkNetwork() {
uni.getNetworkType({
success: ({ networkType }) => {
this.$store.commit('setNetworkConnected', !(networkType === 'none' || networkType === '2g' || networkType === '3g'));
},
});
//
uni.onNetworkStatusChange(({ isConnected, networkType }) => {
this.$store.commit('setNetworkConnected', isConnected && !(networkType === '2g' || networkType === '3g'));
});
},
//
getSystemInfo() {
uni.getSystemInfo({
success: result => {
this.$store.commit('setSystemInfo', result);
},
fail: error => {
console.error('getSystemInfo fail:', error);
},
});
},
//
async signin() {
try {
const data = await uni.$u.api.signin();
if (data && data.token) {
this.$store.commit('user/setUser', data);
this.$store.commit('user/setToken', data.token);
noPhone(data.phone);
} else {
uni.$ui.showToast('返回数据异常');
}
} catch (error) {
console.error('error: ', error);
uni.$ui.showToast(error || '登录失败');
}
},
/**
* 没有手机号 跳转绑定手机号的界面
* @param {string} phone
*/
async noPhone(phone) {
if (!phone) {
// TODO:
// uni.navigateTo({ url: '/pages/phone-bind/phone-bind' });
}
},
},
};
</script>
<style lang="scss">
/*每个页面公共css */
@import "@/uni_modules/vk-uview-ui/index.scss";
@import '@/common/styles/iconfont.scss';
@import '@/common/styles/app.scss';
/*每个页面公共css */
@import '@/uni_modules/vk-uview-ui/index.scss';
@import '@/common/styles/iconfont.scss';
@import '@/common/styles/app.scss';
@import '@/common/styles/tailwind.scss';
@import '@/common/styles/theme/index.scss';
page {
height: 100%;
}
</style>

66
CHANGELOG.md

@ -1,6 +1,68 @@
# 1.0.0 (2021-12-31)
# 1.0.0 (2022-01-11)
### 🌟 新功能
范围|描述|commitId
--|--|--
- | Initial commit | 52b8f49
- | app.vue | [970cf9a](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/970cf9a)
- | first commit | [8dc26de](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/8dc26de)
project | 日常任务面板添加 | [b3f16ff](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/b3f16ff)
theme | theme demo | [9175758](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/9175758)
- | vue3 | [12ed2ad](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/12ed2ad)
- | 使用uview完成api请求 | [1b3efd8](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/1b3efd8)
- | 日历页添加 | [1b46a91](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/1b46a91)
- | 日历页首页 | [561c8e6](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/561c8e6)
- | 时间轴展示 | [8b1b380](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/8b1b380)
- | 时间轴接口 | [a95d005](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/a95d005)
- | 时间轴页面 | [e926b75](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/e926b75)
- | 更新代码 | [392c8cc](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/392c8cc)
- | 添加 timeline | [72dad2b](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/72dad2b)
- | 表单验证 | [8f3bc1e](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/8f3bc1e)
- | 账户名密码登录 | [ebf456e](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/ebf456e)
- | 项目列表 | [a52e6d5](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/a52e6d5)
- | 项目操作面板 | [3beb05e](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/3beb05e)
### 🎨 代码样式
范围|描述|commitId
--|--|--
- | calender格式及细节调整 | [db9602b](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/db9602b)
- | 细节调整 | [bdd5f87](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/bdd5f87)
### 🐛 Bug 修复
范围|描述|commitId
--|--|--
app.vue | 修复获取token报错的问题 | [9120d54](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/9120d54)
createTask | 修复createTask v-model的问题 | [b20d3f0](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/b20d3f0)
- | 修复一些内容 | [3cdb1ce](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/3cdb1ce)
### 📦 持续集成
范围|描述|commitId
--|--|--
- | 添加drone.yml | [9fbae89](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/9fbae89)
### 🔨 代码重构
范围|描述|commitId
--|--|--
- | project init 重构 | [2457a87](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/2457a87)
- | 重构project init 部分 | [c7bf2df](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/c7bf2df)
### 🚀 性能优化
范围|描述|commitId
--|--|--
- | 更新代码 | [0dd443b](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/0dd443b)
### chore
范围|描述|commitId
--|--|--
- | editorconfig update | [0c08089](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/0c08089)
范围|描述|commitId
--|--|--
- | Initial commit | [52b8f49](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/52b8f49)

17
apis/project.js

@ -0,0 +1,17 @@
import Config from '@/common/js/config.js'
const apiUrl = Config.apiUrl;
const defaultwbs = `${apiUrl}/defaultwbs`;
export function setupProject(app) {
uni.$u.api = { ...uni.$u.api } || {};
//根据id获取项目信息
uni.$u.api.findProjectById = param => uni.$u.post(`${defaultwbs}/project/findProjectById`, param);
//创建分享连接
uni.$u.api.createShare = param => uni.$u.post(`${defaultwbs}/share/create`, param);
//点击分享连接
uni.$u.api.clickShare = param => uni.$u.post(`${defaultwbs}/share/click`, param);
};

12
apis/role.js

@ -0,0 +1,12 @@
import Config from '@/common/js/config.js'
const apiUrl = Config.apiUrl;
const defaultwbs = `${apiUrl}/defaultwbs`;
export function setupRole(app) {
uni.$u.api = { ...uni.$u.api } || {};
//根据项目id查找角色
uni.$u.api.findShowRole = param => uni.$u.post(`${defaultwbs}/role/show`, param);
//根据项目id查找所有成员
uni.$u.api.queryChecker = param => uni.$u.post(`${defaultwbs}/deliver/queryChecker`, param);
};

33
apis/tall.js

@ -0,0 +1,33 @@
import Config from '@/common/js/config.js'
const apiUrl = Config.apiUrl;
const tall = `${apiUrl}/tall3/v3.0`;
export function setupTall(app) {
uni.$u.api = { ...uni.$u.api } || {};
// 登录
// uni.$u.api.signin = params => login.index(params);
uni.$u.api.signin = params => uni.$u.http.post(`${tall}/users/signin`, params); // 登录
// 获取图片验证码
uni.$u.api.getImageCode = () => uni.$u.get(`${tall}/users/code`);
// 获取短信验证码
uni.$u.api.getSmsCode = params => uni.$u.get(`${tall}/users/smscode`, params);
// 根据userId获取token
uni.$u.api.getToken = userId => uni.$u.get(`${tall}/users/userId`, { userId });
// 绑定手机号
uni.$u.api.phoneBind = (phone, smsCode) => uni.$u.http.post(`${tall}/users/binding`, { phone, smsCode });
// 是否合并账号
uni.$u.api.phoneMerge = (phone, isMerge) => uni.$u.http.post(`${tall}/users/merge`, { phone, isMerge });
// 修改用户信息
uni.$u.api.updateUserInfo = params => uni.$u.http.post(`${tall}/users/userInfo`, params);
// 获取项目列表
uni.$u.api.getProjects = (startTime, endTime) => uni.$u.post(`${tall}/project/query`, { startTime, endTime });
// 查询日历是否有小红点
uni.$u.api.findRedPoint = (startTime, endTime) => uni.$u.post(`${tall}/project/day`, { startTime, endTime });
// 设置项目顺序
uni.$u.api.setProjectSort = params => uni.$u.post(`${tall}/project/setProjectSort`, params);
// 设置项目父子结构
uni.$u.api.setProjectRelation = params => uni.$u.post(`${tall}/project/setProjectRelation`, params);
// 删除某个项目
uni.$u.api.delProject = projectId => uni.$u.post(`${tall}/project/deleteProject`, { projectId });
}

20
apis/task.js

@ -0,0 +1,20 @@
import Config from '@/common/js/config.js'
const apiUrl = Config.apiUrl;
const defaultwbs = `${apiUrl}/defaultwbs`;
export function setupTask(app) {
uni.$u.api = { ...uni.$u.api } || {};
uni.$u.api.getGlobal = param => uni.$u.post(`${defaultwbs}/task/global`, param);
uni.$u.api.getPermanent = param => uni.$u.post(`${defaultwbs}/task/permanent`, param);
//根据时间基准点和角色查找定期任务
uni.$u.api.getRegularTask = param => uni.$u.post(`${defaultwbs}/task/regular`, param);
//修改任务状态
uni.$u.api.updateTaskType = param => uni.$u.post(`${defaultwbs}/task/type`, param);
//新建任务
uni.$u.api.saveTask = param => uni.$u.post(`${defaultwbs}/task/save`, param);
//克隆任务
uni.$u.api.cloneTask = param => uni.$u.post(`${defaultwbs}/task/clone`, param);
//模糊查询 查找项目下的任务
uni.$u.api.queryTaskOfProject = param => uni.$u.post(`${defaultwbs}/task/queryTaskOfProject`, param);
};

7
apis/wbs.js

@ -0,0 +1,7 @@
import Config from "@/common/js/config.js"
export function setupWbs(app) {
uni.$u.api = { ...uni.$u.api } || {};
// 导入wbs
uni.$u.api.import = formData => this.$upload.chooseAndUpload(`${Config.apiUrl}/wbs`, formData);
}

BIN
common/img/weixinIcon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

15
common/js/config.js

@ -0,0 +1,15 @@
var config = {
baseUrl: 'https://test.tall.wiki',
apiUrl: 'https://test.tall.wiki/gateway',
msgUrl: 'wss://test.tall.wiki/websocket/message/v4.0/ws',
projectPath: 'https://test.tall.wiki/tall-project',
// baseUrl: 'https://www.tall.wiki',
// apiUrl: 'https://www.tall.wiki/gateway',
// msgUrl: 'wss://www.tall.wiki/websocket/message/v4.0/ws';
// projectPath: 'https://www.tall.wiki/tall-project',
version: 'v4.0.0'
};
export default config;

48
common/js/dc-clipboard/clipboard.js

@ -0,0 +1,48 @@
/**
* 设置粘贴板数据
* @param {String} text 要设置的字符串
* 如果未设置参数则清空数据
*/
function setClipboardText(text) {
try {
var os = plus.os.name;
text = text || '';
if ('iOS' == os) {
// var UIPasteboard = plus.ios.importClass('UIPasteboard');
// var pasteboard = UIPasteboard.generalPasteboard();
// pasteboard.setValueforPasteboardType(text, 'public.utf8-plain-text');
var pasteboard = plus.ios.invoke('UIPasteboard', 'generalPasteboard');
plus.ios.invoke(pasteboard, 'setValue:forPasteboardType:', text, 'public.utf8-plain-text');
} else {
var main = plus.android.runtimeMainActivity();
// var Context = plus.android.importClass('android.content.Context');
// var clip = main.getSystemService(Context.CLIPBOARD_SERVICE);
var clip = main.getSystemService('clipboard');
plus.android.invoke(clip, 'setText', text);
}
} catch (e) {
console.error('error @setClipboardText!!');
}
}
function getClipboardText() {
try {
var os = plus.os.name;
if ('iOS' == os) {
var pasteboard = plus.ios.invoke('UIPasteboard', 'generalPasteboard');
return plus.ios.invoke(pasteboard, 'valueForPasteboardType:', 'public.utf8-plain-text')
} else {
var main = plus.android.runtimeMainActivity();
var clip = main.getSystemService('clipboard');
return plus.android.invoke(clip, 'getText');
}
} catch (e) {
console.error('error @getClipboardText!!');
}
}
export default {
setClipboardText,
getClipboardText
}

4484
common/styles/tailwind.scss

File diff suppressed because it is too large

8
common/styles/theme/default.scss

@ -0,0 +1,8 @@
// 默认主题文件
.theme-default {
background-color: #333;
.u-card {
font-size: 24px !important;
color: #0f0;
}
}

5
common/styles/theme/index.scss

@ -0,0 +1,5 @@
// 整合所有主题
// 默认主题
@import './default.scss';
@import './test.scss';

8
common/styles/theme/test.scss

@ -0,0 +1,8 @@
// TODO: 测试用的scss主题样式
.theme-test {
background-color: #fff;
.u-card {
font-size: 24px !important;
background-color: #ff0 !important;
}
}

451
components/Calendar/Calendar.vue

@ -0,0 +1,451 @@
<template>
<view class="zzx-calendar">
<view class="calendar-header">{{ timeStr }}</view>
<!-- 星期几标题 -->
<view class="calendar-weeks">
<view
class="calendar-week"
:class="{ 'text-red-500': week === '六' || week === '日' }"
v-for="(week, index) in data.weeks"
:key="index"
>{{ week }}</view>
</view>
<view class="calendar-content">
<swiper
class="calendar-swiper"
:style="{
width: '100%',
height: calenderHeight,
}"
:indicator-dots="false"
:autoplay="false"
:duration="500"
:current="data.current"
@change="changeSwp"
:circular="true"
>
<swiper-item class="calendar-item" v-for="sItem in data.swiper" :key="sItem">
<view class="calendar-days">
<!-- 当前的 -->
<template v-if="sItem === data.current">
<view
class="calendar-day"
v-for="(item, index) in data.days"
:key="index"
:class="{ 'day-hidden': !item.show }"
@click="clickItem(item)"
>
<view
class="date"
:class="[item.isToday ? 'is-today' : '', item.fullDate === data.selectedDate ? 'is-checked' : '']"
>{{ item.time.getDate() }}</view>
<view class="dot-show" v-if="item.info === '0'" :style="dotStyle"></view>
</view>
</template>
<template v-else>
<!-- 下一个月/ -->
<template v-if="data.current - sItem === 1 || data.current - sItem === -2">
<view
class="calendar-day"
v-for="(item, index) in preDays"
:key="index"
:class="{
'day-hidden': !item.show,
}"
>
<view class="date" :class="[item.isToday ? 'is-today' : '']">{{ item.time.getDate() }}</view>
</view>
</template>
<!-- 上一个月/ -->
<template v-else>
<view
class="calendar-day"
v-for="(item, index) in nextDays"
:key="index"
:class="{
'day-hidden': !item.show,
}"
>
<view class="date" :class="[item.isToday ? 'is-today' : '']">{{ item.time.getDate() }}</view>
</view>
</template>
</template>
</view>
</swiper-item>
</swiper>
<!-- <view class="mode-change" @click="changeMode">
<view :class="weekMode ? 'mode-arrow-bottom' : 'mode-arrow-top'"> </view>
</view>-->
</view>
<view class="flex justify-center u-font-18" style="color: #3b82f6" @click="goToday">今日</view>
</view>
</template>
<script setup>
import { reactive, computed, watchEffect } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import { generateDates, formatDate } from './generateDates.js';
const props = defineProps({
//
dotStyle: {
type: Object,
default: () => ({ background: '#4ade80' }),
},
});
const emit = defineEmits(['handleFindPoint', 'handleFindPoint', 'days-change', 'selected-change']);
const data = reactive({
weeks: ['日', '一', '二', '三', '四', '五', '六'], //
current: 1,
currentYear: '',
currentMonth: '',
currentDate: '',
days: [],
weekMode: false, // false -> true ->
swiper: [0, 1, 2],
selectedDate: formatDate(new Date(), 'yyyy-MM-dd'), //
start: dayjs().startOf('month').valueOf(),
end: dayjs().endOf('month').valueOf(),
});
const store = useStore();
const dotList = computed(() => store.state.project.dotList);
const calenderHeight = computed(() => {
//
//
let h = '35px';
if (!data.weekMode) {
const d = new Date(data.currentYear, data.currentMonth, 0);
const days = d.getDate(); //
const day = new Date(d.setDate(1)).getDay();
// if (day === 0) {
// day = 7;
// }
const pre = 8 - day;
const rows = Math.ceil((days - pre) / 7) + 1;
h = `${35 * rows}px`;
}
return h;
});
//
const timeStr = computed(() => {
let str = '';
const d = new Date(data.currentYear, data.currentMonth - 1, data.currentDate);
const y = d.getFullYear();
const m = d.getMonth() + 1 <= 9 ? `0${d.getMonth() + 1}` : d.getMonth() + 1;
str = `${y}${m}`;
return str;
});
// days
const preDays = computed(() => {
let pres = [];
if (data.weekMode) {
//
const d = new Date(data.currentYear, data.currentMonth - 1, data.currentDate);
d.setDate(d.getDate() - 7);
pres = generateDates(d, 'week');
} else {
//
const d = new Date(data.currentYear, data.currentMonth - 2, 1);
pres = generateDates(d, 'month');
}
return pres;
});
// days
const nextDays = computed(() => {
let next = [];
if (data.weekMode) {
//
const d = new Date(data.currentYear, data.currentMonth - 1, data.currentDate);
d.setDate(d.getDate() + 7);
next = generateDates(d, 'week');
} else {
//
const d = new Date(data.currentYear, data.currentMonth, 1);
next = generateDates(d, 'month');
}
return next;
});
watchEffect(() => {
const days = data.days.slice(0);
const index = days.findIndex(day => day.show);
days.forEach((day, i) => {
dotList.value.forEach((item, j) => {
if (i - index === j) {
day.info = item;
}
});
});
data.days = days;
});
//
const initDate = cur => {
let date = '';
if (cur) {
date = new Date(cur);
} else {
date = new Date();
}
data.currentDate = date.getDate(); //
data.currentYear = date.getFullYear(); //
data.currentMonth = date.getMonth() + 1; //
data.currentWeek = date.getDay() === 0 ? 7 : date.getDay(); // 1...6,0
// const nowY = new Date().getFullYear(); //
// const nowM = new Date().getMonth() + 1;
// const nowD = new Date().getDate(); //
// const nowW = new Date().getDay();
// this.selectedDate = formatDate(new Date(), 'yyyy-MM-dd')
data.days = [];
let days = [];
if (data.weekMode) {
days = generateDates(date, 'week');
// this.selectedDate = days[0].fullDate;
} else {
days = generateDates(date, 'month');
// const sel = new Date(this.selectedDate.replace('-', '/').replace('-', '/'));
// const isMonth = sel.getFullYear() === this.currentYear && (sel.getMonth() + 1) === this.currentMonth;
// if(!isMonth) {
// this.selectedDate = formatDate(new Date(this.currentYear, this.currentMonth-1,1), 'yyyy-MM-dd')
// }
}
//
days.forEach((day, i) => {
dotList.value.forEach((item, j) => {
if (i === j) {
day.info = item;
}
});
});
data.days = days;
// ,
const obj = {
start: '',
end: '',
};
if (data.weekMode) {
obj.start = data.days[0].time;
obj.end = data.days[6].time;
} else {
const start = new Date(data.currentYear, data.currentMonth - 1, 1);
const end = new Date(data.currentYear, data.currentMonth, 0);
obj.start = start;
obj.end = end;
}
emit('days-change', obj);
};
initDate();
//
const daysPre = () => {
if (data.weekMode) {
const d = new Date(data.currentYear, data.currentMonth - 1, data.currentDate);
d.setDate(d.getDate() - 7);
initDate(d);
} else {
const d = new Date(data.currentYear, data.currentMonth - 2, 1);
initDate(d);
}
};
//
const daysNext = () => {
if (data.weekMode) {
const d = new Date(data.currentYear, data.currentMonth - 1, data.currentDate);
d.setDate(d.getDate() + 7);
initDate(d);
} else {
const d = new Date(data.currentYear, data.currentMonth, 1);
initDate(d);
}
};
/**
* 滑动切换上下周期
* 根据前一个减去目前的值我们可以判断是下一个月/周还是上一个月/
* current - pre === 1, -2 下一个月/
* current - pre === -1, 2 上一个月或者上一周
*/
const changeSwp = e => {
const pre = data.current;
const { current } = e.detail;
data.current = current;
if (current - pre === 1 || current - pre === -2) {
//
daysNext();
const arr = data.days.filter(s => s.show);
const end = `${arr[arr.length - 1].fullDate} 23:59:59`;
data.start = dayjs(arr[0].fullDate).valueOf();
data.end = dayjs(end).valueOf();
emit('handleFindPoint', data.start, data.end);
} else {
//
daysPre();
const arr = data.days.filter(s => s.show);
const end = `${arr[arr.length - 1].fullDate} 23:59:59`;
data.start = dayjs(arr[0].fullDate).valueOf();
data.end = dayjs(end).valueOf();
emit('handleFindPoint', data.start, data.end);
}
};
//
// const changeMode = () => {
// const premode = this.weekMode;
// let isweek = false;
// if (premode) {
// isweek = !!this.days.find((item) => item.fullDate === this.selectedDate);
// }
// this.weekMode = !this.weekMode;
// let d = new Date(this.currentYear, this.currentMonth - 1, this.currentDate);
// const sel = new Date(this.selectedDate.replace('-', '/').replace('-', '/'));
// const isMonth = sel.getFullYear() === this.currentYear && sel.getMonth() + 1 === this.currentMonth;
// if ((this.selectedDate && isMonth) || isweek) {
// d = new Date(this.selectedDate.replace('-', '/').replace('-', '/'));
// }
// this.initDate(d);
// };
//
const clickItem = e => {
data.selectedDate = e.fullDate;
emit('selected-change', e);
};
//
const goToday = () => {
const d = new Date();
initDate(d);
};
</script>
<style lang="scss" scoped>
.zzx-calendar {
width: 100%;
height: auto;
background-color: #fff;
padding-bottom: 10px;
.calendar-header {
text-align: center;
padding: 16px 0;
position: relative;
font-size: 15px;
}
.calendar-weeks {
width: 100%;
display: flex;
flex-flow: row nowrap;
margin-bottom: 10px;
justify-content: center;
align-items: center;
font-size: 12px;
color: #9ca3af;
font-weight: bold;
.calendar-week {
width: calc(100% / 7);
height: 100%;
text-align: center;
}
}
swiper {
width: 100%;
height: 60upx;
}
.calendar-content {
min-height: 30px;
}
.calendar-swiper {
min-height: 35px;
transition: height ease-out 0.3s;
}
.calendar-item {
margin: 0;
padding: 0;
height: 100%;
}
.calendar-days {
display: flex;
flex-flow: row wrap;
width: 100%;
height: 100%;
overflow: hidden;
font-size: 14px;
.calendar-day {
width: calc(100% / 7);
height: 35px;
text-align: center;
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
align-items: center;
position: relative;
}
}
.day-hidden {
visibility: hidden;
}
.mode-change {
display: flex;
justify-content: center;
margin-top: 5px;
.mode-arrow-top {
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 5px solid #ff6633;
}
.mode-arrow-bottom {
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 5px solid #ff6633;
}
}
.is-today {
background: #ffffff;
border: 1upx solid #ff6633;
border-radius: 50%;
color: #ff6633;
}
.is-checked {
background: #ff6633;
color: #ffffff;
}
.date {
width: 25px;
height: 25px;
line-height: 25px;
margin: 0 auto;
border-radius: 25px;
}
.dot-show {
width: 6px;
height: 6px;
// background: red;
border-radius: 5px;
position: absolute;
top: 2px;
right: 10px;
}
}
</style>

136
components/Calendar/generateDates.js

@ -0,0 +1,136 @@
/*
*此函数的作用是根据传入的一个日期返回这一周的日期或者这一个月的日期
* 如果是月的话注意还包含上个月和下个月的日期月的话总共数据有 6 * 7 = 42
*
*/
/*
* 时间格式化函数
* 重要提示微信小程序new Date('2020-04-16')在ios中无法获取时间对象
* 解决方式: 建议将时间都格式化成'2020/04/16 00:00:00'的格式
* 函数示例: formatDate(new Date(), 'YYYY/MM/dd hh:mm:ss')
*/
export const formatDate = (date, fmt) => {
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
let o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds(),
};
for (let k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
let str = o[k] + '';
fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str));
}
}
return fmt;
};
const padLeftZero = str => {
return ('00' + str).substr(str.length);
};
// 判断是不是date对象
export const judgeType = s => {
// 函数返回数据的具体类型
return Object.prototype.toString.call(s).slice(8, -1);
};
export const equalDate = (d1, d2) => {
let result = false;
if (d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate()) {
result = true;
}
return result;
};
/* ,2020-04-04
*/
export const dateEqual = (before, after) => {
before = new Date(before.replace('-', '/').replace('-', '/'));
after = new Date(after.replace('-', '/').replace('-', '/'));
if (before.getTime() - after.getTime() === 0) {
return true;
} else {
return false;
}
};
export const generateDates = (date = new Date(), type = 'week') => {
const result = [];
if (judgeType(date) === 'Date') {
// 年,月,日
const y = date.getFullYear();
const m = date.getMonth();
const d = date.getDate();
const days = new Date(y, m + 1, 0).getDate();
// 获取日期是星期几
// let weekIndex = date.getDay() === 0 ? 7 : date.getDay();
let weekIndex = date.getDay();
if (type === 'month') {
const dobj = new Date(y, m, 1);
// weekIndex = dobj.getDay() === 0 ? 7 : dobj.getDay();
weekIndex = dobj.getDay();
}
if (type === 'week') {
for (let i = weekIndex; i > 0; i--) {
const dtemp = new Date(y, m, d);
dtemp.setDate(dtemp.getDate() - i);
result.push({
time: dtemp,
show: true,
fullDate: formatDate(dtemp, 'yyyy-MM-dd'),
isToday: equalDate(new Date(), dtemp),
});
}
for (let i = 0; i <= 7 - weekIndex; i++) {
const dtemp = new Date(y, m, d);
dtemp.setDate(dtemp.getDate() + i);
result.push({
time: dtemp,
show: true,
fullDate: formatDate(dtemp, 'yyyy-MM-dd'),
isToday: equalDate(new Date(), dtemp),
});
}
} else if (type === 'month') {
// 上个月
for (let i = weekIndex; i > 0; i--) {
const dtemp = new Date(y, m, 1);
dtemp.setDate(dtemp.getDate() - i);
result.push({
time: dtemp,
show: false,
fullDate: formatDate(dtemp, 'yyyy-MM-dd'),
isToday: equalDate(new Date(), dtemp),
});
}
// 这个月的日期
for (let i = 0; i < days; i++) {
const dtemp = new Date(y, m, 1);
dtemp.setDate(dtemp.getDate() + i);
result.push({
time: dtemp,
show: true,
fullDate: formatDate(dtemp, 'yyyy-MM-dd'),
isToday: equalDate(new Date(), dtemp),
});
}
const len = 42 - result.length;
// 下个月的日期
for (let i = 1; i <= len; i++) {
const dtemp = new Date(y, m + 1, 0);
dtemp.setDate(dtemp.getDate() + i);
result.push({
time: dtemp,
show: false,
fullDate: formatDate(dtemp, 'yyyy-MM-dd'),
isToday: equalDate(new Date(), dtemp),
});
}
}
}
return result;
};

82
components/Globals/Globals.vue

@ -1,8 +1,86 @@
<template>
<theme class="m-2" >
<u-card
@click="openCard"
:show-foot="false"
:show-head="false"
:style="{ 'max-height': globalsHeight + 'px' }"
border-radius="25"
margin="0"
class="global-container"
>
<template v-slot:body>
<scroll-view :scrollY="true" :style="{ 'max-height': globalsHeight - 30 + 'px' }">
<skeleton :banner="false" :loading="!globals.length" :row="3" animate class="u-line-2 skeleton"></skeleton>
<view class="grid gap-2">
<view v-for="item in globals" :key="item.id">
<template v-if="item.plugins && item.plugins.length">
<view v-for="(pluginArr, i) in item.plugins" :key="i">
<template class="p-0 u-col-between" v-if="pluginArr.length">
<Plugin
:class="[`row-span-${plugin.row}`, `col-span-${plugin.col}`]"
:task="item"
:key="plugin.pluginTaskId"
:plugin-task-id="plugin.pluginTaskId"
:plugin-id="plugin.pluginId"
:param="plugin.param"
:style-type="plugin.styleType || 0"
v-for="plugin in pluginArr"
/>
</template>
</view>
</template>
<!-- 任务名插件 -->
<p-task-title :task="item" v-else />
<!-- 交付物插件 -->
<p-deliver></p-deliver>
</view>
</view>
</scroll-view>
</template>
</u-card>
</theme>
</template>
<script>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
import Skeleton from '@/components/Skeleton/Skeleton.vue';
const sysHeight = uni.getSystemInfoSync().screenHeight; //
const globalsHeight = Math.floor(((sysHeight - 44 - 30 - 10) / 5) * 4); //
const store = useStore();
const isShrink = computed(() => store.state.task.isShrink); //
const globals = computed(() => store.getters['task/globals']);
console.log('globals: ', globals.value);
//
function openCard() {
if (isShrink.value) {
store.commit('task/setShrink', false);
}
}
</script>
<style>
<style scoped lang="scss">
.u-card-wrap {
background-color: $u-bg-color;
padding: 1px;
}
.u-body-item {
font-size: 32rpx;
color: #333;
padding: 20rpx 10rpx;
}
.u-body-item image {
width: 120rpx;
flex: 0 0 120rpx;
height: 120rpx;
border-radius: 8rpx;
margin-left: 12rpx;
}
</style>

139
components/Plugin/Plugin.vue

@ -0,0 +1,139 @@
<template>
<view class="u-font-14" style="height: 100%">
插件面板
<!-- <view v-if="data.pluginContent" @click="setStorage">
<view
:data-did="task.detailId"
:data-param="param"
:data-pdu="task.planDuration"
:data-pid="projectId"
:data-pstart="task.planStart"
:data-rdu="task.realDuration"
:data-rid="roleId"
:data-tid="task.id"
:data-tname="task.name"
:data-token="token"
:data-rstart="task.realStart"
:data-uid="userId"
style="height: 100%"
v-html="data.pluginContent"
></view>
</view> -->
<!-- <view v-else @click="setStorage"> -->
<!-- <plugin-default /> -->
<!-- <component :task="task" :is="pluginComponent"></component> -->
<!-- <p-task-title :task="task" v-if="pluginId === '1'" />
<p-task-description :task="task" v-if="pluginId === '2'" />
<p-task-duration-delay :task="task" v-if="pluginId === '3'" />
<p-task-start-time-delay :task="task" v-if="pluginId === '4'" />
<p-upload-deliverable :task="task" v-if="pluginId === '5' && isMine" />
<p-delivery-history :task="task" v-if="pluginId === '5' && !isMine" />
<p-subtasks :task="task" v-if="pluginId === '6'" />
<p-subproject :task="task" v-if="pluginId === '7'" />
<p-task-countdown :task="task" v-if="pluginId === '8'" />
<p-manage-project :task="task" v-if="pluginId === '9'" />
<p-manage-role :task="task" v-if="pluginId === '10'" />
<p-manage-member :task="task" v-if="pluginId === '11'" />
<p-manage-task :task="task" v-if="pluginId === '12'" />
<p-wbs-import :task="task" v-if="pluginId === '13' || pluginId === '14'" />
<p-deliver-check :task="task" v-if="pluginId === '15'" /> -->
<!-- </view> -->
</view>
</template>
<script setup>
import { reactive, nextTick, computed } from 'vue';
import { useStore } from 'vuex';
const props = defineProps({
task: { default: () => {}, type: Object },
pluginId: { default: '1', type: String },
styleType: { default: 0, type: Number },
pluginTaskId: { default: '', type: String },
param: { type: String, default: '' },
});
// const data = reactive({ pluginContent: null });
const store = useStore();
const roleId = computed(() => store.state.role.roleId);
// const token = computed(() => store.state.user.token);
// const userId = computed(() => store.getters['user/userId']);
const projectId = computed(() => store.getters['project/projectId']);
// const isMine = computed(() => store.getters['role/isMine']);
//
// pluginComponent() {
// const target = this.$t.plugin.defaults.find(item => item.id === +this.pluginId);
// if (!target) return '';
// return target.component;
// },
// script dom
// function handleDom(js) {
// const domList = Array.from(document.getElementsByTagName('script'));
// const index = domList.findIndex(item => item.id === `p${props.pluginTaskId}`);
// if (index >= 0) {
// document.body.removeChild(document.getElementById(`p${props.pluginTaskId}`));
// }
// const scriptDom = document.createElement('script');
// scriptDom.id = `p${props.pluginTaskId}`;
// scriptDom.setAttribute('data-type', 'plugin');
// scriptDom.innerHTML = js;
// nextTick(() => {
// document.body.append(scriptDom);
// });
// }
//
// async function getPlugin() {
// const params = { pluginId: props.pluginId, styleType: props.styleType };
// uni.$catchReq.getOtherPlugin(params, (err, data) => {
// if (err) {
// console.error('err: ', err);
// } else {
// if (!data || !data.id) return;
// const reg = /data-root=["|']?(\w+)["|']?/gi;
// let uuid = '';
// // FIXME: js, html
// if (data.html) {
// // data-root=xxx xxx pluginTaskId
// if (reg.test(data.html)) {
// uuid = RegExp.$1;
// const str = data.html.replace(new RegExp(uuid, 'g'), `p${props.pluginTaskId}`);
// data.pluginContent = str;
// } else {
// data.pluginContent = data.html;
// }
// const str = data.js.replace(new RegExp(uuid, 'g'), `p${props.pluginTaskId}`);
// handleDom(str);
// }
// }
// });
// if (data.js) {
// if (reg.test(data.js)) {
// const uuid = RegExp.$1;
// const str = data.js.replace(new RegExp(uuid, 'g'), `p${this.pluginTaskId}`);
// this.handleDom(str);
// } else {
// this.handleDom(data.js);
// }
// }
// }
console.log('props.pluginId: ', props.pluginId);
if (props.pluginId === '5') {
// id
store.dispatch('role/getAllMembers', { projectId: projectId.value });
}
// getPlugin();
// storage
async function setStorage() {
uni.$storage.setStorageSync('roleId', roleId.value);
}
</script>

480
components/PrettyExchange/PrettyExchange.vue

@ -0,0 +1,480 @@
<template>
<view>
<scroll-view scroll-y="true">
<view v-if="!data.changeEvent">
<view :id="'cu-' + index" :key="item.id" class="cu-item flex-col" v-for="(item, index) in data.itemList">
<ProjectItem
class="w-full"
:index="index"
:item="item"
:menuList="data.menuList"
@setData="setData"
@openSubProject="openSubProject"
/>
</view>
</view>
<view v-else>
<view
:id="'cu-' + index"
:key="index"
:style="{ 'background-color': item.color }"
@touchend="stops($event, index)"
@touchmove.stop.prevent="move"
@touchstart="start($event, index)"
class="cu-item flex-col" v-for="(item, index) in data.itemList"
>
<view class="border-100 bg-blue-500" v-if="item.showTopBorder"></view>
<!-- 内容区 -->
<!-- 父项目 -->
<view class="w-full">
<view class="flex items-center justify-between p-3">
<u-icon class="mover" name="https://www.tall.wiki/staticrec/drag.svg" size="48"></u-icon>
<view class="flex-1 px-3">
<view class="flex items-center mb-1">
<view class="mr-2">{{ item.name }}</view>
<!-- 状态 TODO:-->
<view class="px-2 text-xs text-green-400 bg-green-100 rounded-full flex-shrink-0">进行中</view>
</view>
<view class="flex items-center text-xs text-gray-400">
<view class="pr-2">{{ dayjs(+item.startTime).format('MM-DD HH:mm') }}</view>
<view class="pl-2">{{ dayjs(+item.endTime).format('MM-DD HH:mm') }}</view>
</view>
</view>
<!-- 箭头 -->
<view v-if="item.sonProjectList && item.sonProjectList.length">
<u-icon @click="openSubProject(item.sonProjectList.length, index)" class="text-gray-400" name="arrow-up" size="14px" v-if="item.show"></u-icon>
<u-icon @click="openSubProject(item.sonProjectList.length, index)" class="text-gray-400" name="arrow-down" size="14px" v-else></u-icon>
</view>
<u-icon class="text-gray-400" name="arrow-right" size="14px" v-else></u-icon>
</view>
<!-- 父项目 end -->
<!-- 子项目 -->
<view class="ml-8" v-if="item.show">
<view :id="'cu-' + index + '-' + subIndex" :key="subIndex"
@touchend.stop.prevent="stops($event, index + '-' + subIndex, item.sonProjectList.length)"
@touchmove.stop.prevent="move($event, item.sonProjectList.length)"
@touchstart.stop.prevent="start($event, index + '-' + subIndex)"
class="cu-item flex-col" v-for="(subItem, subIndex) in item.sonProjectList">
<view class="flex items-center justify-between p-3 w-full">
<u-icon class="mover" name="https://www.tall.wiki/staticrec/drag.svg" size="48">
</u-icon>
<view class="flex-1 px-3">
<view class="flex items-center">
<view class="mr-2">{{ subItem.name }}</view>
<!-- 状态 -->
<view
:class="
subItem.status === 0
? 'text-blue-400 bg-blue-100'
: subItem.status === 1
? 'text-green-400 bg-green-100'
: subItem.status === 2
? 'text-red-400 bg-red-100'
: 'text-gray-400 bg-gray-100'
"
class="px-2 text-xs text-gray-400 bg-gray-100 rounded-full flex-shrink-0">
{{ subItem.status === 0 ? '未开始' : subItem.status === 1 ? '进行中' : subItem.status === 2 ? '暂停' : '已完成' }}
</view>
</view>
</view>
<!-- 箭头 -->
<u-icon class="text-gray-400" name="arrow-right" size="14px"></u-icon>
</view>
</view>
</view>
</view>
<!-- 内容区 end -->
<view class="border-100 bg-blue-500" v-if="item.showBorder"></view>
<view class="border-80 bg-blue-500" v-if="item.showSubBorder"></view>
</view>
</view>
</scroll-view>
<!-- 移动悬浮 begin -->
<view v-if="data.showMoveImage">
<view :style="{ left: moveLeft + 'px', top: moveTop + 'px' }" class="cu-item absolute">
<ProjectItem class="w-full" :item="moveItem" />
</view>
</view>
<!-- 移动悬浮 end -->
<!-- 项目操作面板 -->
<u-action-sheet :list="data.menuList" :tips="data.tips" @click="chooseAction" v-model="data.showMenu"></u-action-sheet>
</view>
</template>
<script setup>
import { ref, onMounted, watch, computed } from 'vue';
import ProjectItem from '@/components/Projects/ProjectItem.vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
const store = useStore();
const projects = computed(() => store.state.project.projects);
const data = ref({
itemTop: 0,
itemLeft: 0,
itemHeight: 0, //
subItemHeight: 0, //
itemWidth: 0, //
showMoveImage: false,
moveItem: '',
moveLeft: 0,
moveTop: 0,
deltaLeft: 0,
deltaTop: 0,
beginleft: 0,
begintop: 0,
itemList: [],
setSubItem: false,
changeEvent: false,
showMenu: false,
tips: {
text: '',
color: '#909399',
fontSize: 28,
},
projectId: 0,
menuList: [{ text: '复制' }, { text: '编辑' }, { text: '删除' }, { text: '置顶' }, { text: '排序' }],
// show: false,
border: 'border border-blue-500 shadow rounded-md',
showBorder: false,
showItemIndex: undefined,
});
watch(projects, (val) => {
data.value.itemList = val;
data.value.itemList.forEach(item => {
item.showBorder = false;
item.showSubBorder = false;
item.showTopBorder = false;
});
})
onMounted(() => {
data.value.itemList = projects.value;
data.value.itemList.forEach(item => {
item.showBorder = false;
item.showSubBorder = false;
item.showTopBorder = false;
});
});
//
function openSubProject(length, index) {
setProjectItemShow({ index, show: data.value.itemList[index].show ? false : true });
if (length && index) {
this.$emit('changeHeight', length, index);
}
data.value.showItemIndex = index;
}
//
function getDate() {
const query = uni.createSelectorQuery().in(this);
query.select(`#cu-0`).boundingClientRect(res => {
console.log('data: ', res);
data.value.begintop = res.top;
data.value.beginleft = res.left;
}).exec();
}
function setData(flag, projectId, tips) {
data.value.showMenu = flag;
data.value.projectId = projectId;
data.value.tips = tips;
}
function chooseAction(e) {
let obj = { index: e, projectId: data.value.projectId };
// this.$emit('chooseAction', data);
actionFun(obj);
}
//
function actionFun(obj) {
let action = data.value.menuList[obj.index].text;
if (action === '排序') {
data.value.changeEvent = true;
uni.$ui.showToast('请移动进行排序');
}
if (action === '删除') {
data.value.changeEvent = false;
delProject(obj.projectId);
}
if (data.value.showItemIndex !== undefined) {
setProjectItemShow({ index: data.value.showItemIndex, show: true });
}
}
function isNumber(val) {
return val === +val;
}
function start(e, index) {
console.log('开始', e);
setTimeout(() => {
getDate();
}, 300);
if (isNumber(index)) {
data.value.setSubItem = false;
const query = uni.createSelectorQuery().in(this);
console.log('2222', query)
query.select(`#cu-${index}`).boundingClientRect(res => {
data.value.moveTop = res.top;
data.value.moveLeft = res.left;
data.value.moveItem = data.value.itemList[index];
data.value.itemWidth = res.width;
data.value.itemHeight = res.height;
}).exec();
} else {
let arr = index.split('-');
data.value.setSubItem = true;
const query = uni.createSelectorQuery().in(this);
query.select(`#cu-${arr[0] - 0}`).boundingClientRect(res => {
data.value.itemHeight = res.height;
}).exec();
query.select(`#cu-${index}`).boundingClientRect(res => {
data.value.moveTop = res.top;
data.value.moveLeft = res.left;
data.value.moveItem = data.value.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
data.value.itemWidth = res.width;
data.value.subItemHeight = res.height;
}).exec();
}
}
function move(e, length) {
console.log('移动');
data.value.showMoveImage = true; //
const touch = e.touches[0];
if (data.value.deltaLeft == 0) {
//
data.value.deltaLeft = touch.pageX - data.value.moveLeft;
data.value.deltaTop = touch.pageY - data.value.moveTop;
}
data.value.moveLeft = touch.pageX - data.value.deltaLeft;
data.value.moveTop = touch.pageY - data.value.deltaTop;
let lastIndex = (lastIndex = findOverIndex(touch.pageY, length));
console.log('111111', lastIndex);
// 线
for (let i = 0; i < data.value.itemList.length; i++) {
if (data.value.moveLeft > 35) {
data.value.itemList[i].showBorder = false;
data.value.itemList[i].showTopBorder = false;
if (i === lastIndex) {
data.value.itemList[i].showSubBorder = true;
} else {
data.value.itemList[i].showSubBorder = false;
}
} else {
if (lastIndex === -1) {
data.value.itemList[0].showTopBorder = true;
data.value.itemList[i].showSubBorder = false;
data.value.itemList[i].showBorder = false;
} else {
data.value.itemList[i].showSubBorder = false;
data.value.itemList[i].showTopBorder = false;
if (i === lastIndex) {
data.value.itemList[i].showBorder = true;
} else {
data.value.itemList[i].showBorder = false;
}
}
}
}
}
function stops(e, index, length) {
console.log('结束');
const touch = e.mp.changedTouches[0];
let lastIndex = (lastIndex = findOverIndex(touch.pageY, length));
//
for (let i = 0; i < data.value.itemList.length; i++) {
//
if (data.value.itemList[i].showTopBorder) {
if (isNumber(index)) {
let Value = data.value.itemList[index];
data.value.itemList.unshift(Value);
data.value.itemList.splice(index + 1, 1);
} else {
let arr = index.split('-');
let Value = data.value.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
data.value.itemList.unshift(Value);
data.value.itemList[arr[0] - 0].sonProjectList.splice([arr[1] - 0], 1);
const options = {
id: Value.id,
parentId: 0,
};
this.$emit('change', options);
}
//
clearSet(i);
this.$emit('change', data.value.itemList);
return;
}
//
if (data.value.itemList[i].showBorder) {
if (isNumber(index)) {
let Value = data.value.itemList[index];
data.value.itemList.splice(i + 1, 0, Value);
if (i < index) {
data.value.itemList.splice(index + 1, 1);
} else {
data.value.itemList.splice(index, 1);
}
} else {
let arr = index.split('-');
let Value = data.value.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
data.value.itemList.splice(i + 1, 0, Value);
data.value.itemList[arr[0] - 0].sonProjectList.splice([arr[1] - 0], 1);
const options = {
id: Value.id,
parentId: 0,
};
this.$emit('change', options);
}
//
clearSet(i);
this.$emit('change', data.value.itemList);
return;
}
//
if (data.value.itemList[i].showSubBorder) {
if (isNumber(index)) {
let Value = data.value.itemList[index];
if (data.value.itemList[lastIndex - 1].sonProjectList && data.value.itemList[lastIndex - 1].sonProjectList.length) {
data.value.itemList[lastIndex - 1].sonProjectList.push(Value);
} else {
data.value.itemList[lastIndex].sonProjectList = [Value];
}
data.value.itemList.splice(index, 1);
//
clearSet(i);
const options = {
id: Value.id,
parentId: data.value.itemList[lastIndex - 1].id,
};
this.$emit('change', options);
} else {
let arr = index.split('-');
let Value = data.value.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
if (data.value.itemList[lastIndex].sonProjectList && data.value.itemList[lastIndex].sonProjectList.length) {
data.value.itemList[lastIndex].sonProjectList.push(Value);
} else {
data.value.itemList[lastIndex].sonProjectList = [Value];
}
data.value.itemList[arr[0] - 0].sonProjectList.splice([arr[1] - 0], 1);
//
clearSet(i);
const options = {
id: Value.id,
parentId: data.value.itemList[lastIndex].id,
};
this.$emit('change', options);
const options1 = {
id: Value.id,
parentId: 0,
};
this.$emit('change', options1);
}
return;
}
}
}
//
function clearSet(i) {
data.value.itemList[i].showBorder = false;
data.value.itemList[i].showSubBorder = false;
data.value.itemList[i].showTopBorder = false;
data.value.deltaLeft == 0;
data.value.showMoveImage = false;
data.value.setSubItem = false;
data.value.changeEvent = false;
data.value.showItemIndex = undefined;
}
//
function findOverIndex(posY) {
//
let leng = data.value.itemList.length * data.value.itemHeight; //
if (posY < data.value.begintop) {
return -1;
}
for (var i = 0; i < data.value.itemList.length; i++) {
let begin = data.value.itemHeight * i + data.value.begintop;
let end = data.value.itemHeight * i + data.value.begintop + data.value.itemHeight;
if (begin <= posY && end >= posY) {
return i;
}
}
if (posY > leng) {
//
return data.value.itemList.length - 1;
} else if (posY < data.value.begintop) {
return 0;
}
}
//
function delProject(id) {
uni.showModal({
title: '',
content: '是否删除项目?',
showCancel: true,
success: async ({ confirm }) => {
if (confirm) {
await this.$u.api.delProject(id);
let flag_index = 0;
data.value.itemList.forEach((item, index) => {
if (item.id == id) {
flag_index = index;
}
});
data.value.itemList.splice(flag_index, 1);
setProjects(data.value.itemList);
}
},
});
}
</script>
<style lang="scss" scoped>
.cu-item {
width: 100%;
display: flex;
align-items: center;
font-size: 14px;
}
.border-100 {
width: 92%;
height: 4rpx;
}
.border-80 {
width: 84%;
height: 2px;
margin-left: 30px;
}
</style>

134
components/Projects/ProjectItem.vue

@ -0,0 +1,134 @@
<template>
<view class="w-full">
<!-- 有子项目 -->
<view class="flex items-center justify-between p-3">
<u-icon @click="openMenu(item)" class="mover" name="https://www.tall.wiki/staticrec/drag.svg" size="48"></u-icon>
<view @click="openProject(item)" class="flex-1 px-3">
<view class="flex items-center mb-1">
<view class="mr-2">{{ item.name }}</view>
<!-- 状态 TODO:-->
<view class="px-2 text-xs text-green-400 bg-green-100 rounded-full flex-shrink-0">进行中</view>
</view>
<view class="flex items-center text-xs text-gray-400">
<view class="pr-2">{{ dayjs(item.startTime).format('MM-DD HH:mm') }}</view>
<view class="pl-2">{{ dayjs(item.endTime).format('MM-DD HH:mm') }}</view>
</view>
</view>
<!-- 箭头 -->
<view v-if="item.sonProjectList && item.sonProjectList.length">
<u-icon @click="$emit('openSubProject', item.sonProjectList.length, index)" class="text-gray-400" name="arrow-up" size="14px" v-if="item.show"></u-icon>
<u-icon @click="$emit('openSubProject', item.sonProjectList.length, index)" class="text-gray-400" name="arrow-down" size="14px" v-else></u-icon>
</view>
<u-icon @click="openProject(item)" class="text-gray-400" name="arrow-right" size="14px" v-else></u-icon>
</view>
<!-- 有子项目 -->
<view class="ml-8" v-if="item.show">
<view :id="'cu-' + index + '-' + subIndex" :key="subIndex" class="cu-item flex-col" v-for="(subItem, subIndex) in item.sonProjectList">
<!-- <view :key="subItem.id" v-for="subItem in item.sonProjectList"> -->
<view class="flex items-center justify-between p-3">
<u-icon @click="openMenu(subItem)" class="mover" name="https://www.tall.wiki/staticrec/drag.svg" size="48"></u-icon>
<view @click="openProject(subItem)" class="flex-1 px-3">
<view class="flex items-center">
<view class="mr-2">{{ subItem.name }}</view>
<!-- 状态 -->
<view
:class="
subItem.status === 0
? 'text-blue-400 bg-blue-100'
: subItem.status === 1
? 'text-green-400 bg-green-100'
: subItem.status === 2
? 'text-red-400 bg-red-100'
: 'text-gray-400 bg-gray-100'
"
class="px-2 text-xs text-gray-400 bg-gray-100 rounded-full flex-shrink-0"
>
{{ subItem.status === 0 ? '未开始' : subItem.status === 1 ? '进行中' : subItem.status === 2 ? '暂停' : '已完成' }}
</view>
</view>
</view>
<!-- 箭头 -->
<u-icon @click="openProject(subItem)" class="text-gray-400" name="arrow-right" size="14px"></u-icon>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import dayjs from 'dayjs';
import { useStore } from 'vuex';
import config from '@/common/js/config.js';
defineProps({
item: {
type: Object,
default: () => {},
},
index: {
type: Number,
default: 0,
},
// menuList: {
// type: Array,
// default: () => [],
// },
});
const emit = defineEmits(['setData']);
const store = useStore();
const userId = computed(() => store.getters['user/userId']);
const data = ref({
showMenu: false,
tips: {
text: '',
color: '#909399',
fontSize: 28,
},
// show: false,
// border: 'border border-blue-500 shadow rounded-md',
// showBorder: false,
projectId: 0,
});
//
function openProject(project) {
const gateway = config.apiUrl;
const url = `${gateway}/defaultwbs`;
const { name, id } = project;
uni.navigateTo({ url: `/pages/project/project?u=${userId.value}&p=${id}&pname=${name}&url=${encodeURIComponent(url)}` });
}
/**
* 弹出项目操作面板
*/
function openMenu(project) {
data.showMenu = true;
data.projectId = project.id;
data.tips.text = project.name;
emit('setData', data.showMenu, data.projectId, data.tips);
// this.$emit('setData', data.showMenu, data.projectId, data.tips);
}
</script>
<style lang="scss" scoped>
.border-100 {
height: 4rpx;
margin: 0 20rpx;
}
.border-80 {
height: 4rpx;
margin: 0 20rpx 0 90rpx;
}
</style>

57
components/Projects/Projects.vue

@ -0,0 +1,57 @@
<template>
<view class="py-3 mt-4 bg-white u-font-15">
<PrettyExchange @change="change" />
</view>
</template>
<script setup>
function change(options) {
if (options instanceof Array) {
let projectIdList = [];
let arr = [];
options.forEach(item => {
projectIdList.push(item.id);
arr.push(item.name);
});
setProjectSort(projectIdList);
} else {
setProjectRelation(options);
}
}
/**
* 设置项目顺序
* @param { Array } projectIdList 项目id
*/
async function setProjectSort(projectIdList) {
try {
const params = { projectIdList };
await uni.$u.api.setProjectSort(params);
uni.$ui.showToast('排序修改成功');
} catch (error) {
console.log('error: ', error);
uni.$ui.showToast(error.msg || '排序修改失败');
}
this.$emit('getProjects');
}
/**
* 设置项目父子结构
* @param { string } id 当前移动的项目的id
* @param { string } parentId 父项目的id
*/
async function setProjectRelation(options) {
try {
const params = options;
await uni.$u.api.setProjectRelation(params);
uni.$ui.showToast('排序修改成功');
} catch (error) {
console.error('error: ', error);
uni.$ui.showToast(error.msg || '排序修改失败');
}
this.$emit('getProjects');
}
</script>
<style lang="scss" scoped>
</style>

232
components/Roles/Roles.vue

@ -1,8 +1,236 @@
<template>
<view class="px-2 bg-white wrap">
<view class="home-box u-skeleton">
<scroll-view :enable-flex="true" :scroll-left="data.scrollLeft" :throttle="false" scroll-with-animation scroll-x @scroll="scroll">
<view class="tab-box">
<!-- 角色项
default-tab-choice 我的角色 && 当前展示
default-tab-item 我的角色 && 当前不展示
tab-choice 不是我的 && 当前展示
-->
<view
:class="{
'default-tab-choice': item.mine == 1 && roleId === item.id,
'default-tab-item': item.mine == 1 && roleId !== item.id,
'tab-choice': item.mine == 0 && roleId === item.id,
}"
:key="index"
@click="changeRole(item.id, index)"
class="tab-item"
v-for="(item, index) in data.roles"
>
<view class="tab-children u-skeleton-fillet u-font-14">{{ item.name }}</view>
</view>
</view>
</scroll-view>
</view>
<!-- 骨架屏 -->
<u-skeleton :animation="true" :loading="data.loading" bg-color="#fff"></u-skeleton>
</view>
</template>
<script>
<script setup>
import { reactive, computed, watchEffect, onMounted, nextTick } from 'vue';
import { useStore } from 'vuex';
const data = reactive({
tabIndex: 0, // 访 index 0
tabList: [], // tab dom
scrollLeft: 0, // scrollview
loading: false, //
roles: [{ id: 1, name: '项目经理', mine: 0, pm: 1, sequence: 1 }, { id: 2, name: '运维', mine: 0, pm: 0, sequence: 2 }],
roleLeft: 0,
});
const store = useStore();
const visibleRoles = computed(() => store.state.role.visibleRoles);
const roleId = computed(() => store.state.role.roleId);
const tasks = computed(() => store.state.task.tasks);
watchEffect(() => {
if (visibleRoles.value && visibleRoles.value.length) {
data.roles = visibleRoles.value;
data.loading = false;
}
});
onMounted(() => {
if (!visibleRoles.value || !visibleRoles.value.length) {
data.loading = true;
} else {
data.roles = visibleRoles.value;
}
nextTick(() => {
const query = uni.createSelectorQuery().in(this);
query
.selectAll('.tab-children')
.boundingClientRect(res => {
if (res.length) {
data.roleLeft = res[0].left;
}
})
.exec();
});
});
function scroll(e) {
data.scrollLeft = e.detail.scrollLeft;
}
//
function setCurrentRole(index) {
const query = uni.createSelectorQuery().in(this);
query
.selectAll('.tab-children')
.boundingClientRect(res => {
res.forEach(item => {
data.tabList.push({ width: item.width });
});
})
.exec();
const system = uni.getSystemInfoSync(); //
//
const screenWidth = system.windowWidth;
//
let left = 0;
for (let i = 0; i < index; i++) {
left += data.tabList[i].width + (data.roleLeft - 8) * 2;
}
left += (data.tabList[index].width + (data.roleLeft - 8) * 2) / 2;
if (left > screenWidth) {
data.scrollLeft = left - screenWidth + screenWidth / 2;
} else if (left > screenWidth / 2) {
data.scrollLeft = left - screenWidth / 2;
} else if (left < screenWidth / 2) {
data.scrollLeft = 0;
}
}
// script
function clearPluginScript() {
try {
const scripts = document.querySelectorAll('script[data-type=plugin]');
for (let i = 0; i < scripts.length; i++) {
document.body.removeChild(scripts[i]);
}
} catch (error) {
console.error('clearPluginScript error: ', error);
}
}
//
// projectroleId
// projectroleId
function changeRole(id, index) {
try {
// script
clearPluginScript();
nextTick(() => {
store.commit('role/setRoleId', id);
// index
setCurrentRole(index);
});
} catch (error) {
console.error('role.vue changeRole error: ', error);
}
}
</script>
<style>
<style lang="scss" scoped>
.home-box {
// sticky
position: sticky;
top: 0;
background: #fff; //
/* #ifdef H5 */
// h5 44px
top: 88rpx;
/* #endif */
.tab-box {
position: relative;
white-space: nowrap;
// height: 84rpx;
.tab-item {
// width: 20%;
// height: 84rpx;
padding: 20rpx 24rpx;
position: relative;
display: inline-block;
text-align: center;
font-size: 30rpx;
transition-property: background-color, width;
}
.default-tab-item {
color: $roleChoiceColor;
}
.default-tab-choice {
//
position: relative;
color: $roleChoiceColor;
font-weight: 600;
}
.default-tab-choice:before {
content: '';
position: absolute;
left: 0;
bottom: -20rpx;
width: 100%;
height: 6rpx;
border-radius: 2rpx;
background: $roleChoiceColor;
}
.tab-choice {
//
position: relative;
color: $uni-color-primary;
font-weight: 600;
}
.tab-choice:before {
content: '';
position: absolute;
left: 0;
bottom: -20rpx;
width: 100%;
height: 6rpx;
border-radius: 2rpx;
background: $uni-color-primary;
}
}
}
// //
/* #ifndef APP-NVUE */
::-webkit-scrollbar,
::-webkit-scrollbar,
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
/* #ifdef H5 */
// 穿H5scroll-view
scroll-view ::v-deep ::-webkit-scrollbar {
display: none;
}
/* #endif */
.skeleton {
height: 44rpx;
}
</style>

173
components/Skeleton/Skeleton.vue

@ -0,0 +1,173 @@
<template>
<view>
<view :class="[avatarClass, animationClass]" class="lx-skeleton" v-show="loading">
<view :class="[avatarShapeClass, bannerClass]" :style="{ width: avatarSize, height: avatarSize }" class="avatar-class"></view>
<view :style="{ width: rowWidth }" class="row">
<view class="row-class lx-skeleton_title" v-if="title"></view>
<view :key="index" class="row-class" v-for="(item, index) in row"></view>
</view>
</view>
<slot v-if="!loading"></slot>
</view>
</template>
<script setup>
import { computed } from 'vue';
/**
* skeleton 骨架屏
* @description 用于加载数据时占位图显示跟Vant-UI用法相似但比Vant-UI更灵活
* @property {Boolean} loading 是否显示骨架屏默认为true
* @property {Number | String} row 段落行数默认为3
* @property {Boolean | Number} rowWidth 段落行宽度默认为100%
* @property {Boolean | String} title 是否显示标题默认为false
* @property {Boolean | String} banner 是否显示banner默认为false
* @property {Boolean | String} animate 是否开启动画默认为false
* @property {Boolean | String} avatar 头像位置
* @property {String} avatarSize 头像大小
* @property {String} avatarShape 头像形状默认为circle
*
* */
const props = defineProps({
loading: {
type: Boolean,
default: true,
},
row: {
type: Number,
default: 3,
},
title: {
type: String,
default: '',
},
avatar: {
type: String,
default: '',
},
animate: {
type: Boolean,
default: false,
},
avatarSize: { type: String },
rowWidth: {
type: String,
default: '100%',
},
avatarShape: {
type: String,
default: 'circle',
},
banner: {
type: Boolean,
default: false,
},
// avator-size:{
// type: String,
// defualt: '32px'
// }
});
const avatarClass = computed(() => {
if (props.avatar === 'top') {
return ['lx-skeleton_avator__top'];
}
if (props.avatar === 'left') {
return ['lx-skeleton_avator__left'];
} return '';
});
const animationClass = computed(() => [props.animate ? 'lx-skeleton_animation' : '']);
const slotClass = computed(() => [!props.loading ? 'show' : 'hide']);
const avatarShapeClass = computed(() => [props.avatarShape == 'round' ? 'lx-skeleton_avator__round' : '']);
const bannerClass = computed(() => [props.banner ? 'lx-skeleton_banner' : '']);
</script>
<style lang="scss" scoped>
.lx-skeleton {
background-color: #fff;
padding: 12px;
}
.lx-skeleton_avator__left {
display: flex;
width: 100%;
}
.lx-skeleton_avator__left .avatar-class,
.lx-skeleton_avator__top .avatar-class {
background-color: #f2f3f5;
border-radius: 50%;
width: 32px;
height: 32px;
}
.lx-skeleton_avator__left .avatar-class.lx-skeleton_avator__round,
.lx-skeleton_avator__top .avatar-class.lx-skeleton_avator__round {
border-radius: 0;
width: 32px;
height: 32px;
}
.lx-skeleton_avator__left .avatar-class {
margin-right: 16px;
}
.lx-skeleton_avator__top .avatar-class {
margin: 0 auto 12px auto;
}
.row-class {
width: 100%;
height: 16px;
background-color: #f2f3f5;
margin-top: 12px;
}
.row-class:first-child {
margin-top: 0;
}
.row {
flex: 1;
}
.lx-skeleton_avator__left .row {
width: calc(100% - 48px);
}
.row-class:last-child {
width: 60%;
}
.lx-skeleton_animation .row-class {
animation-duration: 1.5s;
animation-name: blink;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
@keyframes blink {
50% {
opacity: 0.6;
}
}
.lx-skeleton_title {
width: 40%;
}
.show {
display: block;
}
.hide {
display: none;
}
.lx-skeleton .lx-skeleton_banner {
width: 92%;
margin: 10px auto;
height: 64px;
border-radius: 0;
background-color: #f2f3f5;
}
</style>

13
components/Theme/Theme.vue

@ -0,0 +1,13 @@
<template>
<view :class="[theme]">
<slot></slot>
</view>
</template>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const theme = computed(() => store.state.theme);
</script>

126
components/TimeLine/TimeLine.vue

@ -1,8 +1,126 @@
<template>
<!-- 时间间隔栏 -->
<!-- <Barrier /> -->
<scroll-view
:lower-threshold="300"
scroll-y="true"
:upper-threshold="300"
:scroll-into-view="scrollToTaskId"
@scroll="scroll"
@scrolltolower="handleScrollBottom"
@scrolltoupper="handleScrollTop"
id="scroll"
>
<!-- 时间轴 -->
<!-- <u-divider bg-color="#f3f4f6" class="pt-5" fontSize="14px" v-if="topEnd">到顶啦</u-divider> -->
<TimeBox />
<!-- <u-divider bg-color="#f3f4f6" class="pb-5" fontSize="14px" v-if="bottomEnd">到底啦</u-divider> -->
</scroll-view>
</template>
<script>
</script>
<script setup>
import { reactive, computed, defineExpose, defineEmits } from 'vue';
import { useStore } from 'vuex';
// import Barrier from './component/Barrier.vue';
import dayjs from 'dayjs';
import TimeBox from './component/TimeBox.vue';
const store = useStore();
// const visibleRoles = computed(() => store.state.role.visibleRoles);
// const scrollTop = computed(() => store.state.task.scrollTop);
const tasks = computed(() => store.state.task.tasks);
const topEnd = computed(() => store.state.task.topEnd);
const bottomEnd = computed(() => store.state.task.bottomEnd);
const showSkeleton = computed(() => store.state.task.showSkeleton);
const timeNode = computed(() => store.state.task.timeNode);
const scrollToTaskId = computed(() => store.state.task.scrollToTaskId);
const timeGranularity = computed(() => store.getters['task/timeGranularity']);
const emit = defineEmits(['getTasks']);
const data = reactive({ top: 0 });
<style>
</style>
//
function scroll(e) {
data.top = e.detail.scrollTop;
store.commit('task/setShrink', data.top > data.scrollTop);
store.commit('task/setScrollTop', data.top);
}
//
async function handleScrollTop() {
if (!tasks.value || !tasks.value.length || showSkeleton.value) return;
const startTime = tasks.value[0].planStart - 0;
if (topEnd.value) {
//
console.warn('滚动到顶部没有数据时: ');
const addTasks = uni.$task.setPlaceholderTasks(startTime, true, timeGranularity.value);
store.commit('task/setUpTasks', addTasks);
} else {
//
console.warn('滚动到顶部有数据时: ');
const detailId = tasks.value.findIndex(task => task.detailId);
const timeNodeValue = tasks.value[detailId].planStart - 0;
const upQuery = {
timeNode: timeNodeValue,
queryType: 0,
queryNum: 6,
};
await emit('getTasks', upQuery);
}
}
//
async function handleScrollBottom() {
if (!tasks.value || !tasks.value.length || showSkeleton.value) return;
const startTime = tasks.value[tasks.value.length - 1].planStart - 0;
if (bottomEnd.value) {
//
console.warn('滚动到底部没有数据时: ');
const addTasks = uni.$task.setPlaceholderTasks(startTime, false, timeGranularity.value);
store.commit('task/setDownTasks', addTasks);
} else {
// =+
console.warn('滚动到底部有数据时: ');
const arr = [];
tasks.value.forEach(task => {
if (task.detailId) {
arr.push(task);
}
});
const nextQueryTime = +uni.$time.add(+arr[arr.length - 1].planStart, 1, timeGranularity.value);
const downQuery = {
timeNode: nextQueryTime,
queryType: 1,
queryNum: 6,
};
await emit('getTasks', downQuery);
}
}
//
function setScrollPosition() {
// storagetaskId id
const taskId = uni.$storage.getStorageSync('taskId');
if (taskId) {
store.commit('task/setScrollToTaskId', `a${taskId}`);
uni.$storage.setStorageSync('taskId', ''); //
} else {
const item = tasks.value.find(task => task.detailId);
if (item) {
store.commit('task/setScrollToTaskId', `a${item.id}`);
} else {
// taskId
// 线id 线
const task = tasks.value.find(item => dayjs(+item.planStart).isSame(timeNode.value, timeGranularity.value));
task && store.commit('task/setScrollToTaskId', `a${task.id}`); // task id
}
}
}
defineExpose({
setScrollPosition
})
</script>

38
components/TimeLine/component/Barrier.vue

@ -0,0 +1,38 @@
<!--
* @Author: aBin
* @email: binbin0314@126.com
* @Date: 2021-07-19 14:22:54
* @LastEditors: aBin
* @LastEditTime: 2021-07-20 11:46:04
-->
<template>
<view class>
<!-- :class="{ active: cycleTasks.time.start === filter.startTime }" -->
<view class="cycle-time active">
<!-- {{ $util.formatStartTimeToCycleTime(filter.time, cycleTasks.time.start) }} -->
2021年30周
</view>
</view>
</template>
<script setup>
</script>
<style scoped lang="scss">
.cycle-time {
padding: 8rpx 16rpx;
margin-bottom: 16rpx;
background: #fafafc;
color: $uni-text-color;
font-size: 28rpx;
position: sticky;
top: -1px;
left: 0;
z-index: 99;
&.active {
background: $uni-color-primary;
color: $uni-text-color-inverse;
}
}
</style>

177
components/TimeLine/component/TaskTools.vue

@ -0,0 +1,177 @@
<template>
<view>
<view class="flex justify-between" style="min-width: 90px; position: relative">
<u-icon custom-prefix="custom-icon" name="C-bxl-redux" size="17px"></u-icon>
<u-icon custom-prefix="custom-icon" name="attachment" size="21px"></u-icon>
<!-- <u-icon custom-prefix="custom-icon" name="moneycollect" size="20px"></u-icon> -->
<u-icon name="xuanxiang" custom-prefix="custom-icon" size="21px" @click="operation"></u-icon>
<!-- 右上角 ... 弹窗 -->
<view class="popup border shadow-md" v-if="data.show">
<!-- <view class="flex justify-center pb-3 border-b-1">
<span>添加插件</span>
</view> -->
<view class="flex justify-center pb-3 border-b-1">
<span @click="createTask">新建任务</span>
</view>
<view class="flex pt-3 justify-center">
<span>克隆任务</span>
</view>
</view>
</view>
<!-- 遮罩 -->
<view class="mask" v-if="data.maskShow" @click="closeMask"></view>
<!-- 新建任务弹窗 -->
<CreateTask
:startTime="data.startTime"
:endTime="data.endTime"
:task="task"
:source="'regular'"
@showTime="showTime"
@closeMask="closeMask"
class="thirdPopup flex transition-transform"
v-if="data.createTaskShow"
/>
<u-picker title="开始时间" mode="time" v-model="data.showStart" :params="data.params" @confirm="confirmStartTime"></u-picker>
<u-picker title="结束时间" mode="time" v-model="data.showEnd" :params="data.params" @confirm="confirmEndTime"></u-picker>
</view>
</template>
<script setup>
import { reactive } from 'vue';
import CreateTask from '@/components/Title/components/CreateTask.vue';
defineProps({ task: { type: Object, default: () => {} } });
const data = reactive({
show: false, // ...
createTaskShow: false, //
secondShow: false, //
maskShow: false, //
showStart: false,
showEnd: false,
startTime: '', //
endTime: '', //
params: {
year: true,
month: true,
day: true,
hour: true,
minute: true,
second: true,
},
});
//
function operation() {
// this.$t.ui.showToast('');
data.show = !data.show;
}
//
function createTask() {
// ...
data.show = false;
//
data.maskShow = true;
//
data.createTaskShow = true;
}
//
function closeMask() {
//
data.maskShow = false;
//
data.secondShow = false;
//
data.createTaskShow = false;
}
function showTime() {
data.showStart = !data.showStart;
}
//
function confirmStartTime(e) {
data.startTime = `${e.year}-${e.month}-${e.day} ${e.hour}:${e.minute}:${e.second}`;
data.showEnd = true;
}
//
function confirmEndTime(e) {
data.endTime = `${e.year}-${e.month}-${e.day} ${e.hour}:${e.minute}:${e.second}`;
}
</script>
<style lang="scss" scoped>
.mask {
width: 100%;
height: 100vh;
z-index: 21;
position: fixed;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.3);
}
.thirdPopup {
background: #ffffff;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 33;
border-radius: 5px;
width: 90%;
}
.popup {
width: 110px;
background: #fff;
position: absolute;
right: 0;
top: 35px;
z-index: 99;
padding: 15px 0;
color: black;
animation: opacity 1s ease-in;
}
@keyframes opacity {
0% {
opacity: 0;
}
50% {
opacity: 0.8;
}
100% {
opacity: 1;
}
}
::v-deep .u-slot-content {
min-width: 0;
}
::v-deep .u-dropdown__content {
min-height: 120px !important;
height: auto !important;
overflow-y: auto;
background: #fff !important;
transition: none !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
::v-deep .u-dropdown__menu__item .u-flex {
justify-content: space-between;
width: 100%;
height: 100%;
flex-wrap: nowrap;
border: 1px solid #afbed1;
padding: 0 8px;
}
::v-deep .u-dropdown__content__mask {
display: none;
}
</style>

132
components/TimeLine/component/TimeBox.vue

@ -0,0 +1,132 @@
<template>
<!-- v-finger:pinch="pinchHandler" -->
<view class="column" >
<view v-if="tasks && tasks.length">
<view :key="task.id" v-for="task in tasks" :id="`a${task.id}`">
<view class="flex">
<TimeStatus :task="task" />
<view class="flex items-center justify-between flex-1 ml-2 task-column">
<view v-if="task.process !== 4">{{ $moment(+task.planStart).format(startTimeFormat) }}</view>
<view v-else>{{ $moment(+task.planStart).format('D日') }}</view>
<!-- 任务功能菜单 -->
<TaskTools v-if="task.process !== 4" :task="task" />
</view>
</view>
<view class="plugin">
<view class="h-3" v-if="task.process === 4"></view>
<view class="ml-3 overflow-hidden shadow-lg task-box">
<u-card :show-foot="false" :show-head="false" :style="{ height: setHeight(task.panel) }" class="h-16" margin="0" v-if="showSkeleton">
<view slot="body">
<view>
<skeleton :banner="false" :loading="true" :row="4" animate class="mt-2 u-line-2 skeleton"></skeleton>
</view>
</view>
</u-card>
<u-card
@click="onClickTask(task.planStart - 0, task.id)"
:show-foot="false"
:show-head="false"
:style="{ height: setHeight(task.panel) }"
class="h-16"
margin="0"
v-if="tasks && tasks.length && task.process !== 4 && !showSkeleton"
>
<!-- 任务面板插件 -->
<view slot="body">
<view class="p-0 u-col-between">
<view :key="pIndex" v-for="(row, pIndex) in task.plugins">
<!-- <view class="grid gap-2 grid-cols-1" v-if="row.length">
<Plugin
:class="[`row-span-${plugin.row}`, `col-span-${plugin.col}`]"
:task="task"
:key="plugin.pluginTaskId"
:plugin-task-id="plugin.pluginTaskId"
:plugin-id="plugin.pluginId"
:param="plugin.param"
:style-type="data.styleType || 0"
v-for="plugin in row"
/>
</view> -->
</view>
</view>
</view>
</u-card>
</view>
</view>
</view>
</view>
<!-- 局部弹框操作栏 -->
<!-- <Tips /> -->
</view>
</template>
<script setup>
import { useStore } from 'vuex';
import { computed, reactive } from 'vue';
import TimeStatus from './TimeStatus.vue';
import TaskTools from './TaskTools.vue';
// const data = reactive({
// currentComponent: '',
// styleType: 0,
// });
const store = useStore();
const roleId = computed(() => store.state.role.roleId);
const timeUnit = computed(() => store.state.task.timeUnit);
const tasks = computed(() => store.state.task.tasks);
const showSkeleton = computed(() => store.state.task.showSkeleton);
const startTimeFormat = computed(() => store.getters['task/startTimeFormat']);
//
function setHeight(panel) {
if (panel && panel.height) {
return `${panel.height}px`;
}
return 'auto';
}
/**
* 点击了定期任务的面板 更新可变的日常任务
* @param {number} planStart 任务计划开始时间
* @param {string} taskId 任务id
*/
function onClickTask(planStart, taskId) {
const param = { roleId: roleId.value, timeNode: planStart, timeUnit: timeUnit.value };
store.dispatch('task/getGlobal', param);
uni.$storage.setStorageSync('taskId', taskId);
uni.$storage.setStorageSync('roleId', roleId.value);
}
function pinchHandler(evt) {
// evt.scale
console.log(`缩放:${evt.zoom}`);
}
</script>
<style scoped lang="scss">
.task-box {
border-radius: 24rpx;
}
.column {
padding: 24px 14px;
}
.task-column {
height: 33px;
}
.plugin {
margin-top: 8px;
margin-bottom: 8px;
margin-left: 15px;
border-left: 2px solid #d1d5db;
}
::v-deep .ml-2 {
margin-left: 16px;
}
::v-deep .ml-3 {
margin-left: 20px;
}
</style>

317
components/TimeLine/component/TimeStatus.vue

@ -0,0 +1,317 @@
<template>
<view class="u-font-14">
<view
class="flex items-center justify-center rounded-full icon-column"
:style="{ color: orderStyle.color }"
@click="changeStatus(task.process, $event)"
>
<!-- 1进行中 2暂停中 3已完成 -->
<u-circle-progress
:percent="orderStyle.persent - 0"
:active-color="orderStyle.color"
bg-color="rgba(255,255,255,0)"
border-width="4"
:width="task.process !== 4 ? 66 : 50"
v-if="task.process === 1 || task.process === 2 || task.process === 3"
>
<view class="u-progress-content">
<view class="u-progress-dot"></view>
<view class="u-progress-info">
<u-icon :name="orderStyle.icon" v-if="orderStyle.icon" size="15px"></u-icon>
<template v-else>{{ data.durationText }}</template>
</view>
</view>
</u-circle-progress>
<!-- 0未开始 4添加任务 -->
<view class="flex items-center justify-center rounded-full progress-box" v-else :class="task.process === 4 ? 'progress-box-4' : ''">
<view class="u-progress-content">
<view class="u-progress-dot"></view>
<view class="u-progress-info">
<span v-if="orderStyle.icon">
<u-icon :name="orderStyle.icon" v-if="task.process !== 4" size="15px"></u-icon>
<u-icon :name="orderStyle.icon" v-else size="15px"></u-icon>
</span>
<template v-else>{{ data.durationText }}</template>
</view>
</view>
</view>
</view>
<!-- 遮罩 -->
<view class="mask" v-if="data.maskShow" @click="closeMask"></view>
<!-- 新建任务弹窗 -->
<CreateTask
:startTime="data.startTime"
:endTime="data.endTime"
:task="task"
:source="'regular'"
@showTime="showTime"
@closeMask="closeMask"
class="thirdPopup flex transition-transform"
v-if="data.createTaskShow"
/>
<u-picker title="开始时间" mode="time" v-model="data.showStart" :params="data.params" @confirm="confirmStartTime"></u-picker>
<u-picker title="结束时间" mode="time" v-model="data.showEnd" :params="data.params" @confirm="confirmEndTime"></u-picker>
</view>
</template>
<script setup>
import { reactive, onMounted, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import CreateTask from '../../Title/components/CreateTask.vue';
const props = defineProps({ task: { type: Object, default: () => {} } });
const data = reactive({
time: '',
start: [{ text: '确认开始任务', color: 'blue' }],
pause: [{ text: '继续' }, { text: '重新开始任务', color: 'blue' }, { text: '结束' }],
proceed: [{ text: '暂停' }, { text: '重新开始任务', color: 'blue' }, { text: '结束' }],
again: [{ text: '重新开始任务', color: 'blue' }],
timer: null,
durationText: 0,
maskShow: false, //
createTaskShow: false, //
startTime: '', //
endTime: '', //
showStart: false,
showEnd: false,
params: {
year: true,
month: true,
day: true,
hour: true,
minute: true,
second: true,
},
});
const store = useStore();
const tip = computed(() => store.state.task.tip);
const status = computed(() => props.task ? props.task.process : 0);
const taskName = computed(() => props.task ? props.task.name : '');
const taskId = computed(() => props.task ? props.task.id : '');
//
function computeCyclePersent() {
if (!props.task || !props.task.realStart || !props.task.planDuration) return 100;
const { realStart, planDuration } = props.task;
return (((Date.now() - +realStart) * 100) / +planDuration).toFixed(2);
}
const orderStyle = computed(() => {
//
// 0 1 2 3
let color = '#9CA3AF';
let icon = 'play-right-fill';
let persent = 100;
switch (status.value) {
case 1: //
color = '#60A5FA';
icon = '';
if (+computeCyclePersent() > 100) {
persent = 96;
} else {
persent = computeCyclePersent();
}
break;
case 2: //
color = '#F87171';
icon = 'pause';
persent = 50; // TODO:
break;
case 3: //
color = '#34D399';
icon = 'checkmark';
persent = 100;
break;
case 4: //
color = '#60A5FA';
icon = 'plus';
persent = 100;
break;
default:
//
color = '#9CA3AF';
icon = 'play-right';
persent = 100;
break;
}
return { color, icon, persent };
});
// unMounted(() => {
// if (data.timer) {
// clearInterval(data.timer);
// data.timer = null;
// }
// });
/**
* 计算tip的标题内容
*/
function genetateTips(type, content) {
if (type === 0) {
return `确认开始任务"${content}"吗?`;
}
if (type === 3) {
return '是否要重新开始此任务';
}
return '请选择要执行的操作';
}
//
function addTask() {
// uni.$ui.showToast('');
//
data.maskShow = true;
//
data.createTaskShow = true;
}
/**
* 点击了图标 修改任务状态
* @param {object} event
*/
function changeStatus(process, event) {
if (process === 4) {
addTask();
return;
}
// return false;
tip.status = status;
tip.taskId = taskId;
tip.left = event.target.x;
tip.top = event.target.y;
tip.show = true;
tip.text = genetateTips(status, taskName);
store.commit('task/setTip', tip);
}
//
function closeMask() {
//
data.maskShow = false;
//
data.createTaskShow = false;
}
function showTime(type) {
if (type === 1) {
data.showStart = !data.showStart;
} else {
data.showEnd = !data.showEnd;
}
}
//
function confirmStartTime(e) {
data.startTime = `${e.year}-${e.month}-${e.day} ${e.hour}:${e.minute}:${e.second}`;
data.showEnd = true;
}
//
function confirmEndTime(e) {
data.endTime = `${e.year}-${e.month}-${e.day} ${e.hour}:${e.minute}:${e.second}`;
}
//
// = realStart() + planDuration()
// = -
// = realStart + planDuration - Date.now()
function computeDurationText() {
const { realStart, planDuration } = props.task;
const leftTime = (realStart-0 || 0) + (planDuration-0 || 0) - Date.now(); //
const { num, time } = uni.$time.computeDurationText(leftTime);
if (num <= 0) {
clearInterval(data.timer);
data.timer = null;
}
data.durationText = num;
return time;
}
function updateDurationText(time) {
if (data.timer) {
clearInterval(data.timer);
data.timer = null;
}
if (!time) return;
setInterval(() => {
computeDurationText();
}, time);
}
onMounted(() => {
// TODO:
const time = computeDurationText();
updateDurationText(time);
});
</script>
<style scoped lang="scss">
.icon-column {
height: 33px;
width: 33px;
}
.one {
height: 33px;
width: 33px;
}
.progress-box {
background: rgba(255, 255, 255, 0);
width: 33px;
height: 33px;
border: 2px solid #9ca3af;
}
.progress-box-4 {
width: 25px;
height: 25px;
border: 2px solid #60a5fa;
}
.mask {
width: 100%;
height: 100vh;
z-index: 21;
position: fixed;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.3);
}
.thirdPopup {
background: #ffffff;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 33;
border-radius: 5px;
width: 90%;
}
::v-deep .u-dropdown__content {
min-height: 120px !important;
height: auto !important;
overflow-y: auto;
background: #fff !important;
transition: none !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
::v-deep .u-dropdown__menu__item .u-flex {
justify-content: space-between;
width: 100%;
height: 100%;
flex-wrap: nowrap;
border: 1px solid #afbed1;
padding: 0 8px;
}
::v-deep .u-dropdown__content__mask {
display: none;
}
</style>

0
components/TimeLine/component/Title.vue

81
components/Tips/Tips.vue

@ -0,0 +1,81 @@
<template>
<view
class="fixed shadow-2xl"
style="z-index: 1000"
:style="{
left: tip.left + 'px',
top: data.height - tip.top > 110 ? tip.top + 'px' : '',
bottom: data.height - tip.top > 110 ? '' : '10px',
}"
id="u-icard"
>
<u-card :title="title" style="width: 500rpx; margin: 0 !important" v-if="tip.show" titleSize="28" :headStyle="data.headStyle" :footStyle="data.footStyle">
<view class="" slot="body">{{ tip.text }}</view>
<view class="flex justify-end" slot="foot">
<u-button size="mini" @click="onCancel">取消</u-button>
<u-button v-if="tip.status === 1" size="mini" @click="onChangeStatus(1)">暂停</u-button>
<u-button v-if="tip.status === 2" size="mini" @click="onChangeStatus(2)">继续</u-button>
<u-button v-if="tip.status === 1 || tip.status === 2" size="mini" @click="onChangeStatus(0)">重新开始</u-button>
<u-button v-if="tip.status === 1 || tip.status === 2" type="primary" size="mini" @click="onChangeStatus(3)">结束</u-button>
<u-button v-if="tip.status === 0 || tip.status === 3" type="primary" size="mini" @click="onChangeStatus(0)">确定</u-button>
</view>
</u-card>
</view>
</template>
<script setup>
import { useStore, onMounted, computed, reactive } from 'vuex';
defineProps({ title: { default: '提示', type: String } });
const store = useStore();
const tip = computed(() => store.state.task.tip);
const data = reactive({
footStyle: { padding: '4px 15px' },
headStyle: { paddingTop: '8px', paddingBottom: '8px' },
height: 0,
});
onMounted(() => {
const system = uni.getSystemInfoSync();
data.height = system.windowHeight;
});
/**
* 执行修改任务状态的动作
* @param {number} type 状态码 0开始 1暂停 2继续 3完成 默认0
*/
async function onChangeStatus(type) {
try {
const param = { id: data.tip.taskId, type };
await uni.$u.api.updateTaskType(param);
if (type === 0) {
uni.$ui.showToast('项目已重新开始');
} else if (type === 1) {
uni.$ui.showToast('项目已暂停');
} else if (type === 2) {
uni.$ui.showToast('项目继续');
} else if (type === 3) {
uni.$ui.showToast('项目结束');
}
data.tip.show = false;
// TODO:
// location.reload();
// this.$router.go(0);
} catch (error) {
console.error(error);
uni.$ui.showToast(error.msg || '操作失败');
}
}
//
function onCancel() {
store.commit('task/setTipShow', false);
}
//
// function onConfirm() {
// onCancel();
// }
</script>

221
components/Title/Title.vue

@ -1,8 +1,225 @@
<template>
<view>
<!-- :is-back="false" -->
<u-navbar :custom-back="onBack" class="overflow-hidden">
<view class="flex justify-start flex-1 px-3 font-bold min-0">
<view class="truncate">{{ project.name }}</view>
</view>
<view class="mr-2" slot="right">
<u-icon class="m-1" name="xuanzhong2" custom-prefix="custom-icon" size="20px" @click="lwbs"></u-icon>
<u-icon class="m-1" name="shuaxin1" custom-prefix="custom-icon" size="20px" @click="projectOverview"></u-icon>
<u-icon class="m-1" name="home" custom-prefix="custom-icon" size="20px" @click="openIndex"></u-icon>
<u-icon class="m-1" name="xuanxiang" custom-prefix="custom-icon" size="20px" @click="operation"></u-icon>
</view>
</u-navbar>
<view
class="mask"
v-if="data.maskShow"
@click="closeMask"
style="width: 100%; height: 100vh; z-index: 21; position: fixed; background: rgba(0, 0, 0, 0.3)"
></view>
<!-- 右上角 ... 弹窗 -->
<view class="popup border shadow-md" v-if="data.show">
<view class="flex pb-3 border-b-1">
<u-icon name="plus-circle" size="36" style="margin: 0 15px 3px 0"></u-icon>
<view @click="createTask">新建任务</view>
<!-- <view>新建任务</view> -->
</view>
<view class="flex pt-3">
<u-icon name="share" size="32" style="margin: 0 15px 3px 0"></u-icon>
<view @click="share">分享项目</view>
</view>
</view>
<!-- 分享项目弹窗 -->
<ShareProject v-if="data.secondShow" class="second-popup" />
<!-- 新建任务弹窗 -->
<CreateTask
:startTime="data.startTime"
:endTime="data.endTime"
@showTime="showTime"
@closeMask="closeMask"
class="third-popup flex transition-transform"
v-if="data.createTaskShow"
/>
<u-picker title="开始时间" mode="time" v-model="data.showStart" :params="data.params" @confirm="confirmStartTime"></u-picker>
<u-picker title="结束时间" mode="time" v-model="data.showEnd" :params="data.params" @confirm="confirmEndTime"></u-picker>
</view>
</template>
<script>
<script setup>
import { reactive, computed } from 'vue';
import { useStore } from 'vuex';
import CreateTask from './components/CreateTask.vue';
import ShareProject from './components/ShareProject.vue';
const data = reactive({
show: false, // ...
createTaskShow: false, //
secondShow: false, //
maskShow: false, //
showStart: false,
showEnd: false,
startTime: '', //
endTime: '', //
params: {
year: true,
month: true,
day: true,
hour: true,
minute: true,
second: true,
},
});
const store = useStore();
const project = computed(() => store.state.project.project);
const userId = computed(() => store.getters['user/userId']);
function showTime() {
data.showStart = !data.showStart;
}
//
function confirmStartTime(e) {
data.startTime = `${e.year}-${e.month}-${e.day} ${e.hour}:${e.minute}:${e.second}`;
data.showEnd = true;
}
//
function confirmEndTime(e) {
data.endTime = `${e.year}-${e.month}-${e.day} ${e.hour}:${e.minute}:${e.second}`;
}
//
function onBack() {
// eslint-disable-next-line no-undef
const pages = getCurrentPages(); //
console.log('历史pages: ', pages.length);
if (pages.length > 1) {
uni.navigateBack();
} else {
// this.$u.route('/', { u: this.userId });
uni.reLaunch({ url: `/pages/index/index?u=${userId.value}` });
}
}
// LWBS
function lwbs() {
// uni.$ui.showToast('LWBS');
}
//
function projectOverview() {
// uni.$ui.showToast('');
}
//
function openIndex() {
uni.webView.reLaunch({ url: `/pages/index/index?u=${this.userId}` });
}
//
function operation() {
// uni.$ui.showToast('');
data.show = !data.show;
}
//
function createTask() {
// ...
data.show = false;
//
data.maskShow = true;
//
data.createTaskShow = true;
}
//
function share() {
// ...
data.show = false;
//
data.maskShow = true;
//
data.secondShow = true;
}
//
function closeMask() {
//
data.maskShow = false;
//
data.secondShow = false;
//
data.createTaskShow = false;
}
</script>
<style>
<style lang="scss" scoped>
.second-popup {
background: #ffffff;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 33;
border-radius: 5px;
width: 90%;
}
.third-popup {
background: #ffffff;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 33;
border-radius: 5px;
width: 90%;
}
.popup {
width: 40%;
background: #fff;
position: absolute;
right: 0;
z-index: 99;
padding: 15px;
color: black;
animation: opacity 0.5s ease-in;
}
@keyframes opacity {
0% {
opacity: 0;
}
50% {
opacity: 0.8;
}
100% {
opacity: 1;
}
}
::v-deep .u-slot-content {
min-width: 0;
}
::v-deep .u-dropdown__content {
min-height: 120px !important;
height: auto !important;
overflow-y: auto;
background: #fff !important;
transition: none !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
::v-deep .u-dropdown__menu__item .u-flex {
justify-content: space-between;
width: 100%;
height: 100%;
flex-wrap: nowrap;
border: 1px solid #afbed1;
padding: 0 8px;
}
::v-deep .u-dropdown__content__mask {
display: none;
}
</style>

466
components/Title/components/CreateTask copy.vue

@ -0,0 +1,466 @@
<template>
<div class="new-projects-box">
<div class="form">
<!-- 项目名称 -->
<view class="mb-3 font-bold text-base flex justify-center text-black">新建任务</view>
<div class="flex items-center mb-2">
<div>名称<span class="text-red-500">*</span></div>
<u-input max-length="5" v-model="name" :type="type" :border="border" />
</div>
<!-- 起止时间 -->
<div class="mb-2">
<div>起止时间</div>
<u-input placeholder="请选择起止时间" v-model="timeValue" :type="type" :border="border" @click="$emit('showTime')" />
</div>
<!-- 多选框 -->
<div class="flex justify-between items-center">
<div>负责人<span class="text-red-500">*</span></div>
<div class="flex-1" v-if="hasRole">{{ roleName }}</div>
<div label="负责人" class="flex-1" v-else>
<u-dropdown disabled ref="uDropdown" placeholder="请选择负责人">
<u-dropdown-item :title="roleList">
<view class="slot-content bg-white">
<div
class="flex flex-row justify-between mb-1 drop-item"
v-for="(role, roleIndex) in roleOptions"
:key="roleIndex"
@click="change(roleIndex)"
>
<view v-model="role.id">{{ role.name }}</view>
<u-icon v-if="role.dropdownShow" name="checkbox-mark" color="#2979ff" size="28"></u-icon>
</div>
</view>
</u-dropdown-item>
</u-dropdown>
</div>
</div>
<!-- 下拉图标 -->
<div class="flex justify-center my-6">
<u-icon v-if="arrow" name="arrow-down" size="28" @click="openDropdown"></u-icon>
<u-icon v-else name="arrow-up" size="28" @click="closeSecondDropdown"></u-icon>
</div>
<!-- 下拉框的内容 -->
<div v-if="show" class="mb-6">
<!-- 描述 -->
<div class="flex items-center mb-2">
<div>描述</div>
<u-input v-model="description" max-length="48" type="textarea" height="36" auto-height :border="border" />
</div>
<!-- 所属项目 -->
<div class="w flex items-center mb-2">
<div>所属项目<span class="text-red-500">*</span></div>
<div>{{ project.name }}</div>
</div>
<!-- 所属任务 -->
<div class="w flex items-center mb-2" v-if="task && task.id">
<div>所属任务</div>
<div>{{ task.name }}</div>
</div>
<!-- 上道工序 -->
<div class="flex items-center mb-2">
<div>上道工序</div>
<InputSearch
@searchPrevTask="searchPrevTask"
:dataSource="allTasks"
@select="handleChange"
@clearAllTasks="clearAllTasks"
placeholder="请输入上道工序"
/>
</div>
<!-- 检查人多选框 -->
<div class="flex justify-between items-center">
<div>检查人<span class="text-red-500">*</span></div>
<div label="检查人" class="flex-1">
<u-dropdown ref="dropdown">
<u-dropdown-item :title="checkerList">
<view class="slot-content bg-white">
<div
class="flex flex-row justify-between mb-1 drop-item"
v-for="(checkoutOption, Index) in checkoutOptions"
:key="Index"
@click="choose(Index)"
>
<view v-model="checkoutOption.value">{{ checkoutOption.name }}</view>
<u-icon v-if="checkoutOption.dropdownShow" name="checkbox-mark" color="#2979ff" size="28"></u-icon>
</div>
</view>
</u-dropdown-item>
</u-dropdown>
</div>
</div>
<!-- 是否是日常任务 -->
<div class="flex justify-between items-center mt-6">
是否是日常任务
<u-switch v-model="isGlobal" size="28"></u-switch>
</div>
<div class="mt-6">
<div>交付物</div>
<div v-for="(sort, sortIndex) in deliverSort" :key="sortIndex">
<u-input
@blur="addDeliverInput"
v-model="sort.name"
:placeholder="`交付物名称${sortIndex + 1}`"
:type="type"
:border="border"
/>
</div>
</div>
</div>
<div class="flex items-center mb-6">
<u-button type="primary" size="medium" @click="setParameters">提交</u-button>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
export default {
props: {
startTime: {
type: String,
default: '',
},
endTime: {
type: String,
default: '',
},
task: {
type: Object,
default: null,
},
},
data() {
return {
arrow: true,
show: false,
isGlobal: false, //
name: '', //
showChooseTime: false,
timeValue: '', //
description: '', //
projectShow: false, //
processTaskId: '', //
type: 'text',
border: true,
roleList: undefined, //
checkerList: undefined, //
roleOptions: [], //
checkoutOptions: [], //
roleIdList: [], // id
checkerIdList: [], // id
deliverables: [], //
deliverSort: [{ name: '' }], //
allTasks: [],
roleName: '', //
hasRole: false, //
};
},
computed: {
...mapState('role', ['visibleRoles', 'roleId']),
...mapState('project', ['project']),
...mapState('task', ['tasks']),
...mapGetters('project', ['projectId']),
},
watch: {
endTime(val) {
if (val) {
this.timeValue = this.startTime + ' 至 ' + val;
}
},
},
mounted() {
//
if (this.visibleRoles.length) {
this.visibleRoles.forEach(role => {
role.dropdownShow = false;
role.status = false;
});
}
this.roleOptions = this.$u.deepClone(this.visibleRoles);
this.checkoutOptions = this.$u.deepClone(this.visibleRoles);
//
if (this.roleId) {
const item = this.visibleRoles.find(r => r.id === this.roleId);
if (item) {
this.roleName = item.name;
this.hasRole = true;
}
}
},
methods: {
...mapMutations('task', ['updateTasks']),
...mapActions('task', ['getPermanent']),
//
change(index) {
let arr = [...this.roleOptions];
//
arr[index].dropdownShow = !arr[index].dropdownShow;
//
this.roleList = arr[index].name;
let shows = '';
// arr
arr.map(val => {
if (val.dropdownShow === true) {
shows += val.name + ',';
this.roleIdList.push(val.id);
}
});
this.roleOptions = [...arr];
// ','
this.roleList = shows.slice(0, shows.length - 1);
},
//
choose(index) {
let arr = [...this.checkoutOptions];
//
arr[index].dropdownShow = !arr[index].dropdownShow;
//
this.checkerList = arr[index].name;
let shows = '';
// arr
arr.map(val => {
if (val.dropdownShow === true) {
shows += val.name + ',';
this.checkerIdList.push(val.id);
}
});
this.checkoutOptions = [...arr];
// ','
this.checkerList = shows.slice(0, shows.length - 1);
// this.roleList = arr[value - 1].name;
},
//
openDropdown() {
this.arrow = !this.arrow;
this.show = true;
},
//
closeSecondDropdown() {
this.arrow = !this.arrow;
this.show = false;
},
/**
* 模糊查询 查找项目下的任务
* @param name 任务名
* @param projectId 项目id
*/
async searchPrevTask(val) {
try {
const params = { name: val, projectId: this.projectId };
const data = await this.$u.api.queryTaskOfProject(params);
this.allTasks = data;
return data;
} catch (error) {
console.error('error: ', error);
}
},
//
handleChange(data) {
console.log('data', data);
this.processTaskId = data.detailId;
},
//
clearAllTasks() {
this.allTasks = [];
},
//
addDeliverInput() {
if (this.deliverSort[this.deliverSort.length - 1].name) {
this.deliverSort.push({ name: '' });
}
},
//
async setParameters() {
const {
projectId,
task,
name,
startTime,
endTime,
hasRole,
roleIdList,
roleId,
description,
processTaskId,
checkerIdList,
isGlobal,
} = this;
if (!name) {
uni.$uiowToast('请输入名称');
return;
}
if ((!roleIdList || !roleIdList.length) && !hasRole) {
uni.$uiowToast('请选择负责人');
return;
}
if (!checkerIdList || !checkerIdList.length) {
uni.$uiowToast('请选择检查人');
return;
}
const deliverList = [];
this.deliverSort.forEach(item => {
if (item.name) {
deliverList.push(item.name);
}
});
const params = {
name,
startTime: startTime ? this.$moment(startTime).format('x') - 0 : '',
endTime: endTime ? this.$moment(endTime).format('x') - 0 : '',
roleIdList: hasRole ? [roleId] : roleIdList,
description,
projectId,
parentTaskId: task && task.id ? task.id : '', //
processTaskId, // TODO
checkerIdList,
global: isGlobal ? 1 : 0,
deliverList,
};
await this.handleSubmit(params);
},
/**
* 新建任务
* @param name 任务名
* @param startTime 开始时间
* @param endTime 结束时间
* @param roleIdList 负责人id数组
* @param description 描述
* @param projectId 所属项目id
* @param parentTaskId 所属任务id
* @param processTaskId 上道工序任务id
* @param checkerIdList 检查人id数组
* @param global 是否日常任务 0 1
* @param deliverList 交付物名字数组
*/
async handleSubmit(params) {
try {
const data = await this.$u.api.saveTask(params);
// TODO or
this.$emit('closeMask');
const newTasks = {
data: data[0],
processTaskId: params.processTaskId,
};
// store
//
if (!this.task || !this.task.id) {
this.addNewTasks(newTasks);
}
} catch (error) {
this.$emit('closeMask');
console.error('error: ', error);
}
},
// tasks
addNewTasks(data) {
const oldTasks = this.$u.deepClone(this.tasks);
let res = data.data;
console.log('添加任务后更新tasks', data);
//
if (data.processTaskId) {
const index = oldTasks.find(item => item.detailId === data.processTaskId);
if (index) {
oldTasks.splice(index + 1, 0, res);
}
} else {
this.setAddPosition(res, oldTasks);
}
},
//
setAddPosition(res, oldTasks) {
console.log('设置添加位置', res, oldTasks);
if (res.planStart - 0 < oldTasks[0].planStart - 0) {
//
oldTasks.splice(0, 0, res);
} else if (res.planStart - 0 === oldTasks[0].planStart - 0) {
//
oldTasks.splice(1, 0, res);
} else if (res.planStart - 0 >= oldTasks[oldTasks.length - 1].planStart - 0) {
//
oldTasks.splice(-1, 0, res);
} else {
//
for (let i = 0; i < oldTasks.length; i++) {
const item = oldTasks[i];
if (res.planStart - 0 > item.planStart - 0) {
if (res.planStart - 0 <= oldTasks[i + 1].planStart - 0) {
oldTasks.splice(i + 1, 0, res);
// console.log('res: ', res);
// return;
}
}
}
}
// TODO:
console.log('oldTasks: ', oldTasks);
this.updateTasks([...oldTasks]);
const params = { roleId: this.roleId, projectId: this.projectId };
this.getPermanent(params);
},
},
};
</script>
<style lang="scss" scoped>
.form {
display: flex;
flex-direction: column;
width: 100%;
max-height: 400px;
overflow-y: scroll;
}
.drop-item {
border-bottom: 1px solid #f1f1f1;
padding: 16rpx;
}
::v-deep.u-input--border {
border: none;
border-radius: 0;
}
::v-deep.u-dropdown__menu__item > uni-view {
border: none !important;
padding: 5px;
}
.u-input {
border-bottom: 1px solid #dcdfe6;
}
.new-projects-box {
margin-top: 20px;
padding: 15px;
width: 100%;
overflow: hidden;
}
.w {
width: 300px;
height: 39px;
}
::v-deep .u-dropdown__menu__item .u-flex {
border: 0 !important;
border-bottom: 1px solid #dcdfe6 !important;
padding: 0 20rpx;
}
</style>

583
components/Title/components/CreateTask.vue

@ -0,0 +1,583 @@
<template>
<div class="new-projects-box">
<div class="form">
<!-- 项目名称 -->
<view class="new-projects-title font-bold text-base flex justify-center items-center text-black">新建任务</view>
<div class="form-item flex items-center">
<div class="mr-4">名称<span class="text-red-500">*</span></div>
<u-input :maxlength="8" v-model="name" type="text" :inputAlign="'right'" :clearable="false" placeholder="请输入任务名称" />
</div>
<!-- 开始时间 -->
<div class="form-item flex items-center">
<div class="mr-4">开始时间<span class="text-red-500" v-if="source === 'regular'">*</span></div>
<div class="flex justify-end items-center flex-1">
<u-input
placeholder="请选择开始时间"
v-model="startTime"
type="text"
:inputAlign="'right'"
:clearable="false"
@click="$emit('showTime', 1)"
/>
<u-icon class="ml-1" name="arrow-right" color="#969799" size="28"></u-icon>
</div>
</div>
<!-- 结束时间 -->
<div class="form-item flex items-center">
<div class="mr-4">结束时间<span class="text-red-500" v-if="source === 'regular'">*</span></div>
<div class="flex justify-end items-center flex-1">
<u-input
placeholder="请选择结束时间"
v-model="endTime"
type="text"
:inputAlign="'right'"
:clearable="false"
@click="$emit('showTime', 2)"
/>
<u-icon class="ml-1" name="arrow-right" color="#969799" size="28"></u-icon>
</div>
</div>
<!-- 多选框 -->
<div class="form-item flex justify-between items-center">
<div class="mr-4">负责人<span class="text-red-500">*</span></div>
<div class="flex-1 text-right" v-if="hasRole">{{ roleName }}</div>
<div label="负责人" class="flex-1" v-else>
<u-dropdown disabled ref="uDropdown" placeholder="请选择负责人">
<u-dropdown-item :title="roleList">
<view class="slot-content bg-white">
<div
class="flex flex-row justify-between mb-1 drop-item"
v-for="(role, roleIndex) in roleOptions"
:key="roleIndex"
@click="change(roleIndex)"
>
<view>{{ role.name }}</view>
<u-icon v-if="role.dropdownShow" name="checkbox-mark" color="#2979ff" size="28"></u-icon>
</div>
</view>
</u-dropdown-item>
</u-dropdown>
</div>
</div>
<!-- 描述 -->
<div class="form-item flex items-center">
<div class="mr-4">描述</div>
<u-input v-model="description" :maxlength="48" type="text" :inputAlign="'right'" :clearable="false" />
</div>
<!-- 所属项目 -->
<div class="form-item flex items-center">
<div class="mr-4">所属项目<span class="text-red-500">*</span></div>
<div class="flex-1 text-right">{{ project.name }}</div>
</div>
<!-- 所属任务 -->
<div class="form-item flex items-center" v-if="task && task.id">
<div class="mr-4">所属任务</div>
<div class="flex-1 text-right">{{ task.name }}</div>
</div>
<!-- 上道工序 -->
<div class="form-item flex items-center">
<div class="mr-4">上道工序</div>
<InputSearch
@searchPrevTask="searchPrevTask"
:dataSource="allTasks"
@select="handleChange"
@clearAllTasks="clearAllTasks"
placeholder="请输入上道工序"
/>
</div>
<!-- 检查人多选框 -->
<div class="form-item flex justify-between items-center">
<div class="mr-4 flex-shrink-0">检查人<span class="text-red-500">*</span></div>
<div label="检查人" class="flex-1" style="width: calc(100% - 65px)">
<u-dropdown ref="dropdown">
<u-dropdown-item :title="checkerList">
<view class="slot-content bg-white">
<div
class="flex flex-row justify-between mb-1 drop-item"
v-for="(checkoutOption, Index) in checkoutOptions"
:key="Index"
@click="choose(Index)"
>
<view>{{ checkoutOption.name }}</view>
<u-icon v-if="checkoutOption.dropdownShow" name="checkbox-mark" color="#2979ff" size="28"></u-icon>
</div>
</view>
</u-dropdown-item>
</u-dropdown>
</div>
</div>
<!-- 是否是日常任务 -->
<div class="form-item flex justify-between items-center" v-if="!source">
是否是日常任务
<u-switch v-model="isGlobal" size="28"></u-switch>
</div>
<div class="form-item flex justify-between items-center" v-for="(sort, sortIndex) in deliverSort" :key="sortIndex">
<div class="mr-4">{{ `交付物${sortIndex + 1}` }}</div>
<div class="flex-1">
<u-input
@blur="addDeliverInput"
v-model="sort.name"
:placeholder="`交付物名称${sortIndex + 1}`"
type="text"
:inputAlign="'right'"
:clearable="false"
/>
</div>
</div>
<!-- <div class="form-item flex items-center">
<div class="mr-4">添加插件</div>
<u-input :maxlength="8" v-model="name" type="text" :inputAlign="'right'" :clearable="false" placeholder="选择插件" />
</div> -->
<div class="form-btn flex items-center mt-4">
<u-button type="primary" size="medium" @click="setParameters">提交</u-button>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
export default {
props: {
startTime: {
type: String,
default: '',
},
endTime: {
type: String,
default: '',
},
task: {
type: Object,
default: null,
},
source: {
type: String,
default: '',
},
},
data() {
return {
// arrow: true,
// show: false,
isGlobal: false, //
name: '', //
// showChooseTime: false,
// timeValue: '', //
// taskStartTime: '', //
// taskEndTime: '', //
description: '', //
// projectShow: false, //
processTaskId: '', //
// type: 'text',
// border: true,
roleList: undefined, //
checkerList: undefined, //
roleOptions: [], //
checkoutOptions: [], //
roleIdList: [], // id
checkerIdList: [], // id
deliverables: [], //
deliverSort: [{ name: '' }], //
allTasks: [],
roleName: '', //
hasRole: false, //
};
},
computed: {
...mapState('role', ['visibleRoles', 'roleId']),
...mapState('project', ['project']),
...mapState('task', ['tasks']),
...mapGetters('project', ['projectId']),
},
// watch: {
// endTime(val) {
// if (val) {
// this.timeValue = this.startTime + ' ' + val;
// }
// },
// },
mounted() {
//
if (this.visibleRoles.length) {
this.visibleRoles.forEach(role => {
role.dropdownShow = false;
role.status = false;
});
}
this.roleOptions = this.$u.deepClone(this.visibleRoles);
this.checkoutOptions = this.$u.deepClone(this.visibleRoles);
//
if (this.roleId) {
const item = this.visibleRoles.find(r => r.id === this.roleId);
if (item) {
this.roleName = item.name;
this.hasRole = true;
}
}
},
methods: {
...mapMutations('task', ['updateTasks']),
...mapActions('task', ['getPermanent']),
//
change(index) {
let arr = [...this.roleOptions];
//
arr[index].dropdownShow = !arr[index].dropdownShow;
//
this.roleList = arr[index].name;
let shows = '';
// arr
arr.map(val => {
if (val.dropdownShow === true) {
shows += val.name + ',';
this.roleIdList.push(val.id);
}
});
this.roleOptions = [...arr];
// ','
this.roleList = shows.slice(0, shows.length - 1);
},
//
choose(index) {
let arr = [...this.checkoutOptions];
//
arr[index].dropdownShow = !arr[index].dropdownShow;
//
this.checkerList = arr[index].name;
let shows = '';
// arr
arr.map(val => {
if (val.dropdownShow === true) {
shows += val.name + ',';
this.checkerIdList.push(val.id);
}
});
this.checkoutOptions = [...arr];
// ','
this.checkerList = shows.slice(0, shows.length - 1);
// this.roleList = arr[value - 1].name;
},
//
// openDropdown() {
// this.arrow = !this.arrow;
// this.show = true;
// },
// //
// closeSecondDropdown() {
// this.arrow = !this.arrow;
// this.show = false;
// },
/**
* 模糊查询 查找项目下的任务
* @param name 任务名
* @param projectId 项目id
*/
async searchPrevTask(val) {
try {
const params = { name: val, projectId: this.projectId };
const data = await this.$u.api.queryTaskOfProject(params);
this.allTasks = data;
return data;
} catch (error) {
console.error('error: ', error);
}
},
//
handleChange(data) {
console.log('data', data);
this.processTaskId = data.detailId;
},
//
clearAllTasks() {
this.allTasks = [];
},
//
addDeliverInput() {
if (this.deliverSort[this.deliverSort.length - 1].name) {
this.deliverSort.push({ name: '' });
}
},
//
async setParameters() {
if (this.source) {
this.isGlobal = 0;
}
const {
projectId,
task,
name,
startTime,
endTime,
hasRole,
roleIdList,
roleId,
description,
processTaskId,
checkerIdList,
isGlobal,
} = this;
if (!name) {
uni.$ui.showToast('请输入任务名称');
return;
}
if (!isGlobal) {
if (!startTime) {
uni.$ui.showToast('定期任务时间不能为空');
return;
}
}
if (startTime && endTime) {
let start = startTime ? this.$moment(startTime).format('x') - 0 : '';
let end = endTime ? this.$moment(endTime).format('x') - 0 : '';
if (start > end) {
uni.$ui.showToast('结束时间不能小于开始时间');
return;
}
}
if ((!roleIdList || !roleIdList.length) && !hasRole) {
uni.$ui.showToast('请选择负责人');
return;
}
if (!checkerIdList || !checkerIdList.length) {
uni.$ui.showToast('请选择检查人');
return;
}
const deliverList = [];
this.deliverSort.forEach(item => {
if (item.name) {
deliverList.push(item.name);
}
});
const params = {
name,
startTime: startTime ? this.$moment(startTime).format('x') - 0 : '',
endTime: endTime ? this.$moment(endTime).format('x') - 0 : '',
roleIdList: hasRole ? [roleId] : roleIdList,
description,
projectId,
parentTaskId: task && task.process !== 4 && task.id ? task.id : '', //
processTaskId, // TODO
checkerIdList,
global: isGlobal ? 1 : 0,
deliverList,
};
await this.handleSubmit(params);
},
/**
* 新建任务
* @param name 任务名
* @param startTime 开始时间
* @param endTime 结束时间
* @param roleIdList 负责人id数组
* @param description 描述
* @param projectId 所属项目id
* @param parentTaskId 所属任务id
* @param processTaskId 上道工序任务id
* @param checkerIdList 检查人id数组
* @param global 是否日常任务 0 1
* @param deliverList 交付物名字数组
*/
async handleSubmit(params) {
try {
const data = await this.$u.api.saveTask(params);
// TODO or
this.$emit('closeMask');
const newTasks = {
data: data[0],
processTaskId: params.processTaskId,
};
// store
// --
// if (!this.task || !this.task.id || this.task.process === 4) {
// this.addNewTasks(newTasks);
// }
this.addNewTasks(newTasks);
} catch (error) {
this.$emit('closeMask');
console.error('error: ', error);
}
},
// tasks
addNewTasks(data) {
const oldTasks = this.$u.deepClone(this.tasks);
let res = data.data;
//
if (data.processTaskId) {
const index = oldTasks.find(item => item.detailId === data.processTaskId);
if (index) {
oldTasks.splice(index + 1, 0, res);
}
} else {
this.setAddPosition(res, oldTasks);
}
},
//
setAddPosition(res, oldTasks) {
if (res.planStart - 0 < oldTasks[0].planStart - 0) {
//
oldTasks.splice(0, 0, res);
} else if (res.planStart - 0 === oldTasks[0].planStart - 0) {
//
oldTasks.splice(1, 0, res);
} else if (res.planStart - 0 >= oldTasks[oldTasks.length - 1].planStart - 0) {
//
oldTasks.splice(-1, 0, res);
} else {
//
for (let i = 0; i < oldTasks.length; i++) {
const item = oldTasks[i];
if (res.planStart - 0 > item.planStart - 0) {
if (res.planStart - 0 <= oldTasks[i + 1].planStart - 0) {
oldTasks.splice(i + 1, 0, res);
// console.log('res: ', res);
// return;
}
}
}
}
// TODO:
console.log('oldTasks: ', oldTasks);
this.updateTasks([...oldTasks]);
const params = { roleId: this.roleId, projectId: this.projectId };
this.getPermanent(params);
},
},
};
</script>
<style lang="scss" scoped>
.form {
// display: flex;
// flex-direction: column;
width: 100%;
max-height: 400px;
overflow-y: scroll;
}
.drop-item {
border-bottom: 1px solid #f1f1f1;
padding: 16rpx 32rpx;
}
// ::v-deep.u-input--border {
// border: none;
// border-radius: 0;
// padding: 0 !important;
// }
::v-deep.u-dropdown__menu__item > uni-view {
border: none !important;
padding: 5px;
}
// .u-input {
// border-bottom: 1px solid #dcdfe6;
// }
.new-projects-box {
margin-top: 20px;
// padding: 15px;
width: 100%;
overflow: hidden;
color: #595959;
}
.new-projects-title {
height: 60px;
}
.form-item {
padding: 0 16px;
height: 48px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.form-item ::v-deep .input-group uni-input {
border: 0;
font-size: 14px;
text-align: right;
}
.form-btn {
height: 60px;
}
.form-btn .u-btn {
width: 156px;
height: 32px;
font-size: 16px;
border-radius: 4px;
}
.w {
width: 300px;
height: 39px;
}
::v-deep .u-dropdown__menu {
height: 48px !important;
}
::v-deep .u-dropdown__menu__item {
width: 100%;
}
::v-deep .u-dropdown__menu__item .u-flex {
border: 0 !important;
// border-bottom: 1px solid #dcdfe6 !important;
padding: 0 !important;
}
::v-deep .u-dropdown__menu__item__arrow {
margin-left: 10px;
flex-shrink: 0;
}
::v-deep .u-dropdown__menu__item__text {
width: calc(100% - 23px);
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
::v-deep .u-dropdown__content {
top: 48px !important;
box-shadow: 0 4px 6px 1px rgba(0, 0, 0, 0.1) !important;
}
</style>

210
components/Title/components/ShareProject.vue

@ -0,0 +1,210 @@
<template>
<view class="flex justify-center">
<view class="content p-3 pb-8">
<view class="mb-3 font-bold text-base flex justify-center">创建分享链接</view>
<view class="flex flex-col">
<view class="mb-1">用户以什么角色加入项目</view>
<!-- 下拉多选 -->
<view class="uni-list">
<view class="uni-list-cell">
<view class="uni-list-cell-db ml-2" v-if="rolesArray.length">
<picker @change="changeRole" :value="index" :range="rolesArray">
<view class="uni-input">{{ allRolesName[index].name }}</view>
</picker>
</view>
</view>
</view>
<!-- 复制链接 -->
<view class="link flex items-center mt-4">
<view class="link-url">{{ links }}</view>
<u-button
style="border-radius: 0; height: 100%"
type="primary"
v-clipboard:copy="copyText"
v-clipboard:success="copySuccess"
v-clipboard:error="copyError"
>
复制链接
</u-button>
</view>
<view @click="select">
<!-- 全选按钮 -->
<!-- <view class="flex mt-4">
<view>
<u-checkbox-group>
<u-checkbox v-model="checked" @change="checkedAll"></u-checkbox>
</u-checkbox-group>
</view>
<view>已选择({{ this.quantity }})</view>
<view style="color: #f37378; margin-left: 20px">批量删除</view>
</view> -->
<!-- 多选框 -->
<!-- <view>
<u-checkbox-group class="checkboxs flex flex-1 items-center mt-4" v-for="(item, index) in list" :key="index">
<div class="flex-1 flex items-center">
<u-checkbox v-model="item.checked"></u-checkbox>
<u-avatar :src="item.src" size="55" style="background: #d8dce0; margin-right: 10px"></u-avatar>
<div style="width: 60%; font-size: 12px">
<div style="color: gray">{{ item.name }}</div>
<div style="color: #c4d0e1">{{ item.joinMethod }}</div>
</div>
</div>
</u-checkbox-group>
</view> -->
</view>
</view>
</view>
</view>
</template>
<script>
import { mapGetters, mapState } from 'vuex';
export default {
data() {
return {
rolesArray: [],
allRolesName: [],
index: 0,
links: '', //
copyText: '',
checked: false, //
roleName: '观众',
//
list: [
{
name: '冯老师',
src: '',
joinMethod: '文件创建者',
role: '观众',
checked: false,
disabled: false,
},
{
name: '马壮',
src: '',
joinMethod: '通过链接加入',
role: '干系人',
checked: false,
disabled: false,
},
{
name: '张野',
src: '',
joinMethod: '通过链接加入',
role: '观众',
checked: false,
disabled: false,
},
],
quantity: 0, //
path: '',
};
},
computed: {
...mapState('role', ['visibleRoles', 'invisibleRoles']),
...mapState('project', ['project']),
...mapGetters('project', ['projectId']),
},
mounted() {
this.$nextTick(() => {
this.path = window.location.href.split('?')[0];
const { path, projectId } = this;
const params = { path: `${path}`, projectId, roleId: '0' };
this.creatShare(params);
});
if (this.visibleRoles.length || this.invisibleRoles.length) {
const arr = this.visibleRoles.concat(this.invisibleRoles);
arr.forEach(role => {
let item = { id: '', name: '' };
item.id = role.id;
item.name = role.name;
this.allRolesName.push(item);
this.rolesArray.push(role.name);
});
const firstItem = { id: '0', name: '观众' };
this.allRolesName.unshift(firstItem);
this.rolesArray.unshift('观众');
}
},
methods: {
//
async changeRole(e) {
this.index = e.target.value;
this.roleName = this.allRolesName[this.index].name;
const { path, projectId } = this;
const params = { path, projectId, roleId: this.allRolesName[this.index].id };
await this.creatShare(params);
},
//
copySuccess() {
uni.$uiowToast('复制成功');
},
//
copyError() {
uni.$uiowToast('复制失败,请稍后重试');
},
/**
* 创建分享链接
* @param path 路径前缀
* @param projectId 项目id
* @param roleId 角色id
*/
async creatShare(params) {
try {
const data = await this.$u.api.createShare(params);
this.links = data.path;
this.copyText = `邀请您加入${this.project.name}的项目,角色为${this.roleName},链接为${data.path}&url=${this.$t.domain}`;
} catch (error) {
console.error('error: ', error);
}
},
//
select() {
this.quantity = 0;
this.list.forEach(val => {
if (val.checked == true) {
this.quantity++;
}
});
},
//
checkedAll() {
this.list.map(val => {
val.checked = !this.checked;
});
},
},
};
</script>
<style lang="scss" scoped>
.content {
width: 100%;
max-height: 400px;
}
.link {
height: 40px;
border: 1px solid #afbed1;
}
.link-url {
color: #afbed1;
width: 80%;
line-height: 40px;
margin-left: 5px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
</style>

43
components/Upload/Upload.vue

@ -0,0 +1,43 @@
<template>
<view class="upload">
<u-icon name="plus" size="24px" class="flex justify-center w-12 h-12 bg-blue-100 rounded-full shadow-md" @click="handleUpload"></u-icon>
</view>
</template>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
const emit = defineEmits(['success', 'error']);
const store = useStore();
const userId = computed(() => store.getters['user/userId']);
// wbs
const handleUpload = async cur => {
try {
const res = await uni.$u.api.import();
// WBS
//
emit('success');
res.url && (uni.$t.domain = res.url);
setTimeout(() => {
uni.navigateTo({ url: `/pages/project/project?u=${userId.value}&p=${res.id}&pname=${res.pname}&url=${res.url}` });
}, 2000);
} catch (error) {
console.error('error: ', error);
emit('error', error);
}
};
</script>
<style lang="scss" scoped>
.upload {
position: absolute;
right: 10px;
bottom: 0;
transform: translate3d(0, 50%, 0);
color: $uni-color-primary !important;
}
</style>

2
config/task.js

@ -0,0 +1,2 @@
// 每页加载颗粒度的个数
export default { pageCount: 10 };

17
config/time.js

@ -0,0 +1,17 @@
export default {
timeUnits: [
// 时间颗粒度
{ id: 0, value: '毫秒', format: 'x', cycle: 'YY-M-D HH:mm:ss', granularity: 'millisecond' },
{ id: 1, value: '秒', format: 'x', cycle: 'YY-M-D HH:mm:ss', granularity: 'second' },
{ id: 2, value: '分', format: 'ss', cycle: 'YY-M-D HH:mm', granularity: 'minute' },
{ id: 3, value: '时', format: 'mm', cycle: 'YY-M-D HH时', granularity: 'hour' },
{ id: 4, value: '天', format: 'D日 HH:mm', cycle: 'YY-M-D', granularity: 'day' },
{ id: 5, value: '周', format: 'D日 HH:mm', cycle: '', granularity: 'week' },
{ id: 6, value: '月', format: 'D日 H:m', cycle: 'YYYY年', granularity: 'month' },
{ id: 7, value: '季度', format: '', cycle: 'YYYY年', granularity: 'quarter' },
{ id: 8, value: '年', format: 'YYYY', cycle: '', granularity: 'year' },
{ id: 9, value: '年代', format: '', cycle: '', granularity: '' },
{ id: 10, value: '世纪', format: '', cycle: '', granularity: '' },
{ id: 11, value: '千年', format: '', cycle: '', granularity: '' },
],
};

194
hooks/project/useGetTasks.js

@ -0,0 +1,194 @@
import { computed, nextTick } from 'vue';
import { useStore } from 'vuex';
import { flatten } from 'lodash';
import dayjs from 'dayjs';
export default function useGetTasks() {
const store = useStore();
const tasks = computed(() => store.state.task.tasks);
const roleId = computed(() => store.state.role.roleId);
const timeNode = computed(() => store.state.task.timeNode);
const timeUnit = computed(() => store.state.task.timeUnit);
const projectId = computed(() => store.getters['project/projectId']);
const timeGranularity = computed(() => store.getters['task/timeGranularity']);
// 初始化 定期任务
async function initPlanTasks() {
setPrevPlaceholderTasks(); // 向上加载空数据
setNextPlaceholderTasks(); // 向下加载空数据
await getInitTasks(); // 获取初始数据
}
// 切换了 颗粒度 || 角色时候 获取初始定期任务
function getInitTasks() {
// 预加载 上下的定期任务
function preloadFn(that) {
const detailId = tasks.value.findIndex(task => task.detailId);
const arr = [];
tasks.value.forEach(task => {
if (task.detailId) {
arr.push(task);
}
});
if (detailId !== -1) {
// 只要有1个真实的任务 就预加载上下周期的任务
const { pageCount } = uni.$task;
nextTick(() => {
// 向上拿数据
getTasks({
timeNode: +tasks.value[detailId].planStart,
queryType: 0,
queryNum: pageCount,
});
// 向下拿数据
const nextQueryTime = +uni.$time.add(+arr[arr.length - 1].planStart, 1, timeGranularity.value);
getTasks({
timeNode: nextQueryTime,
queryType: 1,
queryNum: pageCount,
});
});
} else {
// 没有任务 上下显示时间刻度
// 向上加载
setPrevPlaceholderTasks();
// // 向下加载
setNextPlaceholderTasks();
}
}
// 根据时间基准点和角色查找定期任务
getTasks({
queryType: 0,
}); // 向上获取定期任务数据
// 根据项目id获取角色列表
// 向下获取定期任务数据
getTasks(
{
queryType: 1,
},
preloadFn,
);
}
/**
* 生成getTasks所用的参数
* @param {object} query getTasks传递的参数
*/
function generateGetTaskParam(query) {
return {
roleId: roleId.value,
timeNode: query.timeNode || timeNode.value,
timeUnit: query.timeUnit || timeUnit.value,
queryNum: query.queryNum || 3,
queryType: query.queryType,
projectId: projectId.value,
};
}
/**
* 根据时间基准点和角色查找定期任务
* @param {object} query
* @param {string} query.roleId 角色id
* @param {string} query.timeNode 时间基准点 默认当前
* @param {string} query.timeUnit 时间颗粒度 默认天
* @param {string} query.queryNum 查找颗粒度数量 默认3个
* @param {number} query.queryType 0向上查找 1向下查找(默认) 下查包含自己上查不包含
*/
function getTasks(query, fn) {
store.commit('task/setShowSkeleton', false);
const params = generateGetTaskParam(query);
uni.$catchReq.getRegularTask(params, (err, data) => {
store.commit('task/setShowSkeleton', false);
if (err) {
// TODO: 提示错误
console.error('err: ', err);
} else {
store.commit('task/setShowScrollTo', true);
// 有数据用数据替换刻度
// 没有数据 继续加载刻度
if (data && data.length) {
replacePrevData(data, params.queryType);
params.queryType === 0 ? store.commit('task/setTopEnd', false) : store.commit('task/setBottomEnd', false);
} else {
// TODO: 0 -> 向上 1 -> 向下
params.queryType === 0 ? setPrevPlaceholderTasks() : setNextPlaceholderTasks();
}
if (tasks.value.length && fn) {
fn(this);
}
}
});
}
/**
* 用拿到的新数据 替换 时间刻度/旧数据
* 先对比 新旧数据的 始末时间 补齐刻度
* 再遍历对比 用任务替换刻度
* @param {array} data 服务端返回的新数据 上边已经处理过空值
* @param {number} type 0 -> 向上 1->向下
*/
function replacePrevData(data, type) {
const obj = { tasks: tasks.value, data, timeGranularity: timeGranularity.value };
let oldTasks = fillPlaceholderTask(obj); // 已经上下补齐时间刻度的
// 遍历对比 用任务替换刻度
// TODO: tasks越来越多 遍历越来越多 需要优化
oldTasks.forEach((taskItem, index) => {
const arr = data.filter(dataItem => dayjs(+dataItem.planStart).isSame(+taskItem.planStart, timeGranularity.value));
if (arr && arr.length) {
oldTasks.splice(index, 1, [...arr]); // 这里加入的数据是array类型的, [{},{},[],[],{}]
}
});
oldTasks = flatten(oldTasks); // 1维拍平
store.commit('task/clearTasks');
type === 0 ? store.commit('task/setUpTasks', oldTasks) : store.commit('task/setDownTasks', oldTasks);
}
/**
* 超出旧数据上下限 补齐时间刻度到新数据的起始时间颗粒度
*/
function fillPlaceholderTask(obj) {
const { prev, next } = uni.$task.computeFillPlaceholderTaskCount(obj);
if (prev) {
const newTasks = uni.$task.setPlaceholderTasks(+obj.tasks[0].planStart, true, obj.timeGranularity, prev);
store.commit('task/setUpTasks', newTasks);
}
if (next) {
const newTasks = uni.$task.setPlaceholderTasks(+obj.tasks[tasks.length - 1].planStart, false, obj.timeGranularity, next);
store.commit('task/setDownTasks', newTasks);
}
return tasks.value;
}
// 设置时间轴向上的空数据
function setPrevPlaceholderTasks() {
store.commit('task/setTopEnd', true);
let startTime = '';
if (!tasks.value || !tasks.value.length) {
startTime = Date.now(); // 没有任务就应该是时间基准点
} else {
startTime = tasks.value[0].planStart - 0; // 有任务就是第一个任务的计划开始时间
}
const placeholderTasks = uni.$task.setPlaceholderTasks(startTime, true, timeGranularity.value);
store.commit('task/setUpTasks', placeholderTasks);
}
// 设置时间轴向下的空数据
function setNextPlaceholderTasks() {
store.commit('task/setBottomEnd', true);
let startTime = '';
if (!tasks.value || !tasks.value.length) {
startTime = Date.now();
} else {
startTime = +tasks.value[tasks.value.length - 1].planStart;
}
const initData = uni.$task.setPlaceholderTasks(startTime, false, timeGranularity.value);
store.commit('task/setDownTasks', initData);
}
return {
initPlanTasks,
getTasks,
};
}

122
hooks/project/useInit.js

@ -0,0 +1,122 @@
import { computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { useStore } from 'vuex';
export default function useInit() {
const store = useStore();
const token = computed(() => store.state.user.token);
onLoad(options => {
if (options.share && options.share === '1') {
shareInit(options);
} else {
init(options);
}
});
// onMounted(() => {
// const system = uni.getSystemInfoSync();
// height.value = `${system.windowHeight}px`;
// });
/**
* 通过项目id获取项目信息
* @param {object} params 提交的参数
*/
async function getProjectById(params) {
try {
const data = await uni.$u.api.findProjectById(params);
store.commit('project/setProject', data);
// 根据项目id获取角色列表
getRoles(params);
} catch (error) {
console.log('error: ', error || '获取项目信息失败');
}
}
/**
* 通过项目id获取角色信息
* @param {string} projectId
* @param {object} params 提交的参数
*/
function getRoles(params) {
uni.$catchReq.findShowRole(params, (err, data) => {
if (err) {
console.error('err: ', err || '获取角色信息失败');
} else {
store.commit('role/setInvisibleRoles', data ? data.invisibleList : []);
store.commit('role/setVisibleRoles', data ? data.visibleList : []);
setInitialRoleId(data ? data.visibleList : []);
}
});
}
// 设置 初始显示角色信息
function setInitialRoleId(visibleList) {
if (!visibleList || !visibleList.length) return;
const index = visibleList.findIndex(item => +item.mine === 1);
const currentRole = index > 0 ? visibleList[index] : visibleList[0];
const storageRoleId = uni.$storage.getStorageSync('roleId');
const currentRoleId = storageRoleId || (currentRole ? currentRole.id : '');
store.commit('role/setRoleId', currentRoleId);
// 清空storage
uni.$storage.setStorageSync('roleId', '');
}
/**
* 初始化
* @param {object | null} options
*/
function init(options) {
// 参数里有项目名称 就设置标题里的项目名称
options && options.pname && store.commit('project/setProjectName', options.pname);
if (!options || !options.p) {
uni.$ui.showToast('缺少项目信息参数'); // 没有项目id参数
} else {
if (options.p !== uni.$storage.getStorageSync('projectId')) {
console.log('切项目了');
uni.$storage.setStorageSync('roleId', '');
}
// 根据项目id获取项目信息
const params = { projectId: options.p, num: 0 };
getProjectById(params);
// 根据项目id获取成员列表
store.dispatch('role/getAllMembers', { projectId: options.p });
}
}
// 分享链接来的初始化
async function shareInit(options) {
const storageUser = uni.$storage.getStorageSync('user');
const user = storageUser ? JSON.parse(storageUser) : null;
if (user && user.id) {
await store.dispatch('user/getToken', user.id);
const res = await clickShare({ code: options.shareId });
if (res && res.projectId) {
let query = { ...uni.$route.query };
query = { u: user.id, p: res.projectId };
uni.$router.push({ path: uni.$route.path, query });
init(query);
}
} else {
uni.$ui.showToast('缺少用户信息参数,请登录');
}
}
/**
* 点击分享连接
* @param {any} commit
* @param {object} param 请求参数
*/
async function clickShare(param) {
try {
const data = await uni.$catchReq.clickShare(param);
return data;
} catch (error) {
uni.$ui.showToast(error.msg || '获取失败');
}
}
return { init };
}

7
hooks/theme/useTheme.js

@ -0,0 +1,7 @@
import { computed } from 'vue';
import { useStore } from 'vuex';
export default function useTheme() {
const store = useStore();
const theme = computed(() => store.state.theme);
return theme;
}

37
hooks/user/useGetToken.js

@ -0,0 +1,37 @@
import { computed } from 'vue';
import { useStore } from 'vuex';
/**
* 初始化
* token userId处理
* 1.1 store里有token 且没过期直接使用store的token
* 1.2 store里的token不可用 查localStorage
* 因为一开始就将local的数据同步到了store里所以不用管local的数据了
* 2. store里token不可用 查userId 通过store里的userId获取token
* url local的userId 一开始就同步到了store里所以不用考虑
* @param {object | null} options
*/
export default async function useGetToken() {
const store = useStore();
const token = computed(() => store.state.user.token);
const tokenIsAvailable = computed(() => store.getters['user/tokenIsAvailable']); // token是否可用
const userId = computed(() => store.getters['user/userId']);
debugger;
if (token.value && tokenIsAvailable.value) {
// 1.1 store里有token 且没过期直接:使用store的token
return token.value;
} else {
// 2. 根据userId获取token
if (userId.value) {
try {
const { token } = await store.dispatch('user/getTokenByUserId', userId.value);
return token;
} catch (error) {
console.error('error: ', error);
return null;
}
} else {
return null;
}
}
}

10
hooks/user/useGetUserIdFromLocal.js

@ -0,0 +1,10 @@
// 获取本地localStorage里的userId
export default function useGetUserIdFromLocal() {
try {
const userLocal = uni.$storage.getStorageSync('user');
const user = JSON.parse(userLocal);
return user.id;
} catch (error) {
return null;
}
}

285
hooks/user/userMixin - 副本.js

@ -0,0 +1,285 @@
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import clipboard from "@/common/js/dc-clipboard/clipboard.js"
export default function mixinInit {
const store = useStore();
const user = computed(() => store.state.user.user);
const rules = ref({
phone: [
{
required: true,
message: '请输入手机号',
trigger: ['change','blur'],
},
{
validator: (rule, value, callback) => {
// 调用uView自带的js验证规则,详见:https://www.uviewui.com/js/test.html
return this.$u.test.mobile(value);
},
message: '手机号码不正确',
// 触发器可以同时用blur和change,二者之间用英文逗号隔开
trigger: ['change','blur'],
}
],
verificationCodeValue: [
{
required: true,
message: '请输入图形验证码',
trigger: ['change','blur'],
},
{
type: 'number',
message: '图形验证码只能为数字',
trigger: ['change','blur'],
}
],
smsCode: [
{
required: true,
message: '请输入验证码',
trigger: ['change','blur'],
},
{
type: 'number',
message: '验证码只能为数字',
trigger: ['change','blur'],
}
],
account: [
{
required: true,
message: '请输入用户名',
trigger: ['change','blur'],
},
{
min: 2,
max: 20,
message: '用户名长度在2到20个字符',
trigger: ['change','blur'],
},
{
pattern: /^[a-zA-Z0-9._-]{2,20}$/,
message: '请输入2-20位字母、数字、汉字或字符"_ - ."',
trigger: ['change','blur'],
}
],
password: [
{
required: true,
message: '请输入密码',
trigger: ['change','blur'],
},
{
min: 6,
max: 20,
message: '密码长度在6到20个字符',
trigger: ['change','blur'],
},
{
// 正则不能含有两边的引号
pattern: /^[a-zA-Z0-9._-]{6,20}$/,
message: '请输入6-20位字母、数字、汉字或字符"_ - ."',
trigger: ['change','blur'],
}
],
});
const errorType = ref(['message']);
const labelPosition = ref('left');
const border = ref(false);
const smsCode = ref(''); // 短信验证码
const showInterval = ref(false);
const interval = ref(120);
const codeTimer = ref(null);
const showPaste = ref(false);
// 获取图形验证码
async function getImageCode() {
console.log('5555')
uni.$ui.showLoading();
try {
const data = await uni.$u.api.getImageCode();
const { imageBase64, verificationCodeId } = data;
imageBase64 = imageBase64 || '';
verificationCodeId = verificationCodeId || '';
uni.$ui.hideLoading();
} catch (error) {
uni.$ui.hideLoading();
uni.$ui.showToast(error);
}
}
return {
// errorType,
// rules,
// labelPosition,
// border,
getImageCode,
// hasvalue,
// getCode,
// getCodeInterval,
// checkRules,
// setCode,
// getClipboardContents,
// verifyPhone,
// verifyLoginname,
// handleWxLogin
}
}
// const mixin = {
// computed: mapState('user', ['user']),
// onReady() {
// this.$refs.uForm.setRules(this.rules);
// },
// methods: {
// ...mapActions('user', ['sendCode']),
// 获取图形验证码
// async getImageCode() {
// this.$util.showLoading();
// try {
// const data = await uni.$u.api.getImageCode();
// const { imageBase64, verificationCodeId } = data;
// this.imageBase64 = imageBase64 || '';
// this.verificationCodeId = verificationCodeId || '';
// uni.hideLoading();
// } catch (error) {
// uni.hideLoading();
// uni.$ui.showToast(error);
// }
// },
// //有图片验证码的值
// hasvalue() {
// if(this.model.smsCode || this.model.showPaste) return
// if (!this.verifyPhone(this.model.phone)) {
// uni.$ui.showToast('请输入正确的手机号');
// return;
// }
// if (!this.model.verificationCodeValue) {
// uni.$ui.showToast('请输入图形验证码');
// return;
// }
// this.getCode();
// },
// // 获取验证码
// async getCode() {
// try {
// const { phone, verificationCodeValue } = this.model;
// const { verificationCodeId } = this;
// if (!verificationCodeId || !verificationCodeValue) {
// uni.$ui.showToast('缺少图形验证码参数');
// return;
// }
// const params = {
// phone,
// verificationCodeId,
// verificationCodeValue,
// };
// const date = await store.dispatch('user/sendCode', params);
// getCodeInterval();
// showPaste.value = true;
// } catch (err) {
// throw err;
// }
// },
// // 获取验证码倒计时
// getCodeInterval() {
// this.showInterval = true;
// this.codeTimer = setInterval(() => {
// if (this.interval === 0) {
// clearInterval(this.codeTimer);
// this.codeTimer = null;
// this.showInterval = false;
// this.interval = 120;
// return;
// }
// this.interval = this.interval - 1;
// }, 1000);
// },
// // 验证信息
// checkRules() {
// // const { smsCode, phone, user } = this;
// if (!this.verifyPhone(phone.value)) {
// uni.$ui.showToast('请输入正确的手机号');
// return false;
// }
// if (!smsCode.value) {
// uni.$ui.showToast('验证码无效');
// return false;
// }
// if (phone.value === user.value.phone) {
// uni.$ui.showToast('新手机号不能与旧手机号相同');
// return;
// }
// return true;
// },
// // 粘贴
// setCode() {
// // 获取粘贴板内容
// // 小程序平台
// //#ifdef MP-WEIXIN
// var _this = this
// uni.getClipboardData({
// success (res) {
// _this.smsCode = res.data;
// }
// });
// //#endif
// // 非小程序平台
// //#ifndef MP-WEIXIN
// this.getClipboardContents()
// //#endif
// },
// // 非小程序平台粘贴
// async getClipboardContents() {
// try {
// const text = await navigator.clipboard.readText();
// this.smsCode = text;
// } catch (err) {
// console.error('Failed to read clipboard contents: ', err);
// }
// },
// /**
// * 验证手机号格式
// * @param {string} phone 手机号
// */
// verifyPhone(phone) {
// const phoneExg = /^1\d{10}$/;
// return phoneExg.test(phone);
// },
// /**
// * 验证账号/密码 格式
// * @param {string} account 账号
// */
// verifyLoginname(account) {
// const accountExg = /^[a-zA-Z0-9._-]{2,20}$/;
// return accountExg.test(account);
// },
// // 微信登录
// handleWxLogin() {
// const origin = 'https://test.tall.wiki/pt-mui'; // 测试
// const appid = 'wxd1842e073e0e6d91';
// const state = 'wx_web';
// const href = 'https://open.weixin.qq.com/connect/qrconnect';
// // eslint-disable-next-line
// window.location.href =
// `${href}?appid=${appid}&redirect_uri=${origin}&response_type=code&scope=snsapi_login&state=${state}#wechat_redirect`;
// },
// // }
// };

123
hooks/user/userMixin.js

@ -0,0 +1,123 @@
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import clipboard from "@/common/js/dc-clipboard/clipboard.js"
export default function userMixin() {
const store = useStore();
const user = computed(() => store.state.user.user);
const rules = ref({
phone: [{
required: true,
message: '请输入手机号',
trigger: ['change', 'blur'],
},
{
validator: (rule, value, callback) => {
// 调用uView自带的js验证规则,详见:https://www.uviewui.com/js/test.html
return this.$u.test.mobile(value);
},
message: '手机号码不正确',
// 触发器可以同时用blur和change,二者之间用英文逗号隔开
trigger: ['change', 'blur'],
}
],
verificationCodeValue: [{
required: true,
message: '请输入图形验证码',
trigger: ['change', 'blur'],
},
{
type: 'number',
message: '图形验证码只能为数字',
trigger: ['change', 'blur'],
}
],
smsCode: [{
required: true,
message: '请输入验证码',
trigger: ['change', 'blur'],
},
{
type: 'number',
message: '验证码只能为数字',
trigger: ['change', 'blur'],
}
],
account: [{
required: true,
message: '请输入用户名',
trigger: ['change', 'blur'],
},
{
min: 2,
max: 20,
message: '用户名长度在2到20个字符',
trigger: ['change', 'blur'],
},
{
pattern: /^[a-zA-Z0-9._-]{2,20}$/,
message: '请输入2-20位字母、数字、汉字或字符"_ - ."',
trigger: ['change', 'blur'],
}
],
password: [{
required: true,
message: '请输入密码',
trigger: ['change', 'blur'],
},
{
min: 6,
max: 20,
message: '密码长度在6到20个字符',
trigger: ['change', 'blur'],
},
{
// 正则不能含有两边的引号
pattern: /^[a-zA-Z0-9._-]{6,20}$/,
message: '请输入6-20位字母、数字、汉字或字符"_ - ."',
trigger: ['change', 'blur'],
}
],
});
const errorType = ['message'];
const labelPosition = 'left';
const border = false;
const smsCode = ref(''); // 短信验证码
const showInterval = ref(false);
const interval = ref(120);
const codeTimer = ref(null);
const showPaste = ref(false);
// 获取图形验证码
async function getImageCode() {
console.log('5555')
uni.$ui.showLoading();
try {
const data = await uni.$u.api.getImageCode();
const { imageBase64, verificationCodeId } = data;
imageBase64 = imageBase64 || '';
verificationCodeId = verificationCodeId || '';
uni.$ui.hideLoading();
} catch (error) {
uni.$ui.hideLoading();
uni.$ui.showToast(error);
}
}
return {
rules,
errorType,
labelPosition,
border,
getImageCode,
// hasvalue,
// getCode,
// getCodeInterval,
// checkRules,
// setCode,
// getClipboardContents,
// verifyPhone,
// verifyLoginname,
// handleWxLogin
}
}

71
main.js

@ -1,23 +1,58 @@
import App from './App'
import App from './App';
import cache from '@/utils/cache.js';
import cacheAndRequest from '@/utils/cacheAndRequest.js';
import { createSSRApp } from 'vue';
import { setupDayjs } from '@/utils/dayjs.js';
import { setupHttp } from '@/utils/request.js';
import { setupProject } from '@/apis/project.js';
import { setupRole } from '@/apis/role.js';
import { setupTall } from '@/apis/tall.js';
import { setupTask } from '@/apis/task.js';
import { setupWbs } from '@/apis/wbs.js';
import storage from '@/utils/storage.js';
import store from './store';
import task from '@/utils/task.js';
import time from '@/utils/time.js';
import timeConfig from '@/config/time';
import taskConfig from '@/config/task';
import uView from './uni_modules/vk-uview-ui'; // 引入 uView UI
import ui from '@/utils/ui.js';
import upload from '@/utils/upload.js';
// #ifndef VUE3
import Vue from 'vue'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
app.use(uView) // 使用 uView UI
const app = createSSRApp(App);
app.config.globalProperties.$cache = cache;
app.config.globalProperties.$catchReq = cacheAndRequest;
app.config.globalProperties.$storage = storage;
app.config.globalProperties.$time = time;
app.config.globalProperties.$ui = ui;
app.config.globalProperties.$upload = upload;
app.config.globalProperties.$task = task;
app.config.globalProperties.$timeConfig = timeConfig;
app.config.globalProperties.$taskConfig = taskConfig;
uni.$cache = cache;
uni.$catchReq = cacheAndRequest;
uni.$storage = storage;
uni.$time = time;
uni.$ui = ui;
uni.$upload = upload;
uni.$task = task;
uni.$timeConfig = timeConfig;
uni.$taskConfig = taskConfig;
setupDayjs(app);
app.use(uView); // 使用 uView UI
app.use(store);
setupHttp(app);
setupTall(app);
setupProject(app);
setupRole(app);
setupTask(app);
setupWbs(app);
return {
app
}
app,
};
}
// #endif

115
package.json

@ -1,54 +1,61 @@
{
"name": "tall-4",
"version": "1.0.0",
"description": "",
"main": "main.js",
"dependencies": {},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.8.1",
"@typescript-eslint/parser": "^5.8.1",
"commitizen": "^4.2.4",
"commitlint": "^16.0.1",
"conventional-changelog": "^3.1.25",
"conventional-changelog-cli": "^2.2.2",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.2.0",
"husky": "^7.0.4",
"lint-staged": "^12.1.4",
"prettier": "^2.5.1",
"right-pad": "^1.0.1",
"vue-cli-plugin-commitlint": "^1.0.12"
},
"browserslist": [
"Android >= 4",
"ios >= 8"
],
"config": {
"commitizen": {
"path": "./node_modules/vue-cli-plugin-commitlint/lib/cz"
}
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,json,css,vue}": [
"eslint --fix",
"git add"
],
"*.js": "eslint --cache --fix"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"cz": "npm run log && git add . && git cz",
"log": "conventional-changelog --config ./node_modules/vue-cli-plugin-commitlint/lib/log -i CHANGELOG.md -s -r 0"
},
"author": "",
"license": "ISC"
}
{
"name": "tall-4",
"version": "1.0.0",
"description": "",
"main": "main.js",
"dependencies": {
"axios": "^0.24.0",
"dayjs": "^1.10.7",
"lodash": "^4.17.21",
"qs": "^6.10.2"
},
"devDependencies": {
"@dcloudio/uni-app": "^3.0.0-alpha-3000020210521001",
"@typescript-eslint/eslint-plugin": "^5.8.1",
"@typescript-eslint/parser": "^5.8.1",
"commitizen": "^4.2.4",
"commitlint": "^16.0.1",
"conventional-changelog": "^3.1.25",
"conventional-changelog-cli": "^2.2.2",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.2.0",
"husky": "^7.0.4",
"lint-staged": "^12.1.4",
"prettier": "^2.5.1",
"right-pad": "^1.0.1",
"vue-cli-plugin-commitlint": "^1.0.12"
},
"browserslist": [
"Android >= 4",
"ios >= 8"
],
"config": {
"commitizen": {
"path": "./node_modules/vue-cli-plugin-commitlint/lib/cz"
}
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,json,css,vue}": [
"eslint --fix",
"git add"
],
"*.js": "eslint --cache --fix"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"cz": "npm run log && git add . && git cz",
"fix": "eslint --fix",
"log": "conventional-changelog --config ./node_modules/vue-cli-plugin-commitlint/lib/log -i CHANGELOG.md -s -r 0"
},
"author": "",
"license": "ISC"
}

26
pages.json

@ -3,14 +3,36 @@
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
"navigationBarText": "TALL"
}
},
{
"path": "pages/project/project",
"style": {
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/user/accountLogin",
"style": {
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarTitleText": "TALL",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"easycom": {
"autoscan": true,
"custom": {
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue",
"^p-(.*)": "@/plugins/p-$1/p-$1.vue",
"theme": "@/components/Theme/Theme.vue"
}
}
}

171
pages/index/index.vue

@ -1,66 +1,135 @@
<template>
<view :style="{ height: height }" class="flex flex-col overflow-hidden u-font-14">
<!-- 标题栏 -->
<Title />
<view class="container flex flex-col flex-1 mx-auto overflow-hidden bg-gray-100">
<!-- 角色栏 -->
<Roles />
<!-- <view class="flex flex-col h-full bg-gray-50" @click="openAuth"> -->
<view class="flex flex-col h-full bg-gray-50">
<view class="relative" @touchmove="onMove">
<!-- 日历 -->
<Calendar @selected-change="onDateChange" :show-back="true" ref="calendar" @handleFindPoint="handleFindPoint" />
<!-- 上传 导入wbs -->
<Upload @success="onUploadSuccess" @error="onUploadError" />
</view>
<!-- 日常任务面板 -->
<Globals />
<!-- 项目列表 -->
<Projects @getProjects="getProjects" class="flex-1 overflow-y-auto" />
<!-- 定期任务面板 -->
<TimeLine @getTasks="getTasks" class="flex-1 overflow-hidden" ref="timeLine" />
</view>
<!-- 全局提示框 -->
<u-top-tips ref="uTips"></u-top-tips>
</view>
</template>
<script setup>
import {
ref, onMounted
} from 'vue';
import Navbar from '@/components/Title/Title.vue';
import Roles from '@/components/Roles/Roles.vue';
import Globals from '@/components/Globals/Globals.vue';
import TimeLine from '@/components/TimeLine/TimeLine.vue';
import { reactive, computed, watchEffect, ref } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
const store = useStore();
const token = computed(() => store.state.user.token);
const uTips = ref(null);
const data = reactive({
calendar: null,
days: [],
});
// token
watchEffect(() => {
if (!token.value) return;
if (token.value) {
getProjects();
handleFindPoint();
}
});
let height = ref(null);
//
function getProjects(start = dayjs().startOf('day').valueOf(), end = dayjs().endOf('day').valueOf()) {
// const data = await this.$u.api.getProjects(start, end);
uni.$catchReq.getProjects(start, end, (err, data) => {
if (err) {
console.error('err: ', err);
} else {
data.forEach(item => {
item.show = false;
});
store.commit('project/setProjects', data);
}
});
}
onMounted(() => {
const system = uni.getSystemInfoSync();
height.value = system.windowHeight + 'px';
});
async function handleFindPoint(start, end) {
try {
const startTime = start || dayjs().startOf('month').valueOf();
const endTime = end || dayjs().endOf('month').valueOf();
const res = await uni.$u.api.findRedPoint(startTime, endTime);
store.commit('project/setDotList', res);
} catch (error) {
console.log('error: ', error);
}
}
function getTasks() {
//
const onDateChange = event => {
const day = dayjs(event.fullDate);
const start = day.startOf('date').valueOf();
const end = day.endOf('date').valueOf();
getProjects(start, end);
};
}
//
const onUploadSuccess = () => {
uTips.show({
title: '导入成功,即将打开新项目',
type: 'success',
duration: '3000',
});
};
//
const onUploadError = error => {
uTips.show({
title: error || '导入失败',
type: 'error',
duration: '6000',
});
};
// /
function onMove(event) {
const y = event.changedTouches[0].pageY;
if (y - prevY > 0) {
// weekMode=true weekMode=false
data.value.calendar.weekMode && (data.value.calendar.weekMode = false);
} else if (y - prevY < 0) {
// weekMode=false weekMode=true
!data.value.calendar.weekMode && (data.value.calendar.weekMode = true);
}
prevY = y;
data.value.calendar.initDate();
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>

142
pages/project/project.vue

@ -0,0 +1,142 @@
<template>
<view :style="{ height: height }" class="flex flex-col overflow-hidden u-font-14">
<!-- 标题栏 -->
<Title />
<view class="container flex flex-col flex-1 mx-auto overflow-hidden bg-gray-100">
<!-- 角色栏 -->
<Roles />
<!-- 日常任务面板 -->
<Globals />
<!-- 定期任务面板 -->
<TimeLine @getTasks="getTasks" class="flex-1 overflow-hidden" ref="timeLine" />
<!-- TODO: DEBUG: -->
<u-button @click="$store.commit('setTheme', 'theme-test')">测试切换主题</u-button>
</view>
</view>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
import { useStore } from 'vuex';
import useInit from '@/hooks/project/useInit';
import useGetTasks from '@/hooks/project/useGetTasks';
const initHook = useInit();
const getTasksHook = useGetTasks();
const store = useStore();
const roleId = computed(() => store.state.role.roleId);
const timeNode = computed(() => store.state.task.timeNode);
const timeUnit = computed(() => store.state.task.timeUnit);
const projectId = computed(() => store.getters['project/projectId']);
const userId = computed(() => store.getters['user/userId']);
const newProjectInfo = computed(() => store.state.task.newProjectInfo);
const showScrollTo = computed(() => store.state.task.showScrollTo);
const height = ref(null);
const timeLine = ref(null);
onMounted(() => {
const system = uni.getSystemInfoSync();
height.value = `${system.windowHeight}px`;
});
//
function getGlobalData() {
const param = {
roleId: roleId.value,
timeNode: timeNode.value,
timeUnit: timeUnit.value,
projectId: projectId.value,
};
store.dispatch('task/getGlobal', param);
}
//
function clearTasksData() {
//
store.commit('task/setPermanents', []);
store.commit('task/setDailyTasks', []);
//
store.commit('task/clearTasks');
//
//
store.commit('task/clearEndFlag');
}
/**
* 当时间基准点发生变化时
* 重新根据时间和角色查询普通日常任务
* 永久日常任务不发生 改变
*/
watch(timeNode, newValue => {
if (newValue && roleId.value) {
console.log('当时间基准点发生变化时');
clearTasksData();
getGlobalData(); //
// initPlanTasks(); //
getTasksHook.initPlanTasks(); //
//
let timer = null;
timer = setInterval(() => {
if (showScrollTo.value) {
clearInterval(timer);
timeLine.value.setScrollPosition();
}
}, 500);
}
});
/**
* 当角色发生变化时
* 重新查询永久日常任务和普通日常任务
* 注意: 切换角色后 重新设置了时间基准点 时间基准点一定会变
* 所以监听时间基准点获取 可变日常任务即可 这里不用获取 避免重复获取
*/
watch(roleId, newValue => {
if (newValue) {
console.log('当角色发生变化时', newValue);
store.commit('task/setTimeNode', Date.now());
//
const params = {
roleId: newValue,
projectId: projectId.value,
};
store.dispatch('task/getPermanent', params);
}
});
/**
* 当时间基准点发生变化时
* 重新根据时间和角色查询普通日常任务
* 永久日常任务不发生改变
*/
watch(newProjectInfo, newValue => {
console.log('当时间基准点发生变化时');
if (newValue && newValue.value.projectId && newValue.value.url) {
uni.$u.route('/', {
u: userId.value,
p: newValue.value.projectId,
url: newValue.value.url,
});
clearTasksData();
store.commit('role/setRoleId', '');
const options = uni.$route.query;
initHook.init(options);
}
});
function getTasks(params) {
getTasksHook.initPlanTasks(params); //
}
</script>
<style lang="scss" scoped>
.border-b {
border-bottom: 1px solid #e4e7ed;
}
</style>

96
pages/user/accountLogin.vue

@ -0,0 +1,96 @@
<template>
<view class="u-p-l-50 u-p-r-50 u-p-t-30">
<u-form :model="model" ref="loginForm" :rules="mixinInit.rules" :error-type="mixinInit.errorType">
<u-form-item :label-position="mixinInit.labelPosition" label="用户名" prop="account" label-width="150">
<u-input :border="mixinInit.border" placeholder="请输入用户名" v-model="model.account" type="text"></u-input>
</u-form-item>
<u-form-item :label-position="mixinInit.labelPosition" label="密码" prop="password" label-width="150">
<u-input :password-icon="true" :border="mixinInit.border" type="password" v-model="model.password" placeholder="请输入密码">
</u-input>
</u-form-item>
<view class="flex flex-nowrap">
<view class="flex-sub"></view>
<view class="u-m-t-30 u-font-12 text-gray-400" @click="openPage('/pages/user/forgetPassword')">忘记密码</view>
</view>
</u-form>
<view class="u-m-t-50">
<u-button @click="submit" type="primary">立即登录</u-button>
</view>
<view class="flex justify-between">
<view class="u-m-t-30" style="color: #2885ED;" @click="openPage('/pages/user/rigister')"> 新用户注册</view>
<view class="u-m-t-30" style="color: #2885ED;" @click="openPage('/pages/user/login')">手机号登录 </view>
</view>
<view style="margin-top: 200rpx;text-align: center; color: #999999;font-size: 35rpx;">
快速登录
</view>
<view style="text-align: center; margin-top: 20rpx;">
<image src="/common/img/weixinIcon.png" mode="" style="width: 85rpx;height: 85rpx;"></image>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import userMixin from '@/hooks/user/userMixin'
const store = useStore();
const mixinInit = userMixin();
const userInfo = uni.$storage.getStorageSync('user');
const user = ref({});
const loginForm = ref(null);
const model = ref({
account: '',
password: ''
})
if (userInfo) {
user.value = JSON.parse(userInfo);
}
const submit = () => {
loginForm.value.validate(data => {
console.log(data);
});
}
async function login() {
try {
uni.$ui.showLoading();
if (account.value === user.value.account) {
uni.$ui.showToast('当前账户已登录');
} else {
const params = ref({
client: 1,
data: {
identifier: account.value,
credential: password.value,
},
type: 3,
});
let res = await uni.$u.api.signin(params.value);
store.commit('user/setToken', res.token);
store.commit('user/setUser', res);
uni.$storage.setStorageSync('anyringToken', res.token || '');
uni.$storage.setStorageSync('user', JSON.stringify(res));
}
uni.navigateTo({
url: '/pages/index/index'
});
uni.$ui.hideLoading();
} catch (error) {
uni.$ui.hideLoading();
uni.$ui.showToast(error);
}
}
</script>
<style>
</style>

8
pages/user/login.vue

@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>

266
pages/user/mixin.js

@ -0,0 +1,266 @@
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import clipboard from "@/common/js/dc-clipboard/clipboard.js"
export default function mixinInit {
const store = useStore();
const user = computed(() => store.state.user.user);
const rules = ref({
phone: [
{
required: true,
message: '请输入手机号',
trigger: ['change','blur'],
},
{
validator: (rule, value, callback) => {
// 调用uView自带的js验证规则,详见:https://www.uviewui.com/js/test.html
return this.$u.test.mobile(value);
},
message: '手机号码不正确',
// 触发器可以同时用blur和change,二者之间用英文逗号隔开
trigger: ['change','blur'],
}
],
verificationCodeValue: [
{
required: true,
message: '请输入图形验证码',
trigger: ['change','blur'],
},
{
type: 'number',
message: '图形验证码只能为数字',
trigger: ['change','blur'],
}
],
smsCode: [
{
required: true,
message: '请输入验证码',
trigger: ['change','blur'],
},
{
type: 'number',
message: '验证码只能为数字',
trigger: ['change','blur'],
}
],
account: [
{
required: true,
message: '请输入用户名',
trigger: ['change','blur'],
},
{
min: 2,
max: 20,
message: '用户名长度在2到20个字符',
trigger: ['change','blur'],
},
{
pattern: /^[a-zA-Z0-9._-]{2,20}$/,
message: '请输入2-20位字母、数字、汉字或字符"_ - ."',
trigger: ['change','blur'],
}
],
password: [
{
required: true,
message: '请输入密码',
trigger: ['change','blur'],
},
{
min: 6,
max: 20,
message: '密码长度在6到20个字符',
trigger: ['change','blur'],
},
{
// 正则不能含有两边的引号
pattern: /^[a-zA-Z0-9._-]{6,20}$/,
message: '请输入6-20位字母、数字、汉字或字符"_ - ."',
trigger: ['change','blur'],
}
],
});
const errorType = ref(['message']);
const labelPosition = ref('left');
const border = ref(false);
const smsCode = ref(''); // 短信验证码
const showInterval = ref(false);
const interval = ref(120);
const codeTimer = ref(null);
const showPaste = ref(false);
return {
errorType,
// getImageCode,
// hasvalue,
// getCode,
// getCodeInterval,
// checkRules,
// setCode,
// getClipboardContents,
// verifyPhone,
// verifyLoginname,
// handleWxLogin
}
}
// const mixin = {
// computed: mapState('user', ['user']),
// onReady() {
// this.$refs.uForm.setRules(this.rules);
// },
// methods: {
// ...mapActions('user', ['sendCode']),
// 获取图形验证码
// async getImageCode() {
// this.$util.showLoading();
// try {
// const data = await uni.$u.api.getImageCode();
// const { imageBase64, verificationCodeId } = data;
// this.imageBase64 = imageBase64 || '';
// this.verificationCodeId = verificationCodeId || '';
// uni.hideLoading();
// } catch (error) {
// uni.hideLoading();
// uni.$ui.showToast(error);
// }
// },
// //有图片验证码的值
// hasvalue() {
// if(this.model.smsCode || this.model.showPaste) return
// if (!this.verifyPhone(this.model.phone)) {
// uni.$ui.showToast('请输入正确的手机号');
// return;
// }
// if (!this.model.verificationCodeValue) {
// uni.$ui.showToast('请输入图形验证码');
// return;
// }
// this.getCode();
// },
// // 获取验证码
// async getCode() {
// try {
// const { phone, verificationCodeValue } = this.model;
// const { verificationCodeId } = this;
// if (!verificationCodeId || !verificationCodeValue) {
// uni.$ui.showToast('缺少图形验证码参数');
// return;
// }
// const params = {
// phone,
// verificationCodeId,
// verificationCodeValue,
// };
// const date = await store.dispatch('user/sendCode', params);
// getCodeInterval();
// showPaste.value = true;
// } catch (err) {
// throw err;
// }
// },
// // 获取验证码倒计时
// getCodeInterval() {
// this.showInterval = true;
// this.codeTimer = setInterval(() => {
// if (this.interval === 0) {
// clearInterval(this.codeTimer);
// this.codeTimer = null;
// this.showInterval = false;
// this.interval = 120;
// return;
// }
// this.interval = this.interval - 1;
// }, 1000);
// },
// // 验证信息
// checkRules() {
// // const { smsCode, phone, user } = this;
// if (!this.verifyPhone(phone.value)) {
// uni.$ui.showToast('请输入正确的手机号');
// return false;
// }
// if (!smsCode.value) {
// uni.$ui.showToast('验证码无效');
// return false;
// }
// if (phone.value === user.value.phone) {
// uni.$ui.showToast('新手机号不能与旧手机号相同');
// return;
// }
// return true;
// },
// // 粘贴
// setCode() {
// // 获取粘贴板内容
// // 小程序平台
// //#ifdef MP-WEIXIN
// var _this = this
// uni.getClipboardData({
// success (res) {
// _this.smsCode = res.data;
// }
// });
// //#endif
// // 非小程序平台
// //#ifndef MP-WEIXIN
// this.getClipboardContents()
// //#endif
// },
// // 非小程序平台粘贴
// async getClipboardContents() {
// try {
// const text = await navigator.clipboard.readText();
// this.smsCode = text;
// } catch (err) {
// console.error('Failed to read clipboard contents: ', err);
// }
// },
// /**
// * 验证手机号格式
// * @param {string} phone 手机号
// */
// verifyPhone(phone) {
// const phoneExg = /^1\d{10}$/;
// return phoneExg.test(phone);
// },
// /**
// * 验证账号/密码 格式
// * @param {string} account 账号
// */
// verifyLoginname(account) {
// const accountExg = /^[a-zA-Z0-9._-]{2,20}$/;
// return accountExg.test(account);
// },
// // 微信登录
// handleWxLogin() {
// const origin = 'https://test.tall.wiki/pt-mui'; // 测试
// const appid = 'wxd1842e073e0e6d91';
// const state = 'wx_web';
// const href = 'https://open.weixin.qq.com/connect/qrconnect';
// // eslint-disable-next-line
// window.location.href =
// `${href}?appid=${appid}&redirect_uri=${origin}&response_type=code&scope=snsapi_login&state=${state}#wechat_redirect`;
// },
// // }
// };

9
plugins/p-deliver/p-deliver.vue

@ -0,0 +1,9 @@
<template>
<view class="deliver-container">p-deliver</view>
</template>
<script setup>
</script>
<style lang="scss"></style>

10
plugins/p-task-title/p-task-title.vue

@ -0,0 +1,10 @@
<template>
<!-- 任务名插件 -->
<theme>
<view>{{ task.name }}</view>
</theme>
</template>
<script setup>
defineProps({ task: { type: Object, default: () => {} } });
</script>

3
store/db/actions.js

@ -1,3 +0,0 @@
const actions = {};
export default actions;

3
store/db/getters.js

@ -1,3 +0,0 @@
const getters = {};
export default getters;

12
store/db/index.js

@ -1,12 +0,0 @@
import state from './state';
import getters from './getters';
import mutations from './mutations';
import actions from './actions';
export default {
namespaced: true,
state,
getters,
mutations,
actions,
};

3
store/db/mutations.js

@ -1,3 +0,0 @@
const mutations = {};
export default mutations;

7
store/db/state.js

@ -1,7 +0,0 @@
const state = {
db: null, // indexedDB对象
name: 'TALL_indexedDB',
version: 1,
};
export default state;

42
store/index.js

@ -1,16 +1,16 @@
import Vue from 'vue';
import Vuex from 'vuex';
import messages from './messages/index';
import project from './project/index';
import role from './role/index';
import socket from './socket/index';
import task from './task/index';
import user from './user/index';
import { createStore } from 'vuex';
import project from './project/index.js';
import role from './role/index.js';
import socket from './socket/index.js';
import task from './task/index.js';
import user from './user/index.js';
// 不属于具体模块的 应用级的 store内容
const state = {
theme: 'theme-default',
networkConnected: true, // 网络是否连接
forceUseStorage: true, // 强制启用storage
systemInfo: null, // 系统设备信息
};
const getters = {
@ -30,7 +30,29 @@ const mutations = {
setNetworkConnected(state, networkConnected) {
state.networkConnected = networkConnected;
},
/**
* 设置系统信息的数据
* @param {object} state
* @param {object | null} data 获取到的数据
*/
setSystemInfo(state, data) {
state.systemInfo = data;
},
/**
* 设置主题
* @param {object} state
* @param {string} theme 主题名称 默认theme-default
*/
setTheme(state, theme) {
state.theme = theme || 'theme-default';
},
};
Vue.use(Vuex);
export default new Vuex.Store({ state, getters, mutations, modules: { user, messages, socket, project, role, task } });
export default createStore({
state,
getters,
mutations,
modules: { user, socket, project, role, task },
});

3
store/messages/getters.js

@ -1,3 +0,0 @@
const getters = {};
export default getters;

4
store/messages/index.js

@ -1,4 +0,0 @@
import state from './state';
import mutations from './mutations';
export default { namespaced: true, state, mutations };

85
store/messages/mutations.js

@ -1,85 +0,0 @@
import storage from '@/utils/storage';
const { setStorageSync, getStorageSync, removeStorageSync } = storage;
const mutations = {
/**
* 初始化消息栈
* @param {object} state
* @param {string} type
* type:
* syncMessages 同步消息栈
* faultMessages 故障消息 未处理消息栈
* faults 所有的故障消息栈
*/
messagesInit(state, type) {
const messages = getStorageSync(type) ? JSON.parse(getStorageSync(type)) : [];
state[type] = messages;
},
/**
* 将新 消息添加到 消息栈 最前边
* @param { object } state
* @param { object } data
* data: message, type
* message 消息对象
* type:
* syncMessages 同步消息栈
* faultMessages 故障消息 未处理消息栈
* faults 所有的故障消息栈
* game 游戏的消息
*
* cache: boolean true 本地存储 false 不存储
*/
messagesAdd(state, data) {
const messages = state[data.type];
if (messages.length > 0) {
const result = messages.find(msg => msg.id === data.message.id);
if (result) return;
}
messages.unshift(data.message);
// eslint-disable-next-line no-param-reassign
state[data.type] = messages;
if (data.cache) {
setStorageSync(data.type, JSON.stringify(messages));
}
},
/**
* 通过消息id移除指定 同步 消息
* @param { object } state
* @param { object } data
* data: messageId, type
* messageId: 要移除的消息的messageId
* type:
* syncMessages 同步消息栈
* faultMessages 故障消息 未处理消息栈
* faults 所有的故障消息栈
* cache: boolean true 本地存储 false 不存储
*/
messagesRemoveById(state, data) {
const messages = state[data.type];
const index = messages.findIndex(msg => msg.id === data.messageId);
if (index < 0) return;
messages.splice(index, 1);
// eslint-disable-next-line no-param-reassign
state[data.type] = messages;
if (data.cache) {
setStorageSync(data.type, JSON.stringify(messages));
}
},
/**
* 清除指定type的消息
* @param {any} state
* @param {object} data
* data: type, cache
*/
messagesClear(state, data) {
state[data.type] = [];
if (data.cache) {
removeStorageSync(data.type);
}
},
};
export default mutations;

8
store/messages/state.js

@ -1,8 +0,0 @@
const state = {
syncMessages: [], // 同步消息
faultMessages: [], // 新收到的未处理的 故障消息
faults: [], // 所有的故障消息
game: [], // 游戏的消息
};
export default state;

1
store/project/getters.js

@ -4,7 +4,6 @@ const getters = {
* @param {object} project
*/
projectId({ project }) {
uni.$t.storage.setStorageSync('projectId', project.id);
return project.id;
},
};

4
store/project/index.js

@ -1,7 +1,7 @@
import state from './state';
import actions from './actions';
import getters from './getters';
import mutations from './mutations';
import actions from './actions';
import state from './state';
export default {
namespaced: true,

19
store/project/mutations.js

@ -12,6 +12,25 @@ const mutations = {
}
},
/**
* 设置子项目收缩展开
* @param { object } state
* @param { object } options options:{ index,show }
*/
setProjectItemShow(state, options) {
if (options.show) {
for (var i = 0; i < state.projects.length; i++) {
if (i === options.index) {
state.projects[i].show = true;
} else {
state.projects[i].show = false;
}
}
} else {
state.projects[options.index].show = false;
}
},
/**
* 设置当前项目信息
* @param { object } state

2
store/role/actions.js

@ -9,7 +9,7 @@ const actions = {
const data = await uni.$u.api.queryChecker(params);
commit('setMembers', data);
} catch (error) {
uni.$t.ui.showToast(error.msg || '成员查询失败');
uni.$ui.showToast(error.msg || '成员查询失败');
}
},
};

363
store/socket/actions.js

@ -1,154 +1,211 @@
const WS_BASE_URL = process.env.VUE_APP_MSG_URL;
let prevTime = 0;
let socketMsgQueue = []; // socket消息队列
let sendHeartTimer = null;
const actions = {
// 初始化socket
initSocket({ commit, dispatch, state, rootState }) {
if (state.lockSocket) return;
const { token } = rootState.user;
if (!token) return;
commit('setLockSocket', true);
commit('setSocket', uni.connectSocket({ url: WS_BASE_URL, complete: () => {} }));
dispatch('onSocketOpen');
dispatch('onSocketMessage');
dispatch('onSocketClose');
state.socket.onError(errMsg => console.error(errMsg));
commit('setLockSocket', false);
},
// 监听ws打开
onSocketOpen({ dispatch, commit, state }) {
// eslint-disable-next-line no-unused-vars
state.socket.onOpen(res => {
// console.log('ws open: ', res);
commit('setConnected', true);
prevTime = Date.now();
// this.auth();
dispatch('auth');
for (let i = 0; i < socketMsgQueue.length; i++) {
dispatch('sendSocketMessage', socketMsgQueue[i]);
}
socketMsgQueue = [];
});
},
// 监听收到的ws消息
onSocketMessage({ dispatch, state }) {
state.socket.onMessage(res => {
// console.log('收到消息:', res);
prevTime = Date.now();
if (!res || !res.data || !JSON.parse(res.data)) return;
const resData = JSON.parse(res.data);
const { messageSet, ackId } = resData;
// 处理消息体对象
messageSet.forEach(item => dispatch('handleMessagesData', item));
ackId && dispatch('sendSocketMessage', { type: 'Ack', data: { ackId } });
});
},
/**
* 处理收到的消息内容
* @param {object} item 单个消息体对象
*/
handleMessagesData({ dispatch, commit }, item) {
const data = JSON.parse(item.data);
switch (data.type) {
case 'Sync': // 开始某个节点
commit('messages/messagesAdd', { message: data, type: 'syncMessages' }, { root: true });
break;
case 'taskStatus': // 任务状态修改相关消息
commit('task/setTaskStatus', data.data, { root: true });
break;
case 'switchoverProject': // 打开新项目消息
commit('task/setNewProjectInfo', data.data, { root: true });
break;
// case 'Chrome': // !收到开始游戏的消息
// console.log('handleMessagesData', data);
// // @ts-ignore
// util.openGameApp({
// type: data.data.type,
// projectId: data.data.projectId,
// id: data.data.recordId,
// token: rootState.user.token,
// });
// break;
// case 'Deliver': // 交付物相关消息
// commit('messages/messagesAdd', { type: 'checkMessages', message: data }, { root: true });
// break;
case 'ChannelStatus':
dispatch('handleAuthMessage', data);
break;
// case 'switchoverProject': // 康复相关消息
// dispatch('home/getProjectById', data.data.projectId, { root: true });
// break;
// case 'startDrill': // 康复开始训练相关消息
// console.log('setStartDrillInfo', data.data);
// commit('home/setStartDrillMessages', data.data, { root: true });
// break;
default:
break;
}
},
// 发送消息
sendSocketMessage({ state }, data) {
if (state.connected) {
const msg = JSON.stringify({ toDomain: 'Server', data: JSON.stringify(data) });
state.socket.send({ data: msg });
} else {
socketMsgQueue.push(data);
}
},
// 监听关闭事件
onSocketClose({ dispatch, commit, state }) {
// console.log('onSocketClose');
state.socket.onClose(() => {
commit('setConnected', false);
if (sendHeartTimer) clearInterval(sendHeartTimer);
setTimeout(() => {
dispatch('initSocket');
}, 300);
});
},
// websocket发送channelId进行认证
auth({ dispatch, rootState }) {
const { token } = rootState.user;
if (!token) return;
const data = { type: 'Auth', data: { token } };
dispatch('sendSocketMessage', data);
},
// 心跳检测
sendHeart({ dispatch, state }) {
if (sendHeartTimer) clearInterval(sendHeartTimer);
sendHeartTimer = setInterval(() => {
if (Date.now() - prevTime >= 15000) {
dispatch('sendSocketMessage', { type: 'Ping' });
if (Date.now() - prevTime >= 20000) {
state.socket.close();
}
}
}, 5000);
},
/**
* 处理auth认证返回的ChannelStatus消息
* @param {object} data 消息内容对象
*/
handleAuthMessage({ commit, dispatch }, data) {
if (data.data.authed) {
dispatch('sendHeart');
} else {
uni.$u.toast('消息系统认证失败, 请退出重新登录');
uni.$t.removeStorageSync('anyringToken');
commit('setSocket', null);
}
},
};
const WS_BASE_URL = 'wss://test.tall.wiki/websocket/message/v4.0/ws'; // 测试
// const WS_BASE_URL = 'wss://www.tall.wiki/websocket/message/v4.0/ws'; // 生产
let prevTime = 0;
let socketMsgQueue = []; // socket消息队列
let sendHeartTimer = null;
const actions = {
// 初始化socket
initSocket({ commit, dispatch, state, rootState }) {
if (state.lockSocket) return;
const {
token
} = rootState.user;
if (!token) return;
commit('setLockSocket', true);
commit('setSocket', uni.connectSocket({
url: WS_BASE_URL,
complete: () => {}
}));
dispatch('onSocketOpen');
dispatch('onSocketMessage');
dispatch('onSocketClose');
state.socket.onError(errMsg => console.error(errMsg));
commit('setLockSocket', false);
},
// 监听ws打开
onSocketOpen({
dispatch,
commit,
state
}) {
// eslint-disable-next-line no-unused-vars
state.socket.onOpen(res => {
// console.log('ws open: ', res);
commit('setConnected', true);
prevTime = Date.now();
// this.auth();
dispatch('auth');
for (let i = 0; i < socketMsgQueue.length; i++) {
dispatch('sendSocketMessage', socketMsgQueue[i]);
}
socketMsgQueue = [];
});
},
// 监听收到的ws消息
onSocketMessage({
dispatch,
state
}) {
state.socket.onMessage(res => {
// console.log('收到消息:', res);
prevTime = Date.now();
if (!res || !res.data || !JSON.parse(res.data)) return;
const resData = JSON.parse(res.data);
const {
messageSet,
ackId
} = resData;
// 处理消息体对象
messageSet.forEach(item => dispatch('handleMessagesData', item));
ackId && dispatch('sendSocketMessage', {
type: 'Ack',
data: {
ackId
}
});
});
},
/**
* 处理收到的消息内容
* @param {object} item 单个消息体对象
*/
handleMessagesData({
dispatch,
commit
}, item) {
const data = JSON.parse(item.data);
switch (data.type) {
case 'Sync': // 开始某个节点
commit('messages/messagesAdd', {
message: data,
type: 'syncMessages'
}, {
root: true
});
break;
case 'taskStatus': // 任务状态修改相关消息
commit('task/setTaskStatus', data.data, {
root: true
});
break;
// case 'Chrome': // !收到开始游戏的消息
// console.log('handleMessagesData', data);
// // @ts-ignore
// util.openGameApp({
// type: data.data.type,
// projectId: data.data.projectId,
// id: data.data.recordId,
// token: rootState.user.token,
// });
// break;
// case 'Deliver': // 交付物相关消息
// commit('messages/messagesAdd', { type: 'checkMessages', message: data }, { root: true });
// break;
case 'ChannelStatus':
dispatch('handleAuthMessage', data);
break;
// case 'switchoverProject': // 康复相关消息
// dispatch('home/getProjectById', data.data.projectId, { root: true });
// break;
// case 'startDrill': // 康复开始训练相关消息
// console.log('setStartDrillInfo', data.data);
// commit('home/setStartDrillMessages', data.data, { root: true });
// break;
default:
break;
}
},
// 发送消息
sendSocketMessage({
state
}, data) {
if (state.connected) {
const msg = JSON.stringify({
toDomain: 'Server',
data: JSON.stringify(data)
});
state.socket.send({
data: msg
});
} else {
socketMsgQueue.push(data);
}
},
// 监听关闭事件
onSocketClose({
dispatch,
commit,
state
}) {
// console.log('onSocketClose');
state.socket.onClose(() => {
commit('setConnected', false);
if (sendHeartTimer) clearInterval(sendHeartTimer);
setTimeout(() => {
dispatch('initSocket');
}, 300);
});
},
// websocket发送channelId进行认证
auth({
dispatch,
rootState
}) {
const {
token
} = rootState.user;
if (!token) return;
const data = {
type: 'Auth',
data: {
token
}
};
dispatch('sendSocketMessage', data);
},
// 心跳检测
sendHeart({
dispatch,
state
}) {
if (sendHeartTimer) clearInterval(sendHeartTimer);
sendHeartTimer = setInterval(() => {
if (Date.now() - prevTime >= 15000) {
dispatch('sendSocketMessage', {
type: 'Ping'
});
if (Date.now() - prevTime >= 20000) {
state.socket.close();
}
}
}, 5000);
},
/**
* 处理auth认证返回的ChannelStatus消息
* @param {object} data 消息内容对象
*/
handleAuthMessage({
commit,
dispatch
}, data) {
if (data.data.authed) {
dispatch('sendHeart');
} else {
uni.$u.toast('消息系统认证失败, 请退出重新登录');
uni.$t.removeStorageSync('anyringToken');
commit('setSocket', null);
}
},
};
export default actions;

15
store/socket/index.js

@ -1,5 +1,10 @@
import state from './state';
import mutations from './mutations';
import actions from './actions';
export default { namespaced: true, state, mutations, actions };
import state from './state';
import mutations from './mutations';
import actions from './actions';
export default {
namespaced: true,
state,
mutations,
actions
};

50
store/socket/mutations.js

@ -1,26 +1,26 @@
const mutations = {
// 设置socket实例
setSocket(state, socket) {
state.socket = socket;
},
/**
* 设置socket连接状态
* @param {Object} state
* @param {boolean} connected 是否连接 true -> 连接
*/
setConnected(state, connected) {
state.connected = connected;
},
/**
* 设置连接锁 正在连接中 锁上 避免多个连接同时发出
* @param {Object} state
* @param {boolean} lockSocket 是否正在连接的过程中
*/
setLockSocket(state, lockSocket) {
state.lockSocket = lockSocket;
},
};
const mutations = {
// 设置socket实例
setSocket(state, socket) {
state.socket = socket;
},
/**
* 设置socket连接状态
* @param {Object} state
* @param {boolean} connected 是否连接 true -> 连接
*/
setConnected(state, connected) {
state.connected = connected;
},
/**
* 设置连接锁 正在连接中 锁上 避免多个连接同时发出
* @param {Object} state
* @param {boolean} lockSocket 是否正在连接的过程中
*/
setLockSocket(state, lockSocket) {
state.lockSocket = lockSocket;
},
};
export default mutations;

12
store/socket/state.js

@ -1,7 +1,7 @@
const state = {
socket: null, // websocket实例
connected: false, // 是否处于连接状态
lockSocket: false, // 是否正在连接状态
};
const state = {
socket: null, // websocket实例
connected: false, // 是否处于连接状态
lockSocket: false, // 是否正在连接状态
};
export default state;

4
store/task/actions.js

@ -5,7 +5,7 @@ const actions = {
* @param {string} roleId 角色id
*/
getPermanent({ commit }, param) {
uni.$t.$q.getPermanent(param, (err, data) => {
uni.$catchReq.getPermanent(param, (err, data) => {
if (err) {
console.error('err: ', err);
} else {
@ -20,7 +20,7 @@ const actions = {
* @param {object} param 请求参数 roleId, timeNode, timeUnit
*/
getGlobal({ commit }, param) {
uni.$t.$q.getGlobal(param, (err, data) => {
uni.$catchReq.getGlobal(param, (err, data) => {
if (err) {
console.error('err: ', err);
} else {

4
store/task/getters.js

@ -5,13 +5,13 @@ const getters = {
},
unitConfig({ timeUnit }) {
const target = uni.$t.timeConfig.timeUnits.find(item => item.id === timeUnit);
const target = uni.$timeConfig.timeUnits.find(item => item.id === timeUnit);
return target;
},
// 计算任务开始时间的格式
startTimeFormat(state, { unitConfig }) {
return unitConfig.format || 'M月D日 HH:mm';
return unitConfig.format || 'D日 HH:mm';
},
// 计算颗粒度 对应的 dayjs add 的单位

17
store/user/actions.js

@ -1,17 +1,22 @@
import ui from '@/utils/ui.js';
const actions = {
/**
* 通过userId获取token
* @param {any} commit
* @param {string} userId 用户id
*/
async getToken({ commit }, userId) {
async getTokenByUserId({ commit }, userId) {
try {
const data = await uni.$u.api.getToken(userId);
commit('setToken', data.token);
commit('setUser', data);
return data;
const res = await uni.$u.api.getToken(userId);
commit('setToken', res.token);
commit('setUser', res);
uni.$storage.setStorageSync('anyringToken', res.token || '');
uni.$storage.setStorageSync('user', JSON.stringify(res));
return res;
} catch (error) {
uni.$t.ui.showToast(error.msg || '获取个人信息失败');
ui.showToast(error.msg || '获取个人信息失败');
throw error;
}
},
};

7
store/user/getters.js

@ -1,3 +1,5 @@
import { dayjs } from '@/utils/dayjs';
const getters = {
// 获取用户的id
userId({ user }) {
@ -9,6 +11,11 @@ const getters = {
return '';
}
},
// token是否过期
tokenIsAvailable({ tokenExpiredTime }) {
return dayjs().isSameOrBefore(tokenExpiredTime);
},
};
export default getters;

22
store/user/index.js

@ -1,12 +1,12 @@
import state from './state';
import getters from './getters';
import mutations from './mutations';
import actions from './actions';
export default {
namespaced: true,
state,
getters,
mutations,
actions,
import state from './state';
import getters from './getters';
import mutations from './mutations';
import actions from './actions';
export default {
namespaced: true,
state,
getters,
mutations,
actions,
};

15
store/user/mutations.js

@ -6,7 +6,6 @@ const mutations = {
*/
setToken(state, token) {
state.token = token || '';
uni.$t.storage.setStorageSync(uni.$t.app.tokenKey, token || '');
},
/**
@ -16,8 +15,18 @@ const mutations = {
*/
setUser(state, user) {
if (!user) return;
state.user = { ...user };
uni.$t.storage.setStorageSync('user', JSON.stringify(user));
state.user = {
...user,
};
},
/**
* 设置user数据
* @param {object} state
* @param {number} tokenExpiredTime 过期时间ms
*/
setTokenExpiredTime(state, tokenExpiredTime) {
state.tokenExpiredTime = tokenExpiredTime;
},
};

3
store/user/state.js

@ -1,5 +1,8 @@
import { dayjs } from '@/utils/dayjs';
const state = {
token: '',
tokenExpiredTime: dayjs().add('1', 'day'), // DEBUG:
user: null,
};
export default state;

2
uni.scss

@ -26,12 +26,14 @@ $uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
$roleChoiceColor: #f59e0b;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
$uni-bg-color-transparent: rgba(255, 255, 255, 0); //透明色
/* 边框颜色 */
$uni-border-color:#c8c7cc;

12
uni_modules/uni-calendar/changelog.md

@ -0,0 +1,12 @@
## 1.4.3(2021-09-22)
- 修复 startDate、 endDate 属性失效的 bug
## 1.4.2(2021-08-24)
- 新增 支持国际化
## 1.4.1(2021-08-05)
- 修复 弹出层被 tabbar 遮盖 bug
## 1.4.0(2021-07-30)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.3.16(2021-05-12)
- 新增 组件示例地址
## 1.3.15(2021-02-04)
- 调整为uni_modules目录规范

546
uni_modules/uni-calendar/components/uni-calendar/calendar.js

@ -0,0 +1,546 @@
/**
* @1900-2100区间内的公历农历互转
* @charset UTF-8
* @github https://github.com/jjonline/calendar.js
* @Author Jea杨(JJonline@JJonline.Cn)
* @Time 2014-7-21
* @Time 2016-8-13 Fixed 2033hexAttribution Annals
* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug
* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
* @Version 1.0.3
* @公历转农历calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
* @农历转公历calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
*/
/* eslint-disable */
var calendar = {
/**
* 农历1900-2100的润大小信息表
* @Array Of Property
* @return Hex
*/
lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
/** Add By JJonline@JJonline.Cn**/
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
0x0d520], // 2100
/**
* 公历每个月份的天数普通表
* @Array Of Property
* @return Number
*/
solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
/**
* 天干地支之天干速查表
* @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
* @return Cn string
*/
Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
/**
* 天干地支之地支速查表
* @Array Of Property
* @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
* @return Cn string
*/
Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
/**
* 天干地支之地支速查表<=>生肖
* @Array Of Property
* @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
* @return Cn string
*/
Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
/**
* 24节气速查表
* @Array Of Property
* @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
* @return Cn string
*/
solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
/**
* 1900-2100各年的24节气日期速查表
* @Array Of Property
* @return 0x string For splice
*/
sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
'97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
'97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
'97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
'97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
'97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
'9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
'97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
'97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
'7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
'9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
'97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
'9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
'9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
'9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
'977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
'977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
'977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
'7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
'7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
'665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
/**
* 数字转中文速查表
* @Array Of Property
* @trans ['日','一','二','三','四','五','六','七','八','九','十']
* @return Cn string
*/
nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
/**
* 日期转农历称呼速查表
* @Array Of Property
* @trans ['初','十','廿','卅']
* @return Cn string
*/
nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
/**
* 月份转农历称呼速查表
* @Array Of Property
* @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
* @return Cn string
*/
nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
/**
* 返回农历y年一整年的总天数
* @param lunar Year
* @return Number
* @eg:var count = calendar.lYearDays(1987) ;//count=387
*/
lYearDays: function (y) {
var i; var sum = 348
for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
return (sum + this.leapDays(y))
},
/**
* 返回农历y年闰月是哪个月若y年没有闰月 则返回0
* @param lunar Year
* @return Number (0-12)
* @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
*/
leapMonth: function (y) { // 闰字编码 \u95f0
return (this.lunarInfo[y - 1900] & 0xf)
},
/**
* 返回农历y年闰月的天数 若该年没有闰月则返回0
* @param lunar Year
* @return Number (02930)
* @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
*/
leapDays: function (y) {
if (this.leapMonth(y)) {
return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
}
return (0)
},
/**
* 返回农历y年m月非闰月的总天数计算m为闰月时的天数请使用leapDays方法
* @param lunar Year
* @return Number (-12930)
* @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
*/
monthDays: function (y, m) {
if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1
return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
},
/**
* 返回公历(!)y年m月的天数
* @param solar Year
* @return Number (-128293031)
* @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
*/
solarDays: function (y, m) {
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
var ms = m - 1
if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
} else {
return (this.solarMonth[ms])
}
},
/**
* 农历年份转换为干支纪年
* @param lYear 农历年的年份数
* @return Cn string
*/
toGanZhiYear: function (lYear) {
var ganKey = (lYear - 3) % 10
var zhiKey = (lYear - 3) % 12
if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
},
/**
* 公历月日判断所属星座
* @param cMonth [description]
* @param cDay [description]
* @return Cn string
*/
toAstro: function (cMonth, cDay) {
var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
},
/**
* 传入offset偏移量返回干支
* @param offset 相对甲子的偏移量
* @return Cn string
*/
toGanZhi: function (offset) {
return this.Gan[offset % 10] + this.Zhi[offset % 12]
},
/**
* 传入公历(!)y年获得该年第n个节气的公历日期
* @param y公历年(1900-2100)n二十四节气中的第几个节气(1~24)从n=1(小寒)算起
* @return day Number
* @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
*/
getTerm: function (y, n) {
if (y < 1900 || y > 2100) { return -1 }
if (n < 1 || n > 24) { return -1 }
var _table = this.sTermInfo[y - 1900]
var _info = [
parseInt('0x' + _table.substr(0, 5)).toString(),
parseInt('0x' + _table.substr(5, 5)).toString(),
parseInt('0x' + _table.substr(10, 5)).toString(),
parseInt('0x' + _table.substr(15, 5)).toString(),
parseInt('0x' + _table.substr(20, 5)).toString(),
parseInt('0x' + _table.substr(25, 5)).toString()
]
var _calday = [
_info[0].substr(0, 1),
_info[0].substr(1, 2),
_info[0].substr(3, 1),
_info[0].substr(4, 2),
_info[1].substr(0, 1),
_info[1].substr(1, 2),
_info[1].substr(3, 1),
_info[1].substr(4, 2),
_info[2].substr(0, 1),
_info[2].substr(1, 2),
_info[2].substr(3, 1),
_info[2].substr(4, 2),
_info[3].substr(0, 1),
_info[3].substr(1, 2),
_info[3].substr(3, 1),
_info[3].substr(4, 2),
_info[4].substr(0, 1),
_info[4].substr(1, 2),
_info[4].substr(3, 1),
_info[4].substr(4, 2),
_info[5].substr(0, 1),
_info[5].substr(1, 2),
_info[5].substr(3, 1),
_info[5].substr(4, 2)
]
return parseInt(_calday[n - 1])
},
/**
* 传入农历数字月份返回汉语通俗表示法
* @param lunar month
* @return Cn string
* @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
*/
toChinaMonth: function (m) { // 月 => \u6708
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
var s = this.nStr3[m - 1]
s += '\u6708'// 加上月字
return s
},
/**
* 传入农历日期数字返回汉字表示法
* @param lunar day
* @return Cn string
* @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
*/
toChinaDay: function (d) { // 日 => \u65e5
var s
switch (d) {
case 10:
s = '\u521d\u5341'; break
case 20:
s = '\u4e8c\u5341'; break
break
case 30:
s = '\u4e09\u5341'; break
break
default :
s = this.nStr2[Math.floor(d / 10)]
s += this.nStr1[d % 10]
}
return (s)
},
/**
* 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是立春
* @param y year
* @return Cn string
* @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
*/
getAnimal: function (y) {
return this.Animals[(y - 4) % 12]
},
/**
* 传入阳历年月日获得详细的公历农历object信息 <=>JSON
* @param y solar year
* @param m solar month
* @param d solar day
* @return JSON object
* @eg:console.log(calendar.solar2lunar(1987,11,01));
*/
solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
// 年份限定、上限
if (y < 1900 || y > 2100) {
return -1// undefined转换为数字变为NaN
}
// 公历传参最下限
if (y == 1900 && m == 1 && d < 31) {
return -1
}
// 未传参 获得当天
if (!y) {
var objDate = new Date()
} else {
var objDate = new Date(y, parseInt(m) - 1, d)
}
var i; var leap = 0; var temp = 0
// 修正ymd参数
var y = objDate.getFullYear()
var m = objDate.getMonth() + 1
var d = objDate.getDate()
var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
for (i = 1900; i < 2101 && offset > 0; i++) {
temp = this.lYearDays(i)
offset -= temp
}
if (offset < 0) {
offset += temp; i--
}
// 是否今天
var isTodayObj = new Date()
var isToday = false
if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
isToday = true
}
// 星期几
var nWeek = objDate.getDay()
var cWeek = this.nStr1[nWeek]
// 数字表示周几顺应天朝周一开始的惯例
if (nWeek == 0) {
nWeek = 7
}
// 农历年
var year = i
var leap = this.leapMonth(i) // 闰哪个月
var isLeap = false
// 效验闰月
for (i = 1; i < 13 && offset > 0; i++) {
// 闰月
if (leap > 0 && i == (leap + 1) && isLeap == false) {
--i
isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
} else {
temp = this.monthDays(year, i)// 计算农历普通月天数
}
// 解除闰月
if (isLeap == true && i == (leap + 1)) { isLeap = false }
offset -= temp
}
// 闰月导致数组下标重叠取反
if (offset == 0 && leap > 0 && i == leap + 1) {
if (isLeap) {
isLeap = false
} else {
isLeap = true; --i
}
}
if (offset < 0) {
offset += temp; --i
}
// 农历月
var month = i
// 农历日
var day = offset + 1
// 天干地支处理
var sm = m - 1
var gzY = this.toGanZhiYear(year)
// 当月的两个节气
// bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
// 依据12节气修正干支月
var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
if (d >= firstNode) {
gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
}
// 传入的日期的节气与否
var isTerm = false
var Term = null
if (firstNode == d) {
isTerm = true
Term = this.solarTerm[m * 2 - 2]
}
if (secondNode == d) {
isTerm = true
Term = this.solarTerm[m * 2 - 1]
}
// 日柱 当月一日与 1900/1/1 相差天数
var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
var gzD = this.toGanZhi(dayCyclical + d - 1)
// 该日期所属的星座
var astro = this.toAstro(m, d)
return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
},
/**
* 传入农历年月日以及传入的月份是否闰月获得详细的公历农历object信息 <=>JSON
* @param y lunar year
* @param m lunar month
* @param d lunar day
* @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
* @return JSON object
* @eg:console.log(calendar.lunar2solar(1987,9,10));
*/
lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
var isLeapMonth = !!isLeapMonth
var leapOffset = 0
var leapMonth = this.leapMonth(y)
var leapDay = this.leapDays(y)
if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
var day = this.monthDays(y, m)
var _day = day
// bugFix 2016-9-25
// if month is leap, _day use leapDays method
if (isLeapMonth) {
_day = this.leapDays(y, m)
}
if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
// 计算农历的时间差
var offset = 0
for (var i = 1900; i < y; i++) {
offset += this.lYearDays(i)
}
var leap = 0; var isAdd = false
for (var i = 1; i < m; i++) {
leap = this.leapMonth(y)
if (!isAdd) { // 处理闰月
if (leap <= i && leap > 0) {
offset += this.leapDays(y); isAdd = true
}
}
offset += this.monthDays(y, i)
}
// 转换闰月农历 需补充该年闰月的前一个月的时差
if (isLeapMonth) { offset += day }
// 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
var calObj = new Date((offset + d - 31) * 86400000 + stmap)
var cY = calObj.getUTCFullYear()
var cM = calObj.getUTCMonth() + 1
var cD = calObj.getUTCDate()
return this.solar2lunar(cY, cM, cD)
}
}
export default calendar

12
uni_modules/uni-calendar/components/uni-calendar/i18n/en.json

@ -0,0 +1,12 @@
{
"uni-calender.ok": "ok",
"uni-calender.cancel": "cancel",
"uni-calender.today": "today",
"uni-calender.MON": "MON",
"uni-calender.TUE": "TUE",
"uni-calender.WED": "WED",
"uni-calender.THU": "THU",
"uni-calender.FRI": "FRI",
"uni-calender.SAT": "SAT",
"uni-calender.SUN": "SUN"
}

8
uni_modules/uni-calendar/components/uni-calendar/i18n/index.js

@ -0,0 +1,8 @@
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}

12
uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hans.json

@ -0,0 +1,12 @@
{
"uni-calender.ok": "确定",
"uni-calender.cancel": "取消",
"uni-calender.today": "今日",
"uni-calender.SUN": "日",
"uni-calender.MON": "一",
"uni-calender.TUE": "二",
"uni-calender.WED": "三",
"uni-calender.THU": "四",
"uni-calender.FRI": "五",
"uni-calender.SAT": "六"
}

12
uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hant.json

@ -0,0 +1,12 @@
{
"uni-calender.ok": "確定",
"uni-calender.cancel": "取消",
"uni-calender.today": "今日",
"uni-calender.SUN": "日",
"uni-calender.MON": "一",
"uni-calender.TUE": "二",
"uni-calender.WED": "三",
"uni-calender.THU": "四",
"uni-calender.FRI": "五",
"uni-calender.SAT": "六"
}

181
uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue

@ -0,0 +1,181 @@
<template>
<view class="uni-calendar-item__weeks-box" :class="{
'uni-calendar-item--disable':weeks.disable,
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
'uni-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay) ,
'uni-calendar-item--before-checked':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked':weeks.afterMultiple,
}"
@click="choiceDate(weeks)">
<view class="uni-calendar-item__weeks-box-item">
<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
<text class="uni-calendar-item__weeks-box-text" :class="{
'uni-calendar-item--isDay-text': weeks.isDay,
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
'uni-calendar-item--before-checked':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked':weeks.afterMultiple,
'uni-calendar-item--disable':weeks.disable,
}">{{weeks.date}}</text>
<text v-if="!lunar&&!weeks.extraInfo && weeks.isDay" class="uni-calendar-item__weeks-lunar-text" :class="{
'uni-calendar-item--isDay-text':weeks.isDay,
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
'uni-calendar-item--before-checked':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked':weeks.afterMultiple,
}">{{todayText}}</text>
<text v-if="lunar&&!weeks.extraInfo" class="uni-calendar-item__weeks-lunar-text" :class="{
'uni-calendar-item--isDay-text':weeks.isDay,
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
'uni-calendar-item--before-checked':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked':weeks.afterMultiple,
'uni-calendar-item--disable':weeks.disable,
}">{{weeks.isDay ? todayText : (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>
<text v-if="weeks.extraInfo&&weeks.extraInfo.info" class="uni-calendar-item__weeks-lunar-text" :class="{
'uni-calendar-item--extra':weeks.extraInfo.info,
'uni-calendar-item--isDay-text':weeks.isDay,
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
'uni-calendar-item--before-checked':weeks.beforeMultiple,
'uni-calendar-item--multiple': weeks.multiple,
'uni-calendar-item--after-checked':weeks.afterMultiple,
'uni-calendar-item--disable':weeks.disable,
}">{{weeks.extraInfo.info}}</text>
</view>
</view>
</template>
<script>
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from './i18n/index.js'
const { t } = initVueI18n(messages)
export default {
emits:['change'],
props: {
weeks: {
type: Object,
default () {
return {}
}
},
calendar: {
type: Object,
default: () => {
return {}
}
},
selected: {
type: Array,
default: () => {
return []
}
},
lunar: {
type: Boolean,
default: false
}
},
computed: {
todayText() {
return t("uni-calender.today")
},
},
methods: {
choiceDate(weeks) {
this.$emit('change', weeks)
}
}
}
</script>
<style lang="scss" scoped>
.uni-calendar-item__weeks-box {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
}
.uni-calendar-item__weeks-box-text {
font-size: $uni-font-size-base;
color: $uni-text-color;
}
.uni-calendar-item__weeks-lunar-text {
font-size: $uni-font-size-sm;
color: $uni-text-color;
}
.uni-calendar-item__weeks-box-item {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
width: 100rpx;
height: 100rpx;
}
.uni-calendar-item__weeks-box-circle {
position: absolute;
top: 5px;
right: 5px;
width: 8px;
height: 8px;
border-radius: 8px;
background-color: $uni-color-error;
}
.uni-calendar-item--disable {
background-color: rgba(249, 249, 249, $uni-opacity-disabled);
color: $uni-text-color-disable;
}
.uni-calendar-item--isDay-text {
color: $uni-color-primary;
}
.uni-calendar-item--isDay {
background-color: $uni-color-primary;
opacity: 0.8;
color: #fff;
}
.uni-calendar-item--extra {
color: $uni-color-error;
opacity: 0.8;
}
.uni-calendar-item--checked {
background-color: $uni-color-primary;
color: #fff;
opacity: 0.8;
}
.uni-calendar-item--multiple {
background-color: $uni-color-primary;
color: #fff;
opacity: 0.8;
}
.uni-calendar-item--before-checked {
background-color: #ff5a5f;
color: #fff;
}
.uni-calendar-item--after-checked {
background-color: #ff5a5f;
color: #fff;
}
</style>

551
uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue

@ -0,0 +1,551 @@
<template>
<view class="uni-calendar">
<view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}" @click="clean"></view>
<view v-if="insert || show" class="uni-calendar__content" :class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow}">
<view v-if="!insert" class="uni-calendar__header uni-calendar--fixed-top">
<view class="uni-calendar__header-btn-box" @click="close">
<text class="uni-calendar__header-text uni-calendar--fixed-width">{{cancelText}}</text>
</view>
<view class="uni-calendar__header-btn-box" @click="confirm">
<text class="uni-calendar__header-text uni-calendar--fixed-width">{{okText}}</text>
</view>
</view>
<view class="uni-calendar__header">
<view class="uni-calendar__header-btn-box" @click.stop="pre">
<view class="uni-calendar__header-btn uni-calendar--left"></view>
</view>
<picker mode="date" :value="date" fields="month" @change="bindDateChange">
<text class="uni-calendar__header-text">{{ (nowDate.year||'') +' / '+( nowDate.month||'')}}</text>
</picker>
<view class="uni-calendar__header-btn-box" @click.stop="next">
<view class="uni-calendar__header-btn uni-calendar--right"></view>
</view>
<text class="uni-calendar__backtoday" @click="backtoday">{{todayText}}</text>
</view>
<view class="uni-calendar__box">
<view v-if="showMonth" class="uni-calendar__box-bg">
<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
</view>
<view class="uni-calendar__weeks">
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{SUNText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{monText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{TUEText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{WEDText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{THUText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{FRIText}}</text>
</view>
<view class="uni-calendar__weeks-day">
<text class="uni-calendar__weeks-day-text">{{SATText}}</text>
</view>
</view>
<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" :selected="selected" :lunar="lunar" @change="choiceDate"></calendar-item>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import Calendar from './util.js';
import calendarItem from './uni-calendar-item.vue'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from './i18n/index.js'
const { t } = initVueI18n(messages)
/**
* Calendar 日历
* @description 日历组件可以查看日期选择任意范围内的日期打点操作常用场景如酒店日期预订火车机票选择购买日期上下班打卡等
* @tutorial https://ext.dcloud.net.cn/plugin?id=56
* @property {String} date 自定义当前时间默认为今天
* @property {Boolean} lunar 显示农历
* @property {String} startDate 日期选择范围-开始日期
* @property {String} endDate 日期选择范围-结束日期
* @property {Boolean} range 范围选择
* @property {Boolean} insert = [true|false] 插入模式,默认为false
* @value true 弹窗模式
* @value false 插入模式
* @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
* @property {Array} selected 打点期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
* @property {Boolean} showMonth 是否选择月份为背景
* @event {Function} change 日期改变`insert :ture` 时生效
* @event {Function} confirm 确认选择`insert :false` 时生效
* @event {Function} monthSwitch 切换月份时触发
* @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
*/
export default {
components: {
calendarItem
},
emits:['close','confirm','change','monthSwitch'],
props: {
date: {
type: String,
default: ''
},
selected: {
type: Array,
default () {
return []
}
},
lunar: {
type: Boolean,
default: false
},
startDate: {
type: String,
default: ''
},
endDate: {
type: String,
default: ''
},
range: {
type: Boolean,
default: false
},
insert: {
type: Boolean,
default: true
},
showMonth: {
type: Boolean,
default: true
},
clearDate: {
type: Boolean,
default: true
}
},
data() {
return {
show: false,
weeks: [],
calendar: {},
nowDate: '',
aniMaskShow: false
}
},
computed:{
/**
* for i18n
*/
okText() {
return t("uni-calender.ok")
},
cancelText() {
return t("uni-calender.cancel")
},
todayText() {
return t("uni-calender.today")
},
monText() {
return t("uni-calender.MON")
},
TUEText() {
return t("uni-calender.TUE")
},
WEDText() {
return t("uni-calender.WED")
},
THUText() {
return t("uni-calender.THU")
},
FRIText() {
return t("uni-calender.FRI")
},
SATText() {
return t("uni-calender.SAT")
},
SUNText() {
return t("uni-calender.SUN")
},
},
watch: {
date(newVal) {
// this.cale.setDate(newVal)
this.init(newVal)
},
startDate(val){
this.cale.resetSatrtDate(val)
this.cale.setDate(this.nowDate.fullDate)
this.weeks = this.cale.weeks
},
endDate(val){
this.cale.resetEndDate(val)
this.cale.setDate(this.nowDate.fullDate)
this.weeks = this.cale.weeks
},
selected(newVal) {
this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
this.weeks = this.cale.weeks
}
},
created() {
//
this.cale = new Calendar({
// date: new Date(),
selected: this.selected,
startDate: this.startDate,
endDate: this.endDate,
range: this.range,
})
//
// this.cale.setDate(this.date)
this.init(this.date)
// this.setDay
},
methods: {
// 穿
clean() {},
bindDateChange(e) {
const value = e.detail.value + '-1'
console.log(this.cale.getDate(value));
this.init(value)
},
/**
* 初始化日期显示
* @param {Object} date
*/
init(date) {
this.cale.setDate(date)
this.weeks = this.cale.weeks
this.nowDate = this.calendar = this.cale.getInfo(date)
},
/**
* 打开日历弹窗
*/
open() {
//
if (this.clearDate && !this.insert) {
this.cale.cleanMultipleStatus()
// this.cale.setDate(this.date)
this.init(this.date)
}
this.show = true
this.$nextTick(() => {
setTimeout(() => {
this.aniMaskShow = true
}, 50)
})
},
/**
* 关闭日历弹窗
*/
close() {
this.aniMaskShow = false
this.$nextTick(() => {
setTimeout(() => {
this.show = false
this.$emit('close')
}, 300)
})
},
/**
* 确认按钮
*/
confirm() {
this.setEmit('confirm')
this.close()
},
/**
* 变化触发
*/
change() {
if (!this.insert) return
this.setEmit('change')
},
/**
* 选择月份触发
*/
monthSwitch() {
let {
year,
month
} = this.nowDate
this.$emit('monthSwitch', {
year,
month: Number(month)
})
},
/**
* 派发事件
* @param {Object} name
*/
setEmit(name) {
let {
year,
month,
date,
fullDate,
lunar,
extraInfo
} = this.calendar
this.$emit(name, {
range: this.cale.multipleStatus,
year,
month,
date,
fulldate: fullDate,
lunar,
extraInfo: extraInfo || {}
})
},
/**
* 选择天触发
* @param {Object} weeks
*/
choiceDate(weeks) {
if (weeks.disable) return
this.calendar = weeks
//
this.cale.setMultiple(this.calendar.fullDate)
this.weeks = this.cale.weeks
this.change()
},
/**
* 回到今天
*/
backtoday() {
console.log(this.cale.getDate(new Date()).fullDate);
let date = this.cale.getDate(new Date()).fullDate
// this.cale.setDate(date)
this.init(date)
this.change()
},
/**
* 上个月
*/
pre() {
const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
this.setDate(preDate)
this.monthSwitch()
},
/**
* 下个月
*/
next() {
const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
this.setDate(nextDate)
this.monthSwitch()
},
/**
* 设置日期
* @param {Object} date
*/
setDate(date) {
this.cale.setDate(date)
this.weeks = this.cale.weeks
this.nowDate = this.cale.getInfo(date)
}
}
}
</script>
<style lang="scss" scoped>
.uni-calendar {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
}
.uni-calendar__mask {
position: fixed;
bottom: 0;
top: 0;
left: 0;
right: 0;
background-color: $uni-bg-color-mask;
transition-property: opacity;
transition-duration: 0.3s;
opacity: 0;
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
}
.uni-calendar--mask-show {
opacity: 1
}
.uni-calendar--fixed {
position: fixed;
bottom: calc(var(--window-bottom));
left: 0;
right: 0;
transition-property: transform;
transition-duration: 0.3s;
transform: translateY(460px);
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
}
.uni-calendar--ani-show {
transform: translateY(0);
}
.uni-calendar__content {
background-color: #fff;
}
.uni-calendar__header {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
height: 50px;
border-bottom-color: $uni-border-color;
border-bottom-style: solid;
border-bottom-width: 1px;
}
.uni-calendar--fixed-top {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-between;
border-top-color: $uni-border-color;
border-top-style: solid;
border-top-width: 1px;
}
.uni-calendar--fixed-width {
width: 50px;
// padding: 0 15px;
}
.uni-calendar__backtoday {
position: absolute;
right: 0;
top: 25rpx;
padding: 0 5px;
padding-left: 10px;
height: 25px;
line-height: 25px;
font-size: 12px;
border-top-left-radius: 25px;
border-bottom-left-radius: 25px;
color: $uni-text-color;
background-color: $uni-bg-color-hover;
}
.uni-calendar__header-text {
text-align: center;
width: 100px;
font-size: $uni-font-size-base;
color: $uni-text-color;
}
.uni-calendar__header-btn-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
}
.uni-calendar__header-btn {
width: 10px;
height: 10px;
border-left-color: $uni-text-color-placeholder;
border-left-style: solid;
border-left-width: 2px;
border-top-color: $uni-color-subtitle;
border-top-style: solid;
border-top-width: 2px;
}
.uni-calendar--left {
transform: rotate(-45deg);
}
.uni-calendar--right {
transform: rotate(135deg);
}
.uni-calendar__weeks {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.uni-calendar__weeks-item {
flex: 1;
}
.uni-calendar__weeks-day {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
height: 45px;
border-bottom-color: #F5F5F5;
border-bottom-style: solid;
border-bottom-width: 1px;
}
.uni-calendar__weeks-day-text {
font-size: 14px;
}
.uni-calendar__box {
position: relative;
}
.uni-calendar__box-bg {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.uni-calendar__box-bg-text {
font-size: 200px;
font-weight: bold;
color: $uni-text-color-grey;
opacity: 0.1;
text-align: center;
/* #ifndef APP-NVUE */
line-height: 1;
/* #endif */
}
</style>

354
uni_modules/uni-calendar/components/uni-calendar/util.js

@ -0,0 +1,354 @@
import CALENDAR from './calendar.js'
class Calendar {
constructor({
date,
selected,
startDate,
endDate,
range
} = {}) {
// 当前日期
this.date = this.getDate(new Date()) // 当前初入日期
// 打点信息
this.selected = selected || [];
// 范围开始
this.startDate = startDate
// 范围结束
this.endDate = endDate
this.range = range
// 多选状态
this.cleanMultipleStatus()
// 每周日期
this.weeks = {}
// this._getWeek(this.date.fullDate)
}
/**
* 设置日期
* @param {Object} date
*/
setDate(date) {
this.selectDate = this.getDate(date)
this._getWeek(this.selectDate.fullDate)
}
/**
* 清理多选状态
*/
cleanMultipleStatus() {
this.multipleStatus = {
before: '',
after: '',
data: []
}
}
/**
* 重置开始日期
*/
resetSatrtDate(startDate) {
// 范围开始
this.startDate = startDate
}
/**
* 重置结束日期
*/
resetEndDate(endDate) {
// 范围结束
this.endDate = endDate
}
/**
* 获取任意时间
*/
getDate(date, AddDayCount = 0, str = 'day') {
if (!date) {
date = new Date()
}
if (typeof date !== 'object') {
date = date.replace(/-/g, '/')
}
const dd = new Date(date)
switch (str) {
case 'day':
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
break
case 'month':
if (dd.getDate() === 31) {
dd.setDate(dd.getDate() + AddDayCount)
} else {
dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期
}
break
case 'year':
dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
break
}
const y = dd.getFullYear()
const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
return {
fullDate: y + '-' + m + '-' + d,
year: y,
month: m,
date: d,
day: dd.getDay()
}
}
/**
* 获取上月剩余天数
*/
_getLastMonthDays(firstDay, full) {
let dateArr = []
for (let i = firstDay; i > 0; i--) {
const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
dateArr.push({
date: beforeDate,
month: full.month - 1,
lunar: this.getlunar(full.year, full.month - 1, beforeDate),
disable: true
})
}
return dateArr
}
/**
* 获取本月天数
*/
_currentMonthDys(dateData, full) {
let dateArr = []
let fullDate = this.date.fullDate
for (let i = 1; i <= dateData; i++) {
let isinfo = false
let nowDate = full.year + '-' + (full.month < 10 ?
full.month : full.month) + '-' + (i < 10 ?
'0' + i : i)
// 是否今天
let isDay = fullDate === nowDate
// 获取打点信息
let info = this.selected && this.selected.find((item) => {
if (this.dateEqual(nowDate, item.date)) {
return item
}
})
// 日期禁用
let disableBefore = true
let disableAfter = true
if (this.startDate) {
// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
disableBefore = this.dateCompare(this.startDate, nowDate)
}
if (this.endDate) {
// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
disableAfter = this.dateCompare(nowDate, this.endDate)
}
let multiples = this.multipleStatus.data
let checked = false
let multiplesStatus = -1
if (this.range) {
if (multiples) {
multiplesStatus = multiples.findIndex((item) => {
return this.dateEqual(item, nowDate)
})
}
if (multiplesStatus !== -1) {
checked = true
}
}
let data = {
fullDate: nowDate,
year: full.year,
date: i,
multiple: this.range ? checked : false,
beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate),
afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate),
month: full.month,
lunar: this.getlunar(full.year, full.month, i),
disable: !(disableBefore && disableAfter),
isDay
}
if (info) {
data.extraInfo = info
}
dateArr.push(data)
}
return dateArr
}
/**
* 获取下月天数
*/
_getNextMonthDays(surplus, full) {
let dateArr = []
for (let i = 1; i < surplus + 1; i++) {
dateArr.push({
date: i,
month: Number(full.month) + 1,
lunar: this.getlunar(full.year, Number(full.month) + 1, i),
disable: true
})
}
return dateArr
}
/**
* 获取当前日期详情
* @param {Object} date
*/
getInfo(date) {
if (!date) {
date = new Date()
}
const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
return dateInfo
}
/**
* 比较时间大小
*/
dateCompare(startDate, endDate) {
// 计算截止时间
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
// 计算详细项的截止时间
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
if (startDate <= endDate) {
return true
} else {
return false
}
}
/**
* 比较时间是否相等
*/
dateEqual(before, after) {
// 计算截止时间
before = new Date(before.replace('-', '/').replace('-', '/'))
// 计算详细项的截止时间
after = new Date(after.replace('-', '/').replace('-', '/'))
if (before.getTime() - after.getTime() === 0) {
return true
} else {
return false
}
}
/**
* 获取日期范围内所有日期
* @param {Object} begin
* @param {Object} end
*/
geDateAll(begin, end) {
var arr = []
var ab = begin.split('-')
var ae = end.split('-')
var db = new Date()
db.setFullYear(ab[0], ab[1] - 1, ab[2])
var de = new Date()
de.setFullYear(ae[0], ae[1] - 1, ae[2])
var unixDb = db.getTime() - 24 * 60 * 60 * 1000
var unixDe = de.getTime() - 24 * 60 * 60 * 1000
for (var k = unixDb; k <= unixDe;) {
k = k + 24 * 60 * 60 * 1000
arr.push(this.getDate(new Date(parseInt(k))).fullDate)
}
return arr
}
/**
* 计算阴历日期显示
*/
getlunar(year, month, date) {
return CALENDAR.solar2lunar(year, month, date)
}
/**
* 设置打点
*/
setSelectInfo(data, value) {
this.selected = value
this._getWeek(data)
}
/**
* 获取多选状态
*/
setMultiple(fullDate) {
let {
before,
after
} = this.multipleStatus
if (!this.range) return
if (before && after) {
this.multipleStatus.before = ''
this.multipleStatus.after = ''
this.multipleStatus.data = []
} else {
if (!before) {
this.multipleStatus.before = fullDate
} else {
this.multipleStatus.after = fullDate
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
} else {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
}
}
}
this._getWeek(fullDate)
}
/**
* 获取每周数据
* @param {Object} dateData
*/
_getWeek(dateData) {
const {
fullDate,
year,
month,
date,
day
} = this.getDate(dateData)
let firstDay = new Date(year, month - 1, 1).getDay()
let currentDay = new Date(year, month, 0).getDate()
let dates = {
lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
nextMonthDays: [], // 下个月开始几天
weeks: []
}
let canlender = []
const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
let weeks = {}
// 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
for (let i = 0; i < canlender.length; i++) {
if (i % 7 === 0) {
weeks[parseInt(i / 7)] = new Array(7)
}
weeks[parseInt(i / 7)][i % 7] = canlender[i]
}
this.canlender = canlender
this.weeks = weeks
}
//静态方法
// static init(date) {
// if (!this.instance) {
// this.instance = new Calendar(date);
// }
// return this.instance;
// }
}
export default Calendar

88
uni_modules/uni-calendar/package.json

@ -0,0 +1,88 @@
{
"id": "uni-calendar",
"displayName": "uni-calendar 日历",
"version": "1.4.3",
"description": "日历组件",
"keywords": [
"uni-ui",
"uniui",
"日历",
"",
"打卡",
"日历选择"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

103
uni_modules/uni-calendar/readme.md

@ -0,0 +1,103 @@
## Calendar 日历
> **组件名:uni-calendar**
> 代码块: `uCalendar`
日历组件
> **注意事项**
> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
> - 本组件农历转换使用的js是 [@1900-2100区间内的公历、农历互转](https://github.com/jjonline/calendar.js)
> - 仅支持自定义组件模式
> - `date`属性传入的应该是一个 String ,如: 2019-06-27 ,而不是 new Date()
> - 通过 `insert` 属性来确定当前的事件是 @change 还是 @confirm 。理应合并为一个事件,但是为了区分模式,现使用两个事件,这里需要注意
> - 弹窗模式下无法阻止后面的元素滚动,如有需要阻止,请在弹窗弹出后,手动设置滚动元素为不可滚动
### 安装方式
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
### 基本用法
在 ``template`` 中使用组件
```html
<view>
<uni-calendar
:insert="true"
:lunar="true"
:start-date="'2019-3-2'"
:end-date="'2019-5-20'"
@change="change"
/>
</view>
```
### 通过方法打开日历
需要设置 `insert``false`
```html
<view>
<uni-calendar
ref="calendar"
:insert="false"
@confirm="confirm"
/>
<button @click="open">打开日历</button>
</view>
```
```javascript
export default {
data() {
return {};
},
methods: {
open(){
this.$refs.calendar.open();
},
confirm(e) {
console.log(e);
}
}
};
```
## API
### Calendar Props
| 属性名 | 类型 | 默认值| 说明 |
| | |
| date | String |- | 自定义当前时间,默认为今天 |
| lunar | Boolean | false | 显示农历 |
| startDate | String |- | 日期选择范围-开始日期 |
| endDate | String |- | 日期选择范围-结束日期 |
| range | Boolean | false | 范围选择 |
| insert | Boolean | false | 插入模式,可选值,ture:插入模式;false:弹窗模式;默认为插入模式 |
|clearDate |Boolean |true |弹窗模式是否清空上次选择内容 |
| selected | Array |- | 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}] |
|showMonth | Boolean | true | 是否显示月份为背景 |
### Calendar Events
| 事件名 | 说明 |返回值|
| | | |
| open | 弹出日历组件,`insert :false` 时生效|- |
## 组件示例
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar](https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar)

16
uni_modules/uni-icons/changelog.md

@ -0,0 +1,16 @@
## 1.3.2(2021-12-01)
- 优化 示例可复制图标名称
## 1.3.1(2021-11-23)
- 优化 兼容旧组件 type 值
## 1.3.0(2021-11-19)
- 新增 更多图标
- 优化 自定义图标使用方式
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons)
## 1.1.7(2021-11-08)
## 1.2.0(2021-07-30)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.5(2021-05-12)
- 新增 组件示例地址
## 1.1.4(2021-02-05)
- 调整为uni_modules目录规范

1115
uni_modules/uni-icons/components/uni-icons/icons.js

File diff suppressed because it is too large

89
uni_modules/uni-icons/components/uni-icons/uni-icons.vue

@ -0,0 +1,89 @@
<template>
<!-- #ifdef APP-NVUE -->
<text :style="{ color: color, 'font-size': size + 'px' }" class="uni-icons" @click="_onClick">{{unicode}}</text>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<text :style="{ color: color, 'font-size': size + 'px' }" class="uni-icons" :class="['uniui-'+type,customPrefix,customPrefix?type:'']" @click="_onClick"></text>
<!-- #endif -->
</template>
<script>
import icons from './icons.js';
// #ifdef APP-NVUE
var domModule = weex.requireModule('dom');
import iconUrl from './uniicons.ttf'
domModule.addRule('fontFace', {
'fontFamily': "uniicons",
'src': "url('"+iconUrl+"')"
});
// #endif
/**
* Icons 图标
* @description 用于展示 icons 图标
* @tutorial https://ext.dcloud.net.cn/plugin?id=28
* @property {Number} size 图标大小
* @property {String} type 图标图案参考示例
* @property {String} color 图标颜色
* @property {String} customPrefix 自定义图标
* @event {Function} click 点击 Icon 触发事件
*/
export default {
name: 'UniIcons',
emits:['click'],
props: {
type: {
type: String,
default: ''
},
color: {
type: String,
default: '#333333'
},
size: {
type: [Number, String],
default: 16
},
customPrefix:{
type: String,
default: ''
}
},
data() {
return {
icons: icons.glyphs
}
},
computed:{
unicode(){
let code = this.icons.find(v=>v.font_class === this.type)
if(code){
return unescape(`%u${code.unicode}`)
}
return ''
}
},
methods: {
_onClick() {
this.$emit('click')
}
}
}
</script>
<style lang="scss">
/* #ifndef APP-NVUE */
@import './uniicons.css';
@font-face {
font-family: uniicons;
src: url('./uniicons.ttf') format('truetype');
}
/* #endif */
.uni-icons {
font-family: uniicons;
text-decoration: none;
text-align: center;
}
</style>

663
uni_modules/uni-icons/components/uni-icons/uniicons.css

@ -0,0 +1,663 @@
.uniui-color:before {
content: "\e6cf";
}
.uniui-wallet:before {
content: "\e6b1";
}
.uniui-settings-filled:before {
content: "\e6ce";
}
.uniui-auth-filled:before {
content: "\e6cc";
}
.uniui-shop-filled:before {
content: "\e6cd";
}
.uniui-staff-filled:before {
content: "\e6cb";
}
.uniui-vip-filled:before {
content: "\e6c6";
}
.uniui-plus-filled:before {
content: "\e6c7";
}
.uniui-folder-add-filled:before {
content: "\e6c8";
}
.uniui-color-filled:before {
content: "\e6c9";
}
.uniui-tune-filled:before {
content: "\e6ca";
}
.uniui-calendar-filled:before {
content: "\e6c0";
}
.uniui-notification-filled:before {
content: "\e6c1";
}
.uniui-wallet-filled:before {
content: "\e6c2";
}
.uniui-medal-filled:before {
content: "\e6c3";
}
.uniui-gift-filled:before {
content: "\e6c4";
}
.uniui-fire-filled:before {
content: "\e6c5";
}
.uniui-refreshempty:before {
content: "\e6bf";
}
.uniui-location-filled:before {
content: "\e6af";
}
.uniui-person-filled:before {
content: "\e69d";
}
.uniui-personadd-filled:before {
content: "\e698";
}
.uniui-back:before {
content: "\e6b9";
}
.uniui-forward:before {
content: "\e6ba";
}
.uniui-arrow-right:before {
content: "\e6bb";
}
.uniui-arrowthinright:before {
content: "\e6bb";
}
.uniui-arrow-left:before {
content: "\e6bc";
}
.uniui-arrowthinleft:before {
content: "\e6bc";
}
.uniui-arrow-up:before {
content: "\e6bd";
}
.uniui-arrowthinup:before {
content: "\e6bd";
}
.uniui-arrow-down:before {
content: "\e6be";
}
.uniui-arrowthindown:before {
content: "\e6be";
}
.uniui-bottom:before {
content: "\e6b8";
}
.uniui-arrowdown:before {
content: "\e6b8";
}
.uniui-right:before {
content: "\e6b5";
}
.uniui-arrowright:before {
content: "\e6b5";
}
.uniui-top:before {
content: "\e6b6";
}
.uniui-arrowup:before {
content: "\e6b6";
}
.uniui-left:before {
content: "\e6b7";
}
.uniui-arrowleft:before {
content: "\e6b7";
}
.uniui-eye:before {
content: "\e651";
}
.uniui-eye-filled:before {
content: "\e66a";
}
.uniui-eye-slash:before {
content: "\e6b3";
}
.uniui-eye-slash-filled:before {
content: "\e6b4";
}
.uniui-info-filled:before {
content: "\e649";
}
.uniui-reload:before {
content: "\e6b2";
}
.uniui-micoff-filled:before {
content: "\e6b0";
}
.uniui-map-pin-ellipse:before {
content: "\e6ac";
}
.uniui-map-pin:before {
content: "\e6ad";
}
.uniui-location:before {
content: "\e6ae";
}
.uniui-starhalf:before {
content: "\e683";
}
.uniui-star:before {
content: "\e688";
}
.uniui-star-filled:before {
content: "\e68f";
}
.uniui-calendar:before {
content: "\e6a0";
}
.uniui-fire:before {
content: "\e6a1";
}
.uniui-medal:before {
content: "\e6a2";
}
.uniui-font:before {
content: "\e6a3";
}
.uniui-gift:before {
content: "\e6a4";
}
.uniui-link:before {
content: "\e6a5";
}
.uniui-notification:before {
content: "\e6a6";
}
.uniui-staff:before {
content: "\e6a7";
}
.uniui-vip:before {
content: "\e6a8";
}
.uniui-folder-add:before {
content: "\e6a9";
}
.uniui-tune:before {
content: "\e6aa";
}
.uniui-auth:before {
content: "\e6ab";
}
.uniui-person:before {
content: "\e699";
}
.uniui-email-filled:before {
content: "\e69a";
}
.uniui-phone-filled:before {
content: "\e69b";
}
.uniui-phone:before {
content: "\e69c";
}
.uniui-email:before {
content: "\e69e";
}
.uniui-personadd:before {
content: "\e69f";
}
.uniui-chatboxes-filled:before {
content: "\e692";
}
.uniui-contact:before {
content: "\e693";
}
.uniui-chatbubble-filled:before {
content: "\e694";
}
.uniui-contact-filled:before {
content: "\e695";
}
.uniui-chatboxes:before {
content: "\e696";
}
.uniui-chatbubble:before {
content: "\e697";
}
.uniui-upload-filled:before {
content: "\e68e";
}
.uniui-upload:before {
content: "\e690";
}
.uniui-weixin:before {
content: "\e691";
}
.uniui-compose:before {
content: "\e67f";
}
.uniui-qq:before {
content: "\e680";
}
.uniui-download-filled:before {
content: "\e681";
}
.uniui-pyq:before {
content: "\e682";
}
.uniui-sound:before {
content: "\e684";
}
.uniui-trash-filled:before {
content: "\e685";
}
.uniui-sound-filled:before {
content: "\e686";
}
.uniui-trash:before {
content: "\e687";
}
.uniui-videocam-filled:before {
content: "\e689";
}
.uniui-spinner-cycle:before {
content: "\e68a";
}
.uniui-weibo:before {
content: "\e68b";
}
.uniui-videocam:before {
content: "\e68c";
}
.uniui-download:before {
content: "\e68d";
}
.uniui-help:before {
content: "\e679";
}
.uniui-navigate-filled:before {
content: "\e67a";
}
.uniui-plusempty:before {
content: "\e67b";
}
.uniui-smallcircle:before {
content: "\e67c";
}
.uniui-minus-filled:before {
content: "\e67d";
}
.uniui-micoff:before {
content: "\e67e";
}
.uniui-closeempty:before {
content: "\e66c";
}
.uniui-clear:before {
content: "\e66d";
}
.uniui-navigate:before {
content: "\e66e";
}
.uniui-minus:before {
content: "\e66f";
}
.uniui-image:before {
content: "\e670";
}
.uniui-mic:before {
content: "\e671";
}
.uniui-paperplane:before {
content: "\e672";
}
.uniui-close:before {
content: "\e673";
}
.uniui-help-filled:before {
content: "\e674";
}
.uniui-paperplane-filled:before {
content: "\e675";
}
.uniui-plus:before {
content: "\e676";
}
.uniui-mic-filled:before {
content: "\e677";
}
.uniui-image-filled:before {
content: "\e678";
}
.uniui-locked-filled:before {
content: "\e668";
}
.uniui-info:before {
content: "\e669";
}
.uniui-locked:before {
content: "\e66b";
}
.uniui-camera-filled:before {
content: "\e658";
}
.uniui-chat-filled:before {
content: "\e659";
}
.uniui-camera:before {
content: "\e65a";
}
.uniui-circle:before {
content: "\e65b";
}
.uniui-checkmarkempty:before {
content: "\e65c";
}
.uniui-chat:before {
content: "\e65d";
}
.uniui-circle-filled:before {
content: "\e65e";
}
.uniui-flag:before {
content: "\e65f";
}
.uniui-flag-filled:before {
content: "\e660";
}
.uniui-gear-filled:before {
content: "\e661";
}
.uniui-home:before {
content: "\e662";
}
.uniui-home-filled:before {
content: "\e663";
}
.uniui-gear:before {
content: "\e664";
}
.uniui-smallcircle-filled:before {
content: "\e665";
}
.uniui-map-filled:before {
content: "\e666";
}
.uniui-map:before {
content: "\e667";
}
.uniui-refresh-filled:before {
content: "\e656";
}
.uniui-refresh:before {
content: "\e657";
}
.uniui-cloud-upload:before {
content: "\e645";
}
.uniui-cloud-download-filled:before {
content: "\e646";
}
.uniui-cloud-download:before {
content: "\e647";
}
.uniui-cloud-upload-filled:before {
content: "\e648";
}
.uniui-redo:before {
content: "\e64a";
}
.uniui-images-filled:before {
content: "\e64b";
}
.uniui-undo-filled:before {
content: "\e64c";
}
.uniui-more:before {
content: "\e64d";
}
.uniui-more-filled:before {
content: "\e64e";
}
.uniui-undo:before {
content: "\e64f";
}
.uniui-images:before {
content: "\e650";
}
.uniui-paperclip:before {
content: "\e652";
}
.uniui-settings:before {
content: "\e653";
}
.uniui-search:before {
content: "\e654";
}
.uniui-redo-filled:before {
content: "\e655";
}
.uniui-list:before {
content: "\e644";
}
.uniui-mail-open-filled:before {
content: "\e63a";
}
.uniui-hand-down-filled:before {
content: "\e63c";
}
.uniui-hand-down:before {
content: "\e63d";
}
.uniui-hand-up-filled:before {
content: "\e63e";
}
.uniui-hand-up:before {
content: "\e63f";
}
.uniui-heart-filled:before {
content: "\e641";
}
.uniui-mail-open:before {
content: "\e643";
}
.uniui-heart:before {
content: "\e639";
}
.uniui-loop:before {
content: "\e633";
}
.uniui-pulldown:before {
content: "\e632";
}
.uniui-scan:before {
content: "\e62a";
}
.uniui-bars:before {
content: "\e627";
}
.uniui-cart-filled:before {
content: "\e629";
}
.uniui-checkbox:before {
content: "\e62b";
}
.uniui-checkbox-filled:before {
content: "\e62c";
}
.uniui-shop:before {
content: "\e62f";
}
.uniui-headphones:before {
content: "\e630";
}
.uniui-cart:before {
content: "\e631";
}

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

Loading…
Cancel
Save