Compare commits

...

287 Commits

Author SHA1 Message Date
xuesinan 5b577b02b0 feat: 修改一些东西 3 years ago
xuesinan bfc9684325 fix: 获取任务接口参数 3 years ago
xuesinan 714ba49df1 fix: 切换时间轴模式更新 3 years ago
xuesinan 56fa411897 fix: 返回之后重新打开未刷新 3 years ago
xuesinan e075321690 feat: 删除无用代码 3 years ago
xuesinan 1fe5eb6d6c feat: 时间轴优化 3 years ago
xuesinan f8a333d45d feat: 切换时间轴模式按钮变小 3 years ago
xuesinan 99aa56584c feat: 重复数据删除 3 years ago
xuesinan 0c3bdb0cb8 fix: tall4点击二级项目没反应 3 years ago
xuesinan 919a44ebe3 feat: 隐藏软键盘 3 years ago
xuesinan ae5f526665 feat: 登录页增加数字键盘 3 years ago
xuesinan 5f76e78c74 feat: 交付物2 3 years ago
xuesinan 6b39979d06 feat: 交付物2 3 years ago
xuesinan c063de843f feat: 点击滚动到对应位置 3 years ago
xuesinan 51db122d45 feat: 交付物2 3 years ago
xuesinan 864b0808b4 feat: 交付物2 3 years ago
xuesinan 85749b3d69 fix: 项目列表长按之后位置不对 3 years ago
xuesinan fe5fe4e7d0 feat: 增加位置信息并传参给内嵌插件 3 years ago
xuesinan 3c26b5086d fix: 项目列表不能正常滚动 3 years ago
xuesinan 5a295c838a feat: 首页增加版本号 3 years ago
xuesinan 20da3cfb3e fix: 长按移动项目改变层级关系 3 years ago
xuesinan 7a05a0c7d8 fix: 长按拖动项目 3 years ago
xuesinan c25319a441 feat: 长按改变层级 3 years ago
xuesinan 586a3f0bea feat: 导入子项目、交付物增加角色id、时间轴 3 years ago
xuesinan 02d933e62b fix: 小红点、上传项目刷新项目列表 3 years ago
xuesinan f43d4ba31c feat: 删除项目 3 years ago
xuesinan fb6551188c feat: 首页下拉刷新 3 years ago
xuesinan f0f158dfff fix: 小红点监听 3 years ago
xuesinan 78e31c59be feat: lwbs跳转 3 years ago
xuesinan 0e96e539ab feat: 插件详情页 3 years ago
xuesinan a01de0945b feat: 时间轴版本1 3 years ago
xuesinan c612768a93 feat: 时间轴版本2、根据服务查询项目、区分不同服务 3 years ago
xuesinan bee8705e5e feat: 根据id查找插件配置信息 3 years ago
xuesinan 9010839b95 fix: 插件id重复的显示问题 3 years ago
xuesinan 29e4655d72 fix: 返回错误码不提示 3 years ago
xuesinan f6a8fcefe6 fix: 如果插件列表为空 3 years ago
xuesinan f79918b249 fix: 消息id 3 years ago
xuesinan 86d3fba2d6 fix: 解决打卡插件下拉选项不显示的问题 3 years ago
xuesinan 1e0f50c6d8 fix: renderjs打包h5不触发change事件 3 years ago
xuesinan 04fddb7293 fix: 未登录时查询成员列表出错,导致角色任务获取出错问题 3 years ago
xuesinan d9ea2e8484 feat: app导出 3 years ago
xuesinan 88fdc48fd8 fix: 防止重复 3 years ago
xuesinan fecf00c34d feat: 项目列表小红点 3 years ago
xuesinan 5ed569182a fix: 时间轴小红点、导出 3 years ago
xuesinan eade2dde8d fix: 没有新消息双击按照上一个角色的任务id跳转的问题 3 years ago
xuesinan 880023ad04 fix: 交付物消息提示 3 years ago
xuesinan 2625734dcd feat: 上传新项目结果提示 3 years ago
xuesinan 760888b4c1 fix: 时间轴tab切换角色任务错乱 3 years ago
xuesinan 6141ff9c79 fix: 时间轴tab切换数据不更新 3 years ago
xuesinan c1e12bca90 fix: 时间轴数据错乱 3 years ago
xuesinan a63dfa472d fix: 顶部状态栏不显示问题 3 years ago
xuesinan 42b31a7137 style: 添加注释 3 years ago
xuesinan 0d465a2615 fix: 时间轴方案 3 years ago
xuesinan 77fe17f9f7 fix: 切换项目存储的任务id未清空 3 years ago
xuesinan ef3318719b fix: 小红点修复 3 years ago
xuesinan 11923f3011 feat: 小红点显示逻辑 3 years ago
xuesinan 996a1ca778 feat: socket 3 years ago
xuesinan 1dd3b4bca9 fix: 切换项目任务清空 3 years ago
xuesinan 29d717308f feat: 查找任务时,返回交付物和财务条插件初始数据 3 years ago
xuesinan e15123a542 feat: 插件渲染 3 years ago
xuesinan e736d050c0 feat: 时间轴新策略 3 years ago
xuesinan e25218be75 feat: 时间轴新策略 3 years ago
xuesinan a9bc53ae3d feat: 刻度模式时间轴 3 years ago
xuesinan be384569b0 feat: 合并分支 3 years ago
xuesinan a55d08e8f1 fix: 修复bug 3 years ago
xuesinan 48ee8cb915 fix: 上查下查 3 years ago
xuesinan 1933e1bd66 fix: 向上查向下查 3 years ago
xuesinan 002f270ef7 fix: 上查下查 3 years ago
wally 885c6e2b65 feat: 更新了工作台的图片 4 years ago
wally fe54729fc7 fix(render): 修复了代码片段不能在app上显示的问题 4 years ago
wally 3a89de19fd refactor: APP注释了Render Component; render.vue换成vue2语法 4 years ago
wally fdd668f270 refactor: 重构render.vue 4 years ago
wally d11b35af38 refactor: 细节调整; 4 years ago
wally 79c3051f42 refactor: 细节调整;交付物记录没有数据显示empty 4 years ago
wally e9afef57fe style: 移除财务审计统计插件的DEBUG标识 4 years ago
wally 5fe96de3c7 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
wally 9c487ac6de refactor(财务条): 财务审批跳转路径修改 4 years ago
xuesinan 01388e0b57 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
xuesinan f267c72779 fix: 工作台图片app端不显示 4 years ago
wally 0e14ba91ec refactor: 为了演示所有人都能看到交付物插件 4 years ago
wally ecb943e57b fix: 修复app打开详情页失败的问题 4 years ago
xuesinan 6922f24084 fix: 退出登录 4 years ago
xuesinan 77a137eb95 feat: 时间轴优化 4 years ago
xuesinan f372036fac fix: 解决冲突 4 years ago
xuesinan 17869db2f2 feat: 刷新页面store数据清空 4 years ago
Min5203 e07be5c657 Merge branch 'feat' of http://101.201.226.163:3000/TALL/TALL-MUI-4 into feat 4 years ago
Min5203 d80f19eb67 refactor: 个人和ui,项目资源管理,域资源管理跳转页面完善 4 years ago
wally 4ace5a9304 refactor: 给财务条传参修改为detailId 4 years ago
wally a43fa834dd style: 细节调整 4 years ago
wally a9d14c3c71 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
wally c546659fba fix: render 修复不显示内嵌插件的问题 4 years ago
xuesinan 852e776e91 fix: 解决冲突 4 years ago
xuesinan 9146b40c91 feat: 插件调用 4 years ago
song e69d233f92 feat: 添加财务申请详情页 4 years ago
song dfb145a2dc perf: 解决冲突 4 years ago
song 1cbb2ac121 fix: 拍照上传交付物 4 years ago
wally 5d4c1beb08 feat: 文件上传添加APP的条件判断 4 years ago
wally b07621e87e fix: 交付物未上传显示小红点 4 years ago
wally 2f8573bb3c Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
wally ea78a2f45b refactor: 交付物审核显示天剑增加;增加小红点 4 years ago
xuesinan 6c6ea6e95b feat: 广告页时长 4 years ago
xuesinan 72b35c572e Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
xuesinan 25e1a86692 feat: 登录页标题 4 years ago
Min5203 eca7204a42 Merge branch 'feat' of http://101.201.226.163:3000/TALL/TALL-MUI-4 into feat 4 years ago
Min5203 3feee3f447 refactor: 插件错误名称修改和id分配 4 years ago
xuesinan 83a14c40b7 style: 重排代码格式、删除打印 4 years ago
xuesinan 20c0bfb864 fix: 解决冲突 4 years ago
xuesinan 31f3dc780e feat: 小绿点隐藏 4 years ago
wally 905f86cd93 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
wally c7b8c2aac8 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
song fad37d679a Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
song 742720d0ee feat: 跳转财务详情 4 years ago
wally dc9fcc9db8 refactor: 修改看财务条api地址 4 years ago
Min5203 d3e12a8a0a Merge branch 'feat' of http://101.201.226.163:3000/TALL/TALL-MUI-4 into feat 4 years ago
Min5203 c3caad0377 refactor: 个人终端和ui配置插件 ,域资源管理插件,版本管理插件 4 years ago
xuesinan 6e434e6bd9 feat: 工作台按钮 4 years ago
xuesinan d9e25fcdc8 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
xuesinan d749dfb8cd fix: 广告页不显示 4 years ago
wally fa92a11d7d feat: 给财务条详情页传参 4 years ago
jun feng 5ff7706376 feat: 给财务条传参数 4 years ago
xuesinan b674733790 feat: 工作台功能 4 years ago
xuesinan 04bd5a788a fix: 解决冲突 4 years ago
xuesinan 5935a3dd61 fix: 查询查件详情 4 years ago
wally 7d16f4a49f refactor: 审核添加交付物名称 4 years ago
xuesinan 7b02efcbf2 fix: 获取c插件信息 4 years ago
xuesinan 3e755768e2 style: 删除打印 4 years ago
xuesinan 8dba578b0c fix: 解决默认角色不是第一个时显示出错问题 4 years ago
xuesinan 527b0e5508 feat: 登录按钮 4 years ago
xuesinan 83866e5d09 fix: 登录验证码获取 4 years ago
xuesinan 108e32293c fix: 登录页验证码获取 4 years ago
wally ab4f806122 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
wally ee1817562b feat: 细节调整;解开查所有成员的api 4 years ago
xuesinan b63ade50c2 fix: 广告页、引导页 4 years ago
xuesinan 542d714e6b fix: 查询插件 4 years ago
xuesinan 90c1699301 refactor: 隐藏项目详情页缓存 4 years ago
xuesinan 08bdf74514 feat: 插件api 4 years ago
xuesinan 7de419da1a feat: 登录图标、日历页今日 4 years ago
xuesinan f30bac2dbf feat: 状态栏、导入 4 years ago
xuesinan 9685d55ba5 feat: 缓存改变 4 years ago
xuesinan 2be7903127 feat: 广告页、引导页改为组件 4 years ago
xuesinan b31fa14d9f feat: app端请求路径不对 4 years ago
xuesinan 81d2500a07 feat: 时间轴调整 4 years ago
xuesinan cc8004b325 feat: 服务、插件缓存、导入选择服务列表、 4 years ago
xuesinan c532a934ed feat: 解决时间轴日常任务不显示问题 4 years ago
xuesinan b68272da72 feat: 域名配置 4 years ago
xuesinan fe87d006d2 feat: 广告页、引导页 4 years ago
xuesinan d173a4f834 refactor: 合并deliver分支到feat 4 years ago
xuesinan 1c89806d29 feat: 广告页、引导页 4 years ago
xuesinan 1a835f1c6d feat: 设置项目域名 4 years ago
wally 414106a027 feat(财务): 细节调整;根据任务获取财务条信息 4 years ago
wally 7411d3ad6e style: 插件样式调整 4 years ago
wally 053ac31ab0 refactor(api): 调整mock api放入mock.js下;main中加入环境变量的判断 4 years ago
wally 03a1cdbe2f refactor: 重构财务条组件;添加财务mock 4 years ago
wally daa59f1e47 feat(deliver 交付物): 点击交付物链接webview打开链接 4 years ago
wally 25ccd36e14 refactor(交付物): 调整交付物细节;完善逻辑 4 years ago
wally 5fd88893d0 fix(交付物): 重构交付物审核部分,修复审核bug 4 years ago
wally d7c6e51e64 refactor: 交付物代码整理重构 未完 4 years ago
Min5203 b142651616 refactor: 调整进度条样式 4 years ago
Min5203 8322e9243f feat: 财务条插件的进度条和上方悬浮按钮界面 4 years ago
wally 3ec4ca244c Merge branch 'deliver' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into deliver 4 years ago
wally 435c0bd814 fix(交付物): 修复检查人选择组件之间相互影响的bug 4 years ago
Min5203 915aa06431 refactor: 审核记录查看 4 years ago
Min5203 d8a43286fb Merge branch 'deliver' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into deliver 4 years ago
Min5203 fc4ef13207 refactor: 审核历史记录查看 4 years ago
wally 7f9cf1f0f5 refactor: 细节调整 4 years ago
Min5203 121d43fe3b refactor: 审核记录查看 4 years ago
wally ebf678fa4a style: 细节调整 4 years ago
wally 759ef524fb style: 细节调整 4 years ago
wally 85bb167efb Merge branch 'deliver' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into deliver 4 years ago
wally 4d901ac95d refactor: 提取deliver store;细节调整 4 years ago
Min5203 749cb105d3 refactor: 查看提交历史记录页面数据同步更新 4 years ago
xuesinan 3a61439d37 feat: get请求 4 years ago
Min5203 43ae60495a refactor: 审查接口核对完成 4 years ago
xuesinan 02fb4bf5e3 feat: 刷新token重新运行api 4 years ago
Min5203 f222bdf2f6 refactor: 提交交付物,修改交付物名称,查看交付物历史记录接口完成 4 years ago
Min5203 d9530bc2f1 Merge branch 'deliver' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into deliver 4 years ago
Min5203 7c08530f96 refactor: 提交交付物,查看提交记录,修改交付物标题的接口核对完成 4 years ago
xuesinan 4aa76ff1ad feat: 引导页、广告页 4 years ago
wally b56ff1a865 Merge branch 'deliver' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into deliver 4 years ago
wally 3d1c4637a2 refactor: 细节调整 4 years ago
Min5203 ddbb04cad9 refactor: 修改错误单词‘confirmDeleDte’ 4 years ago
Min5203 db96431312 feat: 审核通过与驳回功能,修改变量和重查代码 4 years ago
Min5203 03a7c35d72 feat: 审核插件的通过与驳回功能 4 years ago
wally ce808c42b4 refactor: deliver检查人重构;更新真实数据的检查人 4 years ago
wally 880cf7c053 fix: 修复p-deliver报错taskRef的问题 4 years ago
xuesinan 8f16ae1c9d feat: token过期策略 4 years ago
Min5203 4f2815f07a refactor: 审核插件的基本信息展示 4 years ago
Min5203 018ba8a6b7 Merge branch 'deliver' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into deliver 4 years ago
Min5203 aa4f17fddb feat: 审核插件的基本信息展示 4 years ago
wally 5ae68e2ac8 feat: 获取交付物信息 4 years ago
wally 568115cda7 docs: deliver http 文件更新 4 years ago
wally 0b7e6ab93c docs: 添加交付物http测试文件 4 years ago
xuesinan bb5c0e36ed feat: 主体颜色 4 years ago
xuesinan 9871356013 feat: 设置状态栏 4 years ago
Min5203 3d58c15a54 refactor: 完善历史记录页面和修改插件的TODO 4 years ago
xuesinan a384f0500d Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
xuesinan 0b4ae7232d fix: 日历列表H5在手机端不显示 4 years ago
song 6e5dc5dade Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
song 12384f930f feat: 将时间轴改成swiper滑动 4 years ago
xuesinan aab6489113 feat: 未登录调用项目列表接口 4 years ago
wally 5f4d47b14b refactor: 交付物插件代码审查 4 years ago
xuesinan 3f60cf8771 feat: 获取手机唯一码 4 years ago
wally e7d9c0a06e Merge branch 'deliver' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into deliver 4 years ago
Min5203 176b7384a6 Merge branch 'deliver' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into deliver 4 years ago
Min5203 99fb88e18f refactor: 修改插件名的输入框和查看历史记录 4 years ago
wally 87ae00d438 style: 交付物相关细节调整 4 years ago
xuesinan 489e218f6c fix: 子组件传参 4 years ago
xuesinan b1d6b72f51 fix: 解决冲突 4 years ago
xuesinan ad0ce75f5a fix: 项目列表排序 4 years ago
wally 4f7cdd9820 Merge branch 'deliver' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into deliver 4 years ago
Min5203 eb02b72f96 refactor: 原有功能提交别的分支 4 years ago
wally 7524b24e11 fix: 上个提交导致的bug 4 years ago
Min5203 ebc01eb964 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
Min5203 84390d5f97 feat: 插件的填写提交,编辑与删除 4 years ago
Min5203 5452e6d29f feat: 插件的填写提交,编辑与删除 4 years ago
xuesinan e676cf07f6 feat: 登录、日历页小绿点、二级项目列表 4 years ago
Min5203 d461252034 feat: 插件的填写与提交,修改与删除 4 years ago
xuesinan 68e9189bbf feat: 注册、用户协议 4 years ago
xuesinan 20a468a48c test: 手机号登录 4 years ago
xuesinan 5ede115cf9 test: 手机号登录 4 years ago
song 52dcfcb159 style: 更新代码 4 years ago
song aa6093a8c8 style: 更新代码 4 years ago
xuesinan c4c78c864a fix: 解决冲突 4 years ago
xuesinan a198527531 feat: 手机号登录 4 years ago
wally f41cd89aa3 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
wally d38262e010 ci: ci update 4 years ago
song c60cd9f31b fix: 解决冲突 4 years ago
song fb5e86b6af feat: 插件面板分开显示 4 years ago
wally 342ac74628 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
wally 8cddc7b3a3 ci: drone 4 years ago
xuesinan 8f455da94c feat: 手机号登录 4 years ago
xuesinan 050b12a130 fix: 删除多余的引入 4 years ago
xuesinan 5374cdbb6d fix: 解决冲突 4 years ago
xuesinan 0486e98e9b refactor: 项目列表 4 years ago
wally d25f2a7ec8 ci: 测试ci' 4 years ago
wally 6ab95f8a81 ci: 测试ci 4 years ago
wally 63ec5a3091 ci: 更新drone.yml 4 years ago
wally dfa3ff8c30 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
wally a57d598b57 ci: 更新drone.yml 4 years ago
xuesinan e475a5150c fix: 解决冲突 4 years ago
xuesinan 88cf48d86d feat: 项目列表新 4 years ago
song 53c6b90990 fix: 插件接口修改 4 years ago
song dcb0079843 fix: 解决warning 4 years ago
song 31fed9bc61 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
song 98abdf6ae5 fix: 时间轴任务 4 years ago
xuesinan 565585b07a feat: 手机号登录 4 years ago
xuesinan 78aa50761f fix: defineExpose, defineEmits不需要引入 4 years ago
xuesinan 902caccfec fix: defineExpose, defineEmits不需要引入 4 years ago
wally f5b52e355d ci: 修改.drone.yml 4 years ago
wally e6c3b92d4d Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
wally 9fbae8997f ci: 添加drone.yml 4 years ago
xuesinan 5f64204e03 feat: 表单验证 4 years ago
xuesinan 8f3bc1ea25 feat: 表单验证 4 years ago
song a88c9331ce fix: 解决冲突 4 years ago
song 8b1b380a6b feat: 时间轴展示 4 years ago
xuesinan 95fdeac461 feat: 用户名密码登录 4 years ago
xuesinan ebf456e47c feat: 账户名密码登录 4 years ago
wally 91757586d3 feat(theme): theme demo 4 years ago
wally b20d3f0300 fix(createTask): 修复createTask v-model的问题 4 years ago
wally b3f16ff1e2 feat(project): 日常任务面板添加 4 years ago
wally db256d1c62 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
wally 9120d541a0 fix(app.vue): 修复获取token报错的问题 4 years ago
xuesinan 1c785547b1 fix: 修复一些内容 4 years ago
xuesinan 3cdb1ce6c2 fix: 修复一些内容 4 years ago
wally 0b05767d92 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
wally bdd5f87c89 style: 细节调整 4 years ago
song 72dad2bed2 feat: 添加 timeline 4 years ago
wally 1792e1fe39 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
wally 2457a87c5c refactor: project init 重构 4 years ago
song a95d00559b feat: 时间轴接口 4 years ago
wally c7bf2df562 refactor: 重构project init 部分 4 years ago
song e926b751fb feat: 时间轴页面 4 years ago
xuesinan 3beb05e3cd feat: 项目操作面板 4 years ago
xuesinan 30c42d3cc3 feat: 解决冲突 4 years ago
xuesinan a52e6d5a4d feat: 项目列表 4 years ago
song 1a47783aa4 feat: 添加上传按钮 4 years ago
song 0dd443b3c1 perf: 更新代码 4 years ago
wally db9602b85f style: calender格式及细节调整 4 years ago
song 219202a254 Merge branch 'feat' of ssh://101.201.226.163:50022/TALL/TALL-MUI-4 into feat 4 years ago
song 1b46a91b6e feat: 日历页添加 4 years ago
wally 0c08089256 chore: editorconfig update 4 years ago
song 94e4a7d644 feat: 添加日历 4 years ago
song 392c8ccbc0 feat: 更新代码 4 years ago
xuesinan 561c8e6639 feat: 日历页首页 4 years ago
xuesinan 970cf9a364 feat: app.vue 4 years ago
xuesinan 1b3efd8026 feat: 使用uview完成api请求 4 years ago
xuesinan 12ed2ad63c feat: vue3 4 years ago
  1. 4
      .editorconfig
  2. 56
      .eslintrc.js
  3. 4
      .hbuilderx/launch.json
  4. 361
      App.vue
  5. 276
      CHANGELOG.md
  6. 3
      README.md
  7. 12
      apis/finance.js
  8. 28
      apis/mock.js
  9. 41
      apis/plugin.js
  10. 16
      apis/project.js
  11. 12
      apis/role.js
  12. 44
      apis/tall.js
  13. 22
      apis/task.js
  14. 13
      apis/wbs.js
  15. 21
      common/js/config.js
  16. 48
      common/js/dc-clipboard/clipboard.js
  17. 4679
      common/styles/tailwind.scss
  18. 88
      common/styles/theme/default.scss
  19. 5
      common/styles/theme/index.scss
  20. 11
      common/styles/theme/test.scss
  21. 104
      components/Adv/Adv.vue
  22. 461
      components/Calendar/Calendar.vue
  23. 136
      components/Calendar/generateDates.js
  24. 89
      components/ChooseChecker/ChooseChecker.vue
  25. 13
      components/DeliverLink/DeliverLink.vue
  26. 110
      components/Globals/Globals.vue
  27. 76
      components/Guide/Guide.vue
  28. 19
      components/ModalBox/ModalBox.vue
  29. 89
      components/Plugin/Plugin.vue
  30. 731
      components/PrettyExchange/PrettyExchange - 副本.vue
  31. 752
      components/PrettyExchange/PrettyExchange.vue
  32. 693
      components/PrettyExchange/PrettyExchange222.vue
  33. 196
      components/Projects/ProjectItem.vue
  34. 64
      components/Projects/Projects.vue
  35. 182
      components/Render/Render.vue
  36. 66
      components/Reviewer/Reviewer.vue
  37. 127
      components/ReviewerSecond/ReviewerSecond.vue
  38. 330
      components/Roles/Roles.vue
  39. 173
      components/Skeleton/Skeleton.vue
  40. 13
      components/Theme/Theme.vue
  41. 126
      components/TimeLine 复制/TimeLine.vue
  42. 38
      components/TimeLine 复制/component/Barrier.vue
  43. 177
      components/TimeLine 复制/component/TaskTools.vue
  44. 132
      components/TimeLine 复制/component/TimeBox.vue
  45. 317
      components/TimeLine 复制/component/TimeStatus.vue
  46. 0
      components/TimeLine 复制/component/Title.vue
  47. 80
      components/TimeLine/TimeLine.vue
  48. 38
      components/TimeLine/component/Barrier.vue
  49. 177
      components/TimeLine/component/TaskTools.vue
  50. 145
      components/TimeLine/component/TimeBox.vue
  51. 317
      components/TimeLine/component/TimeStatus.vue
  52. 0
      components/TimeLine/component/Title.vue
  53. 82
      components/Tips/Tips.vue
  54. 232
      components/Title/Title.vue
  55. 466
      components/Title/components/CreateTask copy.vue
  56. 583
      components/Title/components/CreateTask.vue
  57. 210
      components/Title/components/ShareProject.vue
  58. 93
      components/Upload/Upload.vue
  59. 316
      components/master-keyboard/master-keyboard.scss
  60. 312
      components/master-keyboard/master-keyboard.vue
  61. 22
      components/uni-popup/message.js
  62. 23
      components/uni-popup/popup.js
  63. 246
      components/uni-popup/uni-popup-dialog.vue
  64. 115
      components/uni-popup/uni-popup-message.vue
  65. 171
      components/uni-popup/uni-popup-share.vue
  66. 289
      components/uni-popup/uni-popup.vue
  67. 226
      components/zwp-ring-timing/zwp-ring-timing.vue
  68. 9
      config/deliver.js
  69. 20
      config/index.js
  70. 97
      config/plugin.js
  71. 2
      config/task.js
  72. 17
      config/time.js
  73. 14
      hooks/project/useGenerateWebviewParam.js
  74. 476
      hooks/project/useGetTasks - 副本 (2).js
  75. 343
      hooks/project/useGetTasks - 副本 (3).js
  76. 263
      hooks/project/useGetTasks - 副本.js
  77. 407
      hooks/project/useGetTasks - 最新的.js
  78. 317
      hooks/project/useGetTasks.js
  79. 336
      hooks/project/useGetTasks222.js
  80. 143
      hooks/project/useInit.js
  81. 7
      hooks/theme/useTheme.js
  82. 37
      hooks/user/useGetToken.js
  83. 10
      hooks/user/useGetUserIdFromLocal.js
  84. 277
      hooks/user/userMixin - 数字键盘.js
  85. 275
      hooks/user/userMixin.js
  86. 11
      index.html
  87. 79
      main.js
  88. 80
      manifest.json
  89. 115
      package.json
  90. 149
      pages.json
  91. 5
      pages/404/404.vue
  92. 13
      pages/audit/audit.vue
  93. 106
      pages/business/business.vue
  94. 52
      pages/checkLog/checkLog.vue
  95. 23
      pages/detailWebview/detailWebview.vue
  96. 13
      pages/exarresources/exarresources.vue
  97. 94
      pages/guide/adv.vue
  98. 75
      pages/guide/guide.vue
  99. 274
      pages/index/index.vue
  100. 142
      pages/project/project 复制.vue

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"

361
App.vue

@ -1,36 +1,335 @@
<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'));
// });
// }
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(); //
this.getLocation(); //
const token = this.$store.state.user.token || this.$storage.getStorageSync('anyringToken') || '';
if (token) {
this.$store.commit('user/setToken', token);
}
// 广
// #ifdef APP-PLUS
uni.$storage.setStorageSync('isOpenApp', true);
this.$store.commit('setIsOpenApp', true);
// App
const firstOpenApp = uni.$storage.getStorageSync('firstOpenApp');
this.$store.commit('setFirstOpenApp');
this.getGuide(0);
this.getGuide(1);
// #endif
//
if (!uni.$storage.getStorageSync('businessPlugin')) {
this.getServices();
this.getPlugins();
}
setInterval(() => {
this.getServices();
this.getPlugins();
}, 60000);
// if (!token) {
// this.$ui.showToast(', ');
// // TODO:
// return;
// }
// await this.syncLocalDataToStore('1217647686598135808'); // localStoragestore
// const token = await this.getToken();
// 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 getGuide(type) {
uni.$catchReq.getGuide(type, (err, data) => {
if (err) {
console.error('err: ', err);
} else {
type === 0 ? this.$store.commit('setGuide', data) : this.$store.commit('setAdvs', data);
}
});
},
/**
* 查询服务
*/
async getServices() {
this.$store.dispatch('getBusinessPlugin');
},
/**
* 查询插件
*/
async getPlugins() {
this.$store.dispatch('getAllPlugin');
},
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;
}
// 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);
},
});
},
getLocation() {
const that = this;
uni.getLocation({
type: 'wgs84',
success(res) {
that.$store.commit('setLongitude', res.longitude);
that.$store.commit('setLatitude', res.latitude);
},
});
},
//
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%;
}
.uni-swiper-slides {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.statbar {
width: 750rpx;
height: var(--status-bar-height);
.status_bar {
height: var(--status-bar-height);
width: 100%;
position: absolute;
}
}
/* #ifdef H5 */
.uni-modal {
position: fixed;
z-index: 999;
width: 80%;
max-width: 300px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #ffffff;
text-align: center;
border-radius: 3px;
overflow: hidden;
}
.uni-modal__bd {
padding: 1.3em 1.6em 1.3em;
min-height: 40px;
font-size: 15px;
line-height: 1.4;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
color: #999999;
max-height: 400px;
overflow-x: hidden;
overflow-y: auto;
}
.uni-modal__ft {
position: relative;
line-height: 48px;
font-size: 18px;
display: flex;
}
.uni-modal__btn {
display: block;
flex: 1;
color: #3cc51f;
text-decoration: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
position: relative;
cursor: pointer;
}
.uni-modal__ft:after {
content: ' ';
position: absolute;
left: 0;
top: 0;
right: 0;
height: 1px;
border-top: 1px solid #d5d5d6;
color: #d5d5d6;
transform-origin: 0 0;
transform: scaleY(0.5);
}
.uni-modal__btn:after {
content: ' ';
position: absolute;
left: 0;
top: 0;
width: 1px;
bottom: 0;
border-left: 1px solid #d5d5d6;
color: #d5d5d6;
transform-origin: 0 0;
transform: scaleX(0.5);
}
.uni-modal__btn_default {
color: #353535;
}
.uni-modal__btn_primary {
color: #007aff;
}
/* #endif */
</style>

276
CHANGELOG.md

@ -1,6 +1,278 @@
# 1.0.0 (2021-12-31)
# 1.0.0 (2022-09-09)
### 🌟 新功能
范围|描述|commitId
--|--|--
- | Initial commit | 52b8f49
- | 表单验证 | [8f3bc1e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/8f3bc1e)
- | 财务条插件的进度条和上方悬浮按钮界面 | [8322e92](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/8322e92)
财务 | 细节调整;根据任务获取财务条信息 | [414106a](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/414106a)
- | 插件的填写提交,编辑与删除 | [84390d5](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/84390d5)
- | 插件的填写与提交,修改与删除 | [d461252](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/d461252)
- | 插件调用 | [9146b40](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/9146b40)
- | 插件面板分开显示 | [fb5e86b](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/fb5e86b)
- | 插件详情页 | [0e96e53](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/0e96e53)
- | 插件渲染 | [e15123a](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/e15123a)
- | 插件api | [08bdf74](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/08bdf74)
- | 查找任务时,返回交付物和财务条插件初始数据 | [29d7173](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/29d7173)
- | 导入子项目、交付物增加角色id、时间轴 | [586a3f0](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/586a3f0)
- | 登录、日历页小绿点、二级项目列表 | [e676cf0](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/e676cf0)
- | 登录按钮 | [527b0e5](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/527b0e5)
- | 登录图标、日历页今日 | [7de419d](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/7de419d)
- | 登录页标题 | [25e1a86](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/25e1a86)
- | 登录页增加数字键盘 | [ae5f526](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/ae5f526)
- | 点击滚动到对应位置 | [c063de8](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/c063de8)
- | 服务、插件缓存、导入选择服务列表、 | [cc8004b](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/cc8004b)
- | 给财务条传参数 | [5ff7706](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/5ff7706)
- | 给财务条详情页传参 | [fa92a11](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/fa92a11)
- | 根据id查找插件配置信息 | [bee8705](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/bee8705)
- | 更新代码 | [392c8cc](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/392c8cc)
- | 更新了工作台的图片 | [885c6e2](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/885c6e2)
- | 工作台按钮 | [6e434e6](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/6e434e6)
- | 工作台功能 | [b674733](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/b674733)
- | 广告页、引导页 | [fe87d00](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/fe87d00)
- | 广告页、引导页 | [1c89806](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/1c89806)
- | 广告页、引导页改为组件 | [2be7903](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/2be7903)
- | 广告页时长 | [6c6ea6e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/6c6ea6e)
- | 缓存改变 | [9685d55](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/9685d55)
- | 获取交付物信息 | [5ae68e2](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/5ae68e2)
- | 获取手机唯一码 | [3f60cf8](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/3f60cf8)
- | 将时间轴改成swiper滑动 | [12384f9](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/12384f9)
- | 交付物2 | [5f76e78](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/5f76e78)
- | 交付物2 | [6b39979](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/6b39979)
- | 交付物2 | [51db122](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/51db122)
- | 交付物2 | [864b080](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/864b080)
- | 解决时间轴日常任务不显示问题 | [c532a93](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/c532a93)
- | 刻度模式时间轴 | [a9bc53a](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/a9bc53a)
- | 切换时间轴模式按钮变小 | [f8a333d](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/f8a333d)
- | 日历页首页 | [561c8e6](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/561c8e6)
- | 日历页添加 | [1b46a91](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/1b46a91)
- | 删除无用代码 | [e075321](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/e075321)
- | 删除项目 | [f43d4ba](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/f43d4ba)
- | 上传新项目结果提示 | [2625734](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/2625734)
- | 设置项目域名 | [1a835f1](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/1a835f1)
- | 设置状态栏 | [9871356](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/9871356)
- | 审核插件的基本信息展示 | [aa4f17f](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/aa4f17f)
- | 审核插件的通过与驳回功能 | [03a7c35](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/03a7c35)
- | 时间轴版本1 | [a01de09](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/a01de09)
- | 时间轴版本2、根据服务查询项目、区分不同服务 | [c612768](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/c612768)
- | 时间轴调整 | [81d2500](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/81d2500)
- | 时间轴接口 | [a95d005](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/a95d005)
- | 时间轴新策略 | [e736d05](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/e736d05)
- | 时间轴新策略 | [e25218b](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/e25218b)
- | 时间轴页面 | [e926b75](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/e926b75)
- | 时间轴优化 | [1fe5eb6](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/1fe5eb6)
- | 时间轴优化 | [77a137e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/77a137e)
- | 时间轴展示 | [8b1b380](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/8b1b380)
- | 使用uview完成api请求 | [1b3efd8](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/1b3efd8)
- | 手机号登录 | [a198527](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/a198527)
- | 手机号登录 | [8f455da](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/8f455da)
- | 手机号登录 | [565585b](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/565585b)
- | 首页下拉刷新 | [fb65511](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/fb65511)
- | 首页增加版本号 | [5a295c8](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/5a295c8)
- | 刷新页面store数据清空 | [17869db](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/17869db)
- | 刷新token重新运行api | [02fb4bf](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/02fb4bf)
- | 添加 timeline | [72dad2b](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/72dad2b)
- | 添加财务申请详情页 | [e69d233](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/e69d233)
- | 跳转财务详情 | [742720d](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/742720d)
- | 未登录调用项目列表接口 | [aab6489](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/aab6489)
- | 文件上传添加APP的条件判断 | [5d4c1be](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/5d4c1be)
- | 细节调整;解开查所有成员的api | [ee18175](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/ee18175)
- | 项目操作面板 | [3beb05e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/3beb05e)
- | 项目列表 | [a52e6d5](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/a52e6d5)
- | 项目列表小红点 | [fecf00c](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/fecf00c)
- | 项目列表新 | [88cf48d](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/88cf48d)
- | 小红点显示逻辑 | [11923f3](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/11923f3)
- | 小绿点隐藏 | [31f3dc7](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/31f3dc7)
- | 引导页、广告页 | [4aa76ff](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/4aa76ff)
- | 隐藏软键盘 | [919a44e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/919a44e)
- | 域名配置 | [b68272d](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/b68272d)
- | 增加位置信息并传参给内嵌插件 | [fe5fe4e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/fe5fe4e)
- | 长按改变层级 | [c25319a](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/c25319a)
- | 账户名密码登录 | [ebf456e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/ebf456e)
- | 重复数据删除 | [99aa565](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/99aa565)
- | 主体颜色 | [bb5c0e3](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/bb5c0e3)
- | 注册、用户协议 | [68e9189](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/68e9189)
- | 状态栏、导入 | [f30bac2](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/f30bac2)
- | app.vue | [970cf9a](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/970cf9a)
- | app导出 | [d9ea2e8](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/d9ea2e8)
- | app端请求路径不对 | [b31fa14](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/b31fa14)
deliver 交付物 | 点击交付物链接webview打开链接 | [daa59f1](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/daa59f1)
- | first commit | [8dc26de](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/8dc26de)
- | get请求 | [3a61439](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/3a61439)
- | lwbs跳转 | [78e31c5](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/78e31c5)
project | 日常任务面板添加 | [b3f16ff](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/b3f16ff)
- | socket | [996a1ca](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/996a1ca)
theme | theme demo | [9175758](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/9175758)
- | token过期策略 | [8f16ae1](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/8f16ae1)
- | vue3 | [12ed2ad](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/12ed2ad)
### 🎨 代码样式
范围|描述|commitId
--|--|--
- | 插件样式调整 | [7411d3a](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/7411d3a)
- | 更新代码 | [aa6093a](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/aa6093a)
- | 交付物相关细节调整 | [87ae00d](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/87ae00d)
- | 删除打印 | [3e75576](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/3e75576)
- | 添加注释 | [42b31a7](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/42b31a7)
- | 细节调整 | [a43fa83](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/a43fa83)
- | 细节调整 | [ebf678f](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/ebf678f)
- | 细节调整 | [759ef52](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/759ef52)
- | 细节调整 | [bdd5f87](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/bdd5f87)
- | 移除财务审计统计插件的DEBUG标识 | [e9afef5](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/e9afef5)
- | 重排代码格式、删除打印 | [83a14c4](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/83a14c4)
- | calender格式及细节调整 | [db9602b](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/db9602b)
### 🐛 Bug 修复
范围|描述|commitId
--|--|--
- | 插件接口修改 | [53c6b90](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/53c6b90)
- | 插件id重复的显示问题 | [9010839](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/9010839)
- | 查询插件 | [542d714](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/542d714)
- | 查询查件详情 | [5935a3d](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/5935a3d)
- | 登录页验证码获取 | [108e322](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/108e322)
- | 顶部状态栏不显示问题 | [a63dfa4](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/a63dfa4)
- | 返回错误码不提示 | [29e4655](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/29e4655)
- | 返回之后重新打开未刷新 | [56fa411](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/56fa411)
- | 防止重复 | [88fdc48](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/88fdc48)
- | 工作台图片app端不显示 | [f267c72](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/f267c72)
- | 广告页、引导页 | [b63ade5](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/b63ade5)
- | 广告页不显示 | [d749dfb](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/d749dfb)
- | 获取任务接口参数 | [bfc9684](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/bfc9684)
- | 获取c插件信息 | [7b02efc](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/7b02efc)
- | 交付物未上传显示小红点 | [b07621e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/b07621e)
- | 交付物消息提示 | [880023a](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/880023a)
交付物 | 修复检查人选择组件之间相互影响的bug | [435c0bd](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/435c0bd)
交付物 | 重构交付物审核部分,修复审核bug | [5fd8889](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/5fd8889)
- | 解决打卡插件下拉选项不显示的问题 | [86d3fba](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/86d3fba)
- | 解决默认角色不是第一个时显示出错问题 | [8dba578](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/8dba578)
- | 解决warning | [dcb0079](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/dcb0079)
- | 没有新消息双击按照上一个角色的任务id跳转的问题 | [eade2dd](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/eade2dd)
- | 拍照上传交付物 | [1cbb2ac](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/1cbb2ac)
- | 切换时间轴模式更新 | [714ba49](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/714ba49)
- | 切换项目存储的任务id未清空 | [77fe17f](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/77fe17f)
- | 切换项目任务清空 | [1dd3b4b](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/1dd3b4b)
- | 日历列表H5在手机端不显示 | [0b4ae72](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/0b4ae72)
- | 如果插件列表为空 | [f6a8fce](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/f6a8fce)
- | 删除多余的引入 | [050b12a](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/050b12a)
- | 上查下查 | [48ee8cb](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/48ee8cb)
- | 上查下查 | [002f270](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/002f270)
- | 上个提交导致的bug | [7524b24](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/7524b24)
- | 时间轴方案 | [0d465a2](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/0d465a2)
- | 时间轴任务 | [98abdf6](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/98abdf6)
- | 时间轴数据错乱 | [c1e12bc](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/c1e12bc)
- | 时间轴小红点、导出 | [5ed5691](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/5ed5691)
- | 时间轴tab切换角色任务错乱 | [760888b](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/760888b)
- | 时间轴tab切换数据不更新 | [6141ff9](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/6141ff9)
- | 退出登录 | [6922f24](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/6922f24)
- | 未登录时查询成员列表出错,导致角色任务获取出错问题 | [04fddb7](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/04fddb7)
- | 向上查向下查 | [1933e1b](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/1933e1b)
- | 项目列表不能正常滚动 | [3c26b50](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/3c26b50)
- | 项目列表排序 | [ad0ce75](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/ad0ce75)
- | 项目列表长按之后位置不对 | [85749b3](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/85749b3)
- | 消息id | [f79918b](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/f79918b)
- | 小红点、上传项目刷新项目列表 | [02d933e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/02d933e)
- | 小红点监听 | [f0f158d](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/f0f158d)
- | 小红点修复 | [ef33187](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/ef33187)
- | 修复一些内容 | [3cdb1ce](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/3cdb1ce)
- | 修复app打开详情页失败的问题 | [ecb943e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/ecb943e)
- | 修复bug | [a55d08e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/a55d08e)
- | 修复p-deliver报错taskRef的问题 | [880cf7c](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/880cf7c)
- | 长按拖动项目 | [7a05a0c](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/7a05a0c)
- | 长按移动项目改变层级关系 | [20da3cf](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/20da3cf)
- | 子组件传参 | [489e218](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/489e218)
app.vue | 修复获取token报错的问题 | [9120d54](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/9120d54)
createTask | 修复createTask v-model的问题 | [b20d3f0](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/b20d3f0)
- | defineExpose, defineEmits不需要引入 | [902cacc](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/902cacc)
- | render 修复不显示内嵌插件的问题 | [c546659](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/c546659)
render | 修复了代码片段不能在app上显示的问题 | [fe54729](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/fe54729)
- | renderjs打包h5不触发change事件 | [1e0f50c](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/1e0f50c)
- | tall4点击二级项目没反应 | [0c3bdb0](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/0c3bdb0)
### 📝 文档
范围|描述|commitId
--|--|--
- | 添加交付物http测试文件 | [0b7e6ab](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/0b7e6ab)
- | deliver http 文件更新 | [568115c](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/568115c)
### 📦 持续集成
范围|描述|commitId
--|--|--
- | 测试ci | [6ab95f8](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/6ab95f8)
- | 测试ci' | [d25f2a7](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/d25f2a7)
- | 更新drone.yml | [63ec5a3](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/63ec5a3)
- | 更新drone.yml | [a57d598](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/a57d598)
- | 添加drone.yml | [9fbae89](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/9fbae89)
- | 修改.drone.yml | [f5b52e3](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/f5b52e3)
- | ci update | [d38262e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/d38262e)
- | drone | [8cddc7b](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/8cddc7b)
### 🔧 测试
范围|描述|commitId
--|--|--
- | 手机号登录 | [5ede115](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/5ede115)
### 🔨 代码重构
范围|描述|commitId
--|--|--
财务条 | 财务审批跳转路径修改 | [9c487ac](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/9c487ac)
- | 插件错误名称修改和id分配 | [3feee3f](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/3feee3f)
- | 查看提交历史记录页面数据同步更新 | [749cb10](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/749cb10)
- | 调整进度条样式 | [b142651](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/b142651)
- | 个人和ui,项目资源管理,域资源管理跳转页面完善 | [d80f19e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/d80f19e)
- | 个人终端和ui配置插件 ,域资源管理插件,版本管理插件 | [c3caad0](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/c3caad0)
- | 给财务条传参修改为detailId | [4ace5a9](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/4ace5a9)
- | 交付物插件代码审查 | [5f4d47b](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/5f4d47b)
- | 交付物代码整理重构 未完 | [d7c6e51](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/d7c6e51)
交付物 | 调整交付物细节;完善逻辑 | [25ccd36](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/25ccd36)
- | 交付物审核显示天剑增加;增加小红点 | [ea78a2f](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/ea78a2f)
- | 审查接口核对完成 | [43ae604](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/43ae604)
- | 审核插件的基本信息展示 | [4f2815f](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/4f2815f)
- | 审核记录查看 | [915aa06](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/915aa06)
- | 审核记录查看 | [121d43f](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/121d43f)
- | 审核添加交付物名称 | [7d16f4a](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/7d16f4a)
- | 提交交付物,查看提交记录,修改交付物标题的接口核对完成 | [7c08530](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/7c08530)
- | 提交交付物,修改交付物名称,查看交付物历史记录接口完成 | [f222bdf](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/f222bdf)
- | 提取deliver store;细节调整 | [4d901ac](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/4d901ac)
- | 完善历史记录页面和修改插件的TODO | [3d58c15](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/3d58c15)
- | 为了演示所有人都能看到交付物插件 | [0e14ba9](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/0e14ba9)
- | 细节调整 | [7f9cf1f](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/7f9cf1f)
- | 细节调整 | [3d1c463](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/3d1c463)
- | 细节调整; | [d11b35a](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/d11b35a)
- | 细节调整;交付物记录没有数据显示empty | [79c3051](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/79c3051)
- | 项目列表 | [0486e98](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/0486e98)
- | 修改插件名的输入框和查看历史记录 | [99fb88e](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/99fb88e)
- | 修改错误单词‘confirmDeleDte’ | [ddbb04c](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/ddbb04c)
- | 修改看财务条api地址 | [dc9fcc9](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/dc9fcc9)
- | 隐藏项目详情页缓存 | [90c1699](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/90c1699)
- | 原有功能提交别的分支 | [eb02b72](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/eb02b72)
- | 重构财务条组件;添加财务mock | [03a1cdb](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/03a1cdb)
- | 重构project init 部分 | [c7bf2df](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/c7bf2df)
- | 重构render.vue | [fdd668f](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/fdd668f)
api | 调整mock api放入mock.js下;main中加入环境变量的判断 | [053ac31](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/053ac31)
- | APP注释了Render Component; render.vue换成vue2语法 | [3a89de1](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/3a89de1)
- | deliver检查人重构;更新真实数据的检查人 | [ce808c4](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/ce808c4)
- | project init 重构 | [2457a87](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/2457a87)
### 🚀 性能优化
范围|描述|commitId
--|--|--
- | 更新代码 | [0dd443b](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/0dd443b)
### chore
范围|描述|commitId
--|--|--
- | editorconfig update | [0c08089](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/0c08089)
范围|描述|commitId
--|--|--
- | Initial commit | [52b8f49](https://101.201.226.163:50022/ccsens_tall/TALL-MUI-4/commits/52b8f49)

3
README.md

@ -0,0 +1,3 @@
# 时物链条
[![Build Status](http://101.201.226.163:3001/api/badges/TALL/TALL-MUI-4/status.svg)](http://101.201.226.163:3001/TALL/TALL-MUI-4)

12
apis/finance.js

@ -0,0 +1,12 @@
import { computed } from 'vue';
import store from '@/store/index';
// const fullPath = store.getters['finance/fullPath'];
// console.log('fullPath: ', fullPath);
const domain = computed(() => store.state.domain);
export function setupFinance(app) {
uni.$u.api = { ...uni.$u.api } || {};
// 根据任务id获取财务条信息
uni.$u.api.getFinanceByTask = taskDetailId => uni.$u.post(`${domain.value}/finance/getByTask`, { taskDetailId });
}

28
apis/mock.js

@ -0,0 +1,28 @@
import Config from '@/common/js/config.js';
const { apiUrl } = Config;
const defaultwbs = `${apiUrl}/defaultwbs`;
export function setupMock(app) {
uni.$u.api = { ...uni.$u.api } || {};
// 删除交付物
uni.$u.api.deleteDeliver = param =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 100);
});
// uni.$u.api.getFinanceByTask = taskDetailId => new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve({
// financeId: '123',
// budget: 1000 * 1000,
// bonus: 200 * 1000,
// projectExpend: 500 * 1000,
// dailyExpend: 500 * 1000,
// });
// }, 100);
// });
}

41
apis/plugin.js

@ -0,0 +1,41 @@
import Config from '@/common/js/config.js';
import { computed } from 'vue';
import store from '@/store/index.js';
const { apiUrl } = Config;
// const defaultwbs = `${apiUrl}/defaultwbs`;
const domain = computed(() => store.state.domain);
// console.log('domain: ', domain.value);
export function setupPlugin(app) {
uni.$u.api = { ...uni.$u.api } || {};
// 获取插件信息
// uni.$u.api.getOtherPlugin = param =>
// uni.$u.post(`${apiUrl}/pluginshop/plugin/query?pluginId=${param.pluginId}&styleType=${param.styleType}`);
uni.$u.api.getOtherPlugin = param => uni.$u.post(`${apiUrl}/opt/business/businessPluginById`, param);
// 根据插件id获取服务config
uni.$u.api.getConfigInfo = param => uni.$u.post(`${apiUrl}/ptostall/business/byBusinessPluginId`, param);
// 查询子任务
uni.$u.api.findSonTask = param => uni.$u.post(`${domain.value}/task/findSonTask`, param);
// 查询子项目
uni.$u.api.findSonProject = param => uni.$u.post(`${domain.value}/project/findSonProject`, param);
// 提交交付物
uni.$u.api.saveDeliver = param => uni.$u.post(`${domain.value}/deliver/save`, param);
// 查询任务的交付物历史记录
uni.$u.api.queryDeliverOfTask = param => uni.$u.post(`${domain.value}/deliver/queryDeliverOfTask`, param);
// v4.0
// 根据任务id获取任务的交付物信息(已成功)
uni.$u.api.getDeliverByTaskId = (param, url) => uni.$u.post(`${url || domain.value}/deliver/getDeliver`, param);
// 提交交付物信息(已成功)
uni.$u.api.submitDeliverInfo = (param, url) => uni.$u.post(`${url || domain.value}/deliver/submitDeliver`, param);
// 查看交付物提交历史记录(已成功)
uni.$u.api.getDeliverHistory = (param, url) => uni.$u.post(`${url}/deliver/queryRecord`, param);
// 修改交付物标题名称
uni.$u.api.editDeliverName = param => uni.$u.post(`${domain.value}/deliver/saveDeliver`, param);
// 检查交付物
uni.$u.api.checkDeliver = (param, url) => uni.$u.post(`${url || domain.value}/deliver/checkDeliver`, param);
// 查看检查记录
uni.$u.api.queryCheckLog = param => uni.$u.post(`${domain.value}/deliver/queryCheckLog`, param);
}

16
apis/project.js

@ -0,0 +1,16 @@
import store from '@/store/index.js';
import { computed } from 'vue';
const domain = computed(() => store.state.domain);
export function setupProject(app) {
uni.$u.api = { ...uni.$u.api } || {};
//根据id获取项目信息
uni.$u.api.findProjectById = param => uni.$u.post(`${domain.value}/tall/project/findProjectById`, param);
//创建分享连接
uni.$u.api.createShare = param => uni.$u.post(`${domain.value}/share/create`, param);
//点击分享连接
uni.$u.api.clickShare = param => uni.$u.post(`${domain.value}/share/click`, param);
};

12
apis/role.js

@ -0,0 +1,12 @@
import store from '@/store/index.js';
import { computed } from 'vue';
const domain = computed(() => store.state.domain);
export function setupRole(app) {
uni.$u.api = { ...uni.$u.api } || {};
//根据项目id查找角色
uni.$u.api.findShowRole = param => uni.$u.post(`${domain.value}/tall/role/show`, param);
//根据项目id查找所有成员
uni.$u.api.queryChecker = param => uni.$u.post(`${domain.value}/deliver/queryChecker`, param);
};

44
apis/tall.js

@ -0,0 +1,44 @@
import Config from '@/common/js/config.js'
const apiUrl = Config.apiUrl;
// const tall = `${apiUrl}/tall3/v3.0`;
const tall = `${apiUrl}/ptostall`;
export function setupTall(app) {
uni.$u.api = { ...uni.$u.api } || {};
// 查询广告页和引导页
uni.$u.api.getGuide = type => uni.$u.http.get(`${tall}/business/guide`, { type });
// 查询服务
uni.$u.api.getBusinessPlugin = params => uni.$u.http.post(`${tall}/business/query/businessPlugin`, params);
// 查询本域所有插件
uni.$u.api.getAllPlugin = params => uni.$u.http.post(`${tall}/business/query/plugin`, params);
// 登录
uni.$u.api.signin = params => uni.$u.http.post(`${tall}/users/signin`, params);
// 注册
// uni.$u.api.signup = params => uni.$u.http.post(`${tall}/users/signup`, params);
// 获取图片验证码
uni.$u.api.getImageCode = () => uni.$u.http.get(`${tall}/users/code`);
// 获取短信验证码
uni.$u.api.getSmsCode = params => uni.$u.http.get(`${tall}/users/smscode`, params);
// 根据refreshToken重新获取token
uni.$u.api.getNewToken = refreshToken => uni.$u.http.get(`${tall}/users/refreshToken`, { refreshToken });
// 根据userId获取token
uni.$u.api.getToken = userId => uni.$u.http.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, codes) => uni.$u.post(`${tall}/project/query`, { startTime, endTime, codes: Config.serviceList });
// 查询日历是否有小红点
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.setProjectRelation = params => uni.$u.post(`${tall}/project/drag`, params);
// 删除某个项目
uni.$u.api.delProject = (projectId, url) => uni.$u.post(`${url}/tall/project/delete`, { projectId });
}

22
apis/task.js

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

13
apis/wbs.js

@ -0,0 +1,13 @@
import Config from "@/common/js/config.js"
import store from '@/store/index.js';
import { computed } from 'vue';
const domain = computed(() => store.state.domain);
export function setupWbs(app) {
uni.$u.api = { ...uni.$u.api } || {};
// 导入wbs
uni.$u.api.import = (parentId, formData) => uni.$upload.chooseAndUpload(`${domain.value}/tall/project/wbs?parentId=${parentId}`, formData);
// 导入时查找域内的业务列表
uni.$u.api.getBusinessList = params => uni.$u.http.post(`${Config.apiUrl}/ptostall/business/import/query`, params);
}

21
common/js/config.js

@ -0,0 +1,21 @@
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: 'http://101.201.226.163',
apiUrl: 'http://101.201.226.163/gateway',
msgUrl: 'ws://101.201.226.163:8196/message/v4.0/ws',
projectPath: 'https://101.201.226.163/tall-project',
serviceList: ['ZERO', 'CONTEST', 'PT'],
// 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
}

4679
common/styles/tailwind.scss

File diff suppressed because it is too large

88
common/styles/theme/default.scss

@ -0,0 +1,88 @@
// 默认主题文件
.theme-default {
background-color: #f3f3f3;
.u-card {
font-size: 16px !important;
background-color: #f3f3f3 !important;
}
.mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 100;
}
// 弹出层 内容盒子
.modal-content-wrap {
position: absolute;
left: 50rpx;
top: 50%;
width: 650rpx;
transform: translate3d(0, -50%, 0);
background-color: #fff;
.modal-content-head {
text-align: center;
margin-top: 40rpx;
margin-bottom: 20rpx;
font-size: 16px;
font-weight: 600;
}
.modal-content-body {
padding: 32rpx;
// 审核插件
.common-list {
height: 12.5rem;
overflow-y: scroll;
view {
border-bottom: 1px solid #e5e7eb;
&:last-child {
border-bottom: none;
}
}
}
}
.modal-content-foot {
display: flex;
border-top: 1px solid #d1d5db;
.cancel,
.confirm {
flex: 1;
text-align: center;
@extend .leading-12;
}
.cancel {
border-right: 1px solid #d1d5db;
}
.confirm {
@extend .text-blue-700;
}
// .leading-12 flex-1 text-center delete-modal-border
}
uni-textarea {
width: 596rpx;
height: 140rpx;
box-sizing: border-box !important;
}
}
.link-box {
:deep(.u-input__input) {
color: #60a5fa;
}
}
.progress-dot {
width: 50rpx;
height: 50rpx;
border-radius: 50%;
line-height: 50rpx;
background-color: #fa8c16;
}
}

5
common/styles/theme/index.scss

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

11
common/styles/theme/test.scss

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

104
components/Adv/Adv.vue

@ -0,0 +1,104 @@
<template>
<view class="adv-box relative">
<swiper v-if="imgs.length > 0" class="swiper" :indicator-dots="indicatorDots" :autoplay="autoplay" circular="true">
<swiper-item v-for="(item, index) in imgs" :key="index">
<view class="swiper-item">
<image :src="item"></image>
</view>
</swiper-item>
</swiper>
<image v-else src="/static/adv.jpg"></image>
<view class="time-box absolute" @click="toIndex">{{ time }} 跳过</view>
</view>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const indicatorDots = false;
const autoplay = true;
const advs = computed(() => store.state.advs);
const imgs = ref([]);
if (advs.value) {
imgs.value = JSON.parse(advs.value);
}
watch(advs, () => {
imgs.value = JSON.parse(advs.value);
})
const time = ref(10);
let timer = setInterval(() => {
time.value--;
if (time.value === 0) {
clearInterval(timer);
uni.$storage.setStorageSync('isOpenApp', false);
store.commit('setIsOpenApp', false);
}
}, 1000);
function toIndex() {
uni.$storage.setStorageSync('isOpenApp', false);
store.commit('setIsOpenApp', false);
}
// setTimeout(() => {
// // App
// let firstOpenApp = uni.$storage.getStorageSync('firstOpenApp');
// if (firstOpenApp) {
// // uni.navigateTo({
// // url: '/pages/index/index'
// // })
// } else {
// uni.$storage.setStorageSync('firstOpenApp', true);
// store.commit('setFirstOpenApp');
// // uni.navigateTo({
// // url: '/pages/guide/guide'
// // })
// }
// }, 5000);
</script>
<style lang="scss" scoped>
.adv-box {
width: 100%;
height: calc(100vh - var(--status-bar-height));
image {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.swiper {
height: 100%;
}
.swiper-item {
width: 100%;
height: 100%;
}
.time-box {
bottom: 50px;
right: 10px;
display: inline-block;
width: 60px;
height: 30px;
line-height: 30px;
font-size: 12px;
text-align: center;
background-color: rgba(255, 255, 255, .7);
border-radius: 15px;
color: #666;
border: 1px solid #eee;
}
</style>

461
components/Calendar/Calendar.vue

@ -0,0 +1,461 @@
<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; height: 24px; line-height: 24px;" @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', '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';
let calHeight = 35;
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`;
calHeight = 35 * rows;
}
calHeight = calHeight + 52 + 26 + 24 + 10;
store.commit('project/setCalHeight', calHeight)
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();
d.fullDate = dayjs(d).format('YYYY-MM-DD');
data.selectedDate = d.fullDate;
// initDate(d);
emit('selected-change', d);
};
</script>
<style lang="scss" scoped>
.zzx-calendar {
width: 100%;
height: auto;
background-color: #fff;
padding-bottom: 10px;
.calendar-header {
text-align: center;
height: 52px;
padding: 16px 0;
position: relative;
font-size: 15px;
line-height: 20px;
}
.calendar-weeks {
width: 100%;
display: flex;
flex-flow: row nowrap;
margin-bottom: 10px;
justify-content: center;
align-items: center;
font-size: 12px;
line-height: 16px;
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;
};

89
components/ChooseChecker/ChooseChecker.vue

@ -0,0 +1,89 @@
<template>
<view class="my-3" v-if="data.allMembers && data.allMembers.length">
<view class="flex justify-between">
<view class="flex flex-wrap text-center items-center">
<u-tag
:type="member.checked ? 'primary' : 'info'"
:mode="member.checked ? 'dark' : 'light'"
v-for="(member, index) in data.topMembers"
:key="member.memberId"
class="mb-2 mr-3"
style="width: 60px"
:text="member.name"
:closeable="false"
@click="tagClick(index, member, 'topMembers')"
/>
<span class="ml-2" v-if="!data.show" @click="data.show = true">...</span>
</view>
</view>
<!-- 折叠起来的 -->
<view class="flex flex-wrap text-center items-center" v-if="data.show">
<u-tag
:type="member.checked ? 'primary' : 'info'"
:mode="member.checked ? 'dark' : 'light'"
v-for="(member, index) in data.bottomMembers"
:key="member.memberId"
class="mb-2 mr-3"
style="width: 60px"
:text="member.name"
:closeable="false"
@click="tagClick(index, member, 'bottomMembers')"
/>
<u-icon class="ml-2" name="arrow-up" v-if="data.show" size="26" @click="data.show = false"></u-icon>
</view>
</view>
</template>
<script setup>
import { reactive, computed, onMounted } from 'vue';
import { useStore } from 'vuex';
const props = defineProps({
checkerList: {
default: () => [],
type: Array,
},
});
const data = reactive({
allMembers: [],
show: false,
topMembers: [],
bottomMembers: [],
});
const store = useStore();
const members = computed(() => store.state.role.members);
const emit = defineEmits(['setCheckerList']);
onMounted(() => {
if (members.value && members.value.length) {
data.allMembers = members.value;
// TODO:
data.allMembers.forEach(item => {
item.checked = false;
});
data.topMembers = members.value.slice(0, 3);
data.bottomMembers = members.value.slice(3);
}
});
function tagClick(index, item, membersType) {
//
const arr = uni.$u.deepClone(data[membersType]);
arr[index].checked = !arr[index].checked;
data[membersType] = [...arr];
// idcheckerList
emit('setCheckerList', arr[index].checked, item);
}
//
function clearChecked() {
for (let i = 0; i < data.topMembers.length; i++) {
data.topMembers[i].checked = false;
}
for (let i = 0; i < data.bottomMembers.length; i++) {
data.bottomMembers[i].checked = false;
}
}
</script>

13
components/DeliverLink/DeliverLink.vue

@ -0,0 +1,13 @@
<template>
<view @click="openLink" class="break-all text-blue-400 text-xs my-1"> {{ link }} </view>
</template>
<script setup>
const props = defineProps({ link: String });
function openLink() {
uni.navigateTo({
url: `/pages/detailWebview/detailWebview?url=${props.link}`,
});
}
</script>

110
components/Globals/Globals.vue

@ -1,8 +1,114 @@
<template>
<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="showGlobalSkeleton" :row="3" animate class="u-line-2 skeleton"></skeleton> -->
<view class="grid">
<view class="pb-3">
<view
class="py-3 u-font-14 rounded-md bg-white"
style="height: 100%"
v-if="visibleRoles[roleIndex] && visibleRoles[roleIndex].name === '管理员'"
>
<button class="text-xs bg-blue-500 text-white leading-6" style="width: 500rpx" @click="toWorkbench">工作台</button>
</view>
</view>
<template 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" class="pb-3">
<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-info="plugin"
:plugin-task-id="plugin.pluginTaskId"
:business-plugin-id="plugin.businessPluginId"
:plugin-id="plugin.pluginId"
:param="plugin.param"
:style-type="plugin.styleType || 0"
v-for="plugin in pluginArr"
/>
</template>
</view>
</template>
<!-- 任务名插件 -->
<view v-else class="pb-3">
<Plugin plugin-id="1" :task="item" />
</view>
</template>
</view>
</scroll-view>
</template>
</u-card>
</template>
<script>
<script setup>
import { computed, watch } from 'vue';
import { useStore } from 'vuex';
import Skeleton from '@/components/Skeleton/Skeleton.vue';
defineProps({
globals: {
type: Array,
default: () => [],
},
});
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 showGlobalSkeleton = computed(() => store.state.task.showGlobalSkeleton); //
const visibleRoles = computed(() => store.state.role.visibleRoles); //
const roleIndex = computed(() => store.state.role.roleIndex); //
const roleId = computed(() => store.state.role.roleId); // id
//
function openCard() {
if (isShrink.value) {
store.commit('task/setShrink', false);
}
}
//
function toWorkbench() {
uni.navigateTo({
url: '/pages/workbench/workbench',
});
}
</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>

76
components/Guide/Guide.vue

@ -0,0 +1,76 @@
<template>
<!-- indicator-dots 是否显示面板指示点 -->
<!-- indicator-color 指示点颜色 -->
<!-- indicator-active-color 当前选中的指示点颜色 -->
<swiper class="swiper" :indicator-dots="indicatorDots" :autoplay="autoplay">
<swiper-item v-for="(item, index) in imgs" :key="index">
<view class="swiper-item relative">
<image :src="item"></image>
<u-button class="btn absolute" v-if="index === imgs.length - 1" @click="toIndex">点击进入APP</u-button>
</view>
</swiper-item>
</swiper>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const indicatorDots = true;
const autoplay = false;
const guide = computed(() => store.state.guide);
const imgs = ref([]);
if (guide.value) {
imgs.value = JSON.parse(guide.value);
}
watch(guide, () => {
imgs.value = JSON.parse(guide.value);
})
function toIndex() {
uni.$storage.setStorageSync('firstOpenApp', true);
store.commit('setFirstOpenApp');
// uni.navigateTo({
// url: '/pages/index/index'
// })
}
</script>
<style lang="scss" scoped>
.swiper {
height: calc(100vh - var(--status-bar-height));
image {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.swiper-item {
width: 100%;
height: 100%;
.btn {
bottom: 200px;
display: inline-block;
height: 40px;
line-height: 38px;
background-color: #ECF5FF;
left: 50%;
transform: translateX(-50%);
border-radius: 20px;
color: #2B85E4;
border: 1px solid #2B85E4;
&:after {
border: none;
}
}
}
</style>

19
components/ModalBox/ModalBox.vue

@ -0,0 +1,19 @@
<template>
<!-- 标题部分 -->
<view>
ssss
</view>
<!-- 输入框 -->
<view>
</view>
<!-- 按钮 -->
</template>
<script>
</script>
<style>
</style>

89
components/Plugin/Plugin.vue

@ -0,0 +1,89 @@
<template>
<view class="u-font-14 rounded-md bg-white relative" style="height: 100%" @click="setStorage">
<u-badge :is-dot="true" :offset="[0, 0]" v-show="pluginInfo && pluginInfo.remindNum > 0"></u-badge>
<!-- <plugin-default /> -->
<!-- <component :task="task" :is="pluginComponent"></component> -->
<p-task-title :task="task" v-if="pluginId === '1'" class="p-2" />
<!-- <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 v-else-if="pluginId === '15'" /> -->
<p-deliver-second v-else-if="pluginId === '15'" />
<p-source-manage v-else-if="pluginId === '16'" class="p-2" />
<p-finance-audit v-else-if="pluginId === '17'" class="p-2" />
<p-finance v-else-if="pluginId === '18'" class="p-2" />
<!-- 个人和终端按钮-->
<!-- <p-account-management /> -->
<p-account-management v-else-if="pluginId === '19'" class="p-2" />
<p-domain-source-manage v-else-if="pluginId === '20'" class="p-2" />
<p-project-version-management v-else-if="pluginId === '21'" class="p-2" />
<!-- 任务名和跳转详情页箭头 -->
<p-task-to-detail :task="task" v-else-if="pluginId === '24'" class="p-2"></p-task-to-detail>
<Render
v-else
:task="task"
:pluginId="pluginId"
:styleType="styleType"
:pluginTaskId="pluginTaskId"
:businessPluginId="businessPluginId"
:param="param"
/>
</view>
</template>
<script setup>
import { computed, provide } 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 },
businessPluginId: { default: '', type: String },
param: { type: String, default: '' },
pluginInfo: { default: () => {}, type: Object }
});
provide('task', props.task);
provide('pluginInfo', props.pluginInfo);
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']);
//
// const pluginComponent = computed(() => {
// const target = uni.$pluginConfig.defaults.find(item => item.id === +props.pluginId);
// if (!target) return '';
// return target.component;
// });
// if (props.pluginId === '5') {
// store.dispatch('role/getAllMembers', { projectId: projectId.value });
// }
// storage
async function setStorage() {
uni.$storage.setStorageSync('roleId', roleId.value);
}
</script>

731
components/PrettyExchange/PrettyExchange - 副本.vue

@ -0,0 +1,731 @@
<template>
<view>
<scroll-view scroll-y="true">
<view>
<view
:id="'cu-' + index"
class="cu-item flex-col"
v-for="(item, index) in data.itemList"
:key="item.id"
:style="{ 'background-color': item.color }"
@touchend="stops($event, index)"
@touchmove.stop.prevent="move"
@touchstart="start($event, index)"
>
<!-- <view class="border-100 bg-blue-500" v-if="item.showTopBorder"></view> -->
<view class="w-full" :style="{background: item.styleColor}">
<!-- 有子项目 父项目 -->
<view class="flex items-center justify-between p-3">
<u-icon @click="openMenu(item, index)" 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" :class="{'mb-1': index > 0}">
<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">{{item.status === 1 ? '进行中' : item.status === 2 ? '已结束' : item.status === 0 ? '未开始' : '暂停'}}</view>
</view>
<view v-if="index > 0" 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 class="workbench-btn" v-if="index === 0" @click="toWorkbench">工作台</view>
<view class="remind-box bg-red-500 text-white text-xs" v-if="item.remindNum">{{ item.remindNum > 99 ? '99+' : item.remindNum }}</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 @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"
@touchend.stop.prevent="stops($event, index + '-' + subIndex, item.sonProjectList.length)"
@touchmove.stop.prevent="move($event, item.sonProjectList.length)"
@longpress.stop.prevent="start($event, index + '-' + 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 w-full">
<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>
<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: data.moveLeft + 'px', top: data.moveTop + 'px' }" class="cu-item absolute">
<ProjectItem class="w-full" :item="data.moveItem" />
</view>
</view>
<!-- 移动悬浮 end -->
<!-- 项目操作面板 -->
<u-action-sheet :list="data.menuList" :tips="data.tips" @click="chooseAction" v-model="data.showMenu" :cancel-btn="false"></u-action-sheet>
</view>
</template>
<script setup>
import { reactive, onMounted, watchEffect, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import ProjectItem from '@/components/Projects/ProjectItem.vue';
const store = useStore();
const projects = computed(() => store.state.project.projects);
const remindData = computed(() => store.state.socket.remindData);
const userId = computed(() => store.getters['user/userId']);
const data = reactive({
// itemTop: 0,
// itemLeft: 0,
itemHeight: 0, //
itemWidth: 0, //
subItemHeight: 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 },
clickProject: {},
projectId: 0,
// menuList: [{ text: '' }, { text: '' }, { text: '' }, { text: '' }, { text: '' }],
menuList: [{ text: '导入' }, { text: '导出' }, { text: '删除' }],
// show: false,
// border: 'border border-blue-500 shadow rounded-md',
// showBorder: false, //
showItemIndex: undefined,
isStartMove: 0, //
});
const emit = defineEmits(['changeHeight', 'change']);
//
watchEffect(() => {
if (projects.value) {
data.itemList = projects.value;
data.itemList.forEach(item => {
item.showBorder = false; //
item.showSubBorder = false; //
item.showTopBorder = false; //
item.remindNum = 0;
if (remindData.value) {
remindData.value.forEach(remind => {
const remind_data = JSON.parse(remind.data);
if (remind_data.data.projectId === item.id) {
item.remindNum++;
}
});
}
});
}
});
onMounted(() => {
data.itemList = projects.value;
data.itemList.forEach(item => {
item.showBorder = false; //
item.showSubBorder = false; //
item.showTopBorder = false; //
});
});
//
function openSubProject(length, index) {
store.commit('project/setProjectItemShow', { index, show: !data.itemList[index].show });
if (length && index) {
emit('changeHeight', length, index);
}
data.showItemIndex = index;
}
//
function getDate() {
const query = uni
.createSelectorQuery()
.select('#cu-0')
.fields(
{
id: true,
dataset: true,
rect: true,
size: true,
},
res => {
data.begintop = res.top;
data.beginleft = res.left;
},
)
.exec();
}
// function setData(flag, project, tips) {
// data.showMenu = flag;
// data.projectId = project.id;
// data.tips = tips;
// data.clickProject = project;
// }
function chooseAction(e) {
const obj = {
index: e,
projectId: data.projectId,
url: data.clickProject.url,
projectName: data.tips.text
};
// emit('chooseAction', data);
actionFun(obj);
}
//
function actionFun(obj) {
const action = data.menuList[obj.index].text;
// if (action === '') {
// data.changeEvent = true;
// uni.$ui.showToast('');
// }
if (action === '删除') {
// data.changeEvent = false;
delProject(obj.projectId, obj.url);
}
if (action === '导入') {
// data.changeEvent = false;
store.commit('setDomain', obj.url);
importProject(obj.projectId, obj.projectName);
}
if (action === '导出') {
// data.changeEvent = false;
exportProject(obj.projectId, obj.url);
}
if (data.showItemIndex !== undefined) {
store.commit('project/setProjectItemShow', {
index: data.showItemIndex,
show: true,
});
}
}
function isNumber(val) {
return val === +val;
}
function start(e, index) {
console.log('开始', e);
data.isStartMove = 1;
setTimeout(() => {
getDate();
}, 300);
if (isNumber(index)) {
//
data.setSubItem = false;
const query = uni
.createSelectorQuery()
.select(`#cu-${index}`)
.fields(
{
id: true,
dataset: true,
rect: true,
size: true,
},
res => {
data.moveTop = res.top;
data.moveLeft = res.left;
data.moveItem = data.itemList[index];
data.itemWidth = res.width;
data.itemHeight = res.height;
},
)
.exec();
} else {
//
const arr = index.split('-');
data.setSubItem = true;
const query = uni.createSelectorQuery();
query
.select(`#cu-${arr[0] - 0}`)
.fields(
{
id: true,
dataset: true,
rect: true,
size: true,
},
res => {
data.itemHeight = res.height;
},
)
.exec();
query
.select(`#cu-${index}`)
.fields(
{
id: true,
dataset: true,
rect: true,
size: true,
},
res => {
console.log('res', res)
if (res) {
data.moveTop = res.top;
data.moveLeft = res.left;
data.itemWidth = res.width;
data.subItemHeight = res.height;
}
data.moveItem = data.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
},
)
.exec();
}
}
function move(e, length) {
if (!data.isStartMove) return false;
console.log('移动');
data.showMoveImage = true; //
const touch = e.touches[0];
if (data.deltaLeft == 0) {
//
data.deltaLeft = touch.pageX - data.moveLeft;
data.deltaTop = touch.pageY - data.moveTop;
}
data.moveLeft = touch.pageX - data.deltaLeft;
data.moveTop = touch.pageY - data.deltaTop;
const lastIndex = findOverIndex(touch.pageY, length);
// 线
for (let i = 0; i < data.itemList.length; i++) {
if (data.moveLeft > 35) {
data.itemList[i].showBorder = false;
data.itemList[i].showTopBorder = false;
if (i === lastIndex) {
data.itemList[i].showSubBorder = true;
} else {
data.itemList[i].showSubBorder = false;
}
} else if (lastIndex === -1) {
data.itemList[0].showTopBorder = true;
data.itemList[i].showSubBorder = false;
data.itemList[i].showBorder = false;
} else {
data.itemList[i].showSubBorder = false;
data.itemList[i].showTopBorder = false;
if (i === lastIndex) {
data.itemList[i].showBorder = true;
} else {
data.itemList[i].showBorder = false;
}
}
}
}
function stops(e, index, length) {
console.log('结束', e, index);
data.isStartMove = 0;
const touch = e.changedTouches[0];
const lastIndex = findOverIndex(touch.pageY, length);
//
for (let i = 0; i < data.itemList.length; i++) {
//
if (data.itemList[i].showTopBorder) {
if (isNumber(index)) {
const Value = data.itemList[index];
data.itemList.unshift(Value);
data.itemList.splice(index + 1, 1);
} else {
const arr = index.split('-');
const Value = data.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
data.itemList.unshift(Value);
data.itemList[arr[0] - 0].sonProjectList.splice([arr[1] - 0], 1);
// const options = {
// id: Value.id,
// parentId: 0,
// };
const options = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: '',
};
emit('change', options);
}
//
clearSet(i);
emit('change', data.itemList);
return;
}
//
if (data.itemList[i].showBorder) {
if (isNumber(index)) {
const Value = data.itemList[index];
data.itemList.splice(i + 1, 0, Value);
if (i < index) {
data.itemList.splice(index + 1, 1);
} else {
data.itemList.splice(index, 1);
}
} else {
const arr = index.split('-');
const Value = data.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
data.itemList[arr[0] - 0].sonProjectList.splice([arr[1] - 0], 1);
data.itemList.splice(i + 1, 0, Value);
// const options = {
// id: Value.id,
// parentId: 0,
// };
const options = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: '',
};
emit('change', options);
}
//
clearSet(i);
emit('change', data.itemList);
return;
}
//
if (data.itemList[i].showSubBorder) {
if (isNumber(index)) {
const Value = data.itemList[index];
if (data.itemList[lastIndex - 1].sonProjectList && data.itemList[lastIndex - 1].sonProjectList.length) {
data.itemList[lastIndex - 1].sonProjectList.push(Value);
} else {
data.itemList[lastIndex].sonProjectList = [Value];
}
data.itemList.splice(index, 1);
//
clearSet(i);
// const options = {
// id: Value.id,
// parentId: data.itemList[lastIndex - 1].id,
// };
const options = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: data.itemList[lastIndex - 1].id,
};
emit('change', options);
} else {
const arr = index.split('-');
const Value = data.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
if (data.itemList[lastIndex].sonProjectList && data.itemList[lastIndex].sonProjectList.length) {
data.itemList[lastIndex].sonProjectList.push(Value);
} else {
data.itemList[lastIndex].sonProjectList = [Value];
}
data.itemList[arr[0] - 0].sonProjectList.splice([arr[1] - 0], 1);
//
clearSet(i);
// const options = {
// id: Value.id,
// parentId: data.itemList[lastIndex].id,
// };
const options = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: data.itemList[lastIndex].id,
};
emit('change', options);
// const options1 = {
// id: Value.id,
// parentId: 0,
// };
const options1 = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: '',
};
emit('change', options1);
}
return;
}
}
}
//
function clearSet(i) {
if (i < data.itemList.length) {
data.itemList[i].showBorder = false;
data.itemList[i].showSubBorder = false;
data.itemList[i].showTopBorder = false;
}
data.deltaLeft == 0;
data.showMoveImage = false;
data.setSubItem = false;
// data.changeEvent = false;
data.showItemIndex = undefined;
}
//
function findOverIndex(posY) {
//
const leng = data.itemList.length * data.itemHeight; //
if (posY < data.begintop) {
return -1;
}
for (let i = 0; i < data.itemList.length; i++) {
const begin = data.itemHeight * i + data.begintop;
const end = data.itemHeight * i + data.begintop + data.itemHeight;
if (begin <= posY && end >= posY) {
return i;
}
}
if (posY > leng) {
//
return data.itemList.length - 1;
}
if (posY < data.begintop) {
return 0;
}
}
//
function delProject(id, url) {
uni.showModal({
title: '',
content: '是否删除项目?',
showCancel: true,
success: async ({ confirm }) => {
if (confirm) {
await uni.$u.api.delProject(id, url);
let flag_index = 0;
data.itemList.forEach((item, index) => {
if (item.id == id) {
flag_index = index;
}
});
data.itemList.splice(flag_index, 1);
store.commit('project/setProjects', data.itemList);
}
},
});
}
//
function importProject(id, name) {
uni.showModal({
content: '是否导入到' + name,
showCancel: true,
success: async ({ confirm }) => {
if (confirm) {
try {
const res = await uni.$u.api.import(id);
// WBS
//
emit('success');
// const { apiUrl } = Config;
// let defaultwbs = `${apiUrl}/defaultwbs`;
// res.url && (defaultwbs = res.url);
store.commit('project/setIsRefresh', 1);
setTimeout(() => {
uni.navigateTo({ url: `/pages/project/project?u=${user.value.id}&p=${res.id}&pname=${res.name}&url=${encodeURIComponent(res.url)}` });
}, 2000);
} catch (error) {
console.error('error: ', error);
emit('error', error);
}
}
},
});
}
//
function exportProject(id, url) {
uni.showModal({
title: '',
content: '是否导出项目?',
showCancel: true,
success: async ({ confirm }) => {
if (confirm) {
const data = await uni.$u.post(`${url}/tall/project/exportWbs`, { projectId: id });
// #ifdef H5
window.location.href = data.url;
// #endif
// #ifdef APP-PLUS
uni.downloadFile({
url: data.url, //
success: ({statusCode, tempFilePath}) => {
if (statusCode === 200) {
console.log('下载成功', tempFilePath);
uni.saveFile({
tempFilePath,
success:(res)=>{
uni.$ui.showToast('文件保存路径:' + res.savedFilePath);
//res.savedFilePath
//
// uni.openDocument({
// filePath: res.savedFilePath,
// success:(res)=>console.log('')
// })
},
fail:()=>console.log('下载失败')
})
}
}
});
// #endif
}
},
});
}
//
function toWorkbench() {
uni.navigateTo({ url: '/pages/workbench/workbench' });
}
//
function openProject(project) {
store.commit('task/clearTasks'); //
store.commit('task/clearRealTasks'); //
store.commit('socket/setCurrLocationTaskId', '');
store.commit('task/setAllTasks', []); //
store.commit('task/setUpNextPage', 1);
store.commit('task/setDownNextPage', 1);
store.commit('task/setTimeLineType', 1);
const { name, id, url, businessCode } = project;
uni.navigateTo({ url: `/pages/project/project?u=${userId.value}&p=${id}&pname=${name}&url=${encodeURIComponent(url)}&businessCode=${businessCode}` });
}
/**
* 弹出项目操作面板
*/
function openMenu(project, index) {
if (index === 0) return;
data.showMenu = true;
data.projectId = project.id;
data.tips.text = project.name;
data.clickProject = project;
// emit('setData', data.showMenu, project, data.tips);
}
</script>
<style lang="scss" scoped>
.cu-item {
width: 100%;
display: flex;
align-items: center;
font-size: 14px;
}
.border-100 {
height: 4rpx;
margin: 0 20rpx;
}
.border-80 {
height: 4rpx;
margin: 0 20rpx 0 90rpx;
}
.workbench-btn {
margin-right: 10px;
width: 80px;
height: 30px;
line-height: 30px;
border-radius: 15px;
overflow: hidden;
border: 1px solid #2b85e4;
background-color: #1890ff;
font-size: 12px;
color: #ffffff;
text-align: center;
}
.remind-box {
padding: 0 3px;
min-width: 16px;
height: 16px;
text-align: center;
line-height: 16px;
border-radius: 8px;
font-weight: 100;
}
</style>

752
components/PrettyExchange/PrettyExchange.vue

@ -0,0 +1,752 @@
<template>
<view>
<!-- <scroll-view scroll-y="true" :style="{height: 'calc(100vh - ' + calHeight + 'px - '+ systemInfo.statusBarHeight +'px - 100px)'}"> -->
<scroll-view scroll-y="true" :style="{height: 'calc(100vh - ' + calHeight + 'px - '+ systemInfo.statusBarHeight +'px - 40px)'}">
<view :id="'cu-' + index" class="cu-item flex-col" v-for="(item, index) in data.itemList" :key="item.id"
:style="{ 'background-color': item.color }">
<view class="w-full">
<view class="w-full border-100 bg-blue-500" v-if="item.showTopBorder"></view>
<view class="w-full" :style="{background: item.styleColor}">
<!-- 有子项目 父项目 -->
<view class="h-65 flex items-center justify-between p-3">
<u-icon
class="mover"
name="https://www.tall.wiki/staticrec/drag.svg"
size="48"
@click="openMenu(item, index)"
@touchend="stops($event, index)"
@touchmove.stop.prevent="move"
@longpress="start($event, index)"
></u-icon>
<view @click="openProject(item)" class="flex-1 px-3">
<view class="flex items-center" :class="{'mb-1': index > 0}">
<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">
{{item.status === 1 ? '进行中' : item.status === 2 ? '已结束' : item.status === 0 ? '未开始' : '暂停'}}</view>
</view>
<view v-if="index > 0" 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 class="workbench-btn" v-if="item.businessCode === 'ZERO'" @click="toWorkbench">工作台</view>
<view class="remind-box bg-red-500 text-white text-xs" v-if="item.remindNum">
{{ item.remindNum > 99 ? '99+' : item.remindNum }}</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 @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="h-65 flex items-center justify-between p-3 w-full">
<u-icon
class="mover"
name="https://www.tall.wiki/staticrec/drag.svg"
size="48"
@click="openMenu(subItem)"
@touchend="stops($event, index + '-' + subIndex, item.sonProjectList.length)"
@touchmove.stop.prevent="move($event, item.sonProjectList.length)"
@longpress.stop.prevent="start($event, index + '-' + subIndex)"
></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>
<view class="w-full border-100 bg-blue-500" v-if="item.showBorder"></view>
<view class="w-full border-80 bg-blue-500" v-if="item.showSubBorder"></view>
</view>
</view>
<view class="version-box">
<view>版本号1.0.10</view>
<view>2022.04.06</view>
</view>
</scroll-view>
<!-- 移动悬浮 begin -->
<view v-if="data.showMoveImage">
<view :style="{ left: data.moveLeft + 'px', top: data.moveTop + 'px' }" class="cu-item absolute">
<ProjectItem class="w-full" :item="data.moveItem" />
</view>
</view>
<!-- 移动悬浮 end -->
<!-- 项目操作面板 -->
<u-action-sheet :list="data.menuList" :tips="data.tips" @click="chooseAction" v-model="data.showMenu"
:cancel-btn="false"></u-action-sheet>
</view>
</template>
<script setup>
import { reactive, onMounted, watch, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import ProjectItem from '@/components/Projects/ProjectItem.vue';
const store = useStore();
const projects = computed(() => store.state.project.projects);
const remindData = computed(() => store.state.socket.remindData);
const userId = computed(() => store.getters['user/userId']);
const systemInfo = computed(() => store.state.systemInfo); //
const calHeight = computed(() => store.state.project.calHeight); //
const data = reactive({
// itemTop: 0,
// itemLeft: 0,
begintop: 0, //
beginleft: 0, //
itemHeight: 0, //
itemWidth: 0, //
moveItem: '', //
moveLeft: 0, //
moveTop: 0, //
showMoveImage: false, //
// subItemHeight: 0, //
deltaLeft: 0,
deltaTop: 0,
itemList: [], //
// setSubItem: false, //
changeEvent: false, //
showMenu: false,
tips: {
text: '',
color: '#909399',
fontSize: 28
},
clickProject: {},
projectId: 0,
// menuList: [{ text: '' }, { text: '' }, { text: '' }, { text: '' }, { text: '' }],
menuList: [{
text: '导入'
}, {
text: '导出'
}, {
text: '删除'
}],
// show: false,
// border: 'border border-blue-500 shadow rounded-md',
// showBorder: false, //
showItemIndex: undefined,
isStartMove: 0, //
timer: null
});
const emit = defineEmits(['changeHeight', 'change', 'getProjects']);
//
watch(projects, () => {
if (projects.value) {
data.itemList = projects.value;
data.itemList.forEach(item => {
item.showBorder = false; //
item.showSubBorder = false; //
item.showTopBorder = false; //
item.remindNum = 0;
if (remindData.value) {
remindData.value.forEach(remind => {
const remind_data = JSON.parse(remind.data);
if (remind_data.data.projectId === item.id) {
item.remindNum++;
}
});
}
});
}
})
watch(remindData, () => {
if (projects.value) {
data.itemList = projects.value;
data.itemList.forEach(item => {
if (remindData.value) {
remindData.value.forEach(remind => {
const remind_data = JSON.parse(remind.data);
if (remind_data.data.projectId === item.id) {
item.remindNum++;
}
});
}
});
}
})
onMounted(() => {
data.itemList = projects.value;
data.itemList.forEach(item => {
item.showBorder = false; //
item.showSubBorder = false; //
item.showTopBorder = false; //
});
});
//
function openSubProject(length, index) {
store.commit('project/setProjectItemShow', {
index,
show: !data.itemList[index].show
});
if (length && index) {
emit('changeHeight', length, index);
}
data.showItemIndex = index;
}
//
function getDate() {
const query = uni
.createSelectorQuery()
.select('#cu-0')
.fields({
id: true,
dataset: true,
rect: true,
size: true,
},
res => {
data.begintop = res.top;
data.beginleft = res.left;
},
)
.exec();
}
// function setData(flag, project, tips) {
// data.showMenu = flag;
// data.projectId = project.id;
// data.tips = tips;
// data.clickProject = project;
// }
function chooseAction(e) {
const obj = {
index: e,
projectId: data.projectId,
url: data.clickProject.url,
projectName: data.tips.text
};
// emit('chooseAction', data);
actionFun(obj);
}
//
function actionFun(obj) {
const action = data.menuList[obj.index].text;
// if (action === '') {
// data.changeEvent = true;
// uni.$ui.showToast('');
// }
if (action === '删除') {
// data.changeEvent = false;
delProject(obj.projectId, obj.url);
}
if (action === '导入') {
// data.changeEvent = false;
store.commit('setDomain', obj.url);
importProject(obj.projectId, obj.projectName);
}
if (action === '导出') {
// data.changeEvent = false;
exportProject(obj.projectId, obj.url);
}
if (data.showItemIndex !== undefined) {
store.commit('project/setProjectItemShow', {
index: data.showItemIndex,
show: true,
});
}
}
//
function isNumber(val) {
return val === +val;
}
function start(e, index) {
data.timer = setTimeout(() => {
data.isStartMove = 1;
// console.log('', e, index);
}, 1500);
setTimeout(() => {
getDate(); //
}, 300);
// 1,2
if (isNumber(index)) {
//
// data.setSubItem = false;
const query = uni
.createSelectorQuery()
.select(`#cu-${index}`)
.fields({
id: true,
dataset: true,
rect: true,
size: true,
},
res => {
data.moveTop = res.top;
data.moveLeft = res.left;
data.moveItem = data.itemList[index];
data.itemWidth = res.width;
data.itemHeight = res.height;
},
)
.exec();
} else {
//
const arr = index.split('-');
// data.setSubItem = true;
const query = uni.createSelectorQuery();
query
.select(`#cu-${arr[0] - 0}`)
.fields({
id: true,
dataset: true,
rect: true,
size: true,
},
res => {
data.itemHeight = res.height;
},
)
.exec();
query
.select(`#cu-${index}`)
.fields({
id: true,
dataset: true,
rect: true,
size: true,
},
res => {
if (res) {
data.moveTop = res.top;
data.moveLeft = res.left;
data.itemWidth = res.width;
// data.subItemHeight = res.height;
}
data.moveItem = data.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
},
)
.exec();
}
}
function move(e, length) {
if (!data.isStartMove) return false;
// console.log('');
data.showMoveImage = true; //
const touch = e.touches[0];
if (data.deltaLeft == 0) {
//
data.deltaLeft = touch.pageX - data.moveLeft;
data.deltaTop = touch.pageY - data.moveTop;
}
data.moveLeft = touch.pageX - data.deltaLeft;
data.moveTop = touch.pageY - data.deltaTop;
const lastIndex = findOverIndex(touch.pageY, length);
// 线
for (let i = 0; i < data.itemList.length; i++) {
if (data.moveLeft > 35 && lastIndex > -1) {
data.itemList[i].showBorder = false;
data.itemList[i].showTopBorder = false;
if (i === lastIndex) {
data.itemList[i].showSubBorder = true;
} else {
data.itemList[i].showSubBorder = false;
}
} else if (lastIndex === -1) {
data.itemList[0].showTopBorder = true;
data.itemList[i].showSubBorder = false;
data.itemList[i].showBorder = false;
} else {
data.itemList[i].showSubBorder = false;
data.itemList[i].showTopBorder = false;
if (i === lastIndex) {
data.itemList[i].showBorder = true;
} else {
data.itemList[i].showBorder = false;
}
}
}
}
function stops(e, index, length) {
// console.log('', e, index);
data.isStartMove = 0;
clearTimeout(data.timer);
const touch = e.changedTouches[0];
const lastIndex = findOverIndex(touch.pageY, length);
//
for (let i = 0; i < data.itemList.length; i++) {
//
if (data.itemList[i].showTopBorder) {
if (!isNumber(index)) {
//
const arr = index.split('-');
const Value = data.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
const options = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: '',
};
emit('change', options);
} else {
//
emit('change', data.itemList);
}
//
clearSet(i);
return;
}
//
if (data.itemList[i].showBorder) {
if (!isNumber(index)) {
//
const arr = index.split('-');
const Value = data.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
const options = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: '',
};
emit('change', options);
} else {
//
emit('change', data.itemList);
}
//
clearSet(i);
return;
}
//
if (data.itemList[i].showSubBorder) {
if (isNumber(index)) {
//
const Value = data.itemList[index];
let targetProjectId = '';
if (index === i) {
targetProjectId = data.itemList[i - 1].id;
} else {
targetProjectId = data.itemList[i].id;
}
const options = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: targetProjectId,
};
emit('change', options);
} else {
//
const arr = index.split('-');
const Value = data.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
const options = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: data.itemList[lastIndex].id,
};
emit('change', options);
}
//
clearSet(i);
return;
}
}
}
//
function clearSet(i) {
if (i < data.itemList.length) {
data.itemList[i].showBorder = false;
data.itemList[i].showSubBorder = false;
data.itemList[i].showTopBorder = false;
}
data.deltaLeft == 0;
data.showMoveImage = false;
// data.setSubItem = false;
// data.changeEvent = false;
data.showItemIndex = undefined;
}
//
function findOverIndex(posY) {
if (posY < data.begintop) { //
return -1;
}
for (let i = 0; i < data.itemList.length; i++) {
const begin = 65 * i + data.begintop;
const end = data.begintop + 65 * i + data.itemHeight;
if (begin <= posY && end >= posY) {
return i;
}
}
//
let allNum = data.itemList.length;
data.itemList.forEach(item => {
if (item.show) {
allNum += item.sonProjectList.length;
}
})
const leng = allNum * 65; //
if (posY > (leng + data.begintop)) {
//
return data.itemList.length - 1;
}
}
//
function delProject(id, url) {
uni.showModal({
title: '',
content: '是否删除项目?',
showCancel: true,
success: async ({ confirm }) => {
if (confirm) {
await uni.$u.api.delProject(id, url);
emit('getProjects');
}
},
});
}
//
function importProject(id, name) {
uni.showModal({
content: '是否导入到' + name,
showCancel: true,
success: async ({ confirm }) => {
if (confirm) {
try {
const res = await uni.$u.api.import(id);
// WBS
//
emit('success');
// const { apiUrl } = Config;
// let defaultwbs = `${apiUrl}/defaultwbs`;
// res.url && (defaultwbs = res.url);
store.commit('project/setIsRefresh', 1);
setTimeout(() => {
uni.navigateTo({
url: `/pages/project/project?u=${user.value.id}&p=${res.id}&pname=${res.name}&url=${encodeURIComponent(res.url)}`
});
}, 2000);
} catch (error) {
console.error('error: ', error);
emit('error', error);
}
}
},
});
}
//
function exportProject(id, url) {
uni.showModal({
title: '',
content: '是否导出项目?',
showCancel: true,
success: async ({ confirm }) => {
if (confirm) {
const data = await uni.$u.post(`${url}/tall/project/exportWbs`, { projectId: id });
// #ifdef H5
window.location.href = data.url;
// #endif
// #ifdef APP-PLUS
uni.downloadFile({
url: data.url, //
success: ({ statusCode, tempFilePath }) => {
if (statusCode === 200) {
console.log('下载成功', tempFilePath);
uni.saveFile({
tempFilePath,
success: (res) => {
uni.$ui.showToast('文件保存路径:' + res.savedFilePath);
//res.savedFilePath
//
// uni.openDocument({
// filePath: res.savedFilePath,
// success:(res)=>console.log('')
// })
},
fail: () => console.log('下载失败')
})
}
}
});
// #endif
}
},
});
}
//
function toWorkbench() {
uni.navigateTo({
url: '/pages/workbench/workbench'
});
}
//
function openProject(project) {
store.commit('task/clearTasks'); //
store.commit('task/clearRealTasks'); //
store.commit('socket/setCurrLocationTaskId', '');
store.commit('task/setAllTasks', []); //
store.commit('task/setUpNextPage', 1);
store.commit('task/setDownNextPage', 1);
store.commit('task/setTimeLineType', 1);
const { name, id, url, businessCode } = project;
uni.navigateTo({
url: `/pages/project/project?u=${userId.value}&p=${id}&pname=${name}&url=${encodeURIComponent(url)}&businessCode=${businessCode}`
});
}
/**
* 弹出项目操作面板
*/
function openMenu(project, index) {
if (index === 0) return;
data.showMenu = true;
data.projectId = project.id;
data.tips.text = project.name;
data.clickProject = project;
// emit('setData', data.showMenu, project, data.tips);
}
</script>
<style lang="scss" scoped>
.h-65 {
height: 65px;
}
.cu-item {
width: 100%;
display: flex;
align-items: center;
font-size: 14px;
}
.border-100 {
height: 4rpx;
}
.border-80 {
height: 4rpx;
margin: 0 20rpx 0 90rpx;
}
.workbench-btn {
margin-right: 10px;
width: 80px;
height: 30px;
line-height: 30px;
border-radius: 15px;
overflow: hidden;
border: 1px solid #2b85e4;
background-color: #1890ff;
font-size: 12px;
color: #ffffff;
text-align: center;
}
.remind-box {
padding: 0 3px;
min-width: 16px;
height: 16px;
text-align: center;
line-height: 16px;
border-radius: 8px;
font-weight: 100;
}
.version-box {
padding: 20px 0 10px;
width: 100%;
text-align: center;
background-color: #FFFFFF;
color: #999;
font-size: 12px;
view {
line-height: 20px;
}
}
</style>

693
components/PrettyExchange/PrettyExchange222.vue

@ -0,0 +1,693 @@
<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 class="workbench-btn" v-if="index === 0" @click="toWorkbench">工作台</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: data.moveLeft + 'px', top: data.moveTop + 'px' }" class="cu-item absolute">
<ProjectItem class="w-full" :item="data.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 { reactive, onMounted, watchEffect, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import ProjectItem from '@/components/Projects/ProjectItem.vue';
const store = useStore();
const projects = computed(() => store.state.project.projects);
const remindData = computed(() => store.state.socket.remindData);
const data = reactive({
// itemTop: 0,
// itemLeft: 0,
itemHeight: 0, //
itemWidth: 0, //
subItemHeight: 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 },
clickProject: {},
projectId: 0,
// menuList: [{ text: '' }, { text: '' }, { text: '' }, { text: '' }, { text: '' }],
menuList: [{ text: '导入' }, { text: '导出' }, { text: '删除' }, { text: '排序' }],
// show: false,
// border: 'border border-blue-500 shadow rounded-md',
// showBorder: false, //
showItemIndex: undefined,
});
const emit = defineEmits(['changeHeight', 'change']);
//
watchEffect(() => {
if (projects.value) {
data.itemList = projects.value;
data.itemList.forEach(item => {
item.showBorder = false; //
item.showSubBorder = false; //
item.showTopBorder = false; //
item.remindNum = 0;
if (remindData.value) {
remindData.value.forEach(remind => {
const remind_data = JSON.parse(remind.data);
if (remind_data.data.projectId === item.id) {
item.remindNum++;
}
});
}
});
}
});
onMounted(() => {
data.itemList = projects.value;
data.itemList.forEach(item => {
item.showBorder = false; //
item.showSubBorder = false; //
item.showTopBorder = false; //
});
});
//
function openSubProject(length, index) {
store.commit('project/setProjectItemShow', { index, show: !data.itemList[index].show });
if (length && index) {
emit('changeHeight', length, index);
}
data.showItemIndex = index;
}
//
function getDate() {
const query = uni
.createSelectorQuery()
.select('#cu-0')
.fields(
{
id: true,
dataset: true,
rect: true,
size: true,
},
res => {
data.begintop = res.top;
data.beginleft = res.left;
},
)
.exec();
}
function setData(flag, project, tips) {
data.showMenu = flag;
data.projectId = project.id;
data.tips = tips;
data.clickProject = project;
}
function chooseAction(e) {
const obj = {
index: e,
projectId: data.projectId,
url: data.clickProject.url,
projectName: data.tips.text
};
// emit('chooseAction', data);
actionFun(obj);
}
//
function actionFun(obj) {
const action = data.menuList[obj.index].text;
if (action === '排序') {
data.changeEvent = true;
uni.$ui.showToast('请移动进行排序');
}
if (action === '删除') {
data.changeEvent = false;
delProject(obj.projectId, obj.url);
}
if (action === '导入') {
data.changeEvent = false;
store.commit('setDomain', obj.url);
importProject(obj.projectId, obj.projectName);
}
if (action === '导出') {
data.changeEvent = false;
exportProject(obj.projectId, obj.url);
}
if (data.showItemIndex !== undefined) {
store.commit('project/setProjectItemShow', {
index: data.showItemIndex,
show: true,
});
}
}
function isNumber(val) {
return val === +val;
}
function start(e, index) {
console.log('开始');
setTimeout(() => {
getDate();
}, 300);
if (isNumber(index)) {
//
data.setSubItem = false;
const query = uni
.createSelectorQuery()
.select(`#cu-${index}`)
.fields(
{
id: true,
dataset: true,
rect: true,
size: true,
},
res => {
data.moveTop = res.top;
data.moveLeft = res.left;
data.moveItem = data.itemList[index];
data.itemWidth = res.width;
data.itemHeight = res.height;
},
)
.exec();
} else {
//
const arr = index.split('-');
data.setSubItem = true;
const query = uni.createSelectorQuery();
query
.select(`#cu-${arr[0] - 0}`)
.fields(
{
id: true,
dataset: true,
rect: true,
size: true,
},
res => {
data.itemHeight = res.height;
},
)
.exec();
query
.select(`#cu-${index}`)
.fields(
{
id: true,
dataset: true,
rect: true,
size: true,
},
res => {
data.moveTop = res.top;
data.moveLeft = res.left;
data.moveItem = data.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
data.itemWidth = res.width;
data.subItemHeight = res.height;
},
)
.exec();
}
}
function move(e, length) {
console.log('移动');
data.showMoveImage = true; //
const touch = e.touches[0];
if (data.deltaLeft == 0) {
//
data.deltaLeft = touch.pageX - data.moveLeft;
data.deltaTop = touch.pageY - data.moveTop;
}
data.moveLeft = touch.pageX - data.deltaLeft;
data.moveTop = touch.pageY - data.deltaTop;
const lastIndex = findOverIndex(touch.pageY, length);
// 线
for (let i = 0; i < data.itemList.length; i++) {
if (data.moveLeft > 35) {
data.itemList[i].showBorder = false;
data.itemList[i].showTopBorder = false;
if (i === lastIndex) {
data.itemList[i].showSubBorder = true;
} else {
data.itemList[i].showSubBorder = false;
}
} else if (lastIndex === -1) {
data.itemList[0].showTopBorder = true;
data.itemList[i].showSubBorder = false;
data.itemList[i].showBorder = false;
} else {
data.itemList[i].showSubBorder = false;
data.itemList[i].showTopBorder = false;
if (i === lastIndex) {
data.itemList[i].showBorder = true;
} else {
data.itemList[i].showBorder = false;
}
}
}
}
function stops(e, index, length) {
console.log('结束', e, index, length);
const touch = e.changedTouches[0];
const lastIndex = findOverIndex(touch.pageY, length);
console.log('11111111111', data.itemList)
//
for (let i = 0; i < data.itemList.length; i++) {
//
if (data.itemList[i].showTopBorder) {
if (isNumber(index)) {
const Value = data.itemList[index];
data.itemList.unshift(Value);
data.itemList.splice(index + 1, 1);
} else {
const arr = index.split('-');
const Value = data.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
data.itemList.unshift(Value);
data.itemList[arr[0] - 0].sonProjectList.splice([arr[1] - 0], 1);
// const options = {
// id: Value.id,
// parentId: 0,
// };
const options = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: '',
};
emit('change', options);
}
//
clearSet(i);
emit('change', data.itemList);
return;
}
//
if (data.itemList[i].showBorder) {
if (isNumber(index)) {
const Value = data.itemList[index];
data.itemList.splice(i + 1, 0, Value);
if (i < index) {
data.itemList.splice(index + 1, 1);
} else {
data.itemList.splice(index, 1);
}
} else {
const arr = index.split('-');
const Value = data.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
data.itemList.splice(i + 1, 0, Value);
data.itemList[arr[0] - 0].sonProjectList.splice([arr[1] - 0], 1);
// const options = {
// id: Value.id,
// parentId: 0,
// };
const options = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: '',
};
emit('change', options);
}
//
clearSet(i);
emit('change', data.itemList);
return;
}
//
if (data.itemList[i].showSubBorder) {
if (isNumber(index)) {
const Value = data.itemList[index];
if (data.itemList[lastIndex - 1].sonProjectList && data.itemList[lastIndex - 1].sonProjectList.length) {
data.itemList[lastIndex - 1].sonProjectList.push(Value);
} else {
data.itemList[lastIndex].sonProjectList = [Value];
}
data.itemList.splice(index, 1);
//
clearSet(i);
// const options = {
// id: Value.id,
// parentId: data.itemList[lastIndex - 1].id,
// };
const options = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: data.itemList[lastIndex - 1].id,
};
emit('change', options);
} else {
const arr = index.split('-');
const Value = data.itemList[arr[0] - 0].sonProjectList[arr[1] - 0];
if (data.itemList[lastIndex].sonProjectList && data.itemList[lastIndex].sonProjectList.length) {
data.itemList[lastIndex].sonProjectList.push(Value);
} else {
data.itemList[lastIndex].sonProjectList = [Value];
}
data.itemList[arr[0] - 0].sonProjectList.splice([arr[1] - 0], 1);
//
clearSet(i);
// const options = {
// id: Value.id,
// parentId: data.itemList[lastIndex].id,
// };
const options = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: data.itemList[lastIndex].id,
};
emit('change', options);
// const options1 = {
// id: Value.id,
// parentId: 0,
// };
const options1 = {
businessCode: Value.businessCode,
moveProjectId: Value.id,
targetProjectId: '',
};
emit('change', options1);
}
return;
}
}
}
//
function clearSet(i) {
data.itemList[i].showBorder = false;
data.itemList[i].showSubBorder = false;
data.itemList[i].showTopBorder = false;
data.deltaLeft == 0;
data.showMoveImage = false;
data.setSubItem = false;
data.changeEvent = false;
data.showItemIndex = undefined;
}
//
function findOverIndex(posY) {
//
const leng = data.itemList.length * data.itemHeight; //
if (posY < data.begintop) {
return -1;
}
for (let i = 0; i < data.itemList.length; i++) {
const begin = data.itemHeight * i + data.begintop;
const end = data.itemHeight * i + data.begintop + data.itemHeight;
if (begin <= posY && end >= posY) {
return i;
}
}
if (posY > leng) {
//
return data.itemList.length - 1;
}
if (posY < data.begintop) {
return 0;
}
}
//
function delProject(id, url) {
uni.showModal({
title: '',
content: '是否删除项目?',
showCancel: true,
success: async ({ confirm }) => {
if (confirm) {
await uni.$u.api.delProject(id, url);
let flag_index = 0;
data.itemList.forEach((item, index) => {
if (item.id == id) {
flag_index = index;
}
});
data.itemList.splice(flag_index, 1);
store.commit('project/setProjects', data.itemList);
}
},
});
}
//
function importProject(id, name) {
uni.showModal({
content: '是否导入到' + name,
showCancel: true,
success: async ({ confirm }) => {
if (confirm) {
try {
const res = await uni.$u.api.import(id);
// WBS
//
emit('success');
// const { apiUrl } = Config;
// let defaultwbs = `${apiUrl}/defaultwbs`;
// res.url && (defaultwbs = res.url);
store.commit('project/setIsRefresh', 1);
setTimeout(() => {
uni.navigateTo({ url: `/pages/project/project?u=${user.value.id}&p=${res.id}&pname=${res.name}&url=${encodeURIComponent(res.url)}` });
}, 2000);
} catch (error) {
console.error('error: ', error);
emit('error', error);
}
}
},
});
}
//
function exportProject(id, url) {
uni.showModal({
title: '',
content: '是否导出项目?',
showCancel: true,
success: async ({ confirm }) => {
if (confirm) {
const data = await uni.$u.post(`${url}/tall/project/exportWbs`, { projectId: id });
// #ifdef H5
window.location.href = data.url;
// #endif
// #ifdef APP-PLUS
uni.downloadFile({
url: data.url, //
success: ({statusCode, tempFilePath}) => {
if (statusCode === 200) {
console.log('下载成功', tempFilePath);
uni.saveFile({
tempFilePath,
success:(res)=>{
uni.$ui.showToast('文件保存路径:' + res.savedFilePath);
//res.savedFilePath
//
// uni.openDocument({
// filePath: res.savedFilePath,
// success:(res)=>console.log('')
// })
},
fail:()=>console.log('下载失败')
})
}
}
});
// #endif
}
},
});
}
//
// function toWorkbench() {
// uni.navigateTo({ url: '/pages/workbench/workbench' });
// }
</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;
}
.workbench-btn {
margin-right: 10px;
width: 80px;
height: 36px;
line-height: 36px;
border-radius: 18px;
overflow: hidden;
border: 1px solid #2b85e4;
background-color: #1890ff;
font-size: 12px;
color: #ffffff;
text-align: center;
}
</style>

196
components/Projects/ProjectItem.vue

@ -0,0 +1,196 @@
<template>
<view class="w-full" :style="{background: item.styleColor}">
<!-- 有子项目 父项目 -->
<view class="h-65 flex items-center justify-between p-3">
<u-icon @click="openMenu(item, index)" 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" :class="{'mb-1': index > 0}">
<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">{{item.status === 1 ? '进行中' : item.status === 2 ? '已结束' : item.status === 0 ? '未开始' : '暂停'}}</view>
</view>
<view v-if="index > 0" 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 class="workbench-btn" v-if="index === 0" @click="toWorkbench">工作台</view> -->
<view class="remind-box bg-red-500 text-white text-xs" v-if="item.remindNum">{{ item.remindNum > 99 ? '99+' : item.remindNum }}</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"
@touchend.stop.prevent="stops($event, index + '-' + subIndex, item.sonProjectList.length)"
@touchmove.stop.prevent="move($event, item.sonProjectList.length)"
@longpress.stop.prevent="start($event, index + '-' + 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="h-65 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 { reactive, 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', 'openSubProject']);
const store = useStore();
const userId = computed(() => store.getters['user/userId']);
const data = reactive({
showMenu: false,
tips: {
text: '',
color: '#909399',
fontSize: 28,
},
// show: false,
// border: 'border border-blue-500 shadow rounded-md',
// showBorder: false,
projectId: 0,
});
//
function toWorkbench() {
uni.navigateTo({ url: '/pages/workbench/workbench' });
}
//
function openProject(project) {
store.commit('task/clearTasks'); //
store.commit('task/clearRealTasks'); //
store.commit('socket/setCurrLocationTaskId', '');
store.commit('task/setAllTasks', []); //
store.commit('task/setUpNextPage', 1);
store.commit('task/setDownNextPage', 1);
store.commit('task/setTimeLineType', 1);
const { name, id, url, businessCode } = project;
uni.navigateTo({ url: `/pages/project/project?u=${userId.value}&p=${id}&pname=${name}&url=${encodeURIComponent(url)}&businessCode=${businessCode}` });
}
/**
* 弹出项目操作面板
*/
function openMenu(project, index) {
if (index === 0) return;
data.showMenu = true;
data.projectId = project.id;
data.tips.text = project.name;
emit('setData', data.showMenu, project, data.tips);
}
</script>
<style lang="scss" scoped>
.border-100 {
height: 4rpx;
margin: 0 20rpx;
}
.border-80 {
height: 4rpx;
margin: 0 20rpx 0 90rpx;
}
.workbench-btn {
margin-right: 10px;
width: 80px;
height: 30px;
line-height: 30px;
border-radius: 15px;
overflow: hidden;
border: 1px solid #2b85e4;
background-color: #1890ff;
font-size: 12px;
color: #ffffff;
text-align: center;
}
.remind-box {
padding: 0 3px;
min-width: 16px;
height: 16px;
text-align: center;
line-height: 16px;
border-radius: 8px;
font-weight: 100;
}
.h-65 {
height: 65px;
}
</style>

64
components/Projects/Projects.vue

@ -0,0 +1,64 @@
<template>
<view class="bg-white u-font-15" style="padding: 12px 0; margin-top: 16px;">
<PrettyExchange @change="change" @getProjects="getProjects" />
</view>
</template>
<script setup>
const emit = defineEmits(['getProjects']);
function getProjects() {
emit('getProjects');
}
function change(options) {
if (options instanceof Array) {
// let projectIdList = [];
// let arr = [];
// options.forEach(item => {
// projectIdList.push(item.id);
// arr.push(item.name);
// });
// setProjectSort(projectIdList);
emit('getProjects');
} 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 || '排序修改失败');
}
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 || '层级关系修改失败');
}
emit('getProjects');
}
</script>
<style lang="scss" scoped>
</style>

182
components/Render/Render.vue

@ -0,0 +1,182 @@
<template>
<view class="render-box shadow-lg">
<view
class="render-content"
:id="`render-${task.id}`"
: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"
:prop="pluginInfo"
:change:prop="projectRender.renderDom"
:data-url="domain"
:data-longitude="longitude"
:data-latitude="latitude"
></view>
</view>
</template>
<script>
import { mapState, mapGetters } from 'vuex';
export default {
name: 'RenderHtmlFragment',
props: {
task: { default: () => {}, type: Object },
pluginId: { default: '1', type: String },
styleType: { default: 0, type: Number },
pluginTaskId: { default: '', type: String },
businessPluginId: { default: '', type: String },
param: { type: String, default: '' },
},
data() {
return {
pluginInfo: null,
};
},
computed: {
...mapState(['domain']),
...mapState(['allPlugin', 'businessPlugin', 'longitude', 'latitude']),
...mapState('role', ['roleId']),
...mapState('user', ['token']),
...mapGetters('project', ['projectId']),
...mapGetters('user', ['userId']),
},
async mounted() {
await this.getPlugin();
if (this.param && this.pluginInfo) {
let configParam = JSON.parse(this.param);
this.pluginInfo.config = `var p${this.pluginId}_config = ${this.param}`;
} else {
await this.getConfig();
}
},
methods: {
getPlugin() {
const allPlugin = this.allPlugin || uni.$storage.getStorageSync('allPlugin');
if (allPlugin && JSON.parse(allPlugin)) {
//
try {
const pluginLists = JSON.parse(allPlugin);
// pluginLists find,catch
const pluginTarget = pluginLists.find(item => item.id === this.pluginId);
pluginTarget.renderId = this.task.id;
this.pluginInfo = pluginTarget || null;
} catch (error) {
console.error('error: ', error);
this.pluginInfo = null;
}
} else {
// API
const params = { businessPluginId: this.businessPluginId };
uni.$catchReq.getOtherPlugin(params, (err, res) => {
if (err) {
console.error('err: ', err);
this.pluginInfo = null;
} else {
res.renderId = this.task.id;
this.pluginInfo = res || null;
}
});
}
},
getConfig() {
const businessPlugin = this.businessPlugin || uni.$storage.getStorageSync('businessPlugin');
if (businessPlugin && JSON.parse(businessPlugin)) {
//
const businessPluginLists = JSON.parse(businessPlugin);
businessPluginLists.forEach(item => {
if (item.pluginConfigs) {
const pluginConfig = item.pluginConfigs.find(plugin => plugin.businessPluginId === this.businessPluginId);
if (pluginConfig && pluginConfig.config && this.pluginInfo) {
this.pluginInfo.config = pluginConfig.config;
}
}
});
} else {
// API
const params = { id: this.businessPluginId };
uni.$catchReq.getConfigInfo(params, (err, res) => {
if (err) {
console.error('err: ', err);
} else {
if (this.pluginInfo && res.config) {
this.pluginInfo.config = res.config;
}
}
});
}
},
},
};
</script>
<script module="projectRender" lang="renderjs">
// TODO: 使
let pluginTaskId = '';
export default {
methods: {
/**
* 渲染dom
* @param {object|null} res 插件详情 监听的pluginInfo
*/
renderDom(res) {
if (!res) return;
this.$nextTick(() => {
if (res.html) {
// : 2022130 this.pluginTaskIdAPP
// bug
const content = window.document.getElementById(`render-${res.renderId}`);
content.innerHTML = res.html;
}
if (res.config) {
const script_config = window.document.createElement('script');
script_config.innerHTML = res.config;
window.document.body.appendChild(script_config);
}
if (res.js) {
const script_js = window.document.createElement('script');
script_js.innerHTML = res.js;
window.document.body.appendChild(script_js);
}
})
},
/**
* 从prop data中拿到pluginTaskId, 存入变量中
* 因为目前这里没办法通过this拿到prop data下的数据
* @param {string} propsPluginTaskId
*/
// initPluginTaskId(propsPluginTaskId) {
// pluginTaskId = propsPluginTaskId;
// }
},
};
</script>
<style scoped lang="scss">
.render-box {
border-radius: 8px;
background: #fff;
padding: 16px;
overflow: hidden;
}
button {
border: none !important;
}
</style>

66
components/Reviewer/Reviewer.vue

@ -0,0 +1,66 @@
<template>
<view class="border border-solid border-gray-300 rounded-md mt-3 p-2" @click="collapsed = !collapsed">
<view class="top flex justify-between">
<view class="mr-3 text-sm">审核人</view>
<!-- 展示选择的审核人 -->
<view class="flex item-center justify-end flex-1 text-sm">
<view v-for="item in showCheckers" class="mx-1">
<!-- <u-badge :is-dot="true" is-center></u-badge> -->
{{ item.name }}
</view>
<view class="mx-1" v-show="checkedCheckers.length > 3">...</view>
</view>
<!-- 点击更换图标 -->
<u-icon :name="collapsed ? 'arrow-down' : 'arrow-up'"></u-icon>
</view>
<!-- 隐藏的审核人选项 -->
<view v-show="!collapsed" class="foot mt-2 flex flex-wrap">
<u-button
v-for="item in checkers"
:type="checkedCheckers.find(checker => checker.memberId === item.memberId) ? 'primary' : 'default'"
size="mini"
class="my-1 mx-2"
@click="handleSelectChecker(item)"
>
{{ item.name }}
</u-button>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
//
const collapsed = ref(true);
// store
//
const checkers = computed(() => store.state.role.members);
//
const checkedCheckers = ref([]);
//
const showCheckers = computed(() => (checkedCheckers.value.length > 3 ? checkedCheckers.value.slice(0, 3) : checkedCheckers.value));
defineExpose({ checkedCheckers, collapsed });
/**
* 点击成员 切换检查人的选中状态
* @param {object} member 成员对象
*/
function handleSelectChecker(member) {
const target = checkedCheckers.value.find(item => item.memberId === member.memberId);
if (target) {
//
checkedCheckers.value = checkedCheckers.value.filter(item => item.memberId !== member.memberId);
} else {
checkedCheckers.value.push(member);
}
}
</script>

127
components/ReviewerSecond/ReviewerSecond.vue

@ -0,0 +1,127 @@
<template>
<view class="border border-solid border-gray-300 rounded-md mt-4 p-2 pt-4 pl-1" @click="collapsed = !collapsed">
<view class="relative flex justify-between">
<view class="absolute text-sm bg-white reviewer-title">审核人</view>
<view class="flex flex-wrap">
<template v-for="(item, index) in checkers">
<u-button
v-if="index < 4"
:type="checkedCheckers.find(checker => checker.memberId === item.memberId) ? 'primary' : 'default'"
size="mini"
class="m-1"
@click="handleSelectChecker(item)"
>
{{ item.name }}
</u-button>
</template>
</view>
<!-- 展示选择的审核人 -->
<!-- <view class="flex items-center justify-end flex-1 text-sm">
<view v-for="item in showCheckers" class="mx-1">
{{ item.name }}
</view>
<view class="mx-1" v-show="checkedCheckers.length > 3">...</view>
</view> -->
<!-- 点击更换图标 -->
<u-icon :name="collapsed ? 'arrow-down' : 'arrow-up'"></u-icon>
</view>
<!-- 隐藏的审核人选项 -->
<view v-show="!collapsed" class="foot mt-2 flex flex-wrap">
<template v-for="(item, index) in checkers">
<u-button
v-if="index >= 4"
:type="checkedCheckers.find(checker => checker.memberId === item.memberId) ? 'primary' : 'default'"
size="mini"
class="m-1"
@click="handleSelectChecker(item)"
>
{{ item.name }}
</u-button>
</template>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
const props = defineProps({
dataCheckers: {
type: Array,
default: () => [],
},
});
const store = useStore();
//
const collapsed = ref(true);
// store
//
const checkers = computed(() => store.state.role.members);
const checkersStorage = uni.$storage.getStorageSync('checkers');
if (!checkers.value.length && checkersStorage) {
store.commit('role/setMembers', JSON.parse(checkersStorage));
}
//
let checkedCheckers = ref([]);
if (props.dataCheckers && checkers.value.length > 0) {
props.dataCheckers.forEach(item => {
item.name = item.checkerName;
item.memberId = item.checkerId;
})
checkedCheckers.value = props.dataCheckers;
let arr = [];
checkers.value.forEach(item => {
let data = props.dataCheckers.find(checker => checker.memberId === item.memberId);
if (data) { arr.push(item); }
})
checkers.value.forEach(item => {
let data = props.dataCheckers.find(checker => checker.memberId === item.memberId);
if (!data) { arr.push(item); }
})
store.commit('role/setMembers', arr);
}
//
const showCheckers = computed(() => (checkedCheckers.value.length > 3 ? checkedCheckers.value.slice(0, 3) : checkedCheckers.value));
defineExpose({ checkedCheckers, collapsed });
/**
* 点击成员 切换检查人的选中状态
* @param {object} member 成员对象
*/
function handleSelectChecker(member) {
const target = checkedCheckers.value.find(item => item.memberId === member.memberId);
if (target) {
//
checkedCheckers.value = checkedCheckers.value.filter(item => item.memberId !== member.memberId);
} else {
checkedCheckers.value.push(member);
}
}
</script>
<style scoped lang="scss">
.reviewer-title {
width: 60px;
height: 20px;
line-height: 20px;
text-align: center;
top: -25px;
left: 10px;
}
</style>

330
components/Roles/Roles.vue

@ -1,8 +1,334 @@
<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 relative"
v-for="(item, index) in data.roles"
>
<view class="tab-children relative u-skeleton-fillet u-font-14">
{{ item.name }}
</view>
<view class="remind-box absolute bg-red-500 text-white text-xs" v-if="item.remindNum">{{
item.remindNum > 99 ? '99+' : item.remindNum
}}</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, watch, watchEffect, onMounted, nextTick } from 'vue';
import { useStore } from 'vuex';
import useGetTasks from '@/hooks/project/useGetTasks';
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,
clickNum: 0,
firstClickTime: 0,
secClickTime: 0,
});
const store = useStore();
const getTasksHook = useGetTasks();
const projectId = computed(() => store.getters['project/projectId']);
const visibleRoles = computed(() => store.state.role.visibleRoles);
const roleId = computed(() => store.state.role.roleId);
const allTasks = computed(() => store.state.task.allTasks); //
const remindData = computed(() => store.state.socket.remindData); //
const currLocationTaskId = computed(() => store.state.socket.currLocationTaskId); // id
watch(visibleRoles, () => {
if (remindData.value && visibleRoles.value) {
visibleRoles.value.forEach(role => {
role.remindNum = 0;
remindData.value.forEach(remind => {
const remind_data = JSON.parse(remind.data);
if (projectId.value === remind_data.data.projectId && remind_data.data.roleId === role.id) {
role.remindNum++;
}
});
});
}
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 {
data.clickNum = data.clickNum === 0 ? 1 : data.clickNum + 1;
if (data.clickNum === 1) {
data.firstClickTime = new Date().getTime();
// script
// clearPluginScript();
nextTick(() => {
store.commit('role/setRoleId', id);
store.commit('role/setRoleIndex', index);
// index
setCurrentRole(index);
});
setTimeout(() => {
data.clickNum = data.firstClickTime = data.secClickTime = 0;
}, 500);
} else if (data.clickNum === 2) {
data.secClickTime = new Date().getTime();
if (data.secClickTime - data.firstClickTime < 500) {
const arr = [];
remindData.value.forEach(item => {
const remind_data = JSON.parse(item.data);
if (remind_data.data.taskType === 1 && remind_data.data.roleId === roleId.value) {
arr.push(item);
}
});
let nextLocationTaskId = '';
if (arr.length > 0) {
for (let i = 0; i < arr.length; i++) {
const first_option = JSON.parse(arr[0].data);
const curr_option = JSON.parse(arr[i].data);
if (!currLocationTaskId.value) {
nextLocationTaskId = first_option.data.taskId;
break;
}
if (curr_option.data.taskId === currLocationTaskId.value) {
if (i === arr.length - 1) {
nextLocationTaskId = first_option.data.taskId;
} else {
const next_option = JSON.parse(arr[i + 1].data);
nextLocationTaskId = next_option.data.taskId;
}
}
}
// store.commit('task/setTimeLineType', timeLineType.value === 1 ? 2 : 1);
}
store.commit('socket/setCurrLocationTaskId', nextLocationTaskId);
store.commit('task/clearTasks'); //
store.commit('task/clearRealTasks'); //
store.commit('task/setUpNextPage', 1);
store.commit('task/setDownNextPage', 1);
const params = { pageNum: 1, taskId: nextLocationTaskId, timeNode: new Date().getTime() };
getTasksHook.getTasks(params);
}
data.clickNum = data.firstClickTime = data.secClickTime = 0;
}
} 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;
}
}
}
.remind-box {
top: 0;
right: -5px;
padding: 0 3px;
min-width: 16px;
height: 16px;
text-align: center;
line-height: 16px;
border-radius: 8px;
font-weight: 100;
transform: scale(0.8);
}
// //
/* #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

@ -0,0 +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 setup>
import { reactive, computed } 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 });
//
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"> -->
<view class="ml-3">
<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)"
:style="{ height: setHeight(task.panel) }"
:show-foot="false"
:show-head="false"
class="h-16"
margin="0"
v-if="tasks && tasks.length && task.process !== 4 && !showSkeleton"
>
<template v-slot:body> -->
<view class="h-16" v-if="tasks && tasks.length && task.process !== 4 && !showSkeleton" @click="onClickTask(task.planStart - 0, task.id)">
<view class="p-0 u-col-between grid gap-3">
<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>
<!-- </template>
</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';
import Skeleton from '@/components/Skeleton/Skeleton.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

80
components/TimeLine/TimeLine.vue

@ -1,8 +1,80 @@
<template>
<!-- 时间间隔栏 -->
<!-- <Barrier /> -->
<scroll-view
:lower-threshold="300"
style="height: 800px;"
scroll-y="true"
:upper-threshold="300"
:scroll-into-view="scrollToTaskId"
@scroll="scroll"
@scrolltolower="handleScrollBottom"
@scrolltoupper="handleScrollTop"
id="scroll"
>
<TimeBox :tasks="tasks" />
</scroll-view>
</template>
<script>
</script>
<script setup>
import { reactive, computed } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import TimeBox from './component/TimeBox.vue';
import useGetTasks from '@/hooks/project/useGetTasks';
const props = defineProps({
tasks: {
type: Array,
default: () => []
}
})
const store = useStore();
const getTasksHook = useGetTasks();
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 timeUnit = computed(() => store.state.task.timeUnit);
const scrollToTaskId = computed(() => store.state.task.scrollToTaskId);
const timeGranularity = computed(() => store.getters['task/timeGranularity']);
const downNextPage = computed(() => store.state.task.downNextPage); //
const upNextPage = computed(() => store.state.task.upNextPage); //
const timeLineType = computed(() => store.state.task.timeLineType); //
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 (!props.tasks || !props.tasks.length || showSkeleton.value) return;
//
store.commit('task/updateTasks', props.tasks)
console.warn('滚动到顶部: ');
getTasksHook.setPrevPlaceholderTasks(); //
let params = { pageNum: upNextPage.value, queryType: 0, triggerType: 0 };
getTasksHook.dataRender(params);
}
//
async function handleScrollBottom() {
if (!props.tasks || !props.tasks.length || showSkeleton.value) return;
//
store.commit('task/updateTasks', props.tasks);
console.warn('滚动到底部: ');
getTasksHook.setNextPlaceholderTasks(); //
let params = { pageNum: downNextPage.value, queryType: 1, triggerType: 0 };
getTasksHook.dataRender(params);
}
</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>

145
components/TimeLine/component/TimeBox.vue

@ -0,0 +1,145 @@
<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 class="relative">
<view v-if="task.process !== 4">{{ $moment(+task.planStart).format(startTimeFormat) }}</view>
<view v-else>{{ $moment(+task.planStart).format('M/D') }}</view>
<u-badge :is-dot="true" style="transform: translate3d(2em, -1em, 0)" v-show="task.remindNum > 0"></u-badge>
</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"> -->
<view class="ml-3">
<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>
<view v-if="tasks && tasks.length && task.process !== 4 && !showSkeleton" @click="onClickTask(task.planStart - 0, task.id)">
<view class="p-0 u-col-between grid gap-3">
<view :key="pIndex" v-for="(row, pIndex) in task.plugins">
<view class="grid grid-cols-1" v-if="row.length">
<Plugin
v-for="plugin in row"
:key="plugin.pluginTaskId"
:class="[`row-span-${plugin.row}`, `col-span-${plugin.col}`]"
:task="task"
:plugin-info="plugin"
:plugin-task-id="plugin.pluginTaskId"
:business-plugin-id="plugin.businessPluginId"
:plugin-id="plugin.pluginId"
:param="plugin.param"
:style-type="data.styleType || 0"
/>
</view>
</view>
</view>
</view>
</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';
import Skeleton from '@/components/Skeleton/Skeleton.vue';
const props = defineProps({
tasks: {
type: Array,
default: () => [],
},
});
const data = reactive({
currentComponent: '',
styleType: 0,
});
const store = useStore();
const projectId = computed(() => store.getters['project/projectId']);
const roleId = computed(() => store.state.role.roleId);
const visibleRoles = computed(() => store.state.role.visibleRoles); //
const allTasks = computed(() => store.state.task.allTasks); //
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) {
if (roleId.value) {
const param = { projectId: projectId.value, 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

82
components/Tips/Tips.vue

@ -0,0 +1,82 @@
<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 { onMounted, computed, reactive } from 'vue';
import { useStore } 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>

232
components/Title/Title.vue

@ -1,8 +1,236 @@
<template>
<theme>
<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">{{ titleName || 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>
</theme>
</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';
defineProps({
titleName: {
type: String,
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,
},
});
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}` });
uni.reLaunch({ url: `/pages/index/index` });
}
}
// LWBS
function lwbs() {
// uni.$ui.showToast('LWBS');
}
//
function projectOverview() {
// uni.$ui.showToast('');
}
//
function openIndex() {
// uni.webView.reLaunch({ url: `/pages/index/index?u=${this.userId}` });
uni.reLaunch({ url: `/pages/index/index` });
}
//
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>

93
components/Upload/Upload.vue

@ -0,0 +1,93 @@
<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, ref } from 'vue';
import { useStore } from 'vuex';
import Config from '@/common/js/config.js';
const store = useStore();
const list = ref([]);
const emit = defineEmits(['success', 'error']);
const user = computed(() => store.state.user.user);
const userJson = uni.$storage.getStorageSync('user');
if (userJson) {
const user = JSON.parse(userJson);
store.commit('user/setUser', user);
}
getList();
// wbs
const handleUpload = async cur => {
// #ifdef APP-PLUS
uni.$ui.showToast('APP暂不支持导入');
// #endif
// #ifndef APP-PLUS
if (list.value.length === 1) {
store.commit('setDomain', list.value[0].url);
uni.showModal({
content: '是否上传到' + list.value[0].name,
showCancel: true,
success: async ({ confirm }) => {
if (confirm) {
try {
const res = await uni.$u.api.import('');
// WBS
//
emit('success');
const { apiUrl } = Config;
let defaultwbs = `${apiUrl}/defaultwbs`;
res.url && (defaultwbs = res.url);
store.commit('project/setIsRefresh', 1);
setTimeout(() => {
uni.navigateTo({ url: `/pages/project/project?u=${user.value.id}&p=${res.id}&pname=${res.name}&url=${encodeURIComponent(res.url)}` });
}, 2000);
} catch (error) {
console.error('error: ', error);
emit('error', error);
}
}
},
});
} else {
uni.navigateTo({
url: '/pages/business/business'
})
}
// #endif
};
async function getList() {
try {
const res = await uni.$u.api.getBusinessList();
list.value = res;
} catch (error) {
console.error('error: ', error);
}
}
</script>
<style lang="scss" scoped>
.upload {
position: absolute;
right: 10px;
bottom: 0;
transform: translate3d(0, 50%, 0);
color: $uni-color-primary !important;
z-index: 9;
}
</style>

316
components/master-keyboard/master-keyboard.scss

@ -0,0 +1,316 @@
.master_wrap{
width: 100%;
background-color: #eee;
position: fixed;
bottom: 0;
left: 0;
padding-top: 10rpx;
padding-bottom: 20rpx;
.down_wrap{
width: 100%;
height: 50rpx;
display: flex;
align-items: center;
justify-content: center;
image{
width: 48rpx;
height: 48rpx;
}
}
.kerboard_number{
width: 100%;
display: flex;
justify-content: space-evenly;
flex-flow: wrap;
.number_item{
width: 30%;
height: 80rpx;
margin-top: 10rpx;
background-color: white;
border-radius: 10rpx;
text-align: center;
line-height: 80rpx;
box-sizing: border-box;
display: block;
}
.number_item_active{
width: 30%;
height: 80rpx;
margin-top: 10rpx;
background-color: #888;
border-radius: 10rpx;
text-align: center;
line-height: 80rpx;
box-sizing: border-box;
display: block;
color: white;
}
.number_empty{
width: 30%;
height: 80rpx;
margin-top: 10rpx;
background-color: white;
border-radius: 10rpx;
text-align: center;
line-height: 80rpx;
box-sizing: border-box;
display: block;
// background-color: #eeeeee;
// width: 30%;
// height: 80rpx;
// margin-top: 10rpx;
// border-radius: 10rpx;
}
.number_empty_active{
background-color: #888;
// background-color: #eeeeee;
}
.number_delete{
width: 30%;
height: 80rpx;
margin-top: 10rpx;
background-color: white;
display: flex;
justify-content: center;
align-items: center;
.delete_img{
width: 65rpx;
height: 64rpx;
}
}
.number_delete_active{
width: 30%;
height: 80rpx;
margin-top: 10rpx;
background-color: #888;
display: flex;
justify-content: center;
align-items: center;
border-radius: 10rpx;
.delete_img{
width: 65rpx;
height: 64rpx;
}
}
}
.keyboard_car{
width: 100%;
display: flex;
flex-direction: column;
position: relative;
.car_down{
position: absolute;
top: 10rpx;
left: 15rpx;
width: 48rpx;
height: 48rpx;
}
.tab_wrap{
height: 65rpx;
margin: 0 auto;
background-color: white;
border-radius: 20rpx;
display: flex;
padding-left: 25rpx;
padding-right: 25rpx;
image{
width: 48rpx;
height: 48rpx;
}
.tab_item{
height: 100%;
text-align: center;
line-height: 65rpx;
font-size: 26rpx;
margin: 0 20rpx;
}
.tab_item_active{
height: 100%;
text-align: center;
line-height: 65rpx;
font-size: 26rpx;
margin: 0 20rpx;
text-decoration: underline;
color: #e64d3a;
}
}
.car_content{
width: 100%;
position: relative;
.car_province{
width: 100%;
display: flex;
justify-content: space-evenly;
flex-flow: wrap;
margin: 0;
.province_item{
width: 9.5%;
height: 80rpx;
margin-top: 10rpx;
background-color: white;
border-radius: 10rpx;
text-align: center;
line-height: 80rpx;
box-sizing: border-box;
display: block;
font-size: 28rpx;
font-weight: 450;
}
.province_item_active{
width: 9.5%;
height: 80rpx;
margin-top: 10rpx;
background-color: #888;
border-radius: 10rpx;
text-align: center;
line-height: 80rpx;
box-sizing: border-box;
display: block;
font-size: 28rpx;
font-weight: 450;
color: white;
}
.province_item_disable{
width: 9.5%;
height: 80rpx;
margin-top: 10rpx;
background-color: #fff;
border-radius: 10rpx;
text-align: center;
line-height: 80rpx;
box-sizing: border-box;
display: block;
font-size: 28rpx;
font-weight: 450;
opacity: 0.5;
}
}
.car_province_1{
width: 100%;
display: flex;
margin: 0;
.province_item1{
margin: 0;
width: 9.5%;
height: 80rpx;
margin-top: 10rpx;
background-color: white;
border-radius: 10rpx;
text-align: center;
line-height: 80rpx;
box-sizing: border-box;
display: block;
font-size: 28rpx;
font-weight: 450;
}
.province_item_active{
width: 9.5%;
height: 80rpx;
margin-top: 10rpx;
background-color: #888;
border-radius: 10rpx;
text-align: center;
line-height: 80rpx;
box-sizing: border-box;
display: block;
font-size: 28rpx;
font-weight: 450;
color: white;
}
.province_item_disable{
width: 9.5%;
height: 80rpx;
margin-top: 10rpx;
background-color: #fff;
border-radius: 10rpx;
text-align: center;
line-height: 80rpx;
box-sizing: border-box;
display: block;
font-size: 28rpx;
font-weight: 450;
opacity: 0.5;
}
}
.car_latter{
width: 100%;
display: flex;
justify-content: space-evenly;
flex-flow: wrap;
.latter_item{
width: 9.5%;
height: 80rpx;
margin-top: 10rpx;
background-color: white;
border-radius: 10rpx;
text-align: center;
line-height: 80rpx;
box-sizing: border-box;
display: block;
font-size: 28rpx;
font-weight: 450;
}
.province_item_active{
width: 9.5%;
height: 80rpx;
margin-top: 10rpx;
background-color: #888;
border-radius: 10rpx;
text-align: center;
line-height: 80rpx;
box-sizing: border-box;
display: block;
font-size: 28rpx;
font-weight: 450;
color: white;
}
.province_item_disable{
width: 9.5%;
height: 80rpx;
margin-top: 10rpx;
background-color: #fff;
border-radius: 10rpx;
text-align: center;
line-height: 80rpx;
box-sizing: border-box;
display: block;
font-size: 28rpx;
font-weight: 450;
opacity: 0.5;
}
}
.car_delete{
width: 15%;
height: 80rpx;
margin-top: 10rpx;
background-color: white;
border-radius: 10rpx;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
.delete_img{
width: 65rpx;
height: 64rpx;
}
}
.car_delete_active{
width: 15%;
height: 80rpx;
margin-top: 10rpx;
background-color: #e64d3a;
border-radius: 10rpx;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
.delete_img{
width: 65rpx;
height: 64rpx;
}
}
}
}
}

312
components/master-keyboard/master-keyboard.vue

@ -0,0 +1,312 @@
<template>
<view class="master_wrap" v-show="show" hover-stop-propagation>
<block v-if="keyboardtype == 'number' || keyboardtype == 'digit' || keyboardtype == 'idcard'">
<view class="down_wrap">
<image src="../../static/icon_down.png" @tap.stop="handleCloseKeyboard(false)"></image>
</view>
<view class="kerboard_number">
<view @tap.stop="handleNumberKeyboardClick(item)" :hover-stay-time="100"
:class="item== '-1' ? keyboardtype == 'number' ? 'number_empty' :'number_item' : item=='-2' ? 'number_delete' : 'number_item' "
:hover-class="item== '-1' ? keyboardtype == 'number' ? 'number_empty_active' : 'number_item_active' : item=='-2' ? 'number_delete_active' : 'number_item_active'"
v-for="(item,index) in randomNumberArr" :key="index">
<block v-if="item == '-1'">
<view v-if="keyboardtype == 'number'" @click="toPaste">
粘贴
</view>
<view v-else-if="keyboardtype == 'digit'">
·
</view>
<view v-else-if=" keyboardtype == 'idcard'">
X
</view>
</block>
<block v-else-if="item == '-2'">
<image class="delete_img" src="../../static/icon_delete.png"></image>
</block>
<block v-else>
{{item}}
</block>
</view>
</view>
</block>
<block v-else-if="keyboardtype == 'car'">
<view class="keyboard_car">
<image @tap.stop="handleCloseKeyboard(false)" class="car_down" src="../../static/icon_down.png"></image>
<view class="tab_wrap">
<view @tap.stop="handleCarTabItemClick(index)" :class=" carType === index ?'tab_item_active' : 'tab_item'"
hover-class="tab_item_active" v-for="(item,index) in carTabArr" :key="index">
{{item}}
</view>
</view>
<view class="car_content">
<block v-if="carType === 0">
<view class="car_province">
<view @tap.stop="handleCarKerboardClick(item)"
:class="computedDefaultValueLength ===0 ? 'province_item' :'province_item_disable'"
:hover-class="computedDefaultValueLength ===0 ? 'province_item_active' : 'none'" :hover-stay-time="100"
v-for="(item,index) in sliceProvinceArr" :key="index">
{{item}}
</view>
</view>
<view class="car_province_1">
<view @tap.stop="handleCarKerboardClick(item)"
:class=" (formatCarProvinceFirst(item) === -1 && computedDefaultValueLength ===0 ) ? 'province_item1' :
((newCar && computedDefaultValueLength ===7 && formatCarProvinceFirst(item) !== -1) ||
(!newCar && computedDefaultValueLength ===6 && formatCarProvinceFirst(item) !== -1) ) ? 'province_item1' : 'province_item_disable'"
:hover-class=" (formatCarProvinceFirst(item) === -1 && computedDefaultValueLength ===0) ? 'province_item_active' : 'none'"
:hover-stay-time="100" :style="{'margin-left' : computeItemSpace + '%'}"
v-for="(item,index) in sliceSecondProvinceArr" :key="index">
{{item}}
</view>
</view>
</block>
<block v-else="carType === 1">
<view class="car_latter">
<view @tap.stop="handleCarKerboardClick(item)"
:class=" (computedDefaultValueLength ===1 && formatCarProvinceSecond(item) >-1 || computedDefaultValueLength ===0) ? 'province_item_disable' : 'latter_item'"
:hover-class=" (computedDefaultValueLength ===1 && formatCarProvinceSecond(item) >-1 || computedDefaultValueLength ===0) ? 'none' : 'province_item_active'"
:hover-stay-time="100" v-for="(item,index) in sliceLatterArr" :key="index">
<text>{{item}}</text>
</view>
</view>
<view class="car_province_1">
<view @tap.stop="handleCarKerboardClick(item)"
:class=" (computedDefaultValueLength ===1 && formatCarProvinceSecond(item) >-1 || computedDefaultValueLength ===0) ? 'province_item_disable' : 'province_item1'"
:hover-class=" (computedDefaultValueLength ===1 && formatCarProvinceSecond(item) >-1 || computedDefaultValueLength ===0) ? 'none' : 'province_item_active'"
:hover-stay-time="100" :style="{'margin-left' : computeItemSpace + '%'}"
v-for="(item,index) in sliceSecondLatterArr" :key="index">
{{item}}
</view>
</view>
</block>
<view @tap.stop="handleCarKerboardDeleteClick" class="car_delete" hover-class="car_delete_active"
:hover-stay-time="100" :style="{'top':computeDeleteTop + 'rpx','right' :computeItemSpace + '%' }">
<image class="delete_img" src="../../static/icon_delete.png"></image>
</view>
</view>
</view>
</block>
</view>
</template>
<script>
export default {
name: "master-keyboard",
props: {
keyboardtype: {
type: String,
default: 'number' // number= digit= idcard= car=
},
defaultValue: {
type: String,
default: ''
},
newCar: {
type: Boolean,
default: false
},
randomNumber: {
type: Boolean,
default: false
}
},
data() {
return {
numberArr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
carProvinceArr: ['京', '津', '渝', '沪', '冀', '晋', '辽', '吉', '黑', '苏', '浙', '皖', '闽', '赣', '鲁', '豫',
'鳄', '湘', '粤', '琼', '川', '贵', '云', '陕', '甘', '青', '蒙', '桂', '宁', '新', '藏', '使',
'领', '警', '学', '港', '澳'
],
carLatterArr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
],
carTabArr: ['省份', '字母'],
carType: 0,
show: false,
};
},
computed: {
sliceProvinceArr: function() {
return this.carProvinceArr.slice(0, 30)
},
sliceSecondProvinceArr: function() {
return this.carProvinceArr.slice(30, this.carProvinceArr.length)
},
sliceLatterArr: function() {
return this.carLatterArr.slice(0, 30)
},
sliceSecondLatterArr: function() {
return this.carLatterArr.slice(30, this.carLatterArr.length)
},
computeDeleteTop: function() {
return 80 * 3 + 10 * 3
},
computeItemSpace: function() {
return (1 - 0.095 * 10) / 11 * 100
},
computedDefaultValueLength: function() {
return this.$props.defaultValue.length
},
randomNumberArr: function() {
// if(this.$props.randomNumber){
// this.numberArr.sort(function() {
// return 0.5 - Math.random()
// })
// }
const size = this.numberArr.length
this.numberArr.splice(size - 1, 0, "-1")
this.numberArr.push("-2")
return this.numberArr
}
},
watch: {
show: function(value) {
}
},
onLoad() {},
methods: {
formatCarProvinceFirst(value) {
const list = ['使', '领', '警', '学', '港', '澳', ]
return list.indexOf(value)
},
formatCarProvinceSecond(value) {
const list = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']
return list.indexOf(value)
},
handleCloseKeyboard() {
this.open(false)
},
open(value) {
this.show = value
},
//tab
handleCarTabItemClick(index) {
this.carType = index
},
//
handleCarKerboardClick(e) {
const keyValue = e
let value = this.$props.defaultValue
if (/^[\u4e00-\u9fa5]*$/.test(value) && value.length >= 2) {
value = ''
}
var pattern = new RegExp("[\u4E00-\u9FA5]+");
var pattern2 = new RegExp("[A-Za-z]+");
var pattern3 = new RegExp("[0-9]+");
if (value.length == 0) {
if (!pattern.test(keyValue) || this.formatCarProvinceFirst(keyValue) !== -1) {
return
}
this.carType = 1
} else if (value.length == 1) {
if (!pattern2.test(keyValue)) {
return
}
} else {
if (value.length === (this.$props.newCar ? 7 : 6) && pattern.test(keyValue) && this
.formatCarProvinceFirst(keyValue) == -1) {
return
}
if (pattern.test(keyValue) && value.length !== (this.$props.newCar ? 7 : 6)) {
return
}
}
if (value.length < 7 && !this.$props.newCar) {
this.$emit('keyboardClick', {
value: value + keyValue
})
}
if (value.length < 8 && this.$props.newCar) {
this.$emit('keyboardClick', {
value: value + keyValue
})
}
},
//
handleCarKerboardDeleteClick() {
let deleteValue = this.$props.defaultValue
if (deleteValue == '' || (/^[\u4e00-\u9fa5]*$/.test(deleteValue) && deleteValue.length !== 1)) {
return
}
if (deleteValue.length > 0) {
let count = deleteValue.length
this.$emit('keyboardClick', {
value: deleteValue.substr(0, count - 1)
})
}
},
//
handleNumberKeyboardClick(e) {
const keyValue = e
if (this.$props.keyboardtype == 'number' || this.$props.keyboardtype == 'digit' || this.$props
.keyboardtype == 'idcard') {
switch (keyValue) {
case '-1':
if (this.$props.keyboardtype == 'number' || this.$props.defaultValue == '') {
return
}
let value = this.$props.defaultValue
if (/^[\u4e00-\u9fa5]*$/.test(value) || value.indexOf('.') > -1 || value.indexOf('X') > -1) {
return
}
if (this.$props.keyboardtype == 'idcard') {
if (value.length === 17) {
this.$emit('keyboardClick', {
value: value + 'X'
})
}
}
if (this.$props.keyboardtype == 'digit') {
this.$emit('keyboardClick', {
value: value + '.'
})
}
break
case '-2':
let deleteValue = this.$props.defaultValue
if (deleteValue == '' || /^[\u4e00-\u9fa5]*$/.test(deleteValue)) {
return
}
if (deleteValue.length > 0) {
let count = deleteValue.length
this.$emit('keyboardClick', {
value: deleteValue.substr(0, count - 1)
})
}
break
default:
let initValue = this.$props.defaultValue
if (/^[\u4e00-\u9fa5]*$/.test(initValue)) {
initValue = ''
}
if (this.$props.keyboardtype == 'idcard') {
if (initValue.length < 18) {
this.$emit('keyboardClick', {
value: initValue + keyValue
})
}
return
}
this.$emit('keyboardClick', {
value: initValue + keyValue
})
break
}
}
},
//
toPaste() {
this.$emit('toPaste')
}
}
}
</script>
<style scoped lang="scss">
@import './master-keyboard.scss';
</style>

22
components/uni-popup/message.js

@ -0,0 +1,22 @@
export default {
created() {
if (this.type === 'message') {
// 不显示遮罩
this.maskShow = false;
// 获取子组件对象
this.childrenMsg = null;
}
},
methods: {
customOpen() {
if (this.childrenMsg) {
this.childrenMsg.open();
}
},
customClose() {
if (this.childrenMsg) {
this.childrenMsg.close();
}
},
},
};

23
components/uni-popup/popup.js

@ -0,0 +1,23 @@
import message from './message.js';
// 定义 type 类型:弹出类型:top/bottom/center
const config = {
// 顶部弹出
top: 'top',
// 底部弹出
bottom: 'bottom',
// 居中弹出
center: 'center',
// 消息提示
message: 'top',
// 对话框
dialog: 'center',
// 分享
share: 'bottom',
};
export default {
data() {
return { config: config };
},
mixins: [message],
};

246
components/uni-popup/uni-popup-dialog.vue

@ -0,0 +1,246 @@
<template>
<view class="uni-popup-dialog">
<view class="uni-dialog-title">
<text class="uni-dialog-title-text" :class="['uni-popup__' + dialogType]">{{ title }}</text>
</view>
<view class="uni-dialog-content">
<text class="uni-dialog-content-text" v-if="mode === 'base'">{{ content }}</text>
<input v-else class="uni-dialog-input" v-model="val" type="text" :placeholder="placeholder" :focus="focus" />
</view>
<view class="uni-dialog-button-group">
<view class="uni-dialog-button" @click="close">
<text class="uni-dialog-button-text">取消</text>
</view>
<view class="uni-dialog-button uni-border-left" @click="onOk">
<text class="uni-dialog-button-text uni-button-color">确定</text>
</view>
</view>
</view>
</template>
<script>
/**
* PopUp 弹出层-对话框样式
* @description 弹出层-对话框样式
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} value input 模式下的默认值
* @property {String} placeholder input 模式下输入提示
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} mode = [base|input] 模式
* @value base 基础对话框
* @value input 可输入对话框
* @property {String} content 对话框内容
* @property {Boolean} beforeClose 是否拦截取消事件
* @event {Function} confirm 点击确认按钮触发
* @event {Function} close 点击取消按钮触发
*/
export default {
name: 'uniPopupDialog',
props: {
value: {
type: [String, Number],
default: '',
},
placeholder: {
type: [String, Number],
default: '请输入内容',
},
/**
* 对话框主题 success/warning/info/error 默认 success
*/
type: {
type: String,
default: 'error',
},
/**
* 对话框模式 base/input
*/
mode: {
type: String,
default: 'base',
},
/**
* 对话框标题
*/
title: {
type: String,
default: '提示',
},
/**
* 对话框内容
*/
content: {
type: String,
default: '',
},
/**
* 拦截取消事件 如果拦截取消事件必须监听close事件执行 done()
*/
beforeClose: {
type: Boolean,
default: false,
},
},
data() {
return {
dialogType: 'error',
focus: false,
val: '',
};
},
inject: ['popup'],
watch: {
type(val) {
this.dialogType = val;
},
mode(val) {
if (val === 'input') {
this.dialogType = 'info';
}
},
value(val) {
this.val = val;
},
},
created() {
//
this.popup.mkclick = false;
if (this.mode === 'input') {
this.dialogType = 'info';
this.val = this.value;
} else {
this.dialogType = this.type;
}
},
mounted() {
this.focus = true;
},
methods: {
/**
* 点击确认按钮
*/
onOk() {
this.$emit(
'confirm',
() => {
this.popup.close();
if (this.mode === 'input') this.val = this.value;
},
this.mode === 'input' ? this.val : '',
);
},
/**
* 点击取消按钮
*/
close() {
if (this.beforeClose) {
this.$emit('close', () => {
this.popup.close();
});
return;
}
this.popup.close();
},
},
};
</script>
<style lang="scss" scoped>
.uni-popup-dialog {
width: 300px;
border-radius: 15px;
background-color: #fff;
}
.uni-dialog-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 15px;
padding-bottom: 5px;
}
.uni-dialog-title-text {
font-size: 16px;
font-weight: 500;
}
.uni-dialog-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
padding: 5px 15px 15px 15px;
}
.uni-dialog-content-text {
font-size: 14px;
color: #6e6e6e;
}
.uni-dialog-button-group {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
border-top-color: #f5f5f5;
border-top-style: solid;
border-top-width: 1px;
}
.uni-dialog-button {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
justify-content: center;
align-items: center;
height: 45px;
}
.uni-border-left {
border-left-color: #f0f0f0;
border-left-style: solid;
border-left-width: 1px;
}
.uni-dialog-button-text {
font-size: 14px;
}
.uni-button-color {
color: $uni-color-primary;
}
.uni-dialog-input {
flex: 1;
font-size: 14px;
}
.uni-popup__success {
color: $uni-color-success;
}
.uni-popup__warn {
color: $uni-color-warning;
}
.uni-popup__error {
color: $uni-color-error;
}
.uni-popup__info {
color: #909399;
}
</style>

115
components/uni-popup/uni-popup-message.vue

@ -0,0 +1,115 @@
<template>
<view class="uni-popup-message" :class="'uni-popup__' + [type]">
<text class="uni-popup-message-text" :class="'uni-popup__' + [type] + '-text'">{{ message }}</text>
</view>
</template>
<script>
/**
* PopUp 弹出层-消息提示
* @description 弹出层-消息提示
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} message 消息提示文字
* @property {String} duration 显示时间设置为 0 则不会自动关闭
*/
export default {
name: 'UniPopupMessage',
props: {
/**
* 主题 success/warning/info/error 默认 success
*/
type: {
type: String,
default: 'success',
},
/**
* 消息文字
*/
message: {
type: String,
default: '',
},
/**
* 显示时间设置为 0 则不会自动关闭
*/
duration: {
type: Number,
default: 3000,
},
},
inject: ['popup'],
data() {
return {};
},
created() {
this.popup.childrenMsg = this;
},
methods: {
open() {
if (this.duration === 0) return;
clearTimeout(this.popuptimer);
this.popuptimer = setTimeout(() => {
this.popup.close();
}, this.duration);
},
close() {
clearTimeout(this.popuptimer);
},
},
};
</script>
<style lang="scss" scoped>
.uni-popup-message {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
background-color: #e1f3d8;
padding: 10px 15px;
border-color: #eee;
border-style: solid;
border-width: 1px;
}
.uni-popup-message-text {
font-size: 14px;
padding: 0;
}
.uni-popup__success {
background-color: #e1f3d8;
}
.uni-popup__success-text {
color: #67c23a;
}
.uni-popup__warn {
background-color: #faecd8;
}
.uni-popup__warn-text {
color: #e6a23c;
}
.uni-popup__error {
background-color: #fde2e2;
}
.uni-popup__error-text {
color: #f56c6c;
}
.uni-popup__info {
background-color: #f2f6fc;
}
.uni-popup__info-text {
color: #909399;
}
</style>

171
components/uni-popup/uni-popup-share.vue

@ -0,0 +1,171 @@
<template>
<view class="uni-popup-share">
<view class="uni-share-title">
<text class="uni-share-title-text">{{ title }}</text>
</view>
<view class="uni-share-content">
<view class="uni-share-content-box">
<view class="uni-share-content-item" v-for="(item, index) in bottomData" :key="index" @click.stop="select(item, index)">
<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
<text class="uni-share-text">{{ item.text }}</text>
</view>
</view>
</view>
<view class="uni-share-button-box">
<button class="uni-share-button" @click="close">取消</button>
</view>
</view>
</template>
<script>
export default {
name: 'UniPopupShare',
props: {
title: {
type: String,
default: '分享到',
},
},
inject: ['popup'],
data() {
return {
bottomData: [
{
text: '微信',
icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-2.png',
name: 'wx',
},
{
text: '支付宝',
icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-8.png',
name: 'wx',
},
{
text: 'QQ',
icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/gird-3.png',
name: 'qq',
},
{
text: '新浪',
icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-1.png',
name: 'sina',
},
{
text: '百度',
icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-7.png',
name: 'copy',
},
{
text: '其他',
icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-5.png',
name: 'more',
},
],
};
},
created() {},
methods: {
/**
* 选择内容
*/
select(item, index) {
this.$emit(
'select',
{
item,
index,
},
() => {
this.popup.close();
},
);
},
/**
* 关闭窗口
*/
close() {
this.popup.close();
},
},
};
</script>
<style lang="scss" scoped>
.uni-popup-share {
background-color: #fff;
}
.uni-share-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
height: 40px;
}
.uni-share-title-text {
font-size: 14px;
color: #666;
}
.uni-share-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 10px;
}
.uni-share-content-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: wrap;
width: 360px;
}
.uni-share-content-item {
width: 90px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
padding: 10px 0;
align-items: center;
}
.uni-share-content-item:active {
background-color: #f5f5f5;
}
.uni-share-image {
width: 30px;
height: 30px;
}
.uni-share-text {
margin-top: 10px;
font-size: 14px;
color: #3b4144;
}
.uni-share-button-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
padding: 10px 15px;
}
.uni-share-button {
flex: 1;
border-radius: 50px;
color: #666;
font-size: 16px;
}
.uni-share-button::after {
border-radius: 50px;
}
</style>

289
components/uni-popup/uni-popup.vue

@ -0,0 +1,289 @@
<template>
<view v-if="showPopup" class="uni-popup" :class="[popupstyle]" @touchmove.stop.prevent="clear">
<uni-transition v-if="maskShow" :mode-class="['fade']" :styles="maskClass" :duration="duration" :show="showTrans" @click="onTap" />
<uni-transition :mode-class="ani" :styles="transClass" :duration="duration" :show="showTrans" @click="onTap">
<view class="uni-popup__wrapper-box" @click.stop="clear">
<slot />
</view>
</uni-transition>
</view>
</template>
<script>
import uniTransition from '../uni-transition/uni-transition.vue';
import popup from './popup.js';
/**
* PopUp 弹出层
* @description 弹出层组件为了解决遮罩弹层的问题
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [top|center|bottom] 弹出方式
* @value top 顶部弹出
* @value center 中间弹出
* @value bottom 底部弹出
* @value message 消息提示
* @value dialog 对话框
* @value share 底部分享示例
* @property {Boolean} animation = [ture|false] 是否开启动画
* @property {Boolean} maskClick = [ture|false] 蒙版点击是否关闭弹窗
* @event {Function} change 打开关闭弹窗触发e={show: false}
*/
export default {
name: 'UniPopup',
components: { uniTransition },
props: {
//
animation: {
type: Boolean,
default: true,
},
// top: bottomcenter
// message: ; dialog :
type: {
type: String,
default: 'center',
},
// maskClick
maskClick: {
type: Boolean,
default: true,
},
},
provide() {
return { popup: this };
},
mixins: [popup],
watch: {
/**
* 监听type类型
*/
type: {
handler: function (newVal) {
this[this.config[newVal]]();
},
immediate: true,
},
/**
* 监听遮罩是否可点击
* @param {Object} val
*/
maskClick(val) {
this.mkclick = val;
},
},
data() {
return {
duration: 300,
ani: [],
showPopup: false,
showTrans: false,
maskClass: {
position: 'fixed',
bottom: 0,
top: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(0, 0, 0, 0.4)',
},
transClass: {
position: 'fixed',
left: 0,
right: 0,
},
maskShow: true,
mkclick: true,
popupstyle: 'top',
};
},
created() {
this.mkclick = this.maskClick;
if (this.animation) {
this.duration = 300;
} else {
this.duration = 0;
}
},
methods: {
clear(e) {
// TODO nvue
e.stopPropagation();
},
open() {
this.showPopup = true;
this.$nextTick(() => {
new Promise(resolve => {
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.showTrans = true;
// fixed by mehaotian app
this.$nextTick(() => {
resolve();
});
}, 50);
}).then(res => {
console.log('res: ', res);
//
clearTimeout(this.msgtimer);
this.msgtimer = setTimeout(() => {
this.customOpen && this.customOpen();
}, 100);
this.$emit('change', {
show: true,
type: this.type,
});
});
});
},
close(type) {
this.showTrans = false;
this.$nextTick(() => {
this.$emit('change', {
show: false,
type,
});
clearTimeout(this.timer);
//
this.customOpen && this.customClose();
this.timer = setTimeout(() => {
this.showPopup = false;
}, 300);
});
},
onTap() {
if (!this.mkclick) return;
this.close();
},
/**
* 顶部弹出样式处理
*/
top() {
this.popupstyle = 'top';
this.ani = ['slide-top'];
this.transClass = {
position: 'fixed',
left: 0,
right: 0,
};
},
/**
* 底部弹出样式处理
*/
bottom() {
this.popupstyle = 'bottom';
this.ani = ['slide-bottom'];
this.transClass = {
position: 'fixed',
left: 0,
right: 0,
bottom: 0,
};
},
/**
* 中间弹出样式处理
*/
center() {
this.popupstyle = 'center';
this.ani = ['zoom-out', 'fade'];
this.transClass = {
position: 'fixed',
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column',
/* #endif */
bottom: 0,
left: 0,
right: 0,
top: 0,
justifyContent: 'center',
alignItems: 'center',
};
},
},
};
</script>
<style lang="scss" scoped>
.uni-popup {
position: fixed;
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
}
.uni-popup__mask {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: $uni-bg-color-mask;
opacity: 0;
}
.mask-ani {
transition-property: opacity;
transition-duration: 0.2s;
}
.uni-top-mask {
opacity: 1;
}
.uni-bottom-mask {
opacity: 1;
}
.uni-center-mask {
opacity: 1;
}
.uni-popup__wrapper {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
position: absolute;
}
.top {
/* #ifdef H5 */
top: var(--window-top);
/* #endif */
/* #ifndef H5 */
top: 0;
/* #endif */
}
.bottom {
bottom: 0;
}
.uni-popup__wrapper-box {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
position: relative;
/* iphonex 等安全区设置,底部安全区适配 */
/* #ifndef APP-NVUE */
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
/* #endif */
}
.content-ani {
// transition: transform 0.3s;
transition-property: transform, opacity;
transition-duration: 0.2s;
}
.uni-top-content {
transform: translateY(0);
}
.uni-bottom-content {
transform: translateY(0);
}
.uni-center-content {
transform: scale(1);
opacity: 1;
}
</style>

226
components/zwp-ring-timing/zwp-ring-timing.vue

@ -0,0 +1,226 @@
<template>
<view v-if="showViewToken" class="ring-timing" :style="containerStyles">
<!-- #ifndef APP-NVUE -->
<view class="ring-timing-half ring-timing-left" :style="leftStyles" @transitionend="onTimingEnd" />
<view class="ring-timing-half ring-timing-right" :style="rightStyles" />
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view :ref="`leftHalf${showViewToken}`" class="ring-timing-half ring-timing-left" :style="leftStyles" />
<view :ref="`rightHalf${showViewToken}`" class="ring-timing-half ring-timing-right" :style="rightStyles" />
<!-- #endif -->
<view class="ring-timing-center" :style="centerStyles"><slot /></view>
</view>
</template>
<script>
// #ifdef APP-NVUE
const animation = weex.requireModule('animation')
// #endif
/**
* zwp-ring-timing 圆环计时器不使用canvas和定时器仅css
* @property {String} *mode 模式可选值timing定时器chart当作图表用于展示数据图表模式仅限看看真要做图表还是用canvas好
* @property {String} activeColor 进度条走过的颜色
* @property {String} defaultColor 进度条底色
* @property {String} centerBgColor 圆环中间区域的背景色
* @property {Number} radius 圆形半径或整个组件的一半尺寸包含了圆环的宽度
* @property {Number} barWidth 圆环宽度
* @property {Number} startDeg 进度开始的角度
* @property {Number} duration timing模式的定时时间
* @property {Number} value chart模式的值1~100
* @event {Function} timingEnd timing模式下定时完成的回调
*/
export default {
props: {
mode: {
validator: value => ['timing', 'chart'].includes(value),
default: 'timing'
},
activeColor: {
type: String,
default: '#42b983'
},
defaultColor: {
type: String,
default: '#EEEEEE'
},
centerBgColor: {
type: String,
default: '#FFFFFF'
},
radius: {
type: Number,
default: 100
},
barWidth: {
type: Number,
default: 10
},
startDeg: {
type: Number,
default: 0
},
duration: {
type: Number,
default: 1
},
value: Number
},
data() {
return {
isStart: false,
showViewToken: Date.now() // nvue
}
},
computed: {
containerStyles() {
const { radius, startDeg, activeColor } = this
return {
borderRadius: `${radius}rpx`,
height: `${radius * 2}rpx`,
width: `${radius * 2}rpx`,
transform: `rotate(${startDeg}deg)`,
backgroundColor: activeColor
}
},
leftStyles() {
const { mode, radius, defaultColor, isStart, duration, value } = this
return {
height: `${radius * 2}rpx`,
width: `${radius}rpx`,
backgroundColor: defaultColor,
borderTopLeftRadius: `${radius}rpx`,
borderBottomLeftRadius: `${radius}rpx`,
...(mode == 'timing' ? {
// #ifndef APP-NVUE
transitionDuration: `${isStart ? duration : 0}s`,
transform: `rotate(${isStart ? 180 : -180}deg)`
// #endif
} : {
transform: `rotate(${-180 + value * 3.6}deg)`
})
}
},
rightStyles() {
const { mode, radius, activeColor, defaultColor, isStart, duration, value } = this
return {
height: `${radius * 2}rpx`,
width: `${radius}rpx`,
backgroundColor: defaultColor,
borderTopRightRadius: `${radius}rpx`,
borderBottomRightRadius: `${radius}rpx`,
...(mode == 'timing' ? {
// #ifndef APP-NVUE
backgroundColor: isStart ? activeColor : defaultColor,
transitionDelay: `${isStart ? duration / 2 : 0}s`,
transform: `rotate(${isStart ? 0 : -180}deg)`
// #endif
} : {
backgroundColor: value >= 50 ? activeColor : defaultColor,
transform: `rotate(${value >= 50 ? 0 : -180}deg)`,
})
}
},
centerStyles() {
const { radius, centerBgColor, barWidth, startDeg } = this
return {
borderRadius: `${radius - barWidth}rpx`,
height: `${(radius - barWidth) * 2}rpx`,
width: `${(radius - barWidth) * 2}rpx`,
transform: `translate(-50%, -50%) rotate(-${startDeg}deg)`,
backgroundColor: centerBgColor,
left: `${radius}rpx`,
top: `${radius}rpx`
}
}
},
methods: {
// #ifdef APP-NVUE
createAnimation(direction, styles, callback = () => {}) {
let { showViewToken, duration } = this
let isLeft = direction == 'left'
animation.transition(
this.$refs[`${direction}Half${showViewToken}`],
{
styles,
duration: isLeft ? duration * 1000 : 0,
delay: !isLeft ? (duration / 2) * 1000 : 0,
timingFunction: 'linear'
},
callback
)
},
// #endif
start() {
if (this.mode == 'chart') return
// #ifndef APP-NVUE
this.isStart = true
// #endif
// #ifdef APP-NVUE
const { createAnimation, activeColor, onTimingEnd } = this
createAnimation('left', {
transform: 'rotate(180deg)'
}, onTimingEnd)
createAnimation('right', {
backgroundColor: activeColor,
transform: 'rotate(0)'
})
// #endif
},
end() {
if (this.mode == 'chart') return
// #ifndef APP-NVUE
this.isStart = false
// #endif
// #ifdef APP-NVUE
this.showViewToken = 0
this.$nextTick(() => {
this.showViewToken = Date.now()
})
// #endif
},
onTimingEnd() {
this.$emit('timingEnd')
}
}
}
</script>
<style scoped>
.ring-timing {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
position: relative;
}
.ring-timing-half {
flex: 1;
/* #ifndef APP-NVUE */
transition-property: transform, background-color;
transition-timing-function: linear;
/* #endif */
/* #ifdef APP-NVUE */
transform: rotate(-180deg);
/* #endif */
}
.ring-timing-left {
transform-origin: right center;
}
.ring-timing-right {
transform-origin: left center;
}
.ring-timing-center {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
overflow: hidden;
position: absolute;
}
</style>

9
config/deliver.js

@ -0,0 +1,9 @@
// 上传文件的扩展名
export const UPLOAD_EXTENSION = ['.xls', '.xlsx', '.zip', '.exe', '.pdf', '.doc', '.docx', '.ppt', '.pptx'];
// 审核的快捷用语
export const quickWords = {
RESOLVE: ['加油,再接再厉!', '很棒!', '不错,很详细!', '不详细', '还有需要改进的地方', '驳回审批']
// RESOLVE: ['加油,再接再厉!', '很棒!', '不错,很详细!', '加油,再接再厉'], // 审核通过常用的审批语
// REJECT: ['不详细', '还有需要改进的地方', '驳回审批1', '驳回审批2'], // 审核驳回常用的审批语
};

20
config/index.js

@ -0,0 +1,20 @@
// 环境 (development|production)
export const SCENE = 'development';
// 一些特殊的API区分生产测试环境
export const api = {
baseUrl: {
development: 'https://test.tall.wiki',
production: 'https://www.tall.wiki',
},
msgUrl: {
development: 'wss://test.tall.wiki/websocket/message/v4.0/ws',
production: 'wss://www.tall.wiki/websocket/message/v4.0/ws',
},
upload: {
development: 'https://test.tall.wiki/filedeal/file/upload/multiple',
production: 'http://101.201.226.21/filedeal/file/upload/multiple',
},
};
export const UPLOAD_URL = api.upload[SCENE]; // 多文件上传路径

97
config/plugin.js

@ -0,0 +1,97 @@
// 定义插件相关信息
/* eslint-disable */
export default {
defaults: [
{
id: 1,
name: 'TASK_NAME',
description: '任务名插件',
component: 'p-task-title',
},
{
id: 2,
name: 'TASK_DESCRIPTION',
description: '任务描述插件',
component: 'p-task-description',
},
{
id: 3,
name: 'TASK_DURATION_DELAY',
description: '任务时长延迟插件(+-1min)时间格式可设置',
component: 'p-task-duration-delay',
},
{
id: 4,
name: 'TASK_START_TIME_DELAY',
description: '任务开始时间延迟插件(+-1hour)',
component: 'p-task-start-time-delay',
},
{
id: 5,
name: 'DELIVERABLE',
description: '交付物插件(人 + 交付物)可配置【仅人】 or 【仅交付物】 or 【人+交付物】',
component: 'p-deliverable',
},
{
id: 6,
name: 'SUBTASKS',
description: '子任务插件:显示子任务',
component: 'p-subtasks',
},
{
id: 7,
name: 'SUB_PROJECT',
description: '子项目插件:显示子项目',
component: 'p-sub-project',
},
{
id: 8,
name: 'TASK_COUNTDOWN',
description: '任务倒计时插件',
component: 'p-task-countdown',
},
{
id: 9,
name: 'MANAGE_PROJECT',
description: '项目信息管理插件',
component: 'p-manage-project',
},
{
id: 10,
name: 'MANAGE_ROLE',
description: '角色信息管理插件',
component: 'p-manage-role',
},
{
id: 11,
name: 'MANAGE_MEMBER',
description: '成员信息管理插件',
component: 'p-manage-member',
},
{
id: 12,
name: 'MANAGE_TASK',
description: '任务信息管理插件',
component: 'p-manage-task',
},
{
id: 13,
name: 'WBS_IMPORT',
description: '导入WBS新建项目',
component: 'p-wbs-import',
},
{
id: 14,
name: 'WBS_IMPORT_UPDATE',
description: '导入WBS更新项目',
component: 'p-wbs-update',
},
{
id: 15,
name: 'DELIVER_CHECK',
description: '交付物检查',
component: 'p-deliver-check',
},
], // 默认插件id列表
};

2
config/task.js

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

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: 'M/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: '' },
],
};

14
hooks/project/useGenerateWebviewParam.js

@ -0,0 +1,14 @@
import { computed } from 'vue';
import { useStore } from 'vuex';
export default function useGenerateWebviewParam() {
const store = useStore();
const projectId = computed(() => store.getters['project/projectId']);
const token = computed(() => store.state.user.token);
const projectName = computed(() => store.getters['project/projectName']);
return {
projectId: projectId.value,
token: token.value,
projectName: projectName.value,
};
}

476
hooks/project/useGetTasks - 副本 (2).js

@ -0,0 +1,476 @@
import { computed, nextTick, watch } 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 timeLineType = computed(() => store.state.task.timeLineType); // 时间轴模式
const realTasks = computed(() => store.state.task.realTasks); // 真实任务
const downNextPage = computed(() => store.state.task.downNextPage); // 下一页
const upNextPage = computed(() => store.state.task.upNextPage); // 下一页
const currUpTimeNode = computed(() => store.state.task.currUpTimeNode); // 当前查询的时间
const currDownTimeNode = computed(() => store.state.task.currDownTimeNode); // 当前查询的时间
const roleId = computed(() => store.state.role.roleId);
const timeNode = computed(() => store.state.task.timeNode);
const timeUnit = computed(() => store.state.task.timeUnit);
const visibleRoles = computed(() => store.state.role.visibleRoles);
const allTasks = computed(() => store.state.task.allTasks);
const roleIndex = computed(() => store.state.role.roleIndex);
const projectId = computed(() => store.getters['project/projectId']);
const timeGranularity = computed(() => store.getters['task/timeGranularity']);
const remindData = computed(() => store.state.socket.remindData); // 小红点
const currRoleRealTasks = computed(() => store.state.task.currRoleRealTasks); // 当前角色的真实任务数据
// 初始化 定期任务
async function initPlanTasks() {
// timeLineType.value === 1 ? setNextPlaceholderTasks({}) : '';
await getTasks({}); // 获取初始数据
// await dataRender({});
}
/**
* 生成getTasks所用的参数
* @param {object} query getTasks传递的参数
*/
function generateGetTaskParam(query) {
return {
roleId: roleId.value,
timeNode: query.timeNode || timeNode.value,
timeUnit: query.timeUnit || timeUnit.value,
queryType: query.queryType === 0 ? 0 : 1,
pageNum: query.pageNum || 1,
pageSize: query.pageSize || uni.$taskConfig.pageCount,
taskId: query.taskId || ''
};
}
/**
* 根据时间基准点和角色查找定期任务
* @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) {
store.commit('task/setShowSkeleton', false);
const params = generateGetTaskParam(query);
uni.$catchReq.getTaskByNum(params, (err, data) => {
store.commit('task/setShowSkeleton', false);
if (err) {
// TODO: 提示错误
console.error('err: ', err);
} else {
store.commit('task/setShowScrollTo', true);
params.queryType === 0 ? store.commit('task/setUpRealTasks', data.list) : store.commit('task/setDownRealTasks', data.list);
params.queryType === 0 ? store.commit('task/setUpNextPage', data.nextPage) : store.commit('task/setDownNextPage', data.nextPage); // 下一页
const index = visibleRoles.value.findIndex(role => role.id === roleId.value);
const arr = [...allTasks.value];
arr[index].realTasks = [...realTasks.value];
store.commit('task/setAllTasks', arr);
store.commit('task/setCurrRoleRealTasks', arr[index].realTasks); // 设置当前角色的真实任务数据
// 数据处理
dataRender(params);
}
});
}
function dataRender(params) {
timeLineType.value === 1 ? renderScaleTask(params) : renderConTask(params);
}
// 任务模式
async function renderConTask(params) {
let nextPage = params.queryType === 0 ? upNextPage.value : downNextPage.value; // 下一页的值
let showTasks = tasks.value;
let centerData = await showTaskId(params, showTasks, realTasks.value) || [];
if (centerData.length < 15 && nextPage > 0) {
getTasks({pageNum: nextPage, queryType: params.queryType});
} else {
if (params.queryType === 0) {
showTasks = [...centerData, ...showTasks];
} else {
showTasks = [...showTasks, ...centerData];
}
}
if (showTasks.length < 15 && nextPage === 0 && params.queryType === 1) {
getTasks({pageNum: 1, queryType: 0});
}
if (showTasks.length > 80) {
showTasks = params.queryType === 0 ? showTasks.slice(0, 80) : showTasks.slice(showTasks.length - 80);
}
store.commit('task/clearTasks');
params.queryType === 0 ? store.commit('task/setUpTasks', showTasks) : store.commit('task/setDownTasks', showTasks);
}
// 刻度模式数据处理
async function renderScaleTask(params) {
// params.queryType === 0 ? setPrevPlaceholderTasks(params) : setNextPlaceholderTasks(params);
// let centerData = await showTaskId(params, tasks.value, currRoleRealTasks.value) || [];
let centerData = await showTaskTime(params, tasks.value, currRoleRealTasks.value) || [];
// tasksData(params, centerData, currRoleRealTasks.value);
handleTasksData(params, centerData, currRoleRealTasks.value);
}
// 已显示的任务第一个时间和最后一个时间
async function showTaskTime(params, showTasks, realTasks) {
// 初始值
let centerData = params.queryType === 0 ? [] : realTasks.slice(0, params.pageSize);
/**
* 1判断显示任务中是否有真实任务
* 1-1根据id查找任务
* 1-2根据时间查找任务
* 2查找15个任务
*/
const firstDetailIndex = showTasks.findIndex(task => task.detailId);
if (firstDetailIndex > -1) {
// 显示任务中有真实任务数据
const firstId = showTasks[firstDetailIndex].id;
let lastDetailIndex = -1;
showTasks.forEach((item, index) => {
if (item.detailId) {
lastDetailIndex = index;
}
})
const lastId = showTasks[lastDetailIndex].id;
realTasks.forEach((item, index) => {
if (params.queryType === 1 && item.id === lastId) {
centerData = realTasks.slice(index + 1, index + 1 + params.pageSize) || [];
} else if (params.queryType === 0 && item.id === firstId) {
if (index >= params.pageSize) {
centerData = realTasks.slice(index - params.pageSize, index) || [];
} else {
centerData = realTasks.slice(0, index) || [];
}
}
})
} else {
// 显示任务中没有真实任务数据
// 已显示任务的第一个任务时间
const firstTime = showTasks.length ? showTasks[0].planStart : '';
// 已显示任务的最后一个任务时间
const lastTime = showTasks.length ? showTasks[showTasks.length - 1].planStart : '';
try {
realTasks.forEach((item, index) => {
if (params.queryType === 1) {
if (dayjs(+lastTime).isSame(+item.planStart, timeGranularity.value) || dayjs(+lastTime).isBefore(+item.planStart, timeGranularity.value)) {
centerData = realTasks.slice(index, index + params.pageSize) || [];
throw Error();
}
} else {
if (dayjs(+firstTime).isSame(+item.planStart, timeGranularity.value) || dayjs(+firstTime).isAfter(+item.planStart, timeGranularity.value)) {
if (index >= params.pageSize) {
centerData = realTasks.slice(index - params.pageSize, index) || [];
} else {
centerData = realTasks.slice(0, index) || [];
}
}
}
})
} catch (e) {
console.log('退出循环')
}
}
console.log('111111111', centerData);
return centerData;
}
async function handleTasksData(params, centerData, realTasks) {
/**
* 3查找的任务数量是否>=15
* 3-1
* 判断时间跨度是否>=15
* 3-1-1显示时间刻度范围内的任务
* 3-1-2显示全部任务并删除多余的刻度
* 3-2
* 判断时间跨度是否>=15
* 3-2-1显示时间刻度范围内的任务
* 3-2-2
* 下一页是否为0
* 3-2-2-1无下一页显示任务和刻度之后继续展示刻度
* 3-2-2-1查找下一页数据并重复上述步骤
*/
const startTime = centerData.length ? centerData[0].planStart : '';
const endTime = centerData.length ? centerData[centerData.length - 1].planStart : '';
let centerTime = dayjs(+startTime).add(params.pageSize, timeGranularity.value);\
// 时间跨度是否大于等于15
let isExceed = dayjs(+centerTime).isBefore(+endTime, timeGranularity.value) || dayjs(+centerTime).isSame(+endTime, timeGranularity.value);
let nextPage = params.queryType === 0 ? upNextPage.value : downNextPage.value; // 下一页的值
if (isExceed || centerData.length >= params.pageSize || nextPage === 0) {
params.queryType === 0 ? setPrevPlaceholderTasks(params) : setNextPlaceholderTasks(params);
}
let showTasks = tasks.value; // 显示的数据
const firstDetailIndex = showTasks.findIndex(task => task.detailId); // 显示任务中存在真实任务
const firstId = showTasks[firstDetailIndex].id;
let lastDetailIndex = -1;
showTasks.forEach((item, index) => {
if (item.detailId) {
lastDetailIndex = index;
}
})
const lastId = showTasks[lastDetailIndex].id;
showTasks.forEach((task, index) => {
const arr = centerData.filter(item => dayjs(+item.planStart).isSame(+task.planStart, timeGranularity.value));
if (arr && arr.length) {
if (firstDetailIndex > -1) {
if (params.queryType === 1 && task.id === lastId) {
showTasks.splice(index + 1, 0, [...arr])
} else (params.queryType === 0 && task.id === firstId) {
showTasks.splice(index, 0, [...arr])
}
} else {
showTasks.splice(index, 1, [...arr]);
}
}
})
showTasks = flatten(showTasks); // 1维拍平
if (!isExceed) {
if (centerData.length < params.pageSize && nextPage > 0) {
await getTasks({pageNum: nextPage, queryType: params.queryType});
return;
}
let data = params.queryType === 0 ? centerData[0] : centerData[centerData.length - 1];
showTasks.forEach((item, index) => {
if (item.id === data.id) {
len = index;
}
})
showTasks = params.queryType === 0 ? showTasks.slice(len) : showTasks.slice(0, len + 1);
}
if (showTasks.length > 80) {
showTasks = param.queryType === 0 ? showTasks.slice(0, 80) : showTasks.slice(showTasks.length - 80);
}
store.commit('task/clearTasks');
param.queryType === 0 ? store.commit('task/setUpTasks', showTasks) : store.commit('task/setDownTasks', showTasks);
}
// 已显示的任务第一个id和最后一个id
async function showTaskId(params, showTasks, realTasks) {
const firstDetailIndex = showTasks.findIndex(task => task.detailId);
const firstId = firstDetailIndex === -1 ? 0 : showTasks[firstDetailIndex].id;
let lastDetailIndex = -1;
showTasks.forEach((item, index) => {
if (item.detailId) {
lastDetailIndex = index;
}
})
const lastId = lastDetailIndex === -1 ? 0 : showTasks[lastDetailIndex].id;
let centerData = params.queryType === 0 ? [] : realTasks.slice(0, params.pageSize);
let flag = false;
realTasks.forEach((item, index) => {
if (params.queryType === 1 && item.id === lastId) {
flag = true;
centerData = realTasks.slice(index + 1, index + 1 + params.pageSize) || [];
} else if (params.queryType === 0 && item.id === firstId) {
flag = true;
centerData = realTasks.slice(index - params.pageSize, index) || [];
}
})
if (!flag) {
centerData = 0
}
console.log('111111', centerData, realTasks)
// 1、数量大于等于15 centerData.length >= 15
// 2、时间跨度大于等于15
const startTime = centerData.length ? centerData[0].planStart : '';
const endTime = centerData.length ? centerData[centerData.length - 1].planStart : '';
let centerTime = dayjs(+startTime).add(params.pageSize, timeGranularity.value);
let isExceed = dayjs(+centerTime).isBefore(+endTime, timeGranularity.value) || dayjs(+centerTime).isSame(+endTime, timeGranularity.value);
// 3、下一页不为0 nextPage != 0
let nextPage = params.queryType === 0 ? upNextPage.value : downNextPage.value; // 下一页的值
if (centerData.length < 15 && !isExceed && nextPage > 0) {
await getTasks({pageNum: nextPage, queryType: params.queryType});
// centerData = await showTaskId(params, tasks.value, currRoleRealTasks.value);
}
return centerData;
}
/**
* 刻度模式数据处理
* 大于等于15天真实数据的时间跨度
* 或大于等于15条真实数据的数量
* 不用重新加载数据
* @param {Object} params
*/
function tasksData(query, centerData, realTasks) {
let params = generateGetTaskParam(query);
params.queryType === 0 ? setPrevPlaceholderTasks(params) : setNextPlaceholderTasks(params);
let showTasks = tasks.value; // 显示的数据
let nextPage = param.queryType === 0 ? upNextPage.value : downNextPage.value; // 下一页的值
// 判断条件
let isAccordTerm1 = false, isAccordTerm2 = false;
if (centerData.length > 0) {
// 1.数据数量>=15条
isAccordTerm1 = centerData.length >= param.pageSize;
// 2.数据最后一条数据时间>=当前查询的时间
if (param.queryType === 0) {
let firstData = dayjs(centerData[0].planStart).add(param.pageSize, timeGranularity.value);
isAccordTerm2 = dayjs(+firstData).isBefore(+currUpTimeNode.value, timeGranularity.value) || dayjs(+firstData).isSame(+currUpTimeNode.value, timeGranularity.value);
} else {
let lastData = dayjs(centerData[centerData.length - 1].planStart).subtract(param.pageSize, timeGranularity.value);
isAccordTerm2 = dayjs(+lastData).isAfter(+currDownTimeNode.value, timeGranularity.value) || dayjs(+lastData).isSame(+currDownTimeNode.value, timeGranularity.value);
}
}
// 3.下一页===0
// 不需要添加新数据
if (!isAccordTerm1 && !isAccordTerm2 && nextPage > 0) {
// getTasks({pageNum: nextPage, queryType: param.queryType});
} else {
let tasksArr = [], isReplace = false, firstIndex = -1, selctedIndex = -1, replaceTime = 0;
showTasks.forEach((task, index) => {
const arr = centerData.filter(item => dayjs(+item.planStart).isSame(+task.planStart, timeGranularity.value));
if (arr && arr.length) {
if (task.detailId) {
// 新数据与旧数据时间有重叠
if (param.queryType === 1) {
selctedIndex = index;
tasksArr = [...arr];
} else {
firstIndex = showTasks.findIndex(data => dayjs(+data.planStart).isSame(+task.planStart, timeGranularity.value));
}
} else {
showTasks.splice(index, 1, [...arr]);
}
}
})
if (selctedIndex) {
showTasks.splice(selctedIndex + 1, 0, [...tasksArr])
}
if (firstIndex) {
showTasks.splice(firstIndex, 0, [...tasksArr])
}
showTasks.forEach((task, index) => {
const arr = centerData.filter(item => dayjs(+item.planStart).isSame(+task.planStart, timeGranularity.value));
if (arr && arr.length) {
if (!task.detailId) { // 新数据与旧数据时间有重叠
showTasks.splice(index, 1, [...arr]); // 这里加入的数据是array类型的, [{},{},[],[],{}]
}
}
})
showTasks = flatten(showTasks); // 1维拍平
if (isAccordTerm1 && !isAccordTerm2) {
let len = 0;
let data = param.queryType === 0 ? centerData[0] : centerData[centerData.length - 1];
showTasks.forEach((item, index) => {
if (item.id === data.id) {
len = index;
}
})
showTasks = param.queryType === 0 ? showTasks.slice(len) : showTasks.slice(0, len + 1);
}
if (showTasks.length > 80) {
showTasks = param.queryType === 0 ? showTasks.slice(0, 80) : showTasks.slice(showTasks.length - 80);
}
}
store.commit('task/clearTasks');
param.queryType === 0 ? store.commit('task/setUpTasks', showTasks) : store.commit('task/setDownTasks', showTasks);
}
// 设置时间轴向上的空数据
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/setCurrUpTimeNode', startTime);
store.commit('task/setUpTasks', placeholderTasks);
}
// 设置时间轴向下的空数据
function setNextPlaceholderTasks(params) {
// store.commit('task/setBottomEnd', true);
console.log('ddddddddd')
let startTime = '';
if (!tasks.value || !tasks.value.length) {
startTime = Date.now();
} else {
startTime = dayjs(+tasks.value[tasks.value.length - 1].planStart).add(1, timeGranularity.value).valueOf();
}
if (params.taskId) {
realTasks.value.forEach(item => {
if (item.id === params.taskId) {
startTime = Number(item.planStart);
}
})
}
const initData = uni.$task.setPlaceholderTasks(startTime, false, timeGranularity.value);
store.commit('task/setCurrDownTimeNode', startTime);
store.commit('task/setDownTasks', initData);
}
/**
* 当日常任务发生变化时
* 将新获取到的日常任务放在allTasks里
*/
watch(tasks, newValue => {
// 添加到allTasks里
const index = visibleRoles.value.findIndex(role => role.id === roleId.value);
const arr = [...allTasks.value];
arr[index].task = [...newValue];
store.commit('task/setAllTasks', arr);
});
return {
initPlanTasks,
getTasks,
dataRender
}
}

343
hooks/project/useGetTasks - 副本 (3).js

@ -0,0 +1,343 @@
import { computed, nextTick, watch } 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 timeLineType = computed(() => store.state.task.timeLineType); // 时间轴模式
const realTasks = computed(() => store.state.task.realTasks); // 真实任务
const downNextPage = computed(() => store.state.task.downNextPage); // 下一页
const upNextPage = computed(() => store.state.task.upNextPage); // 下一页
const currUpTimeNode = computed(() => store.state.task.currUpTimeNode); // 当前查询的时间
const currDownTimeNode = computed(() => store.state.task.currDownTimeNode); // 当前查询的时间
const roleId = computed(() => store.state.role.roleId);
const timeNode = computed(() => store.state.task.timeNode);
const timeUnit = computed(() => store.state.task.timeUnit);
const visibleRoles = computed(() => store.state.role.visibleRoles);
const allTasks = computed(() => store.state.task.allTasks);
const roleIndex = computed(() => store.state.role.roleIndex);
const projectId = computed(() => store.getters['project/projectId']);
const timeGranularity = computed(() => store.getters['task/timeGranularity']);
const remindData = computed(() => store.state.socket.remindData); // 小红点
const currRoleRealTasks = computed(() => store.state.task.currRoleRealTasks); // 当前角色的真实任务数据
// 初始化 定期任务
async function initPlanTasks() {
if (timeLineType.value === 1) setNextPlaceholderTasks({});
await getTasks({}); // 获取初始数据
// await dataRender({});
}
/**
* 生成getTasks所用的参数
* @param {object} query getTasks传递的参数
*/
function generateGetTaskParam(query) {
return {
roleId: roleId.value,
timeNode: query.timeNode || timeNode.value,
timeUnit: query.timeUnit || timeUnit.value,
queryType: query.queryType === 0 ? 0 : 1,
pageNum: query.pageNum || 1,
pageSize: query.pageSize || uni.$taskConfig.pageCount,
taskId: query.taskId || ''
};
}
/**
* 根据时间基准点和角色查找定期任务
* @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) {
store.commit('task/setShowSkeleton', false);
const params = generateGetTaskParam(query);
uni.$catchReq.getTaskByNum(params, (err, data) => {
store.commit('task/setShowSkeleton', false);
if (err) {
// TODO: 提示错误
console.error('err: ', err);
} else {
store.commit('task/setShowScrollTo', true);
params.queryType === 0 ? store.commit('task/setUpRealTasks', data.list) : store.commit('task/setDownRealTasks', data.list);
params.queryType === 0 ? store.commit('task/setUpNextPage', data.nextPage) : store.commit('task/setDownNextPage', data.nextPage); // 下一页
const index = visibleRoles.value.findIndex(role => role.id === roleId.value);
const arr = [...allTasks.value];
arr[index].realTasks = [...realTasks.value];
store.commit('task/setAllTasks', arr);
store.commit('task/setCurrRoleRealTasks', arr[index].realTasks); // 设置当前角色的真实任务数据
// 数据处理
dataRender(params);
}
});
}
function dataRender(params) {
timeLineType.value === 1 ? renderScaleTask(params) : renderConTask(params);
}
// 刻度模式数据处理
async function renderScaleTask(query) {
const params = generateGetTaskParam(query);
let centerData = await showTaskTime(params, tasks.value, currRoleRealTasks.value) || [];
await handleTasksData(params, centerData, currRoleRealTasks.value);
}
// 已显示的任务第一个时间和最后一个时间
async function showTaskTime(params, showTasks, realTasks) {
// 初始值
let centerData = params.queryType === 0 ? [] : realTasks.slice(0, params.pageSize);
/**
* 1判断显示任务中是否有真实任务
* 1-1根据id查找任务
* 1-2根据时间查找任务
* 2查找15个任务
*/
const firstDetailIndex = showTasks.findIndex(task => task.detailId);
if (firstDetailIndex > -1) {
// 显示任务中有真实任务数据
const firstId = showTasks[firstDetailIndex].id;
let lastDetailIndex = -1;
showTasks.forEach((item, index) => {
if (item.detailId) {
lastDetailIndex = index;
}
})
const lastId = showTasks[lastDetailIndex].id;
realTasks.forEach((item, index) => {
if (params.queryType === 1 && item.id === lastId) {
centerData = realTasks.slice(index + 1, index + 1 + params.pageSize) || [];
} else if (params.queryType === 0 && item.id === firstId) {
if (index >= params.pageSize) {
centerData = realTasks.slice(index - params.pageSize, index) || [];
} else {
centerData = realTasks.slice(0, index) || [];
}
}
})
} else {
// 显示任务中没有真实任务数据
// 已显示任务的第一个任务时间
const firstTime = showTasks.length ? showTasks[0].planStart : '';
// 已显示任务的最后一个任务时间
const lastTime = showTasks.length ? showTasks[showTasks.length - 1].planStart : '';
try {
realTasks.forEach((item, index) => {
if (params.queryType === 1) {
if (dayjs(+lastTime).isSame(+item.planStart, timeGranularity.value) || dayjs(+lastTime).isBefore(+item.planStart, timeGranularity.value)) {
centerData = realTasks.slice(index, index + params.pageSize) || [];
throw Error();
}
} else {
if (dayjs(+firstTime).isSame(+item.planStart, timeGranularity.value) || dayjs(+firstTime).isAfter(+item.planStart, timeGranularity.value)) {
if (index >= params.pageSize) {
centerData = realTasks.slice(index - params.pageSize, index) || [];
} else {
centerData = realTasks.slice(0, index) || [];
}
}
}
})
} catch (e) {
console.log('退出循环')
}
}
console.log('111111111', centerData);
return centerData;
}
async function handleTasksData(params, centerData, realTasks) {
/**
* 3查找的任务数量是否>=15
* 3-1
* 判断时间跨度是否>=15
* 3-1-1显示时间刻度范围内的任务
* 3-1-2显示全部任务并删除多余的刻度
* 3-2
* 判断时间跨度是否>=15
* 3-2-1显示时间刻度范围内的任务
* 3-2-2
* 下一页是否为0
* 3-2-2-1无下一页显示任务和刻度之后继续展示刻度
* 3-2-2-1查找下一页数据并重复上述步骤
*/
const startTime = centerData.length ? centerData[0].planStart : '';
const endTime = centerData.length ? centerData[centerData.length - 1].planStart : '';
let centerTime = dayjs(+startTime).add(params.pageSize, timeGranularity.value);
// 时间跨度是否大于等于15
let isExceed = dayjs(+centerTime).isBefore(+endTime, timeGranularity.value) || dayjs(+centerTime).isSame(+endTime, timeGranularity.value);
let showTasks = tasks.value; // 显示的数据
const nextPage = params.queryType === 0 ? upNextPage.value : downNextPage.value; // 下一页的值
console.log('下一页', nextPage, isExceed, params.pageSize)
if (isExceed || centerData.length >= params.pageSize || nextPage === 0) {
params.queryType === 0 ? setPrevPlaceholderTasks(params) : setNextPlaceholderTasks(params);
showTasks = tasks.value;
console.log('fffffff', showTasks);
}
if (centerData.length === 0 && nextPage === 0) {
params.queryType === 0 ? setPrevPlaceholderTasks(params) : setNextPlaceholderTasks(params);
showTasks = tasks.value;
console.log('ccccc', showTasks);
} else {
console.log('ttttttttt', showTasks);
const firstDetailIndex = showTasks.findIndex(task => task.detailId); // 显示任务中存在真实任务
const firstId = firstDetailIndex > -1 ? showTasks[firstDetailIndex].id : '';
let lastDetailIndex = -1;
showTasks.forEach((item, index) => {
if (item.detailId) {
lastDetailIndex = index;
}
})
const lastId = lastDetailIndex > -1 ? showTasks[lastDetailIndex].id : '';
showTasks.forEach((task, index) => {
const arr = centerData.filter(item => dayjs(+item.planStart).isSame(+task.planStart, timeGranularity.value));
if (arr && arr.length) {
if (firstDetailIndex > -1) {
if (params.queryType === 1 && task.id === lastId) {
showTasks.splice(index + 1, 0, [...arr])
} else if (params.queryType === 0 && task.id === firstId) {
showTasks.splice(index, 0, [...arr])
}
} else {
showTasks.splice(index, 1, [...arr]);
}
}
})
showTasks = flatten(showTasks); // 1维拍平
if (!isExceed) {
if (centerData.length < params.pageSize && nextPage > 0) {
await getTasks({pageNum: nextPage, queryType: params.queryType});
console.log('oooooooo', showTasks);
return;
}
if (centerData.length) {
let len = 0;
let data = params.queryType === 0 ? centerData[0] : centerData[centerData.length - 1];
showTasks.forEach((item, index) => {
if (item.id === data.id) {
len = index;
}
})
showTasks = params.queryType === 0 ? showTasks.slice(len) : showTasks.slice(0, len + 1);
}
}
}
if (showTasks.length > 80) {
showTasks = params.queryType === 0 ? showTasks.slice(0, 80) : showTasks.slice(showTasks.length - 80);
}
store.commit('task/clearTasks');
params.queryType === 0 ? store.commit('task/setUpTasks', showTasks) : store.commit('task/setDownTasks', showTasks);
}
// 任务模式
async function renderConTask(params) {
let nextPage = params.queryType === 0 ? upNextPage.value : downNextPage.value; // 下一页的值
let showTasks = tasks.value;
let centerData = await showTaskId(params, showTasks, realTasks.value) || [];
if (centerData.length < 15 && nextPage > 0) {
getTasks({pageNum: nextPage, queryType: params.queryType});
} else {
if (params.queryType === 0) {
showTasks = [...centerData, ...showTasks];
} else {
showTasks = [...showTasks, ...centerData];
}
}
if (showTasks.length < 15 && nextPage === 0 && params.queryType === 1) {
getTasks({pageNum: 1, queryType: 0});
}
if (showTasks.length > 80) {
showTasks = params.queryType === 0 ? showTasks.slice(0, 80) : showTasks.slice(showTasks.length - 80);
}
store.commit('task/clearTasks');
params.queryType === 0 ? store.commit('task/setUpTasks', showTasks) : store.commit('task/setDownTasks', showTasks);
}
// 设置时间轴向上的空数据
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/setCurrUpTimeNode', startTime);
store.commit('task/setUpTasks', placeholderTasks);
}
// 设置时间轴向下的空数据
function setNextPlaceholderTasks(params) {
// store.commit('task/setBottomEnd', true);
console.log('ddddddddd')
let startTime = '';
if (!tasks.value || !tasks.value.length) {
startTime = Date.now();
} else {
startTime = dayjs(+tasks.value[tasks.value.length - 1].planStart).add(1, timeGranularity.value).valueOf();
}
if (params.taskId) {
realTasks.value.forEach(item => {
if (item.id === params.taskId) {
startTime = Number(item.planStart);
}
})
}
const initData = uni.$task.setPlaceholderTasks(startTime, false, timeGranularity.value);
store.commit('task/setCurrDownTimeNode', startTime);
store.commit('task/setDownTasks', initData);
}
/**
* 当日常任务发生变化时
* 将新获取到的日常任务放在allTasks里
*/
watch(tasks, newValue => {
// 添加到allTasks里
const index = visibleRoles.value.findIndex(role => role.id === roleId.value);
const arr = [...allTasks.value];
arr[index].task = [...newValue];
store.commit('task/setAllTasks', arr);
});
return {
initPlanTasks,
getTasks,
dataRender
}
}

263
hooks/project/useGetTasks - 副本.js

@ -0,0 +1,263 @@
import { computed, nextTick, watch } 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 basicTasks = computed(() => store.state.task.basicTasks);
const upBasicTasks = computed(() => store.state.task.upBasicTasks);
const nextPage = computed(() => store.state.task.nextPage); // 下一页
const upNextPage = computed(() => store.state.task.upNextPage); // 下一页
// const lastPage = computed(() => store.state.task.lastPage); // 最后一页
const roleId = computed(() => store.state.role.roleId);
const timeNode = computed(() => store.state.task.timeNode);
const timeUnit = computed(() => store.state.task.timeUnit);
const visibleRoles = computed(() => store.state.role.visibleRoles);
const allTasks = computed(() => store.state.task.allTasks);
const roleIndex = computed(() => store.state.role.roleIndex);
const projectId = computed(() => store.getters['project/projectId']);
const timeGranularity = computed(() => store.getters['task/timeGranularity']);
// 初始化 定期任务
async function initPlanTasks() {
await getTasks({}); // 获取初始数据
}
/**
* 根据时间基准点和角色查找定期任务
* @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) {
store.commit('task/setShowSkeleton', false);
const params = {
roleId: roleId.value,
timeUnit: query.timeUnit || timeUnit.value,
queryType: query.queryType === 0 ? 0 : 1,
pageNum: query.pageNum || 1,
pageSize: query.pageSize || uni.$taskConfig.pageCount
}
uni.$catchReq.getTaskByNum(params, (err, data) => {
store.commit('task/setShowSkeleton', false);
if (err) {
// TODO: 提示错误
console.error('err: ', err);
} else {
store.commit('task/setShowScrollTo', true);
if (params.queryType === 0) {
// 将接口返回的数据存储到store
let lists = [...upBasicTasks.value, ...data.list];
store.commit('task/setUpBasicTask', lists);
store.commit('task/setUpNextPage', data.nextPage); // 下一页
} else {
let lists = [...basicTasks.value, ...data.list];
store.commit('task/setBasicTask', lists);
store.commit('task/setNextPage', data.nextPage); // 下一页
}
// 刻度模式数据处理
renderTask(params);
}
});
}
// 刻度模式数据处理
function renderTask(params) {
params.queryType === 0 ? setPrevPlaceholderTasks() : setNextPlaceholderTasks();
tasksData(params);
}
/**
* 刻度模式数据处理
* 大于等于15天真实数据的时间跨度
* 或大于等于15条真实数据的数量
* 不用重新加载数据
* @param {Object} params
*/
function tasksData(params) {
let oldTasks = tasks.value;
let realTasks = params.queryType === 0 ? upBasicTasks.value : basicTasks.value;
// 下一页的值
let next = params.queryType === 0 ? upNextPage.value : nextPage.value;
// 真实数据是否>=15条 || 是否>=15天
let term = tremFilter(params);
if (term || next === 0) {
// 过滤真实数据中在tasks时间段中的数据,并将最后的数据存储到tasksArr数组中
let tasksArr = [], taskLen = 0, lastIndex = 0;
oldTasks.forEach((task, index) => {
const oldArr = oldTasks.filter(item => dayjs(+item.planStart).isSame(+task.planStart, timeGranularity.value));
const arr = realTasks.filter(item => dayjs(+item.planStart).isSame(+task.planStart, timeGranularity.value));
if (arr && arr.length) {
if (task.detailId) {
tasksArr = [...arr];
lastIndex = index;
} else {
taskLen += arr.length; // 在时间刻度内的数据量
oldTasks.splice(index, 1, [...arr]); // 这里加入的数据是array类型的, [{},{},[],[],{}]
}
}
})
// 数据与上一页最后一个数据时间节点相同
if (lastIndex) {
taskLen += tasksArr.length;
oldTasks.splice(lastIndex, 0, [...tasksArr]);
}
oldTasks = flatten(oldTasks); // 1维拍平
if (taskLen === params.pageSize) {
// 如果真实任务所有数据都在tasks时间段中,则将所有任务显示,并且从最后一个真实任务的时间截止,存储到tasks中
let len = 0;
oldTasks.forEach((item, index) => {
if (item.id === realTasks[realTasks.length - 1].id) {
len = index;
}
})
oldTasks = oldTasks.slice(0, len + 1);
params.queryType === 0 ? store.commit('task/setUpBasicTask', []) : store.commit('task/setBasicTask', []);
} else {
// 只有一部分真实数据展示,展示tasks时间段加时间段内的真实数据
params.queryType === 0 ? store.commit('task/setUpBasicTask', realTasks.slice(taskLen)) : store.commit('task/setBasicTask', realTasks.slice(taskLen));
}
store.commit('task/clearTasks');
params.queryType === 0 ? store.commit('task/setUpTasks', oldTasks) : store.commit('task/setDownTasks', oldTasks);
} else {
if (next > 0) {
getTasks({pageNum: params.pageNum, queryType: params.queryType});
}
}
}
/**
* 数据筛选条件
* 大于等于15天真实数据的时间跨度且大于等于15条真实数据的数量不用重新加载数据
*/
function tremFilter(params) {
let time1 = 0, time2 = 0;
let realTasks = basicTasks.value;
if (realTasks.length > 0) {
// 真实数据第一条数据加上15天后的时间
time1 = dayjs(+realTasks[0].planStart).add(params.pageSize, timeGranularity.value);
// 真实数据最后一条数据的时间
time2 = dayjs(realTasks[realTasks.length - 1].planStart, timeGranularity.value);
}
let result1 = false, result2 = false;
// 判断真实任务的时间跨度是否在15天之内
if (time1 > 0 && time2 > 0) {
// 两个时间是否相等
result1 = dayjs(time1).isSame(time2, timeGranularity.value);
// time1是否大于time2
result2 = dayjs(time1).isAfter(time2, timeGranularity.value);
}
let term1 = realTasks.length >= params.pageSize; // 大于等于15条,真实数据的数量
// 大于等于15天,真实数据的时间跨度
let term2 = result1 || result2;
return term1 || term2;
}
/**
* 用拿到的新数据 替换 时间刻度/旧数据
* 先对比 新旧数据的 始末时间 补齐刻度
* 再遍历对比 用任务替换刻度
* @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); // 已经上下补齐时间刻度的
// console.log('oldTasks', oldTasks)
// // 遍历对比 用任务替换刻度
// // 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[obj.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 = dayjs(+tasks.value[tasks.value.length - 1].planStart).add(1, timeGranularity.value);
}
const initData = uni.$task.setPlaceholderTasks(startTime, false, timeGranularity.value);
store.commit('task/setDownTasks', initData);
}
/**
* 当日常任务发生变化时
* 将新获取到的日常任务放在allTasks里
*/
watch(tasks, newValue => {
// console.log('newValue----->tasks: ', tasks.value);
// 添加到allTasks里
const index = visibleRoles.value.findIndex(role => role.id === roleId.value);
const arr = [...allTasks.value];
arr[index].task = [...newValue];
store.commit('task/setAllTasks', arr);
});
return {
initPlanTasks,
getTasks,
renderTask
};
}

407
hooks/project/useGetTasks - 最新的.js

@ -0,0 +1,407 @@
import { computed, nextTick, watch } 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 timeLineType = computed(() => store.state.task.timeLineType); // 时间轴模式
const realTasks = computed(() => store.state.task.realTasks); // 真实任务
const downNextPage = computed(() => store.state.task.downNextPage); // 下一页
const upNextPage = computed(() => store.state.task.upNextPage); // 下一页
const currUpTimeNode = computed(() => store.state.task.currUpTimeNode); // 当前查询的时间
const currDownTimeNode = computed(() => store.state.task.currDownTimeNode); // 当前查询的时间
const roleId = computed(() => store.state.role.roleId);
const timeNode = computed(() => store.state.task.timeNode);
const timeUnit = computed(() => store.state.task.timeUnit);
const visibleRoles = computed(() => store.state.role.visibleRoles);
const allTasks = computed(() => store.state.task.allTasks);
const roleIndex = computed(() => store.state.role.roleIndex);
const projectId = computed(() => store.getters['project/projectId']);
const timeGranularity = computed(() => store.getters['task/timeGranularity']);
const remindData = computed(() => store.state.socket.remindData); // 小红点
const currRoleRealTasks = computed(() => store.state.task.currRoleRealTasks); // 当前角色的真实任务数据
const currRoleShowTasks = computed(() => store.state.task.currRoleShowTasks); // 当前角色的展示任务数据
const currLocationTaskId = computed(() => store.state.socket.currLocationTaskId);
const businessCode = computed(() => store.state.task.businessCode);
// 初始化 定期任务
async function initPlanTasks() {
uni.$ui.showLoading();
await getTasks({}); // 获取初始数据
}
/**
* 生成getTasks所用的参数
* @param {object} query getTasks传递的参数
*/
function generateGetTaskParam(query) {
return {
roleId: roleId.value,
timeNode: query.timeNode || timeNode.value,
timeUnit: query.timeUnit || timeUnit.value,
queryType: query.queryType === 0 ? 0 : 1,
pageNum: query.pageNum || 1,
pageSize: query.pageSize || uni.$taskConfig.pageCount,
taskId: query.taskId || currLocationTaskId.value,
businessCode: query.businessCode || businessCode.value,
triggerType: query.triggerType || 1
};
}
/**
* 根据时间基准点和角色查找定期任务
* @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) {
uni.$ui.showLoading();
store.commit('task/setShowSkeleton', false);
if (businessCode.value === 'ZERO' && currRoleRealTasks.value.length > 0) {
let needTask = query.queryType === 0 ? currRoleRealTasks.value[0] : currRoleRealTasks.value[currRoleRealTasks.value.length - 1];
query.taskId = needTask.id;
query.timeNode = needTask.planStart;
query.businessCode = needTask.businessCode;
}
const params = generateGetTaskParam(query);
uni.$catchReq.getTaskByNum(params, (err, data) => {
store.commit('task/setShowSkeleton', false);
if (err) {
// TODO: 提示错误
console.error('err: ', err);
} else {
store.commit('task/setShowScrollTo', true);
params.queryType === 0 ? store.commit('task/setUpRealTasks', data.list) : store.commit('task/setDownRealTasks', data.list);
const index = visibleRoles.value.findIndex(role => role.id === roleId.value);
const arr = [...allTasks.value];
arr[index].realTasks = [...realTasks.value];
arr[index].upNextPage = params.queryType === 0 ? data.nextPage : 1;
arr[index].downNextPage = params.queryType === 1 ? data.nextPage : 1;
store.commit('task/setAllTasks', arr);
store.commit('task/setCurrRoleRealTasks', arr[index].realTasks); // 设置当前角色的真实任务数据
if (businessCode.value === 'ZERO' && data.list.length < params.pageSize) {
params.queryType === 0 ? store.commit('task/setUpNextPage', 0) : store.commit('task/setDownNextPage', 0); // 下一页
} else if (businessCode.value !== 'ZERO') {
params.queryType === 0 ? store.commit('task/setUpNextPage', arr[index].upNextPage) : store.commit('task/setDownNextPage', arr[index].downNextPage); // 下一页
}
// 数据处理
dataRender(params);
}
});
}
function dataRender(params) {
timeLineType.value === 1 ? renderScaleTask(params) : renderConTask(params);
}
// 刻度模式数据处理
async function renderScaleTask(query) {
const params = generateGetTaskParam(query);
let centerData = await showTaskTime(params, currRoleShowTasks.value, currRoleRealTasks.value) || [];
await handleTasksData(params, centerData, currRoleRealTasks.value);
}
// 已显示的任务第一个时间和最后一个时间
async function showTaskTime(params, showTasks, realTasks) {
/**
* 1判断显示任务中是否有真实任务
* 1-1根据id查找任务
* 1-2根据时间查找任务
* 2查找15个任务
*/
// 初始值
// 显示任务中没有真实任务数据
let centerData = [];
if (realTasks.length > params.pageSize && params.queryType === 0) {
centerData = realTasks.slice(realTasks.length - params.pageSize);
} else {
centerData = realTasks.slice(0, params.pageSize);
}
const firstDetailIndex = showTasks.findIndex(task => task.detailId);
if (firstDetailIndex > -1) {
// 显示任务中有真实任务数据
const firstId = showTasks[firstDetailIndex].id;
let lastDetailIndex = -1;
showTasks.forEach((item, index) => {
if (item.detailId) {
lastDetailIndex = index;
}
})
const lastId = showTasks[lastDetailIndex].id;
realTasks.forEach((item, index) => {
if (params.queryType === 1 && item.id === lastId) {
centerData = realTasks.slice(index + 1, index + 1 + params.pageSize);
} else if (params.queryType === 0 && item.id === firstId) {
centerData = index >= params.pageSize ? realTasks.slice(index - params.pageSize, index) : realTasks.slice(0, index);
}
})
}
return centerData;
}
async function handleTasksData(params, centerData, realTasks) {
let showTasks = currRoleShowTasks.value; // 显示的数据
let firstTime = showTasks.length > 0 ? showTasks[0].planStart : new Date().getTime(); // 显示的数据第一个数据的时间
let lastTime = showTasks.length > 0 ? dayjs(+showTasks[showTasks.length - 1].planStart).add(1, timeGranularity.value) : new Date().getTime(); // 显示的数据最后一个数据的时间
let upTargetTime = dayjs(+firstTime).subtract(params.pageSize, timeGranularity.value);
let downTargetTime = dayjs(+lastTime).add(params.pageSize - 1, timeGranularity.value);
const nextPage = params.queryType === 0 ? upNextPage.value : downNextPage.value; // 下一页的值
// 双击小红点
if (currLocationTaskId.value) {
lastTime = centerData.length ? centerData[0].planStart : new Date().getTime();
}
if (centerData.length) {
let arr = [];
centerData.forEach(v => {
let centerTime = '', // 中间数据+/-15后的数据
isExceed = false; // 时间跨度是否大于15
if (params.queryType) {
isExceed = dayjs(+downTargetTime).isAfter(+v.planStart, timeGranularity.value)
} else {
isExceed = dayjs(+upTargetTime).isBefore(+v.planStart, timeGranularity.value)
}
if (isExceed) {
arr.push(v);
}
})
if (!arr.length) {
params.queryType === 0 ? setPrevPlaceholderTasks(params) : setNextPlaceholderTasks(params);
} else {
if (arr.length === centerData.length && centerData.length < params.pageSize && nextPage > 0) {
getTasks({pageNum: nextPage, queryType: params.queryType});
} else {
let cycleIndex = params.pageSize;
let newArr = [];
let breakTime = ''; // 循环结束时间
let continueTime = ''; // 第一个数据的时间
// 循环开始时间
let currTime = params.queryType === 0 ? upTargetTime : lastTime;
// 符合条件的数据 < centerData的数据
// 或者
// centerData的数据全部符合条件,但是 < 15,而且没有下一页,不能向下查
// 数据需要补齐15天的刻度
// 所以循环长度为params.pageSize
// centerData的数据全部显示,且数量 = 15
// 需要补齐数据中间的空刻度
// 循环长度为 结束时间到开始时间的差
// 当循环到的时间 = 数据最后一条的时间,则跳出循环
if (arr.length === centerData.length && centerData.length === params.pageSize) {
if (params.queryType === 0) {
continueTime = arr[0].planStart;
} else {
breakTime = arr[arr.length - 1].planStart;
}
}
// if (arr.length < centerData.length || (arr.length === centerData.length && centerData.length < params.pageSize && nextPage === 0)) {
const startTime = params.queryType === 0 ? firstTime : dayjs(+lastTime).subtract(1, timeGranularity.value);
let firstArr = arr.filter(item => dayjs(+item.planStart).isSame(+startTime, timeGranularity.value));
if (firstArr.length && params.queryType === 1) {
newArr = [...newArr, ...firstArr];
}
// }
for (let i = 0; i < params.pageSize; i++) {
if (continueTime && dayjs(+currTime).isBefore(+continueTime, timeGranularity.value)) {
currTime = dayjs(+currTime).add(1, timeGranularity.value);
continue;
} else {
let termArr = arr.filter(item => dayjs(+item.planStart).isSame(+currTime, timeGranularity.value));
if (termArr.length === 0) {
const newTasks = uni.$task.setPlaceholderTasks(+currTime, false, timeGranularity.value, 1);
newArr = [...newArr, ...newTasks];
} else {
newArr = [...newArr, ...termArr];
}
if (breakTime && dayjs(+currTime).isSame(+breakTime, timeGranularity.value)) {
break;
} else {
currTime = dayjs(+currTime).add(1, timeGranularity.value);
}
}
}
if (firstArr.length && params.queryType === 0) {
newArr = [...newArr, ...firstArr];
}
const repeatTime = params.queryType === 0 ? newArr[newArr.length - 1].planStart : newArr[0].planStart;
const repeatTimeArr = showTasks.filter(item => dayjs(+item.planStart).isSame(+repeatTime, timeGranularity.value));
if (repeatTimeArr.length) {
const index = repeatTimeArr.findIndex(item => item.detailId);
if (index > -1) {
showTasks = params.queryType === 0 ? [...newArr, ...showTasks] : [...showTasks, ...newArr];
} else {
showTasks = params.queryType === 0 ? showTasks.slice(1) : showTasks.slice(0, showTasks.length - 2);
showTasks = params.queryType === 0 ? [...newArr, ...showTasks] : [...showTasks, ...newArr];
}
} else {
showTasks = params.queryType === 0 ? [...newArr, ...showTasks] : [...showTasks, ...newArr];
}
}
showTasks = flatten(showTasks); // 1维拍平
store.commit('task/clearTasks');
params.queryType === 0 ? store.commit('task/setUpTasks', showTasks) : store.commit('task/setDownTasks', showTasks);
}
} else {
if (nextPage > 0) {
getTasks({pageNum: nextPage, queryType: params.queryType});
} else {
params.queryType === 0 ? setPrevPlaceholderTasks(params) : setNextPlaceholderTasks(params);
}
}
uni.$ui.hideLoading();
// if (showTasks.length > 30) {
// showTasks = params.queryType === 0 ? showTasks.slice(0, 80) : showTasks.slice(showTasks.length - 80);
// }
}
// 任务模式
async function renderConTask(params) {
let nextPage = params.queryType === 0 ? upNextPage.value : downNextPage.value; // 下一页的值
let showTasks = currRoleShowTasks.value;
let centerData = await showTaskTime(params, showTasks, currRoleRealTasks.value) || [];
if (centerData.length < 15 && nextPage > 0) {
getTasks({pageNum: nextPage, queryType: params.queryType});
} else {
if (params.queryType === 0) {
showTasks = [...centerData, ...showTasks];
} else {
showTasks = [...showTasks, ...centerData];
}
}
if (showTasks.length < 15 && nextPage === 0 && params.queryType === 1) {
getTasks({pageNum: 1, queryType: 0});
}
// if (showTasks.length > 80) {
// showTasks = params.queryType === 0 ? showTasks.slice(0, 80) : showTasks.slice(showTasks.length - 80);
// }
store.commit('task/clearTasks');
params.queryType === 0 ? store.commit('task/setUpTasks', showTasks) : store.commit('task/setDownTasks', showTasks);
}
// 设置时间轴向上的空数据
function setPrevPlaceholderTasks() {
store.commit('task/setTopEnd', true);
let startTime = '';
if (!currRoleShowTasks.value || !currRoleShowTasks.value.length) {
startTime = Date.now(); // 没有任务就应该是时间基准点
} else {
startTime = currRoleShowTasks.value[0].planStart - 0; // 有任务就是第一个任务的计划开始时间
}
const placeholderTasks = uni.$task.setPlaceholderTasks(startTime, true, timeGranularity.value);
store.commit('task/setCurrUpTimeNode', startTime);
store.commit('task/setUpTasks', placeholderTasks);
}
// 设置时间轴向下的空数据
function setNextPlaceholderTasks(params) {
// store.commit('task/setBottomEnd', true);
let startTime = '';
if (!currRoleShowTasks.value || !currRoleShowTasks.value.length) {
startTime = Date.now();
} else {
startTime = dayjs(+currRoleShowTasks.value[currRoleShowTasks.value.length - 1].planStart).add(1, timeGranularity.value).valueOf();
}
// if (params.taskId) {
// currRoleRealTasks.value.forEach(item => {
// if (item.id === params.taskId) {
// startTime = Number(item.planStart);
// }
// })
// }
const initData = uni.$task.setPlaceholderTasks(startTime, false, timeGranularity.value);
store.commit('task/setCurrDownTimeNode', startTime);
store.commit('task/setDownTasks', initData);
}
/**
* 当日常任务发生变化时
* 将新获取到的日常任务放在allTasks里
*/
watch(tasks, () => {
// 添加到allTasks里
const index = visibleRoles.value.findIndex(role => role.id === roleId.value);
const arr = [...allTasks.value];
if (arr.length && index > -1) {
if (remindData.value) {
tasks.value.forEach(task => {
task.remindNum = 0;
task.msgId = '';
if (task.plugins && task.plugins.length) {
task.plugins.forEach(pluginArr => {
pluginArr.forEach(plugin => {
plugin.remindNum = 0;
remindData.value.forEach(remind => {
const remind_data = JSON.parse(remind.data);
if (projectId.value === remind_data.data.projectId && roleId.value === remind_data.data.roleId) {
if (remind_data.data.taskId === task.id) {
task.remindNum++;
task.msgId = remind.id;
}
if (remind_data.data.taskId === task.id && remind_data.data.pluginId === plugin.pluginTaskId) {
plugin.remindNum++;
}
}
});
});
});
}
})
}
arr[index].task = [...tasks.value];
store.commit('task/setCurrRoleShowTasks', arr[index].task); // 设置当前角色的展示任务数据
}
store.commit('task/setAllTasks', arr);
});
return {
initPlanTasks,
getTasks,
dataRender
}
}

317
hooks/project/useGetTasks.js

@ -0,0 +1,317 @@
import { computed, nextTick, watch } from 'vue';
import dayjs from 'dayjs';
import { flatten } from 'lodash';
import { useStore } from 'vuex';
export default function useGetTasks() {
const store = useStore();
const tasks = computed(() => store.state.task.tasks); // 当前角色显示任务
const timeLineType = computed(() => store.state.task.timeLineType); // 时间轴模式
const realTasks = computed(() => store.state.task.realTasks); // 当前角色真实任务
const downNextPage = computed(() => store.state.task.downNextPage); // 当前角色下一页 - 向下
const upNextPage = computed(() => store.state.task.upNextPage); // 当前角色下一页 - 向下
// const currUpTimeNode = computed(() => store.state.task.currUpTimeNode); // 当前查询的时间
// const currDownTimeNode = computed(() => store.state.task.currDownTimeNode); // 当前查询的时间
const roleId = computed(() => store.state.role.roleId); // 当前角色id
const timeNode = computed(() => store.state.task.timeNode); // 当前时间节点
const timeUnit = computed(() => store.state.task.timeUnit); // 当前时间单位
const visibleRoles = computed(() => store.state.role.visibleRoles); // 角色列表
const allTasks = computed(() => store.state.task.allTasks); // 所有任务
const roleIndex = computed(() => store.state.role.roleIndex); // 当前角色位置
const projectId = computed(() => store.getters['project/projectId']);
const timeGranularity = computed(() => store.getters['task/timeGranularity']);
const remindData = computed(() => store.state.socket.remindData); // 小红点
// const currRoleRealTasks = computed(() => store.state.task.currRoleRealTasks); // 当前角色的真实任务数据
// const currRoleShowTasks = computed(() => store.state.task.currRoleShowTasks); // 当前角色的展示任务数据
const currLocationTaskId = computed(() => store.state.socket.currLocationTaskId); // 当前定位的任务id
const businessCode = computed(() => store.state.task.businessCode); // 服务名
const scaleTasksStartTime = computed(() => store.state.task.scaleTasksStartTime); // 空时间刻度开始时间
const scaleTasksEndTime = computed(() => store.state.task.scaleTasksEndTime); // 空时间刻度结束时间
// 初始化 定期任务
async function initPlanTasks(params) {
// uni.$ui.showLoading();
if (timeLineType.value === 1) setNextPlaceholderTasks();
await getTasks(params || {}); // 获取初始数据
}
/**
* 生成getTasks所用的参数
* @param {object} query getTasks传递的参数
*/
function generateGetTaskParam(query) {
return {
roleId: roleId.value,
timeNode: query.timeNode || timeNode.value,
timeUnit: query.timeUnit || timeUnit.value,
queryType: query.queryType === 0 ? 0 : 1,
pageNum: query.pageNum || 1,
pageSize: query.pageSize || uni.$taskConfig.pageCount,
taskId: query.taskId || currLocationTaskId.value,
businessCode: query.businessCode || businessCode.value,
triggerType: query.triggerType || 1
};
}
/**
* 根据时间基准点和角色查找定期任务
* @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) {
uni.$ui.showLoading();
store.commit('task/setShowSkeleton', false);
if (businessCode.value === 'ZERO' && realTasks.value.length > 0) {
let needTask = query.queryType === 0 ? realTasks.value[0] : realTasks.value[realTasks.value.length - 1];
query.taskId = needTask.id;
query.timeNode = needTask.planStart;
query.businessCode = needTask.businessCode;
}
const params = generateGetTaskParam(query);
uni.$catchReq.getTaskByNum(params, (err, data) => {
store.commit('task/setShowSkeleton', false);
if (err) {
// TODO: 提示错误
console.error('err: ', err);
} else {
store.commit('task/setShowScrollTo', true);
// 存储当前查询的真实任务
params.queryType === 0 ? store.commit('task/setUpRealTasks', data.list) : store.commit('task/setDownRealTasks', data.list);
params.queryType === 0 ? store.commit('socket/setCurrLocationTaskId', data.list[0].id) : store.commit('socket/setCurrLocationTaskId', data.list[data.list.length - 1].id);
// 下一页
if (data.list.length < params.pageSize) {
params.queryType === 0 ? store.commit('task/setUpNextPage', 0) : store.commit('task/setDownNextPage', 0); // 下一页
} else {
params.queryType === 0 ? store.commit('task/setUpNextPage', 1) : store.commit('task/setDownNextPage', 1); // 下一页
}
// 将真实任务存储到allTasks中对应的角色下
const arr = [...allTasks.value];
arr[roleIndex.value].realTasks = [...realTasks.value];
arr[roleIndex.value].upNextPage = upNextPage.value; // 存储下一页(向上)
arr[roleIndex.value].downNextPage = downNextPage.value; // 存储下一页(向下)
store.commit('task/setAllTasks', arr);
// 数据处理
dataRender(params);
}
});
}
function dataRender(params) {
timeLineType.value === 1 ? renderScaleTask(params) : renderConTask(params);
}
// 刻度模式数据处理
async function renderScaleTask(query) {
const params = generateGetTaskParam(query);
let centerData = await showTaskTime(params, tasks.value, realTasks.value) || [];
await handleTasksData(params, centerData);
}
// 当前需要处理的真实任务
async function showTaskTime(params, showTasks, realTasks) {
/**
* 1初始时显示15个真实任务
* 2再次取值获取显示任务最后一条真实任务的id从下一条开始查找15条不够15条全部查询
*/
// 初始值
// 显示任务中没有真实任务数据
let centerData = [];
if (realTasks.length > params.pageSize && params.queryType === 0) {
centerData = realTasks.slice(realTasks.length - params.pageSize); // 上且数据>15 -- 从最后取15个数据
} else {
centerData = realTasks.slice(0, params.pageSize); // 上且数据<15,下 -- 从开始取15个数据
}
const arr = showTasks.filter(item => item.detailId); // 所有的真实数据
// 显示任务中有真实任务数据
if (arr.length > 0) {
realTasks.forEach((item, index) => {
if (params.queryType === 1 && item.id === arr[arr.length - 1].id) {
centerData = realTasks.slice(index + 1, index + 1 + params.pageSize);
} else if (params.queryType === 0 && item.id === arr[0].id) {
centerData = index >= params.pageSize ? realTasks.slice(index - params.pageSize, index) : realTasks.slice(0, index);
}
})
}
return centerData;
}
async function handleTasksData(params, centerData) {
let oldTasks = tasks.value;
// 符合条件的数据
const newTasks = centerData.filter(item => dayjs(+scaleTasksStartTime.value).isBefore(+item.planStart, timeGranularity.value) && dayjs(+scaleTasksEndTime.value).isAfter(+item.planStart, timeGranularity.value))
// 查找下一页 -- 全部数据符合条件,数据量<15,有下一页
const isNextPage = params.queryType === 1 && downNextPage.value === 1 || params.queryType === 0 && upNextPage.value === 1;
if (newTasks.length === centerData.length && centerData.length < params.pageSize && isNextPage) {
if (centerData.length > 0) {
const taskId = params.queryType === 1 ? centerData[centerData.length - 1].id : centerData[0].id;
getTasks({taskId: taskId, queryType: params.queryType});
} else {
getTasks({queryType: params.queryType});
}
} else {
// 用真实任务数据替换空刻度,不需要继续查找下一页
// 1、一部分数据符合
// 2、全部数据符合条件,
// 2-1、数据量15 -- 需要删除多余的空数据
// 2-2、数据量<15,但是没有下一页
oldTasks.forEach((task, index) => {
let arr = centerData.filter(item => dayjs(+item.planStart).isSame(+task.planStart, timeGranularity.value));
if (arr && arr.length > 0) {
if (task.detailId) {
params.queryType === 1 ? [...oldTasks, ...arr] : [...arr, ...oldTasks];
} else {
oldTasks.splice(index, 1, [...arr]);
}
}
})
// 全部数据符合条件,数据量15,删除多余的空数据
if (newTasks.length === centerData.length && centerData.length === params.pageSize) {
const lastIndex = oldTasks.findIndex(item => item.id === centerData[centerData.length - 1].id);
oldTasks.slice(0, lastIndex);
}
oldTasks = flatten(oldTasks); // 1维拍平
store.commit('task/clearTasks');
params.queryType === 0 ? store.commit('task/setUpTasks', oldTasks) : store.commit('task/setDownTasks', oldTasks);
}
uni.$ui.hideLoading();
}
// 任务模式
async function renderConTask(params) {
let nextPage = params.queryType === 0 ? upNextPage.value : downNextPage.value; // 下一页的值
let showTasks = tasks.value;
let centerData = await showTaskTime(params, showTasks, realTasks.value) || [];
if (centerData.length < 15 && nextPage > 0) {
getTasks({pageNum: nextPage, queryType: params.queryType});
} else {
if (params.queryType === 0) {
showTasks = [...centerData, ...showTasks];
} else {
showTasks = [...showTasks, ...centerData];
}
}
if (showTasks.length < 15 && nextPage === 0 && params.queryType === 1) {
getTasks({pageNum: 1, queryType: 0});
}
// if (showTasks.length > 80) {
// showTasks = params.queryType === 0 ? showTasks.slice(0, 80) : showTasks.slice(showTasks.length - 80);
// }
store.commit('task/clearTasks');
params.queryType === 0 ? store.commit('task/setUpTasks', showTasks) : store.commit('task/setDownTasks', showTasks);
}
// 设置时间轴向上的空数据
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/setScaleTasksStartTime', placeholderTasks[0].planStart);
store.commit('task/setScaleTasksEndTime', placeholderTasks[placeholderTasks.length - 1].planStart);
// store.commit('task/setCurrUpTimeNode', startTime);
store.commit('task/setUpTasks', placeholderTasks);
}
// 设置时间轴向下的空数据
function setNextPlaceholderTasks() {
let startTime = '';
if (!tasks.value || !tasks.value.length) {
startTime = Date.now();
} else {
startTime = dayjs(+tasks.value[tasks.value.length - 1].planStart).add(1, timeGranularity.value).valueOf();
}
const initData = uni.$task.setPlaceholderTasks(startTime, false, timeGranularity.value);
store.commit('task/setScaleTasksStartTime', initData[0].planStart);
store.commit('task/setScaleTasksEndTime', initData[initData.length - 1].planStart);
// store.commit('task/setCurrDownTimeNode', startTime);
store.commit('task/setDownTasks', initData);
}
/**
* 当日常任务发生变化时
* 将新获取到的日常任务放在allTasks里
*/
watch(tasks, () => {
// 添加到allTasks里
const index = visibleRoles.value.findIndex(role => role.id === roleId.value);
const arr = [...allTasks.value];
if (arr.length && index > -1) {
if (remindData.value) {
tasks.value.forEach(task => {
task.remindNum = 0;
task.msgId = '';
if (task.plugins && task.plugins.length) {
task.plugins.forEach(pluginArr => {
pluginArr.forEach(plugin => {
plugin.remindNum = 0;
remindData.value.forEach(remind => {
const remind_data = JSON.parse(remind.data);
if (projectId.value === remind_data.data.projectId && roleId.value === remind_data.data.roleId) {
if (remind_data.data.taskId === task.id) {
task.remindNum++;
task.msgId = remind.id;
}
if (remind_data.data.taskId === task.id && remind_data.data.pluginId === plugin.pluginTaskId) {
plugin.remindNum++;
}
}
});
});
});
}
})
}
arr[index].task = [...tasks.value];
// store.commit('task/setCurrRoleShowTasks', arr[index].task); // 设置当前角色的展示任务数据
}
store.commit('task/setAllTasks', arr);
});
return {
initPlanTasks,
getTasks,
dataRender,
setPrevPlaceholderTasks,
setNextPlaceholderTasks
}
}

336
hooks/project/useGetTasks222.js

@ -0,0 +1,336 @@
import { computed, nextTick, watch } 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 timeLineType = computed(() => store.state.task.timeLineType); // 时间轴模式
const realTasks = computed(() => store.state.task.realTasks); // 真实任务
const downNextPage = computed(() => store.state.task.downNextPage); // 下一页
const upNextPage = computed(() => store.state.task.upNextPage); // 下一页
const currUpTimeNode = computed(() => store.state.task.currUpTimeNode); // 当前查询的时间
const currDownTimeNode = computed(() => store.state.task.currDownTimeNode); // 当前查询的时间
const roleId = computed(() => store.state.role.roleId);
const timeNode = computed(() => store.state.task.timeNode);
const timeUnit = computed(() => store.state.task.timeUnit);
const visibleRoles = computed(() => store.state.role.visibleRoles);
const allTasks = computed(() => store.state.task.allTasks);
const roleIndex = computed(() => store.state.role.roleIndex);
const projectId = computed(() => store.getters['project/projectId']);
const timeGranularity = computed(() => store.getters['task/timeGranularity']);
const remindData = computed(() => store.state.socket.remindData); // 小红点
const currRoleRealTasks = computed(() => store.state.task.currRoleRealTasks); // 当前角色的真实任务数据
const currRoleShowTasks = computed(() => store.state.task.currRoleShowTasks); // 当前角色的展示任务数据
const currLocationTaskId = computed(() => store.state.socket.currLocationTaskId);
// 初始化 定期任务
async function initPlanTasks() {
if (timeLineType.value === 1) setNextPlaceholderTasks({});
console.log('查询定期任务');
await getTasks({}); // 获取初始数据
// await dataRender({});
}
/**
* 生成getTasks所用的参数
* @param {object} query getTasks传递的参数
*/
function generateGetTaskParam(query) {
return {
roleId: roleId.value,
timeNode: query.timeNode || timeNode.value,
timeUnit: query.timeUnit || timeUnit.value,
queryType: query.queryType === 0 ? 0 : 1,
pageNum: query.pageNum || 1,
pageSize: query.pageSize || uni.$taskConfig.pageCount,
taskId: query.taskId || currLocationTaskId.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) {
store.commit('task/setShowSkeleton', false);
const params = generateGetTaskParam(query);
console.log('查询定期任务api', params);
uni.$catchReq.getTaskByNum(params, (err, data) => {
store.commit('task/setShowSkeleton', false);
if (err) {
// TODO: 提示错误
console.error('err: ', err);
} else {
store.commit('task/setShowScrollTo', true);
params.queryType === 0 ? store.commit('task/setUpRealTasks', data.list) : store.commit('task/setDownRealTasks', data.list);
console.log('查询到的真实任务', data.list);
const index = visibleRoles.value.findIndex(role => role.id === roleId.value);
console.log('api当前角色id', roleId.value, index);
const arr = [...allTasks.value];
arr[index].realTasks = [...realTasks.value];
arr[index].upNextPage = params.queryType === 0 ? data.nextPage : 1;
arr[index].downNextPage = params.queryType === 1 ? data.nextPage : 1;
store.commit('task/setAllTasks', arr);
console.log('设置当前真实任务', arr[index].realTasks);
store.commit('task/setCurrRoleRealTasks', arr[index].realTasks); // 设置当前角色的真实任务数据
params.queryType === 0 ? store.commit('task/setUpNextPage', arr[index].upNextPage) : store.commit('task/setDownNextPage', arr[index].downNextPage); // 下一页
// 如果第一次渲染但没有空数据则加载空数据
if (!currRoleShowTasks.value || !currRoleShowTasks.value.length && timeLineType.value === 1) {
setNextPlaceholderTasks(params);
}
// 数据处理
dataRender(params);
}
});
}
function dataRender(params) {
timeLineType.value === 1 ? renderScaleTask(params) : renderConTask(params);
}
// 刻度模式数据处理
async function renderScaleTask(query) {
const params = generateGetTaskParam(query);
console.log('当前角色id', roleId.value);
console.log('当前角色的真实任务', currRoleRealTasks.value);
let centerData = await showTaskTime(params, currRoleShowTasks.value, currRoleRealTasks.value) || [];
console.log('需要显示的真实任务', centerData);
await handleTasksData(params, centerData, currRoleRealTasks.value);
}
// 已显示的任务第一个时间和最后一个时间
async function showTaskTime(params, showTasks, realTasks) {
/**
* 1判断显示任务中是否有真实任务
* 1-1根据id查找任务
* 1-2根据时间查找任务
* 2查找15个任务
*/
// 初始值
// 显示任务中没有真实任务数据
let centerData = [];
if (realTasks.length > params.pageSize && params.queryType === 0) {
centerData = realTasks.slice(realTasks.length - params.pageSize);
} else {
centerData = realTasks.slice(0, params.pageSize);
}
const firstDetailIndex = showTasks.findIndex(task => task.detailId);
if (firstDetailIndex > -1) {
// 显示任务中有真实任务数据
const firstId = showTasks[firstDetailIndex].id;
let lastDetailIndex = -1;
showTasks.forEach((item, index) => {
if (item.detailId) {
lastDetailIndex = index;
}
})
const lastId = showTasks[lastDetailIndex].id;
realTasks.forEach((item, index) => {
if (params.queryType === 1 && item.id === lastId) {
centerData = realTasks.slice(index + 1, index + 1 + params.pageSize);
} else if (params.queryType === 0 && item.id === firstId) {
centerData = index >= params.pageSize ? realTasks.slice(index - params.pageSize, index) : realTasks.slice(0, index);
}
})
}
return centerData;
}
async function handleTasksData(params, centerData, realTasks) {
/**
* 3查找的任务数量是否>=15
* 3-1
* 判断时间跨度是否>=15
* 3-1-1显示时间刻度范围内的任务
* 3-1-2显示全部任务并删除多余的刻度
* 3-2
* 判断时间跨度是否>=15
* 3-2-1显示时间刻度范围内的任务
* 3-2-2
* 下一页是否为0
* 3-2-2-1无下一页显示任务和刻度之后继续展示刻度
* 3-2-2-1查找下一页数据并重复上述步骤
*/
let showTasks = currRoleShowTasks.value; // 显示的数据
const nextPage = params.queryType === 0 ? upNextPage.value : downNextPage.value; // 下一页的值
if (centerData.length) {
let centerDataTime = '', // 中间数据的时间
scaleTime = '', // 空时间节点的开始时间
centerTime = '', // 中间数据+/-15后的数据
isExceed = false; // 时间跨度是否大于15
if (params.queryType) {
centerDataTime = centerData[centerData.length - 1].planStart;
scaleTime = currDownTimeNode.value;
centerTime = dayjs(+centerDataTime).subtract(params.pageSize, timeGranularity.value);
isExceed = dayjs(+centerTime).isSame(+scaleTime, timeGranularity.value) || dayjs(+centerTime).isAfter(+scaleTime, timeGranularity.value)
} else {
centerDataTime = centerData[0].planStart;
scaleTime = currUpTimeNode.value;
centerTime = dayjs(+centerDataTime).add(params.pageSize, timeGranularity.value);
isExceed = dayjs(+centerTime).isSame(+scaleTime, timeGranularity.value) || dayjs(+centerTime).isBefore(+scaleTime, timeGranularity.value)
}
const firstDetailIndex = showTasks.findIndex(task => task.detailId); // 显示任务中存在真实任务
const firstId = firstDetailIndex > -1 ? showTasks[firstDetailIndex].id : '';
let lastDetailIndex = -1;
showTasks.forEach((item, index) => {
if (item.detailId) {
lastDetailIndex = index;
}
})
const lastId = lastDetailIndex > -1 ? showTasks[lastDetailIndex].id : '';
showTasks.forEach((task, index) => {
const arr = centerData.filter(item => dayjs(+item.planStart).isSame(+task.planStart, timeGranularity.value));
if (arr.length) {
if (params.queryType === 1 && task.id === lastId) {
showTasks.splice(index + 1, 0, [...arr])
} else if (params.queryType === 0 && task.id === firstId) {
showTasks.splice(index, 0, [...arr])
} else {
showTasks.splice(index, 1, [...arr])
}
}
})
showTasks = flatten(showTasks); // 1维拍平
if (!isExceed) {
if (centerData.length >= params.pageSize) {
let len = -1;
let data = params.queryType === 0 ? centerData[0] : centerData[centerData.length - 1];
showTasks.forEach((item, index) => {
if (item.id === data.id) {
len = index;
}
})
if (len > -1) {
showTasks = params.queryType === 0 ? showTasks.slice(len) : showTasks.slice(0, len + 1);
}
} else if (nextPage > 0) {
console.log('数据不为空,时间跨度小于15')
getTasks({pageNum: nextPage, queryType: params.queryType});
}
}
} else {
if (nextPage > 0) {
console.log('数据为空')
getTasks({pageNum: nextPage, queryType: params.queryType});
} else {
params.queryType === 0 ? setPrevPlaceholderTasks(params) : setNextPlaceholderTasks(params);
}
}
// if (showTasks.length > 30) {
// showTasks = params.queryType === 0 ? showTasks.slice(0, 80) : showTasks.slice(showTasks.length - 80);
// }
store.commit('task/clearTasks');
params.queryType === 0 ? store.commit('task/setUpTasks', showTasks) : store.commit('task/setDownTasks', showTasks);
}
// 任务模式
async function renderConTask(params) {
let nextPage = params.queryType === 0 ? upNextPage.value : downNextPage.value; // 下一页的值
let showTasks = currRoleShowTasks.value;
let centerData = await showTaskTime(params, showTasks, currRoleRealTasks.value) || [];
if (centerData.length < 15 && nextPage > 0) {
getTasks({pageNum: nextPage, queryType: params.queryType});
} else {
if (params.queryType === 0) {
showTasks = [...centerData, ...showTasks];
} else {
showTasks = [...showTasks, ...centerData];
}
}
if (showTasks.length < 15 && nextPage === 0 && params.queryType === 1) {
getTasks({pageNum: 1, queryType: 0});
}
if (showTasks.length > 80) {
showTasks = params.queryType === 0 ? showTasks.slice(0, 80) : showTasks.slice(showTasks.length - 80);
}
store.commit('task/clearTasks');
params.queryType === 0 ? store.commit('task/setUpTasks', showTasks) : store.commit('task/setDownTasks', showTasks);
}
// 设置时间轴向上的空数据
function setPrevPlaceholderTasks() {
store.commit('task/setTopEnd', true);
let startTime = '';
if (!currRoleShowTasks.value || !currRoleShowTasks.value.length) {
startTime = Date.now(); // 没有任务就应该是时间基准点
} else {
startTime = currRoleShowTasks.value[0].planStart - 0; // 有任务就是第一个任务的计划开始时间
}
const placeholderTasks = uni.$task.setPlaceholderTasks(startTime, true, timeGranularity.value);
store.commit('task/setCurrUpTimeNode', startTime);
store.commit('task/setUpTasks', placeholderTasks);
}
// 设置时间轴向下的空数据
function setNextPlaceholderTasks(params) {
// store.commit('task/setBottomEnd', true);
let startTime = '';
if (!currRoleShowTasks.value || !currRoleShowTasks.value.length) {
startTime = Date.now();
} else {
startTime = dayjs(+currRoleShowTasks.value[currRoleShowTasks.value.length - 1].planStart).add(1, timeGranularity.value).valueOf();
}
if (params.taskId) {
currRoleRealTasks.value.forEach(item => {
if (item.id === params.taskId) {
startTime = Number(item.planStart);
}
})
}
const initData = uni.$task.setPlaceholderTasks(startTime, false, timeGranularity.value);
store.commit('task/setCurrDownTimeNode', startTime);
store.commit('task/setDownTasks', initData);
}
/**
* 当日常任务发生变化时
* 将新获取到的日常任务放在allTasks里
*/
watch(tasks, () => {
// 添加到allTasks里
const index = visibleRoles.value.findIndex(role => role.id === roleId.value);
const arr = [...allTasks.value];
arr[index].task = [...tasks.value];
store.commit('task/setAllTasks', arr);
store.commit('task/setCurrRoleShowTasks', arr[index].task); // 设置当前角色的展示任务数据
});
return {
initPlanTasks,
getTasks,
dataRender
}
}

143
hooks/project/useInit.js

@ -0,0 +1,143 @@
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);
const allTasks = computed(() => store.state.task.allTasks);
const remindData = computed(() => store.state.socket.remindData);
onLoad(options => {
store.commit('setDomain', decodeURIComponent(options.url));
if (options.share && options.share === '1') {
shareInit(options);
} else {
init(options);
}
});
/**
* 初始化
* @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('options', options)
store.commit('project/setProject', options);
store.commit('role/setRoleId', '');
uni.$storage.setStorageSync('roleId', options.roleId || '');
uni.$storage.setStorageSync('projectId', options.p || '');
store.commit('task/setAllTasks', []);
store.commit('task/setBusinessCode', options.businessCode || '');
store.commit('task/setTargetTaskId', options.taskId || '');
}
// 根据项目id获取项目信息
const params = { projectId: options.p, num: 0 };
getProjectById(params);
// 根据项目id获取角色列表
getRoles(params);
// 根据项目id获取成员列表
store.dispatch('role/getAllMembers', { projectId: options.p });
}
}
/**
* 通过项目id获取项目信息
* @param {object} params 提交的参数
*/
async function getProjectById(params) {
try {
const data = await uni.$u.api.findProjectById(params);
store.commit('project/setProject', data);
} catch (error) {
console.error('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 : []);
setAllTasksByRoles(data ? data.visibleList : []);
}
});
}
// 设置 初始显示角色信息
function setInitialRoleId(visibleList) {
if (!visibleList || !visibleList.length) return;
let 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 : '');
if (storageRoleId) {
index = visibleList.findIndex(item => item.id === storageRoleId);
}
store.commit('role/setRoleId', currentRoleId);
store.commit('role/setRoleIndex', index);
// 清空storage
uni.$storage.setStorageSync('roleId', '');
}
// 获取到角色列表,设置所有任务对应的角色
function setAllTasksByRoles(roles) {
const arr = allTasks.value;
if (roles && roles.length) {
roles.forEach(role => {
const item = { role };
arr.push(item);
});
}
store.commit('task/setAllTasks', arr);
}
// 分享链接来的初始化
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;
}
}

277
hooks/user/userMixin - 数字键盘.js

@ -0,0 +1,277 @@
import { ref, computed, reactive } from 'vue';
import { useStore } from 'vuex';
import clipboard from "@/common/js/dc-clipboard/clipboard.js";
import Config from '@/common/js/config.js'
export default function userMixin() {
const store = useStore();
const user = computed(() => store.state.user.user);
const rules = {
phone: [{
required: true,
message: '请输入手机号',
trigger: ['change', 'blur'],
},
{
validator: (rule, value, callback) => {
// 调用uView自带的js验证规则,详见:https://www.uviewui.com/js/test.html
return uni.$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 smsCode = ref(null); // 短信验证码
// const showInterval = ref(false);
// const interval = ref(120);
const codeTimer = ref(null);
// const showPaste = ref(false);
const dataObj = reactive({
showInterval: false,
interval: 60,
showPaste: false
});
//有图片验证码的值
function hasvalue(form, renderData) {
uni.hideKeyboard();//隐藏软键盘
if(form.smsCode || form.showPaste) return
if (!verifyPhone(form.phone)) {
uni.$ui.showToast('请输入正确的手机号');
return;
}
if (!form.verificationCodeValue) {
uni.$ui.showToast('请输入图形验证码');
return;
}
if (!dataObj.showInterval) {
getCode(form, renderData);
}
}
// 获取验证码
async function getCode(form, renderData) {
try {
if (!renderData.verificationCodeId || !form.verificationCodeValue) {
uni.$ui.showToast('缺少图形验证码参数');
return;
}
const params = {
phone: form.phone,
verificationCodeId: renderData.verificationCodeId,
verificationCodeValue: form.verificationCodeValue,
};
const data = await store.dispatch('user/sendCode', params);
if (data) {
getCodeInterval();
dataObj.showPaste = true;
}
} catch (err) {
dataObj.showPaste = false;
throw err;
}
}
// 获取验证码倒计时
function getCodeInterval() {
dataObj.showInterval = true;
codeTimer.value = setInterval(() => {
if (dataObj.interval === 0) {
clearInterval(codeTimer.value);
codeTimer.value = null;
dataObj.showInterval = false;
dataObj.interval = 60;
return;
}
dataObj.interval = dataObj.interval - 1;
}, 1000);
}
// 粘贴
function setCode(form) {
// 获取粘贴板内容
// 小程序平台
//#ifdef MP-WEIXIN
uni.getClipboardData({
success(res) {
form.smsCode = res.data;
},
});
//#endif
// 非小程序平台
//#ifndef MP-WEIXIN
getClipboardContents(form)
//#endif
}
// 非小程序平台粘贴
async function getClipboardContents(form) {
try {
const text = await navigator.clipboard.readText();
form.smsCode = text;
} catch (err) {
console.error('Failed to read clipboard contents: ', err);
}
}
// 验证信息
// function checkRules() {
// if (!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;
// }
/**
* 验证手机号格式
* @param {string} phone 手机号
*/
function verifyPhone(phone) {
const phoneExg = /^1\d{10}$/;
return phoneExg.test(phone);
}
/**
* 验证账号/密码 格式
* @param {string} account 账号
*/
function verifyLoginname(account) {
const accountExg = /^[a-zA-Z0-9._-]{2,20}$/;
return accountExg.test(account);
}
// 微信登录
function handleWxLogin() {
// #ifdef H5
const origin = `${Config.baseUrl}/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`;
// #endif
// #ifdef APP-PLUS
uni.getProvider({
service: 'oauth',
success: function(res) {
console.log(res.provider);
//支持微信、qq和微博等
if (~res.provider.indexOf('weixin')) {
uni.login({
provider: 'weixin',
success: function(loginRes) {
uni.$ui.showToast("App微信获取用户信息成功", loginRes);
console.log("App微信获取用户信息成功", loginRes);
// 获取用户信息
uni.getUserInfo({
provider: 'weixin',
success: function(infoRes) {
uni.$ui.showToast('-------获取微信用户所有-----');
uni.$ui.showToast(infoRes.userInfo.openId);
uni.$ui.showToast(JSON.stringify(infoRes.userInfo));
console.log('-------获取微信用户所有-----');
console.log(infoRes.userInfo.openId);
console.log(JSON.stringify(infoRes.userInfo));
// 提交信息
// that.wxSubmit(infoRes.userInfo.openId)
}
});
// that.getApploginData(loginRes) //请求登录接口方法
},
fail: function(res) {
console.log("App微信获取用户信息失败", res);
}
})
}
}
});
// #endif
}
return {
rules,
// showPaste,
// showInterval,
// interval,
dataObj,
hasvalue,
// checkRules,
setCode,
verifyLoginname,
handleWxLogin
}
}

275
hooks/user/userMixin.js

@ -0,0 +1,275 @@
import { ref, computed, reactive } from 'vue';
import { useStore } from 'vuex';
import clipboard from "@/common/js/dc-clipboard/clipboard.js";
import Config from '@/common/js/config.js'
export default function userMixin() {
const store = useStore();
const user = computed(() => store.state.user.user);
const rules = {
phone: [{
required: true,
message: '请输入手机号',
trigger: ['change', 'blur'],
},
{
validator: (rule, value, callback) => {
// 调用uView自带的js验证规则,详见:https://www.uviewui.com/js/test.html
return uni.$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 smsCode = ref(null); // 短信验证码
// const showInterval = ref(false);
// const interval = ref(120);
const codeTimer = ref(null);
// const showPaste = ref(false);
const dataObj = reactive({
showInterval: false,
interval: 60,
showPaste: false
});
//有图片验证码的值
function hasvalue(form, renderData) {
if(form.smsCode || form.showPaste) return
if (!verifyPhone(form.phone)) {
uni.$ui.showToast('请输入正确的手机号');
return;
}
if (!form.verificationCodeValue) {
uni.$ui.showToast('请输入图形验证码');
return;
}
if (!dataObj.showInterval) {
getCode(form, renderData);
}
}
// 获取验证码
async function getCode(form, renderData) {
try {
if (!renderData.verificationCodeId || !form.verificationCodeValue) {
uni.$ui.showToast('缺少图形验证码参数');
return;
}
const params = {
phone: form.phone,
verificationCodeId: renderData.verificationCodeId,
verificationCodeValue: form.verificationCodeValue,
};
const data = await store.dispatch('user/sendCode', params);
if (data) {
getCodeInterval();
dataObj.showPaste = true;
}
} catch (err) {
dataObj.showPaste = false;
throw err;
}
}
// 获取验证码倒计时
function getCodeInterval() {
dataObj.showInterval = true;
codeTimer.value = setInterval(() => {
if (dataObj.interval === 0) {
clearInterval(codeTimer.value);
codeTimer.value = null;
dataObj.showInterval = false;
dataObj.interval = 60;
return;
}
dataObj.interval = dataObj.interval - 1;
}, 1000);
}
// 粘贴
function setCode() {
// 获取粘贴板内容
// 小程序平台
//#ifdef MP-WEIXIN
uni.getClipboardData({
success(res) {
smsCode.value = res.data;
},
});
//#endif
// 非小程序平台
//#ifndef MP-WEIXIN
getClipboardContents()
//#endif
}
// 非小程序平台粘贴
async function getClipboardContents() {
try {
const text = await navigator.clipboard.readText();
smsCode.value = text;
} catch (err) {
console.error('Failed to read clipboard contents: ', err);
}
}
// 验证信息
function checkRules() {
if (!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;
}
/**
* 验证手机号格式
* @param {string} phone 手机号
*/
function verifyPhone(phone) {
const phoneExg = /^1\d{10}$/;
return phoneExg.test(phone);
}
/**
* 验证账号/密码 格式
* @param {string} account 账号
*/
function verifyLoginname(account) {
const accountExg = /^[a-zA-Z0-9._-]{2,20}$/;
return accountExg.test(account);
}
// 微信登录
function handleWxLogin() {
// #ifdef H5
const origin = `${Config.baseUrl}/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`;
// #endif
// #ifdef APP-PLUS
uni.getProvider({
service: 'oauth',
success: function(res) {
console.log(res.provider);
//支持微信、qq和微博等
if (~res.provider.indexOf('weixin')) {
uni.login({
provider: 'weixin',
success: function(loginRes) {
uni.$ui.showToast("App微信获取用户信息成功", loginRes);
console.log("App微信获取用户信息成功", loginRes);
// 获取用户信息
uni.getUserInfo({
provider: 'weixin',
success: function(infoRes) {
uni.$ui.showToast('-------获取微信用户所有-----');
uni.$ui.showToast(infoRes.userInfo.openId);
uni.$ui.showToast(JSON.stringify(infoRes.userInfo));
console.log('-------获取微信用户所有-----');
console.log(infoRes.userInfo.openId);
console.log(JSON.stringify(infoRes.userInfo));
// 提交信息
// that.wxSubmit(infoRes.userInfo.openId)
}
});
// that.getApploginData(loginRes) //请求登录接口方法
},
fail: function(res) {
console.log("App微信获取用户信息失败", res);
}
})
}
}
});
// #endif
}
return {
rules,
// showPaste,
// showInterval,
// interval,
dataObj,
hasvalue,
checkRules,
setCode,
verifyLoginname,
handleWxLogin
}
}

11
index.html

@ -3,12 +3,19 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
<title></title>
<title></title>
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/vConsole/3.9.0/vconsole.min.js"></script> -->
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
<script type="module" src="/main.js"></script>
<!-- <script>
// init vConsole
var vConsole = new VConsole();
console.log('Hello world');
</script> -->
</body>
</html>

79
main.js

@ -1,23 +1,66 @@
import App from './App'
import { createSSRApp } from 'vue';
import App from './App';
import cache from '@/utils/cache.js';
import cacheAndRequest from '@/utils/cacheAndRequest.js';
import pluginConfig from '@/config/plugin';
import { setupDayjs } from '@/utils/dayjs.js';
import { setupFinance } from '@/apis/finance.js';
import { setupHttp } from '@/utils/request.js';
import { setupMock } from '@/apis/mock.js';
import { setupPlugin } from '@/apis/plugin.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 taskConfig from '@/config/task';
import time from '@/utils/time.js';
import timeConfig from '@/config/time';
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
return {
app
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;
app.config.globalProperties.$pluginConfig = pluginConfig;
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;
uni.$pluginConfig = pluginConfig;
setupDayjs(app);
app.use(uView); // 使用 uView UI
app.use(store);
setupHttp(app);
setupTall(app);
setupProject(app);
setupRole(app);
setupTask(app);
setupWbs(app);
setupPlugin(app);
setupFinance(app);
if (import.meta.env.MODE === 'development') {
setupMock(app); // mock DEBUG:
}
return { app };
}
// #endif

80
manifest.json

@ -1,6 +1,6 @@
{
"name" : "tall-4-project",
"appid" : "__UNI__1EC8558",
"name" : "时物链条",
"appid" : "__UNI__6207504",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
@ -17,7 +17,9 @@
"delay" : 0
},
/* */
"modules" : {},
"modules" : {
"OAuth" : {}
},
/* */
"distribute" : {
/* android */
@ -41,10 +43,56 @@
]
},
/* ios */
"ios" : {},
"ios" : {
"dSYMs" : false
},
/* SDK */
"sdkConfigs" : {}
}
"sdkConfigs" : {
"ad" : {},
"oauth" : {}
},
"icons" : {
"android" : {
"hdpi" : "unpackage/res/icons/72x72.png",
"xhdpi" : "unpackage/res/icons/96x96.png",
"xxhdpi" : "unpackage/res/icons/144x144.png",
"xxxhdpi" : "unpackage/res/icons/192x192.png"
},
"ios" : {
"appstore" : "unpackage/res/icons/1024x1024.png",
"ipad" : {
"app" : "unpackage/res/icons/76x76.png",
"app@2x" : "unpackage/res/icons/152x152.png",
"notification" : "unpackage/res/icons/20x20.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"proapp@2x" : "unpackage/res/icons/167x167.png",
"settings" : "unpackage/res/icons/29x29.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"spotlight" : "unpackage/res/icons/40x40.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png"
},
"iphone" : {
"app@2x" : "unpackage/res/icons/120x120.png",
"app@3x" : "unpackage/res/icons/180x180.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"notification@3x" : "unpackage/res/icons/60x60.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"settings@3x" : "unpackage/res/icons/87x87.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png",
"spotlight@3x" : "unpackage/res/icons/120x120.png"
}
}
},
"splashscreen" : {
"androidStyle" : "default"
}
},
"safearea" : {
"bottom" : {
"offset" : "none"
}
},
"nativePlugins" : {}
},
/* */
"quickapp" : {},
@ -68,5 +116,23 @@
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3"
"vueVersion" : "3",
"h5" : {
"title" : "时物链",
"router" : {
"mode" : "history",
"base" : "/tall/v4.0.0/"
},
"optimization" : {
"treeShaking" : {
"enable" : false
}
},
"devServer" : {
"https" : false
},
"sdkConfigs" : {
"maps" : {}
}
}
}

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"
}

149
pages.json

@ -1,16 +1,135 @@
{
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
{
"pages": [{
"path": "pages/index/index",
"style": {
"navigationBarText": "TALL",
"navigationStyle": "custom",
"enablePullDownRefresh": true
}
},
{
"path": "pages/workbench/workbench",
"style": {
"navigationBarTitleText": "工作台"
}
},
{
"path": "pages/business/business",
"style": {
"navigationBarTitleText": "业务列表"
}
},
{
"path": "pages/project/project",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/user/accountLogin",
"style": {
"navigationBarTitleText": "用户名登录"
}
},
{
"path": "pages/user/login",
"style": {
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/user/rigister",
"style": {
"navigationBarTitleText": "注册"
}
},
{
"path": "pages/user/agreement",
"style": {
"navigationBarTitleText": "注册协议"
}
},
{
"path": "pages/submitLog/submitLog",
"style": {
// "navigationBarTitleText": "交付物上传记录",
"navigationStyle": "custom"
}
},
{
"path": "pages/checkLog/checkLog",
"style": {
"navigationBarTitleText": "审核记录"
}
},
{
"path": "pages/detailWebview/detailWebview",
"style": {
"navigationBarTitleText": "详情页"
}
},
{
"path": "pages/404/404",
"style": {
"navigationBarTitleText": "404"
}
}, {
"path": "pages/audit/audit",
"style": {
"navigationBarTitleText": "个人管理",
"enablePullDownRefresh": false
}
}, {
"path": "pages/uidispose/uidispose",
"style": {
"navigationBarTitleText": "ui配置",
"enablePullDownRefresh": false
}
}, {
"path": "pages/exarresources/exarresources",
"style": {
"navigationBarTitleText": "域资源管理",
"enablePullDownRefresh": false
}
}, {
"path": "pages/projectVersion/projectVersion",
"style": {
"navigationBarTitleText": "项目版本管理",
"enablePullDownRefresh": false
}
},
{
"path": "uni_modules/uni-upgrade-center-app/pages/upgrade-popup",
"style": {
"disableScroll": true,
"app-plus": {
"backgroundColorTop": "transparent",
"background": "transparent",
"titleNView": false,
"scrollIndicator": false,
"popGesture": "none",
"animationType": "fade-in",
"animationDuration": 200
}
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"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"
}
}
}

5
pages/404/404.vue

@ -0,0 +1,5 @@
<template>
<div class="404">404</div>
</template>
<script setup></script>

13
pages/audit/audit.vue

@ -0,0 +1,13 @@
<template>
<image src="../../static/personal.png"></image>
</template>
<script></script>
<style lang="scss" scoped>
image {
width: 100%;
height: 50vh;
object-fit: cover;
}
</style>

106
pages/business/business.vue

@ -0,0 +1,106 @@
<template>
<!-- 这里是适配的状态栏的代码 -->
<view class="statbar">
<view class="status_bar"></view>
</view>
<view class="business-box">
<view class="business-wrap" v-for="(item, index) in list" :key="index" @click="toUpload(item.url)">
<view class="business-info">
<view class="name">{{ item.name }}</view>
<!-- <view class="desc">{{item.url}}</view> -->
</view>
<view class="mwbs-list" v-if="item.mwbsList && item.mwbsList.length > 0">
<view class="mwbs-item" v-for="(val, key) in item.mwbsList" :key="key" @click.stop="toUpload(item.url)">
<view class="name">{{ val.projectName }}</view>
<!-- <view class="desc">{{val}}</view> -->
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import Config from '@/common/js/config.js';
const store = useStore();
const list = ref([]);
const user = computed(() => store.state.user.user);
const userJson = uni.$storage.getStorageSync('user');
if (userJson) {
const user = JSON.parse(userJson);
store.commit('user/setUser', user);
}
getList();
async function getList() {
try {
const res = await uni.$u.api.getBusinessList();
list.value = res;
} catch (error) {
console.error('error: ', error);
}
}
async function toUpload(url) {
store.commit('setDomain', url);
try {
const res = await uni.$u.api.import('');
// WBS
//
uni.$ui.showToast('导入成功,即将打开新项目', 3000);
const { apiUrl } = Config;
let defaultwbs = `${apiUrl}/defaultwbs`;
res.url && (defaultwbs = res.url);
store.commit('project/setIsRefresh', 1);
setTimeout(() => {
uni.navigateTo({ url: `/pages/project/project?u=${user.value.id}&p=${res.id}&pname=${res.name}&url=${encodeURIComponent(res.url)}` });
}, 2000);
} catch (error) {
console.error('error: ', error);
uni.$ui.showToast(error || '导入失败', 6000);
}
}
</script>
<style lang="scss" scoped>
.business-box {
padding: 0 16px;
}
.business-wrap {
width: 100%;
box-sizing: border-box;
.business-info,
.mwbs-item {
padding: 10px;
border-bottom: 1px solid #eeeeee;
}
.name {
line-height: 36px;
}
.desc {
font-size: 12px;
color: #999;
}
.mwbs-list {
padding-left: 16px;
.mwbs-item:last-child {
border-bottom: none;
}
}
}
</style>

52
pages/checkLog/checkLog.vue

@ -0,0 +1,52 @@
<template>
<view class="bg-white p-3 text-gray-400">
<template v-if="checkerList && checkerList.length">
<view v-for="item in checkerList" class="flex justify-between">
<view>
<view class="mb-1 text-gray-800">
{{ item.checkerName }}
</view>
<view class="mb-1 text-xs">
{{ item.remark }}
</view>
<view class="text-xs" v-if="+item.checkTime > 0">
{{ dayjs(+item.checkTime).format('MM-DD HH:mm') }}
</view>
</view>
<view class="flex flex-col justify-center">
<view class="mb-1 text-green-600" v-if="item.status === 1"> 已通过 </view>
<view class="mb-1 text-red-600" v-if="item.status === 2"> 已驳回 </view>
<view v-if="+item.score > 0">
<zwp-ring-timing mode="chart" :value="item.score" active-color="#F59E0B" :radius="30" :bar-width="4">
<text class="text-yellow-500 font-medium">{{ item.score }}</text>
</zwp-ring-timing>
</view>
</view>
</view>
</template>
<u-empty text="暂无审核记录" mode="history" style="margin-top: 120rpx" v-else></u-empty>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import dayjs from 'dayjs';
const checkerList = ref([]);
onLoad(options => {
getQueryCheckLog(options);
});
async function getQueryCheckLog(options) {
try {
const param = { deliverRecordId: options.deliverRecordId };
const data = await uni.$u.api.queryCheckLog(param);
checkerList.value = data;
} catch (error) {
console.log('error: ', error);
uni.$ui.showToast('获取检查交付物历史失败');
}
}
</script>

23
pages/detailWebview/detailWebview.vue

@ -0,0 +1,23 @@
<template>
<web-view :src="src"></web-view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
const src = ref('');
onLoad(options => {
if (!options) {
uni.redirectTo({ url: '/pages/404/404' });
return;
}
if (options.url) {
src.value = decodeURIComponent(options.url);
}
if (options.name) {
uni.setNavigationBarTitle({ title: options.name || '详情页' });
}
});
</script>

13
pages/exarresources/exarresources.vue

@ -0,0 +1,13 @@
<template>
<image src="../../static/exarresources.png"></image>
</template>
<script></script>
<style lang="scss" scoped>
image {
width: 100%;
height: 100vh;
object-fit: cover;
}
</style>

94
pages/guide/adv.vue

@ -0,0 +1,94 @@
<template>
<view class="adv-box relative">
<swiper v-if="imgs.length > 0" class="swiper" :indicator-dots="indicatorDots" :autoplay="autoplay" circular="true">
<swiper-item v-for="(item, index) in imgs" :key="index">
<view class="swiper-item">
<image :src="item"></image>
</view>
</swiper-item>
</swiper>
<image v-else src="/static/adv.jpg"></image>
<view class="time-box absolute">{{ time }} 跳过</view>
</view>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const indicatorDots = true;
const autoplay = true;
const advs = computed(() => store.state.advs);
const imgs = ref([]);
if (advs.value) {
imgs.value = JSON.parse(advs.value);
}
watch(advs, () => {
imgs.value = JSON.parse(advs.value);
})
const time = ref(5);
let timer = setInterval(() => {
time.value--;
if (time.value === 1) clearInterval(timer);
}, 1000);
setTimeout(() => {
// App
let openNum = uni.$storage.getStorageSync('firstOpenApp');
if (openNum === 'true') {
uni.navigateTo({
url: '/pages/index/index'
})
} else {
uni.$storage.setStorageSync('firstOpenApp', true);
uni.navigateTo({
url: '/pages/guide/guide'
})
}
}, 5000);
</script>
<style lang="scss" scoped>
.adv-box {
width: 100%;
height: calc(100vh - var(--status-bar-height));
}
.swiper {
height: calc(100vh - var(--status-bar-height));
}
.swiper-item {
width: 100%;
height: 100%;
image {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.time-box {
bottom: 50px;
right: 10px;
display: inline-block;
width: 60px;
height: 24px;
line-height: 22px;
font-size: 12px;
text-align: center;
background-color: rgba(255, 255, 255, .7);
border-radius: 12px;
color: #666;
border: 1px solid #eee;
}
</style>

75
pages/guide/guide.vue

@ -0,0 +1,75 @@
<template>
<!-- indicator-dots 是否显示面板指示点 -->
<!-- indicator-color 指示点颜色 -->
<!-- indicator-active-color 当前选中的指示点颜色 -->
<swiper class="swiper" :indicator-dots="indicatorDots" :autoplay="autoplay">
<swiper-item v-for="(item, index) in imgs" :key="index">
<view class="swiper-item relative">
<image :src="item"></image>
<u-button class="btn absolute" v-if="index === imgs.length - 1" @click="toIndex">点击进入APP</u-button>
</view>
</swiper-item>
</swiper>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const indicatorDots = true;
const autoplay = false;
const guide = computed(() => store.state.guide);
const imgs = ref([]);
if (guide.value) {
imgs.value = JSON.parse(guide.value);
}
watch(guide, () => {
imgs.value = JSON.parse(guide.value);
})
function toIndex() {
uni.navigateTo({
url: '/pages/index/index'
})
}
</script>
<style lang="scss" scoped>
.swiper {
height: calc(100vh - var(--status-bar-height));
}
.swiper-item {
width: 100%;
height: calc(100vh - var(--status-bar-height));
image {
width: 100%;
height: 100%;
object-fit: cover;
}
.btn {
bottom: 200px;
display: inline-block;
height: 40px;
line-height: 38px;
background-color: #ECF5FF;
left: 50%;
transform: translateX(-50%);
border-radius: 20px;
color: #2B85E4;
border: 1px solid #2B85E4;
&:after {
border: none;
}
}
}
</style>

274
pages/index/index.vue

@ -1,66 +1,236 @@
<template>
<view :style="{ height: height }" class="flex flex-col overflow-hidden u-font-14">
<!-- 标题栏 -->
<Title />
<!-- 这里是适配的状态栏的代码 -->
<view class="statbar">
<view class="status_bar"></view>
</view>
<view class="container flex flex-col flex-1 mx-auto overflow-hidden bg-gray-100">
<!-- 角色栏 -->
<Roles />
<!-- #ifdef APP-PLUS -->
<!-- 广告 -->
<Adv v-if="isOpenApp"></Adv>
<!-- 日常任务面板 -->
<Globals />
<!-- 引导页 -->
<Guide v-else-if="!firstOpenApp"></Guide>
<!-- #endif -->
<!-- 定期任务面板 -->
<TimeLine @getTasks="getTasks" class="flex-1 overflow-hidden" ref="timeLine" />
</view>
</view>
<!-- #ifdef APP-PLUS -->
<theme v-if="!isOpenApp && firstOpenApp" class="relative flex flex-col h-full bg-gray-50">
<!-- #endif -->
<!-- #ifdef H5 -->
<theme class="relative flex flex-col h-full bg-gray-50">
<!-- #endif -->
<view class="relative">
<!-- 日历 -->
<!-- <Calendar @selected-change="onDateChange" :show-back="true" ref="calendar" @handleFindPoint="handleFindPoint" /> -->
<Calendar @selected-change="onDateChange" :show-back="true" ref="calendar" />
<!-- 上传 导入wbs -->
<Upload @success="onUploadSuccess" @error="onUploadError" />
</view>
<!-- 项目列表 -->
<Projects @getProjects="getProjects" class="flex-1 overflow-y-auto" />
<view class="login-box absolute" @click="toLogin">
<text v-if="!userInfo || !userInfo.id">游客</text>
<image v-else src="../../static/headimg4.png" mode=""></image>
</view>
<!-- 全局提示框 -->
<u-top-tips ref="uTips"></u-top-tips>
</theme>
</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, watch, ref } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
import upApp from '../../uni_modules/uni-upgrade-center-app/utils/check-update.js';
import { onPullDownRefresh } from '@dcloudio/uni-app';
upApp();
const store = useStore();
const token = computed(() => store.state.user.token);
const uTips = ref(null);
const firstOpenApp = computed(() => store.state.firstOpenApp); // APP false true
const isOpenApp = computed(() => store.state.isOpenApp); // APP true false
const userInfo = computed(() => store.state.user.user);
const remindData = computed(() => store.state.socket.remindData); //
const isRefresh = computed(() => store.state.project.isRefresh); //
const data = reactive({
calendar: null,
});
const colorList = ['#eee', '#ddd', '#ccc', '#bbb'];
const user = uni.$storage.getStorageSync('user');
if (!userInfo.value && user) {
store.commit('user/setUser', JSON.parse(user));
}
watch(isRefresh, () => {
getProjects();
store.commit('project/setIsRefresh', 0);
})
/**
* 下拉刷新
*/
onPullDownRefresh(() => {
getProjects();
setTimeout(function () {
uni.stopPullDownRefresh();
}, 1000);
})
getProjects();
// handleFindPoint();
let height = ref(null);
//
function getProjects(start = dayjs().startOf('day').valueOf(), end = dayjs().endOf('day').valueOf()) {
uni.$catchReq.getProjects(start, end, (err, data) => {
if (err) {
console.error('err: ', err);
} else {
let arr = [];
data.forEach(item => {
item.show = true;
onMounted(() => {
const system = uni.getSystemInfoSync();
height.value = system.windowHeight + 'px';
});
const index = arr.findIndex(v => v.businessCode === item.businessCode);
if (index === -1) {
arr.push({businessCode: item.businessCode});
}
});
function getTasks() {
data.forEach(item => {
arr.forEach((v, k) => {
if (item.businessCode === v.businessCode) {
item.styleColor = colorList[(k + 1) % colorList.length - 1];
}
})
})
}
store.commit('project/setProjects', data);
}
});
}
//
// 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);
// }
// }
//
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 = () => {
uni.$ui.showToast('导入成功,即将打开新项目', 3000);
};
//
const onUploadError = error => {
uni.$ui.showToast(error || '导入失败', 6000);
};
async function toLogin() {
if (!userInfo.value) {
uni.navigateTo({
url: '/pages/user/login'
});
} else {
try{
await uni.$ui.showModal('', '是否退出登录', true)
signout();
} catch(e) {
console.log(e);
}
}
}
/**
* 退出登录
*/
function signout() {
store.commit('user/setToken', '');
store.commit('user/setUser', null);
store.commit('socket/setSocket', null);
store.commit('socket/setConnected', false);
store.commit('socket/uploadNotificationData', []);
store.commit('socket/uploadRingData', []);
store.commit('socket/uploadRemindData', []);
uni.$storage.setStorageSync('anyringToken', '');
uni.$storage.setStorageSync('refreshToken', '');
uni.$storage.setStorageSync('user', '');
getProjects();
}
</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;
}
<style lang="scss" scoped>
.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;
}
.login-box {
top: 10px;
right: 10px;
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #eeeeee;
text-align: center;
line-height: 40px;
border: 1px solid #ddd;
overflow: hidden;
text {
font-size: 12px;
}
image {
display: inline-block;
width: 40px;
height: 40px;
object-fit: cover;
}
}
</style>

142
pages/project/project 复制.vue

@ -0,0 +1,142 @@
<template>
<theme :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>
</theme>
</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(); //
getTasksHook.initPlanTasks(); //
//
let timer = null;
timer = setInterval(() => {
if (showScrollTo.value) {
clearInterval(timer);
console.log('timeLine: ', timeLine);
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>

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

Loading…
Cancel
Save