Browse Source

refactor(project): 抽离project相关的大部分内容;

小程序主题与project界面分离,并传递参数给project

不兼容变更: project完全独立出去,目前项目只有小程序非project内容;tailwindcss进行了精简
pull/45/head
wally 4 years ago
parent
commit
ada4560996
  1. 1
      CHANGELOG.md
  2. 14
      src/apis/plugin.js
  3. 7
      src/apis/project.js
  4. 7
      src/apis/role.js
  5. 11
      src/apis/task.js
  6. 6
      src/common/styles/index.css
  7. 4668
      src/common/styles/tailwind.scss
  8. 91
      src/components/Globals/Globals.vue
  9. 139
      src/components/Plugin/Plugin.vue
  10. 2
      src/components/Projects/Projects.vue
  11. 233
      src/components/Roles/Roles.vue
  12. 84
      src/components/Skeleton/READ_ME.md
  13. 187
      src/components/Skeleton/Skeleton.vue
  14. 119
      src/components/TimeLine/TimeLine.vue
  15. 42
      src/components/TimeLine/component/Barrier.vue
  16. 11
      src/components/TimeLine/component/TaskTools.vue
  17. 142
      src/components/TimeLine/component/TimeBox.vue
  18. 211
      src/components/TimeLine/component/TimeStatus.vue
  19. 7
      src/components/TimeLine/component/Title.vue
  20. 95
      src/components/Tips/Tips.vue
  21. 65
      src/components/Title/Title.vue
  22. 3
      src/config/db.js
  23. 97
      src/config/plugin.js
  24. 17
      src/config/time.js
  25. 10
      src/main.js
  26. 44
      src/mixins/timeline.js
  27. 10
      src/pages.json
  28. 25
      src/pages/project-webview/project-webview.vue
  29. 376
      src/pages/project/project.vue
  30. 55
      src/pages/test/test.vue
  31. 7
      src/plugins/p-deliver-check/p-deliver-check.vue
  32. 20
      src/plugins/p-deliverable/p-deliverable.vue
  33. 7
      src/plugins/p-manage-member/p-manage-member.vue
  34. 7
      src/plugins/p-manage-project/p-manage-project.vue
  35. 7
      src/plugins/p-manage-role/p-manage-role.vue
  36. 7
      src/plugins/p-manage-task/p-manage-task.vue
  37. 60
      src/plugins/p-subproject/p-subproject.vue
  38. 39
      src/plugins/p-subtasks/p-subtasks.vue
  39. 20
      src/plugins/p-task-countdown/p-task-countdown.vue
  40. 16
      src/plugins/p-task-description/p-task-description.vue
  41. 34
      src/plugins/p-task-duration-delay/p-task-duration-delay.vue
  42. 22
      src/plugins/p-task-start-time-delay/p-task-start-time-delay.vue
  43. 16
      src/plugins/p-task-title/p-task-title.vue
  44. 85
      src/plugins/p-wbs-import/p-wbs-import.vue
  45. BIN
      src/static/local_play1.png
  46. BIN
      src/static/logo.png
  47. 3
      src/store/db/actions.js
  48. 3
      src/store/db/getters.js
  49. 12
      src/store/db/index.js
  50. 3
      src/store/db/mutations.js
  51. 7
      src/store/db/state.js
  52. 9
      src/store/index.js
  53. 3
      src/store/role/actions.js
  54. 3
      src/store/role/getters.js
  55. 12
      src/store/role/index.js
  56. 30
      src/store/role/mutations.js
  57. 7
      src/store/role/state.js
  58. 33
      src/store/task/actions.js
  59. 23
      src/store/task/getters.js
  60. 12
      src/store/task/index.js
  61. 168
      src/store/task/mutations.js
  62. 23
      src/store/task/state.js
  63. 46
      src/test/util/time.test.js
  64. 6
      src/uni.scss
  65. 163
      src/utils/indexedDB.js
  66. 4
      src/utils/tall.js

1
CHANGELOG.md

@ -47,6 +47,7 @@
- | 添加时间轴上下滚动 | 2b81bbc
- | 添加项目排序 | a0b491b
- | 点击日历日期查询项目列表 | c458385
- | 细节调整,添加project-webview | 4d9050b
- | 绑定手机号 | 52e0352
- | 缓存修改 | 63e1f0d
- | 角色栏实现 | 94cd671

14
src/apis/plugin.js

@ -1,14 +0,0 @@
// 插件的地址是固定的
const url = process.env.VUE_APP_API_URL;
const install = (Vue, vm) => {
vm.$u.api = { ...vm.$u.api } || {};
// 获取插件信息
vm.$u.api.getOtherPlugin = param => vm.$u.post(`${url}/pluginshop/plugin/query`, param);
// 查询子任务
vm.$u.api.findSonTask = param => vm.$u.post(`${uni.$t.domain}/task/findSonTask`, param);
// 查询子项目
vm.$u.api.findSonProject = param => vm.$u.post(`${uni.$t.domain}/project/findSonProject`, param);
};
export default { install };

7
src/apis/project.js

@ -1,7 +0,0 @@
const install = (Vue, vm) => {
vm.$u.api = { ...vm.$u.api } || {};
//根据id获取项目信息
vm.$u.api.findProjectById = param => vm.$u.post(`${uni.$t.domain}/project/findProjectById`, param);
};
export default { install };

7
src/apis/role.js

@ -1,7 +0,0 @@
const install = (Vue, vm) => {
vm.$u.api = { ...vm.$u.api } || {};
//根据时间基准点和角色查找定期任务
vm.$u.api.findShowRole = param => vm.$u.post(`${uni.$t.domain}/role/show`, param);
};
export default { install };

11
src/apis/task.js

@ -1,11 +0,0 @@
const install = (Vue, vm) => {
vm.$u.api = { ...vm.$u.api } || {};
vm.$u.api.getGlobal = param => vm.$u.post(`${uni.$t.domain}/task/global`, param);
vm.$u.api.getPermanent = param => vm.$u.post(`${uni.$t.domain}/task/permanent`, param);
//根据时间基准点和角色查找定期任务
vm.$u.api.getRegularTask = param => vm.$u.post(`${uni.$t.domain}/task/regular`, param);
//修改任务状态
vm.$u.api.updateTaskType = param => vm.$u.post(`${uni.$t.domain}/task/type`, param);
};
export default { install };

6
src/common/styles/index.css

@ -1,6 +0,0 @@
/* ./src/common/styles/index.css */
/*! @import */
@tailwind base;
@tailwind components;
@tailwind utilities;

4668
src/common/styles/tailwind.scss

File diff suppressed because it is too large

91
src/components/Globals/Globals.vue

@ -1,91 +0,0 @@
<template>
<view class="m-2" v-if="globals && globals.length">
<u-card
@click="openCard"
:show-foot="false"
:show-head="false"
:style="{ 'max-height': isShrink ? '106rpx' : '340rpx' }"
border-radius="25"
margin="0"
>
<view slot="body">
<scroll-view :scrollY="true" :style="{ 'max-height': isShrink ? '50rpx' : '280rpx' }">
<skeleton :banner="false" :loading="!globals.length" :row="4" animate class="u-line-2 skeleton"></skeleton>
<view class="grid gap-2">
<block v-for="item in globals" :key="item.id">
<template v-if="item.plugins">
<block v-for="(pluginArr, i) in item.plugins" :key="i">
<template class="p-0 u-col-between" v-if="pluginArr.length">
<Plugin
:class="[`row-span-${plugin.row}`, `col-span-${plugin.col}`]"
:task="item"
:key="plugin.pluginTaskId"
:plugin-task-id="plugin.pluginTaskId"
:plugin-id="plugin.pluginId"
:style-type="plugin.styleType || 0"
v-for="plugin in pluginArr"
/>
</template>
</block>
</template>
</block>
</view>
</scroll-view>
</view>
</u-card>
</view>
</template>
<script>
import { mapGetters, mapMutations, mapState } from 'vuex';
import Skeleton from '@/components/Skeleton/Skeleton';
export default {
name: 'Global',
components: { Skeleton },
data() {
return {
// loading: true,
task: null,
};
},
computed: {
...mapState('task', ['isShrink']),
...mapGetters('task', ['globals']),
},
methods: {
...mapMutations('task', ['setShrink']),
//
openCard() {
if (this.isShrink) {
this.setShrink(false);
}
},
},
};
</script>
<style scoped lang="scss">
.u-card-wrap {
background-color: $u-bg-color;
padding: 1px;
}
.u-body-item {
font-size: 32rpx;
color: #333;
padding: 20rpx 10rpx;
}
.u-body-item image {
width: 120rpx;
flex: 0 0 120rpx;
height: 120rpx;
border-radius: 8rpx;
margin-left: 12rpx;
}
</style>

139
src/components/Plugin/Plugin.vue

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

2
src/components/Projects/Projects.vue

@ -49,7 +49,7 @@ export default {
openProject(project) {
const { name, id, url } = project;
url && (uni.$t.domain = url);
this.$u.route('pages/project/project', {
this.$u.route('pages/project-webview/project-webview', {
u: this.userId,
p: id,
pname: name,

233
src/components/Roles/Roles.vue

@ -1,233 +0,0 @@
<template>
<view class="px-2 bg-white wrap">
<view class="home-box u-skeleton">
<scroll-view :enable-flex="true" :scroll-left="scrollLeft" :throttle="false" scroll-with-animation scroll-x>
<view class="tab-box">
<!-- 角色项
default-tab-choice 我的角色 && 当前展示
default-tab-item 我的角色 && 当前不展示
tab-choice 不是我的 && 当前展示
-->
<view
:class="{
'default-tab-choice': item.mine == 1 && roleId === item.id,
'default-tab-item': item.mine == 1 && roleId !== item.id,
'tab-choice': item.mine == 0 && roleId === item.id,
}"
:key="index"
@click="changeRole(item.id, index)"
class="tab-item"
v-for="(item, index) in roles"
>
<view class="tab-children u-skeleton-fillet u-font-14">
{{ item.name }}
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 骨架屏 -->
<u-skeleton :animation="true" :loading="loading" bg-color="#fff"></u-skeleton>
</view>
</template>
<script>
import { mapState, mapMutations, mapActions } from 'vuex';
export default {
name: 'Roles',
data() {
return {
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 },
],
};
},
computed: {
...mapState('role', ['visibleRoles', 'roleId']),
...mapState('task', ['tasks']),
},
watch: {
visibleRoles(val) {
if (val && val.length) {
this.roles = [...this.visibleRoles];
this.loading = false;
}
},
},
mounted() {
if (!this.visibleRoles || !this.visibleRoles.length) {
this.loading = true;
} else {
this.roles = [...this.visibleRoles];
}
},
methods: {
...mapActions('task', ['handleRegularTask']),
...mapMutations('role', ['setRoleId']),
...mapMutations('task', ['setPermanents', 'clearEndFlag']),
//
setCurrentRole(index) {
const query = uni.createSelectorQuery().in(this);
query
.selectAll('.tab-children')
.boundingClientRect(data => {
data.forEach(item => {
this.tabList.push({
width: item.width,
left: item.left,
});
});
})
.exec();
const system = uni.getSystemInfoSync(); //
console.log('system: ', system);
//
let left = 0;
let screenWidth = system.windowWidth;
for (let i = 0; i < index; i++) {
left += this.tabList[i].width + this.tabList[i].left * 2;
}
left += this.tabList[index].width;
this.scrollLeft = left - screenWidth / 2;
},
//
// projectroleId
// projectroleId
changeRole(id, index) {
try {
// script
this.clearPluginScript();
this.$nextTick(() => {
this.setRoleId(id);
// index
this.setCurrentRole(index);
});
} catch (error) {
console.error('role.vue changeRole error: ', error);
}
},
// script
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);
}
},
},
};
</script>
<style lang="scss" scoped>
.home-box {
// sticky
position: sticky;
top: 0;
background: #fff; //
/* #ifdef H5 */
// h5 44px
top: 88rpx;
/* #endif */
.tab-box {
position: relative;
white-space: nowrap;
// height: 84rpx;
.tab-item {
// width: 20%;
// height: 84rpx;
padding: 20rpx 24rpx;
position: relative;
display: inline-block;
text-align: center;
font-size: 30rpx;
transition-property: background-color, width;
}
.default-tab-item {
color: $roleChoiceColor;
}
.default-tab-choice {
//
position: relative;
color: $roleChoiceColor;
font-weight: 600;
}
.default-tab-choice:before {
content: '';
position: absolute;
left: 0;
bottom: -20rpx;
width: 100%;
height: 6rpx;
border-radius: 2rpx;
background: $roleChoiceColor;
}
.tab-choice {
//
position: relative;
color: $uni-color-primary;
font-weight: 600;
}
.tab-choice:before {
content: '';
position: absolute;
left: 0;
bottom: -20rpx;
width: 100%;
height: 6rpx;
border-radius: 2rpx;
background: $uni-color-primary;
}
}
}
// //
/* #ifndef APP-NVUE */
::-webkit-scrollbar,
::-webkit-scrollbar,
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
/* #ifdef H5 */
// 穿H5scroll-view
scroll-view ::v-deep ::-webkit-scrollbar {
display: none;
}
/* #endif */
.skeleton {
height: 44rpx;
}
</style>

84
src/components/Skeleton/READ_ME.md

@ -1,84 +0,0 @@
# skeleton组件
### 1.描述
> 此组件用于加载数据时占位图显示,跟vant-ui骨架屏用法相似,但比vant-ui更灵活
### 2.用法
- 基本用法
代码:
```vue
//基本用法
<skeleton :row="3" animate :loading="loading" >
<view>
content
</view>
</skeleton>
```
- **显示 title ——通过 **title 属性显示title占位图
代码:
```vue
//显示 title——通过 title 属性显示title占位图
<skeleton :row="3" title animate :loading="loading">
<view>
content
</view>
</skeleton>
```
- 显示头像(上面)——通过avatar=‘top’让头像的占位图上面显示
代码:
```vue
<skeleton :avatar="top" avatarAlign="left" :row="3" animate :loading="loading" style="margin-top:24rpx;">
<view>
content
</view>
</skeleton>
```
- 显示头像(左边)——通过avatar=‘left’让头像的占位图左边显示
代码:
```vue
<skeleton title :avatar="left" :row="3" animate :loading="loading" style="margin-top:24rpx;">
<view>
content
</view>
</skeleton>
```
- 显示banner**——通过 **banner属性显示banner占位图(只显示banner,不显示内容占位图时设置row="0")
代码:
```vue
<skeleton banner :row="0" animate :loading="loading" style="margin-top:24rpx;">
<view>
content
</view>
</skeleton>
```
###
### 3. API
### Props
| **属性名** | **说明** | **类型** | **默认值** | 可取值 |
| --- | --- | --- | --- | --- |
| loading | 是否显示骨架屏 | Boolean | true | true/false |
| row | 段落行数 | Number | String | 3 | 0表示不展现 |
| rowWidth | 段落行宽度 | Boolean &#124; Number | '100%' | |
| title | 是否显示标题 | Boolean &#124; String | false | |
| banner | 是否显示banner | Boolean &#124; String | false | |
| animate | 是否开启动画 | Boolean &#124; String | false | |
| avatar | 头像位置 | Boolean &#124; String | ''空 | left/top |
| avatarSize | 头像大小 | String | - | |
| avatarShape | 头像形状 | String | circle | circle/round |

187
src/components/Skeleton/Skeleton.vue

@ -1,187 +0,0 @@
<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>
/**
* 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
*
* */
export default {
props: {
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'
// }
},
computed: {
avatarClass() {
if (this.avatar == 'top') {
return ['lx-skeleton_avator__top'];
} else if (this.avatar == 'left') {
return ['lx-skeleton_avator__left'];
} else {
return '';
}
},
animationClass() {
return [this.animate ? 'lx-skeleton_animation' : ''];
},
slotClass() {
return [!this.loading ? 'show' : 'hide'];
},
avatarShapeClass() {
return [this.avatarShape == 'round' ? 'lx-skeleton_avator__round' : ''];
},
bannerClass() {
return [this.banner ? 'lx-skeleton_banner' : ''];
},
},
data() {
return {};
},
};
</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>

119
src/components/TimeLine/TimeLine.vue

@ -1,119 +0,0 @@
<template>
<!-- 时间间隔栏 -->
<!-- <Barrier /> -->
<scroll-view
:lower-threshold="300"
scroll-y="true"
:upper-threshold="300"
:scroll-into-view="viewId"
@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>
// import Barrier from './component/Barrier.vue';
import { mapState, mapMutations, mapGetters } from 'vuex';
import TimeBox from './component/TimeBox.vue';
import mixin from '@/mixins/timeline';
export default {
name: 'TimeLine',
components: { TimeBox },
mixins: [mixin],
data() {
return { top: 0 };
},
computed: {
...mapState('role', ['visibleRoles']),
...mapState('task', ['scrollTop', 'showTips', 'tasks', 'topEnd', 'bottomEnd', 'showSkeleton', 'timeNode', 'viewId']),
...mapGetters('task', ['timeGranularity']),
},
methods: {
...mapMutations('task', ['setScrollTop', 'setShrink', 'setUpTasks', 'setDownTasks', 'setViewId']),
//
scroll(e) {
this.top = e.detail.scrollTop;
this.setShrink(this.top > this.scrollTop);
this.setScrollTop(this.top);
},
//
async handleScrollTop() {
if (!this.tasks || !this.tasks.length || this.showSkeleton) return;
const startTime = this.tasks[0].planStart - 0;
if ((this.tasks[0].plugins && this.tasks[0].plugins.length === 0) || this.topEnd) {
//
console.warn('滚动到顶部没有数据时: ');
const addTasks = this.setTime(startTime, true);
this.setUpTasks(addTasks);
} else {
//
console.warn('滚动到顶部有数据时: ');
const upQuery = {
timeNode: startTime,
queryType: 0,
queryNum: 6,
};
await this.$emit('getTasks', upQuery);
}
},
//
async handleScrollBottom() {
if (!this.tasks || !this.tasks.length || this.showSkeleton) return;
const { tasks, timeGranularity } = this;
const startTime = tasks[tasks.length - 1].planStart - 0;
if ((tasks[0].plugins && tasks[0].plugins.length === 0) || this.bottomEnd) {
//
console.warn('滚动到底部没有数据时: ');
const addTasks = this.setTime(startTime, false);
this.setDownTasks(addTasks);
} else {
// =+
console.warn('滚动到底部有数据时: ');
const timeNode = this.$t.time.add(startTime, 1, timeGranularity).valueOf();
const downQuery = {
timeNode,
queryType: 1,
queryNum: 6,
};
await this.$emit('getTasks', downQuery);
}
},
//
setScrollPosition() {
const { tasks, timeNode } = this;
for (let i = 0; i < tasks.length; i++) {
const item = tasks[i];
const show = this.$t.time.isSame(+item.planStart, +timeNode, this.timeGranularity);
// storagetimeNode,storetimeNode
const taskId = this.$t.storage.getStorageSync('taskId');
// storage
if (taskId) {
this.setViewId(`a${taskId}`);
this.$t.storage.setStorageSync('taskId', '');
return;
}
if (show) {
this.setViewId(`a${item.id}`);
return;
}
}
},
},
};
</script>

42
src/components/TimeLine/component/Barrier.vue

@ -1,42 +0,0 @@
<!--
* @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>
export default {
name: 'Barrier',
data() {
return {};
},
};
</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>

11
src/components/TimeLine/component/TaskTools.vue

@ -1,11 +0,0 @@
<template>
<view class="flex justify-between" style="min-width: 90px">
<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>
</view>
</template>
<script>
export default {};
</script>

142
src/components/TimeLine/component/TimeBox.vue

@ -1,142 +0,0 @@
<template>
<view class="column">
<!-- v-if="tasks && tasks.length" -->
<view>
<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" />
</view>
</view>
<view class="border-l-2 border-gray-300 plugin">
<view class="h-3" v-if="task.process === 4"></view>
<view class="ml-3 overflow-hidden shadow-lg task-box">
<u-card
:show-foot="false"
:show-head="false"
:style="{ height: setHeight(task.panel) }"
class="h-16"
margin="0"
v-if="showSkeleton"
>
<view slot="body">
<view>
<skeleton :banner="false" :loading="true" :row="4" animate class="mt-2 u-line-2 skeleton"></skeleton>
</view>
</view>
</u-card>
<u-card
@click="onClickTask(task.planStart - 0, task.id)"
:show-foot="false"
:show-head="false"
:style="{ height: setHeight(task.panel) }"
class="h-16"
margin="0"
v-if="tasks && tasks.length && task.process !== 4 && !showSkeleton"
>
<!-- 任务面板插件 -->
<view slot="body">
<view class="p-0 u-col-between">
<view :key="pIndex" v-for="(row, pIndex) in task.plugins">
<view class="grid gap-2" 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="styleType || 0"
v-for="plugin in row"
/>
</view>
</view>
</view>
</view>
</u-card>
</view>
</view>
</view>
</view>
<!-- 局部弹框操作栏 -->
<Tips />
</view>
</template>
<script>
import { mapState, mapMutations, mapGetters, mapActions } from 'vuex';
import Skeleton from '@/components/Skeleton/Skeleton';
import TimeStatus from './TimeStatus.vue';
import TaskTools from './TaskTools.vue';
export default {
name: 'TimeBox',
components: { TimeStatus, Skeleton, TaskTools },
data() {
return { currentComponent: '', styleType: 0 };
},
computed: {
...mapState('role', ['roleId']),
...mapState('task', ['timeUnit', 'tasks', 'taskLoading', 'topEnd', 'bottomEnd', 'showSkeleton']),
...mapGetters('task', ['startTimeFormat']),
},
methods: {
...mapActions('task', ['getGlobal']),
...mapMutations('task', ['setTipsContent', 'setTipsContent']),
//
setHeight(panel) {
if (panel && panel.height) {
return panel.height + 'px';
} else {
return 'auto';
}
},
/**
* 点击了定期任务的面板 更新可变的日常任务
* @param {number} planStart 任务计划开始时间
* @param {string} taskId 任务id
*/
onClickTask(planStart, taskId) {
const param = { roleId: this.roleId, timeNode: planStart, timeUnit: this.timeUnit };
this.getGlobal(param);
this.$t.storage.setStorageSync('taskId', taskId);
this.$t.storage.setStorageSync('roleId', this.roleId);
},
},
};
</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;
}
/deep/ .ml-2 {
margin-left: 16px;
}
/deep/ .ml-3 {
margin-left: 20px;
}
</style>

211
src/components/TimeLine/component/TimeStatus.vue

@ -1,211 +0,0 @@
<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>{{ computeDurationText() }}</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>{{ computeDurationText() }}</template>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
export default {
name: 'TimeStatus',
props: { task: { type: Object, default: () => {} } },
data() {
return {
time: '',
start: [{ text: '确认开始任务', color: 'blue' }],
pause: [{ text: '继续' }, { text: '重新开始任务', color: 'blue' }, { text: '结束' }],
proceed: [{ text: '暂停' }, { text: '重新开始任务', color: 'blue' }, { text: '结束' }],
again: [{ text: '重新开始任务', color: 'blue' }],
timer: null,
};
},
computed: {
...mapState('task', ['tip']),
status() {
return this.task ? this.task.process : 0;
},
taskName() {
return this.task ? this.task.name : '';
},
taskId() {
return this.task ? this.task.id : '';
},
//
// 0 1 2 3
orderStyle() {
let color = '#9CA3AF';
let icon = 'play-right-fill';
let persent = 100;
switch (this.status) {
case 1: //
color = '#60A5FA';
icon = '';
if (+this.computeCyclePersent() > 100) {
persent = 96;
} else {
persent = this.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 };
},
},
methods: {
...mapMutations('task', ['setTip']),
/**
* 点击了图标 修改任务状态
* @param {object} event
*/
changeStatus(process, event) {
if (process === 4) {
this.addTask();
return;
}
// return false;
const { status, taskId, taskName, tip } = this;
tip.status = status;
tip.taskId = taskId;
tip.left = event.target.x;
tip.top = event.target.y;
tip.show = true;
tip.text = this.genetateTips(status, taskName);
this.setTip(tip);
},
//
addTask() {
this.$t.ui.showToast('新建任务');
},
//
computeCyclePersent() {
if (!this.task || !this.task.realStart || !this.task.planDuration) return 100;
const { realStart, planDuration } = this.task;
return (((Date.now() - +realStart) * 100) / +planDuration).toFixed(2);
},
/**
* 计算tip的标题内容
*/
genetateTips(status, content) {
switch (status) {
case 0:
return `确认开始任务"${content}"吗?`;
case 1:
return `请选择要执行的操作`;
case 2:
return `请选择要执行的操作`;
case 3:
return `是否要重新开始此任务`;
}
},
//
// = realStart() + planDuration()
// = -
// = realStart + planDuration - Date.now()
computeDurationText() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
const { realStart, planDuration } = this.task;
const leftTime = +realStart + +planDuration - Date.now(); //
const { num, time } = this.$t.time.computeDurationText(leftTime);
this.$nextTick(() => {
if (!time) return;
this.timer = setTimeout(() => {
this.computeDurationText();
}, time);
});
return num;
},
},
};
</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;
}
</style>

7
src/components/TimeLine/component/Title.vue

@ -1,7 +0,0 @@
<!--
* @Author: aBin
* @email: binbin0314@126.com
* @Date: 2021-07-19 15:40:02
* @LastEditors: aBin
* @LastEditTime: 2021-07-19 15:40:03
-->

95
src/components/Tips/Tips.vue

@ -1,95 +0,0 @@
<template>
<view
class="fixed shadow-2xl"
style="z-index: 1000"
:style="{
left: tip.left + 'px',
top: height - tip.top > 110 ? tip.top + 'px' : '',
bottom: 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="headStyle"
:footStyle="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>
import { mapState, mapMutations } from 'vuex';
export default {
name: 'Tips',
props: { title: { default: '提示', type: String } },
computed: mapState('task', ['tip']),
data() {
return {
footStyle: { padding: '4px 15px' },
headStyle: { paddingTop: '8px', paddingBottom: '8px' },
height: 0,
};
},
mounted() {
const system = uni.getSystemInfoSync();
this.height = system.windowHeight;
},
methods: {
...mapMutations('task', ['setTipShow']),
//
onConfirm() {
this.onCancel();
},
/**
* 执行修改任务状态的动作
* @param {number} type 状态码 0开始 1暂停 2继续 3完成 默认0
*/
async onChangeStatus(type) {
try {
const param = { id: this.tip.taskId, type };
await uni.$u.api.updateTaskType(param);
if (type === 0) {
this.$t.ui.showToast('项目已重新开始');
} else if (type === 1) {
this.$t.ui.showToast('项目已暂停');
} else if (type === 2) {
this.$t.ui.showToast('项目继续');
} else if (type === 3) {
this.$t.ui.showToast('项目结束');
}
this.tip.show = false;
// TODO:
// location.reload();
// this.$router.go(0);
} catch (error) {
console.error(error);
this.$t.ui.showToast(error.msg || '操作失败');
}
},
//
onCancel() {
this.setTipShow(false);
},
},
};
</script>

65
src/components/Title/Title.vue

@ -1,65 +0,0 @@
<template>
<view>
<u-navbar :custom-back="onBack" class="overflow-hidden">
<view class="flex justify-start flex-1 px-3 font-bold min-0">
<view class="truncate">{{ project.name }}</view>
</view>
<view class="mr-2" slot="right">
<u-icon class="m-1" name="xuanzhong2" custom-prefix="custom-icon" size="20px" @click="lwbs"></u-icon>
<u-icon class="m-1" name="shuaxin1" custom-prefix="custom-icon" size="20px" @click="projectOverview"></u-icon>
<u-icon class="m-1" name="home" custom-prefix="custom-icon" size="20px" @click="openIndex"></u-icon>
<u-icon class="m-1" name="xuanxiang" custom-prefix="custom-icon" size="20px" @click="operation"></u-icon>
</view>
</u-navbar>
</view>
</template>
<script>
import { mapGetters, mapState } from 'vuex';
export default {
name: 'ProjectTitle',
data() {
return { showBack: false };
},
computed: {
...mapState('project', ['project']),
...mapGetters('user', ['userId']),
},
methods: {
//
onBack() {
// eslint-disable-next-line no-undef
const pages = getCurrentPages(); //
if (pages.length > 1) {
uni.navigateBack();
} else {
this.$u.route('/', { u: this.userId });
}
},
// LWBS
lwbs() {
// this.$t.ui.showToast('LWBS');
},
//
projectOverview() {
// this.$t.ui.showToast('');
},
//
openIndex() {
this.$u.route('/', { u: this.userId });
},
//
operation() {
// this.$t.ui.showToast('');
},
},
};
</script>
<style lang="scss" scoped>
/deep/ .u-slot-content {
min-width: 0;
}
</style>

3
src/config/db.js

@ -1,3 +0,0 @@
export const db = null; // indexedDB 对象
export const name = 'TALL_indexedDB'; // indexDB name
export const version = 1; // indexDB version

97
src/config/plugin.js

@ -1,97 +0,0 @@
// 定义插件相关信息
/* 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列表
};

17
src/config/time.js

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

10
src/main.js

@ -1,16 +1,10 @@
import './common/styles/index.css';
import App from './App';
import Tall from '@/utils/tall';
import Vue from 'vue';
import dayjs from 'dayjs';
import plugin from '@/apis/plugin.js';
import project from '@/apis/project.js';
import request from '@/utils/request.js';
import role from '@/apis/role.js';
import store from './store';
import tall from '@/apis/tall.js';
import task from '@/apis/task.js';
import uView from 'uview-ui';
import wbs from '@/apis/wbs.js';
@ -39,10 +33,6 @@ const app = new Vue({ ...App, store });
Vue.use(request, app);
Vue.use(tall, app);
Vue.use(project, app);
Vue.use(task, app);
Vue.use(plugin, app);
Vue.use(role, app);
Vue.use(wbs, app);
app.$mount();

44
src/mixins/timeline.js

@ -1,44 +0,0 @@
import { mapGetters } from 'vuex';
const mixin = {
computed: mapGetters('task', ['timeGranularity']),
methods: {
/**
* 设置时间轴空数据
* @param {*} startTime
* @param {*} show true 向上加载,false 向下加载
*/
setTime(startTime, show) {
let { timeGranularity } = this;
let arr = [];
let str = {};
if (show) {
for (let i = 10; i > 0; i--) {
str = {
id: this.$u.guid(20, false, 10),
panel: {},
plugins: [],
process: 4,
planStart: this.$t.time.add(startTime, `-${i}` - 0, timeGranularity).valueOf(),
};
arr.push(str);
}
} else {
for (let i = 0; i < 10; i++) {
str = {
id: this.$u.guid(20, false, 10),
panel: {},
plugins: [],
process: 4,
planStart: this.$t.time.add(startTime, i + 1, timeGranularity).valueOf(),
};
arr.push(str);
}
}
return arr;
},
},
};
export default mixin;

10
src/pages.json

@ -6,13 +6,6 @@
"navigationBarText": "TALL"
}
},
{
"path": "pages/project/project",
"style": {
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/phone-bind/phone-bind",
"style": {
@ -22,7 +15,8 @@
{
"path": "pages/project-webview/project-webview",
"style": {
"navigationBarTitleText": "project-webview"
"navigationStyle": "custom",
"navigationBarTitleText": "项目详情页"
}
}
],

25
src/pages/project-webview/project-webview.vue

@ -1,11 +1,34 @@
<template>
<web-view src="" />
<web-view :src="src" />
</template>
<script>
export default {
data() {
return { src: '' };
},
onLoad(options) {
console.log('options: ', options);
if (!options) {
this.$t.ui.showModal('缺少参数, 请返回重试');
} else {
const { p, u, pname, url } = options;
if (pname) {
uni.setNavigationBarTitle({ title: pname });
}
if (!u) {
this.$t.ui.showToast('缺少用户信息, 请登录');
return;
}
if (!p || !url) {
this.$t.ui.showToast('缺少项目信息, 请重新打开');
return;
}
this.src = `https://www.tall.wiki/tall/v3.0.1/#/pages/project/project?u=${u}&p=${p}&url=${url}&pname=${pname}`;
}
},
};
</script>

376
src/pages/project/project.vue

@ -1,376 +0,0 @@
<template>
<view :style="{ height: height }" class="flex flex-col overflow-hidden u-font-14">
<!-- 标题栏 -->
<Title />
<view class="container flex flex-col flex-1 overflow-hidden bg-gray-100" style="margin: auto">
<!-- 角色栏 -->
<Roles />
<!-- 日常任务面板 -->
<Globals />
<!-- 定期任务面板 -->
<TimeLine @getTasks="getTasks" class="flex-1 overflow-hidden" ref="child" />
</view>
</view>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
import mixin from '@/mixins/timeline';
export default {
mixins: [mixin],
data() {
return { height: '', show: false };
},
computed: {
...mapState('user', ['user', 'token']),
...mapState('role', ['visibleRoles', 'roleId']),
...mapState('task', ['timeNode', 'timeUnit', 'tasks', 'regularTask']),
...mapState('project', ['project']),
...mapGetters('task', ['timeGranularity']),
...mapGetters('project', ['projectId']),
},
onLoad(options) {
this.init(options);
},
watch: {
/**
* 当时间基准点发生变化时
* 重新根据时间和角色查询普通日常任务
* 永久日常任务不发生改变
*/
timeNode(val) {
if (val && this.roleId) {
//
this.initTasks();
}
},
/**
* 当角色发生变化时
* 重新查询永久日常任务和普通日常任务
* 注意: 切换角色后 重新设置了时间基准点 时间基准点一定会变
* 所以监听时间基准点获取 可变日常任务即可 这里不用获取 避免重复获取
*/
roleId(val) {
if (val) {
this.setTimeNode(Date.now());
//
const params = { roleId: val, projectId: this.projectId };
this.getPermanent(params);
}
},
},
mounted() {
const system = uni.getSystemInfoSync();
this.height = system.windowHeight + 'px';
},
onUnload() {
this.clearEndFlag();
this.clearTasks();
this.setRoleId();
},
methods: {
...mapActions('user', ['getToken']),
...mapActions('task', ['getRegulars', 'getPermanent', 'getGlobal']),
...mapMutations('user', ['setToken']),
...mapMutations('project', ['setProject', 'setProjectName']),
...mapMutations('role', ['setInvisibleRoles', 'setVisibleRoles', 'setRoleId']),
...mapMutations('task', [
'setPermanents',
'setUpTasks',
'setDownTasks',
'setDailyTasks',
'setTimeNode',
'clearTasks',
'clearEndFlag',
'setShowSkeleton',
'setTopEnd',
'setBottomEnd',
]),
/**
* 初始化
* @param {object | null} options
*/
init(options) {
if (!this.token) {
// tokenuserIdtoken
// token userId
if (!options || !options.u) {
// u (userId)
this.$t.ui.showToast('缺少用户信息参数');
} else {
this.getToken(options.u);
}
}
//
options && options.pname && this.setProjectName(options.pname);
if (!options || !options.p) {
// id
this.$t.ui.showToast('缺少项目信息参数');
} else {
// id
this.getProjectById({ projectId: options.p });
}
},
/**
* 通过项目id获取项目信息
* @param {string} projectId
* @param {object} params 提交的参数
*/
async getProjectById(params) {
try {
const data = await uni.$u.api.findProjectById(params);
this.setProject(data);
// id
this.getRoles(params);
} catch (error) {
console.log('error: ', error || '获取项目信息失败');
}
},
/**
* 通过项目id获取角色信息
* @param {string} projectId
* @param {object} params 提交的参数
*/
getRoles(params) {
this.$t.$q.findShowRole(params, (err, data) => {
if (err) {
console.error('err: ', err || '获取角色信息失败');
} else {
this.setInvisibleRoles(data ? data.invisibleList : []);
this.setVisibleRoles(data ? data.visibleList : []);
this.setInitialRoleId(data ? data.visibleList : []);
}
});
},
//
async initTasks() {
//
this.setPermanents([]);
this.setDailyTasks([]);
//
this.clearTasks();
//
//
this.clearEndFlag();
//
this.getGlobalData();
//
this.setPrevTasks();
// storage
const storageTaskId = this.$t.storage.getStorageSync('taskId');
this.$nextTick(() => {
if (!storageTaskId) {
this.$refs.child.setScrollPosition();
}
});
//
this.setNextTasks();
await this.getInitTasks();
// id
// storage
this.$nextTick(() => {
if (storageTaskId) {
this.$refs.child.setScrollPosition();
}
});
},
// ||
getInitTasks() {
//
function fn(that) {
const detailId = that.tasks.findIndex(task => task.detailId);
console.log('detailId: ', detailId);
if (detailId !== -1) {
that.$nextTick(() => {
//
const { tasks, timeGranularity } = that;
that.getTasks({ timeNode: +tasks[0].planStart, queryType: 0, queryNum: 6 });
//
const nextQueryTime = +that.$t.time.add(+tasks[tasks.length - 1].planStart, 1, timeGranularity);
that.getTasks({ timeNode: nextQueryTime, queryType: 1, queryNum: 6 });
});
} else {
//
//
// that.setPrevTasks();
// //
// that.setNextTasks();
}
}
//
this.getTasks({ queryType: 0 });
// id
this.getTasks({ queryType: 1 }, fn);
},
//
setInitialRoleId(visibleList) {
if (!visibleList || !visibleList.length) return;
const index = visibleList.findIndex(item => +item.mine === 1);
const currentRole = index > 0 ? visibleList[index] : visibleList[0];
const storageRoleId = this.$t.storage.getStorageSync('roleId');
const currentRoleId = storageRoleId ? storageRoleId : currentRole ? currentRole.id : '';
this.setRoleId(currentRoleId);
// storage
this.$t.storage.setStorageSync('roleId', '');
},
/**
* 根据时间基准点和角色查找定期任务
* @param {object} query
* @param {string} query.roleId 角色id
* @param {string} query.timeNode 时间基准点 默认当前
* @param {string} query.timeUnit 时间颗粒度 默认天
* @param {string} query.queryNum 查找颗粒度数量 默认3个
* @param {string} query.queryType 0向上查找 1向下查找(默认) 下查包含自己上查不包含
*/
getTasks(query, fn) {
const that = this;
that.setShowSkeleton(true);
const { roleId, timeNode, timeUnit, projectId } = that;
const params = {
roleId,
timeNode: query.timeNode || timeNode,
timeUnit: query.timeUnit || timeUnit,
queryNum: query.queryNum || 3,
queryType: query.queryType,
projectId,
};
that.$t.$q.getRegularTask(params, function (err, data) {
console.log('data: ', data);
console.log('that.show: ', that.show);
if (!that.show) {
if (err) {
that.setShowSkeleton(false);
console.error('err: ', err);
} else {
that.show = true;
that.setShowSkeleton(false);
// 0 -> 1 ->
if (data && data.length) {
that.replacePrevData(data, params.queryType);
that.show = false;
} else {
params.queryType === 0 ? that.setPrevTasks() : that.setNextTasks();
that.show = false;
}
if (that.tasks.length && fn) {
fn(that);
}
}
}
});
},
//
getGlobalData() {
const { roleId, timeNode, timeUnit, projectId } = this;
const param = { roleId, timeNode, timeUnit, projectId };
this.getGlobal(param);
},
//
setPrevTasks() {
this.setTopEnd(true);
let sTime = '';
if (!this.tasks || !this.tasks.length) {
sTime = +new Date().getTime();
} else {
sTime = +this.tasks[0].planStart - 0;
}
const initData = this.setTime(sTime, true);
this.setUpTasks(initData);
},
//
setNextTasks() {
this.setBottomEnd(true);
let sTime = '';
if (!this.tasks || !this.tasks.length) {
sTime = +new Date().getTime();
} else {
sTime = +this.tasks[this.tasks.length - 1].planStart - 0;
}
const initData = this.setTime(sTime, false);
this.setDownTasks(initData);
},
//
replacePrevData(data, type) {
let newTasks = [...this.tasks];
let newDate = this.$u.deepClone(data);
console.log('newDate: ', newDate);
if (newDate && newDate.length) {
for (let i = 0; i < newTasks.length; i++) {
// const task = newTasks[i];
let arr = [];
for (let j = 0; j < newDate.length; j++) {
const item = newDate[j];
if (newTasks[i].id === item.id) {
console.log('j', j, i);
newTasks[i] = item;
break;
}
//
if (+newTasks[0].planStart > +newDate[0].planStart) {
this.setPrevTasks();
newTasks = [...this.tasks];
i--;
break;
} else if (+newDate[newDate.length - 1].planStart > +newTasks[newTasks.length - 1].planStart) {
this.setNextTasks();
newTasks = [...this.tasks];
i--;
break;
} else {
//
const taskItem = this.$t.time.isSame(+newTasks[i].planStart, +item.planStart, this.timeGranularity);
if (taskItem) {
arr.push(item);
if (newTasks[i].detailId) {
newTasks.splice(i, 0, item);
} else {
if (arr.length === 1) {
newTasks.splice(i, 1, item);
}
if (arr.length > 1) {
newTasks.splice(i, 0, item);
}
}
i++;
}
}
}
}
}
this.clearTasks();
type === 0 ? this.setUpTasks(newTasks) : this.setDownTasks(newTasks);
console.log('newTasks: ', newTasks.length);
console.log('end');
},
},
};
</script>

55
src/pages/test/test.vue

@ -1,55 +0,0 @@
<template>
<view class="container p-3">
<u-button type="primary" class="mb-3" @click="add">add</u-button>
<u-button type="primary" class="mb-3" @click="findOne">findOne</u-button>
<u-button type="primary" class="mb-3" @click="find">find</u-button>
<u-button type="primary" class="mb-3" @click="update">更新update</u-button>
<u-button type="primary" class="mb-3" @click="remove">删除remove</u-button>
<u-button type="primary" class="mb-3" @click="createIndex">创建索引和查询</u-button>
</view>
</template>
<script>
export default {
created() {
// this.$t.$q.getProjects(this.$moment().startOf('day').valueOf(), this.$moment().endOf('day').valueOf(), (err, data) => {
// if (err) {
// console.error(err);
// } else {
// console.log('data: ' + data);
// }
// });
},
methods: {
add() {
this.$db.create('projects', { id: '124', sex: ' man' });
},
async findOne() {
const data = await this.$db.findOne('projects', '124');
console.log(data);
},
async find() {
const data = await this.$db.find('projects');
console.log(data);
},
async update() {
const data = await this.$db.update('projects', { id: '125', sex: 'woman' });
console.log('update data: ', data);
},
async remove() {
const data = await this.$db.remove('projects', '123');
console.log('update data: ', data);
},
async createIndex() {
// const data = await this.$db.createIndex('projects', 'sex', 'woman');
console.log('update data: ');
},
},
};
</script>

7
src/plugins/p-deliver-check/p-deliver-check.vue

@ -1,7 +0,0 @@
<template>
<view>交付物检查</view>
</template>
<script>
export default {};
</script>

20
src/plugins/p-deliverable/p-deliverable.vue

@ -1,20 +0,0 @@
<template>
<!-- 交付物 -->
<view>交付物</view>
</template>
<script>
export default {
name: 'p-deliverable',
props: { item: { type: Object, default: null } },
data() {
return {};
},
computed: {},
methods: {},
watch: {},
};
</script>
<style></style>

7
src/plugins/p-manage-member/p-manage-member.vue

@ -1,7 +0,0 @@
<template>
<view>成员管理</view>
</template>
<script>
export default {};
</script>

7
src/plugins/p-manage-project/p-manage-project.vue

@ -1,7 +0,0 @@
<template>
<view>项目管理</view>
</template>
<script>
export default {};
</script>

7
src/plugins/p-manage-role/p-manage-role.vue

@ -1,7 +0,0 @@
<template>
<view>角色管理</view>
</template>
<script>
export default {};
</script>

7
src/plugins/p-manage-task/p-manage-task.vue

@ -1,7 +0,0 @@
<template>
<view>任务管理</view>
</template>
<script>
export default {};
</script>

60
src/plugins/p-subproject/p-subproject.vue

@ -1,60 +0,0 @@
<template>
<!-- 子项目插件 -->
<view>
<view v-for="item in sonProject" :key="item.detailId">
<span class="text-xs text-blue-500" @click="openProject(item)">{{ item.name }}</span>
</view>
</view>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: 'p-subproject',
props: {
task: {
type: Object,
default: () => {},
},
},
data() {
return { sonProject: [] };
},
computed: mapGetters('project', ['projectId']),
mounted() {
this.getSonProject();
},
methods: {
async getSonProject() {
try {
const data = await this.$u.api.findSonProject({ projectId: this.task.detailId });
this.sonProject = data;
} catch (error) {
console.error('p-subproject.vue getSonProject error: ', error);
}
},
/**
* 打开项目
* @param {object} project 所点击的项目的信息
*/
openProject(project) {
const { name, id, url } = project;
url && (uni.$t.domain = url);
this.$u.route('pages/project/project', {
u: this.userId,
p: id,
pname: name,
url: encodeURIComponent(url),
});
},
},
watch: {},
};
</script>
<style></style>

39
src/plugins/p-subtasks/p-subtasks.vue

@ -1,39 +0,0 @@
<template>
<view>
<view v-for="item in sonTask" :key="item.detailId">
<span class="text-xs text-gray-500">{{ item.name }}</span>
</view>
</view>
</template>
<script>
export default {
name: 'p-subtasks',
props: {
task: {
type: Object,
default: () => {},
},
},
data() {
return { sonTask: [] };
},
created() {
this.getSonTask();
},
methods: {
async getSonTask() {
try {
const data = await this.$u.api.findSonTask({ detailId: this.task.detailId });
this.sonTask = data;
} catch (error) {
console.error('p-subtasks.vue getSonTask error: ', error);
}
},
},
};
</script>
<style></style>

20
src/plugins/p-task-countdown/p-task-countdown.vue

@ -1,20 +0,0 @@
<template>
<!-- 任务倒计时插件 -->
<view>任务倒计时插件</view>
</template>
<script>
export default {
name: 'p-task-countdown',
props: { item: { type: Object, default: null } },
data() {
return {};
},
computed: {},
methods: {},
watch: {},
};
</script>
<style></style>

16
src/plugins/p-task-description/p-task-description.vue

@ -1,16 +0,0 @@
<template>
<!-- 任务描述 -->
<view>{{ task.description }}</view>
</template>
<script>
export default {
name: 'p-task-description',
props: {
task: {
type: Object,
default: () => {},
},
},
};
</script>

34
src/plugins/p-task-duration-delay/p-task-duration-delay.vue

@ -1,34 +0,0 @@
<template>
<view v-if="realDuration && planDuration">
<!-- 任务时长延迟插件 -->
<!-- 超时 -->
<span class="font-bold text-green-500" v-if="realDuration - 0 > planDuration - 0">
+{{ $t.time.formatDuration(realDuration - planDuration) }}
</span>
<!-- 延时 -->
<span class="font-bold text-red-500" v-if="realDuration - 0 < planDuration - 0">
-{{ $t.time.formatDuration(planDuration - realDuration) }}
</span>
</view>
</template>
<script>
export default {
name: 'p-task-duration-delay',
props: {
task: {
type: Object,
default: () => {},
},
},
computed: {
realDuration() {
return this.task.realDuration;
},
planDuration() {
return this.task.planDuration;
},
},
};
</script>

22
src/plugins/p-task-start-time-delay/p-task-start-time-delay.vue

@ -1,22 +0,0 @@
<template>
<view v-if="realStart && planStart">
<!-- 任务开始时间延迟插件 -->
<!-- 超时 -->
<span>{{ $t.time.formatDuration(+realStart - +planStart) }}</span>
</view>
</template>
<script>
export default {
name: 'p-task-start-time-delay',
props: { task: { type: Object, default: () => {} } },
computed: {
realStart() {
return this.task.realStart;
},
planStart() {
return this.task.planStart;
},
},
};
</script>

16
src/plugins/p-task-title/p-task-title.vue

@ -1,16 +0,0 @@
<template>
<!-- 任务名插件 -->
<view>{{ task.name }}</view>
</template>
<script>
export default {
name: 'p-task-title',
props: {
task: {
type: Object,
default: () => {},
},
},
};
</script>

85
src/plugins/p-wbs-import/p-wbs-import.vue

@ -1,85 +0,0 @@
<template>
<view>
<view @click="handleUpload" v-if="task.name === '导入WBS新建项目'">{{ task.name }}</view>
<view @click="handleUpdate" v-if="task.name === '导入WBS更新项目'">{{ task.name }}</view>
<!-- 全局提示框 -->
<u-top-tips ref="uTips"></u-top-tips>
</view>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex';
export default {
name: 'p-wbs-import',
props: {
task: {
type: Object,
default: () => {},
},
},
data() {
return {};
},
computed: {
...mapGetters('user', ['userId']),
...mapGetters('project', ['projectId']),
},
methods: {
...mapMutations('project', ['setShowAlert']),
// wbs
async handleUpload() {
try {
const data = await this.$u.api.import();
// WBS
//
this.onUploadSuccess();
setTimeout(() => {
this.$u.route('/pages/project/project', {
u: this.userId,
p: data.id,
pname: data.pname,
url: data.url,
});
}, 2000);
} catch (error) {
this.onUploadError(error);
}
},
//
// TODO:
async handleUpdate() {
try {
await this.$u.api.import({ projectId: this.projectId });
// WBS
//
this.onUploadSuccess();
} catch (error) {
this.onUploadError(error);
}
},
//
onUploadSuccess() {
this.$refs.uTips.show({
title: '导入成功,即将打开新项目',
type: 'success',
duration: '3000',
});
},
//
onUploadError(error) {
this.$refs.uTips.show({
title: error || '导入失败',
type: 'error',
duration: '6000',
});
},
},
};
</script>

BIN
src/static/local_play1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 B

BIN
src/static/logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

3
src/store/db/actions.js

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

3
src/store/db/getters.js

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

12
src/store/db/index.js

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

3
src/store/db/mutations.js

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

7
src/store/db/state.js

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

9
src/store/index.js

@ -1,12 +1,9 @@
import Vue from 'vue';
import Vuex from 'vuex';
import db from './db/index';
import user from './user/index';
import messages from './messages/index';
import socket from './socket/index';
import project from './project/index';
import role from './role/index';
import task from './task/index';
import socket from './socket/index';
import user from './user/index';
Vue.use(Vuex);
export default new Vuex.Store({ modules: { db, user, messages, socket, project, role, task } });
export default new Vuex.Store({ modules: { user, messages, socket, project } });

3
src/store/role/actions.js

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

3
src/store/role/getters.js

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

12
src/store/role/index.js

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

30
src/store/role/mutations.js

@ -1,30 +0,0 @@
const mutations = {
/**
* 设置不展示的角色信息
* @param {Object} state
* @param {Array} data 服务端返回的模板数组
*/
setInvisibleRoles(state, data) {
state.invisibleRoles = data || [];
},
/**
* 设置展示的角色信息
* @param {Object} state
* @param {Array} data 服务端返回的模板数组
*/
setVisibleRoles(state, data) {
state.visibleRoles = data || [];
},
/**
* 设置当前角色信息
* @param {Object} state
* @param {string} roleId 当前正在展示的角色的id
*/
setRoleId(state, roleId) {
state.roleId = roleId;
},
};
export default mutations;

7
src/store/role/state.js

@ -1,7 +0,0 @@
const state = {
invisibleRoles: [], // 不展示的角色信息
visibleRoles: [], // 展示的角色信息
roleId: '', // 当前展示查看的角色id
};
export default state;

33
src/store/task/actions.js

@ -1,33 +0,0 @@
const actions = {
/**
* 根据角色查找永久的日常任务
* @param {*} commit
* @param {string} roleId 角色id
*/
getPermanent({ commit }, param) {
uni.$t.$q.getPermanent(param, (err, data) => {
if (err) {
console.error('err: ', err);
} else {
commit('setPermanents', data);
}
});
},
/**
* 根据时间和角色查找日常任务
* @param {*} commit
* @param {object} param 请求参数 roleId, timeNode, timeUnit
*/
getGlobal({ commit }, param) {
uni.$t.$q.getGlobal(param, (err, data) => {
if (err) {
console.error('err: ', err);
} else {
commit('setDailyTasks', data);
}
});
},
};
export default actions;

23
src/store/task/getters.js

@ -1,23 +0,0 @@
const getters = {
// 所有的日常任务 永久 + 可变 日常任务
globals({ dailyTasks, permanents }) {
return [...permanents, ...dailyTasks];
},
unitConfig({ timeUnit }) {
const target = uni.$t.timeConfig.timeUnits.find(item => item.id === timeUnit);
return target;
},
// 计算任务开始时间的格式
startTimeFormat(state, { unitConfig }) {
return unitConfig.format || 'D日 HH:mm';
},
// 计算颗粒度 对应的 dayjs add 的单位
timeGranularity(state, { unitConfig }) {
return unitConfig.granularity;
},
};
export default getters;

12
src/store/task/index.js

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

168
src/store/task/mutations.js

@ -1,168 +0,0 @@
const mutations = {
/**
* 记录时间轴向上滚动的距离
* @param { object } state
* @param { number } num
*/
setScrollTop(state, num) {
state.scrollTop = num;
},
/**
* 记录时间轴向上滚动的距离
* @param { object } state
* @param { string } data
*/
setViewId(state, data) {
state.viewId = data;
},
/**
* 设置日常任务当前是否应该处于收缩状态
* @param { object } state
* @param { boolean } data
*/
setShrink(state, data) {
state.isShrink = data;
},
/**
* 设置tip的值
* @param {object} state
* @param {object} data
*/
setTip(state, data) {
if (!data) return;
state.tip = { ...data };
},
/**
* 是否显示tips
* @param { object } state
* @param { boolean } show
*/
setTipShow(state, show) {
state.tip.show = show;
},
/**
* 是否显示tips
* @param { object } state
* @param { number } status
*/
setStatus(state, status) {
state.tip.status = status;
},
/**
* 设置时间基准点
* @param { object } state
* @param { number } data
*/
setTimeNode(state, data) {
state.timeNode = data;
},
/**
* 设置时间颗粒度
* @param { object } state
* @param { number } data
*/
setTimeUnit(state, data) {
state.timeUnit = data;
},
/**
* 设置向上查到的定期任务数据
* @param {Object} state
* @param {Array} data 服务端返回的模板数组
*/
setUpTasks(state, data) {
if (!state.tasks.length) {
state.tasks = [...data];
} else {
state.tasks = [...data.concat(state.tasks)];
}
},
/**
* 设置向下查到的定期任务数据
* @param {Object} state
* @param {Array} data 服务端返回的模板数组
*/
setDownTasks(state, data) {
if (!state.tasks && !state.tasks.length) {
state.tasks = [...data];
} else {
state.tasks = [...state.tasks.concat(data)];
}
},
/**
* 设置日常任务数据
* @param {Object} state
* @param {Array} data 服务端返回的模板数组
*/
setDailyTasks(state, data) {
state.dailyTasks = data || [];
},
/**
* 设置永久固定任务
* @param {object} state
* @param {array} tasks 服务端查询到的永久日常任务书籍
*/
setPermanents(state, tasks) {
state.permanents = tasks || [];
},
/**
* 设置时间轴是否继续向上查任务
* @param {Object} state
* @param {Boolean} show
*/
setTopEnd(state, show) {
state.topEnd = show;
},
/**
* 设置时间轴是否继续向下查任务
* @param {Object} state
* @param {Boolean} show
*/
setBottomEnd(state, show) {
state.bottomEnd = show;
},
// 清空标志位 如切换角色等使用
clearEndFlag(state) {
state.topEnd = false;
state.bottomEnd = false;
},
// 清空定期任务
clearTasks(state) {
state.tasks = [];
},
/**
* 收到消息设置任务状态
* @param {Object} state
* @param {Array} data 服务端返回的模板数组
*/
setTaskStatus(state, data) {
const item = state.tasks.find(i => i.id === data.id);
item.process = data.taskStatus;
},
/**
* 设置骨架屏是否显示
* @param {Object} state
* @param {Boolean} show
*/
setShowSkeleton(state, show) {
state.showSkeleton = show;
},
};
export default mutations;

23
src/store/task/state.js

@ -1,23 +0,0 @@
const state = {
scrollTop: 0,
viewId: '', // 时间轴自动滚动的位置
isShrink: false, // true: 收起, false:展开
tip: {
taskId: '', // 当前正在修改状态的任务的id
show: false,
status: 0, // 所点击任务的当前状态码
text: '',
left: 0, // 鼠标点击位置距离左边的距离
top: 0, // 鼠标点击位置距离上边的距离
},
timeNode: new Date().getTime(), // 时间基准点
timeUnit: 4, // 时间颗粒度
topEnd: false, // 时间轴向上查任务到顶了
bottomEnd: false, // 时间轴向下查任务到底了
permanents: [], // 永久日常任务
dailyTasks: [], // 日常任务
tasks: [], // 所有的定期任务
showSkeleton: false, // 定期任务骨架屏
};
export default state;

46
src/test/util/time.test.js

@ -1,46 +0,0 @@
import Time from '../../utils/time.js';
// 测试计算进行中剩余时长显示数值
describe('utils/time.js computeDurationText function', () => {
const { computeDurationText } = Time;
// const leftTime = +realStart + +planDuration - Date.now(); // 剩余时间
it ('leftTime is 60ms, num=60, time=16', () => {
expect(computeDurationText(60)).toEqual({ num: 60, time: 16 })
})
it ('leftTime is 300ms, num=300, time=16', () => {
expect(computeDurationText(300)).toEqual({ num: 300, time: 16 })
})
it ('leftTime is 10s20ms, num=10, time=1000', () => {
expect(computeDurationText(10*1000 + 20)).toEqual({ num: 10, time: 1000 })
})
it ('leftTime is 8分钟10s20ms, num=8, time=1000', () => {
expect(computeDurationText(8*60*1000 + 10*1000 + 20)).toEqual({ num: 8, time: 1000 })
})
it ('leftTime is 3小时8分钟10s20ms, num=3, time=1000', () => {
expect(computeDurationText(3*60*60*1000 + 8*60*1000 + 10*1000 + 20)).toEqual({ num: 3, time: 1000 })
})
it ('leftTime is 11天3小时8分钟10s20ms, num=11, time=60 * 60 * 1000', () => {
expect(computeDurationText(11*24*60*60*1000 + 3*60*60*1000 + 8*60*1000 + 10*1000 + 20)).toEqual({ num: 11, time: 60 * 60 * 1000 })
})
it ('leftTime is 2个月11天3小时8分钟10s20ms, num=2, time=60 * 60 * 1000', () => {
expect(computeDurationText(2*30*24*60*60*1000 + 11*24*60*60*1000 + 3*60*60*1000 + 8*60*1000 + 10*1000 + 20)).toEqual({ num: 2, time: 60 * 60 * 1000 })
})
it ('leftTime is 7年2个月11天3小时8分钟10s20ms, num=7, time=60 * 60 * 1000', () => {
expect(computeDurationText(7*12*30*24*60*60*1000 + 2*30*24*60*60*1000 + 11*24*60*60*1000 + 3*60*60*1000 + 8*60*1000 + 10*1000 + 20)).toEqual({ num: 7, time: 60 * 60 * 1000 })
})
it ('leftTime <=0, num=0, time=null', () => {
expect(computeDurationText(-10)).toEqual({ num: 0, time: null })
})
it ('leftTime 不是数字, num=0, time=null', () => {
expect(computeDurationText('abc')).toEqual({ num: 0, time: null })
})
})

6
src/uni.scss

@ -12,10 +12,8 @@
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量同时无需 import 这个文件
*/
@import 'uview-ui/theme.scss';
/* 颜色变量 */
/* 行为相关颜色 */
@import './common/styles/tailwind.scss';
/* 颜色变量 */ /* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;

163
src/utils/indexedDB.js

@ -1,163 +0,0 @@
import { name } from '@/config/db';
import { curry } from 'lodash';
// 创建表
const createCollection = (Vue, db) => {
// projects项目表
!db.objectStoreNames.contains('projects') && db.createObjectStore('projects', { keyPath: 'id' });
// roles 角色表
!db.objectStoreNames.contains('roles') && db.createObjectStore('roles', { keyPath: 'id' });
// plan_tasks 定期任务
!db.objectStoreNames.contains('plan_tasks') && db.createObjectStore('plan_tasks', { keyPath: 'id' });
// fixed_tasks 固定全局任务
Vue.prototype.$db.fixed_tasks = !db.objectStoreNames.contains('fixed_tasks') && db.createObjectStore('fixed_tasks', { keyPath: 'id' });
// variable_tasks 可变全局任务
Vue.prototype.$db.variable_tasks =
!db.objectStoreNames.contains('variable_tasks') && db.createObjectStore('variable_tasks', { keyPath: 'id' });
// plugins 插件表
Vue.prototype.$db.plugins = !db.objectStoreNames.contains('plugins') && db.createObjectStore('plugins', { keyPath: 'id' });
};
/**
* 新增数据
*
* @param {object} db 数据库database
* @param {string} collection 集合/
* @param {object} data 数据
*/
const create = (db, collection, data) => {
return new Promise((resolve, reject) => {
const request = db.transaction([collection], 'readwrite').objectStore(collection).add(data);
request.onsuccess = () => resolve();
request.onerror = event => {
const { name, message } = event.target.error;
if (name === 'ConstraintError') {
reject('数据已存在');
} else {
reject(message);
}
};
});
};
/**
* 找到1条数据
*
* @param {object} db 数据库database
* @param {string} collection 集合/
* @param {string} key 索引关键字 一般是id
*/
const findOne = (db, collection, key) => {
return new Promise((resolve, reject) => {
const request = db.transaction([collection]).objectStore(collection).get(key);
request.onerror = event => reject(event.target.error.message);
request.onsuccess = event => resolve(event.target.result);
});
};
/**
* 找到所有数据
*
* @param {object} db 数据库database
* @param {string} collection 集合/
*/
const find = (db, collection) => {
return new Promise((resolve, reject) => {
const request = db.transaction(collection).objectStore(collection).openCursor();
let result = [];
request.onerror = event => reject(event.target.error.message);
request.onsuccess = event => {
const cursor = event.target.result;
if (cursor) {
result.push(cursor.value);
cursor.continue();
} else {
resolve(result);
}
};
});
};
/**
* 更新数据
*
* @param {object} db 数据库database
* @param {string} collection 集合/
* @param {object} newData 新数据
*/
const update = (db, collection, newData) => {
return new Promise((resolve, reject) => {
const request = db.transaction([collection], 'readwrite').objectStore(collection).put(newData);
request.onerror = event => reject(event.target.error.message);
request.onsuccess = () => resolve(newData);
});
};
/**
* 移除数据 通过关键字
*
* @param {object} db 数据库database
* @param {string} collection 集合/
* @param {string} key 关键字
*/
const remove = (db, collection, key) => {
return new Promise((resolve, reject) => {
const request = db.transaction([collection], 'readwrite').objectStore(collection).delete(key);
request.onerror = event => reject(event.target.error.message);
request.onsuccess = () => resolve();
});
};
/**
* 创建索引
*
* @param {object} db 数据库database
* @param {string} collection 集合/
* @param {string} field 创建索引的字段名称
* @param {string} key 关键字
*/
const createIndexAndFind = (db, collection, field, key) => {
return new Promise((resolve, reject) => {
const store = db.transaction([collection], 'readonly').objectStore(collection);
store.createIndex(field, field);
const index = store.index(field);
const request = index.get(key);
request.onerror = event => reject(event.target.error.message);
request.onsuccess = event => resolve(event.target.result);
});
};
const curriedCreate = curry(create);
export const curriedFindOne = curry(findOne);
export const curriedFind = curry(find);
export const curriedRemove = curry(remove);
export const curriedUpdate = curry(update);
export const curriedIndex = curry(createIndexAndFind);
const install = Vue => {
uni.$db = Vue.prototype.$db = {};
Vue.prototype.$db.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
const request = Vue.prototype.$db.indexedDB.open(name, Date.now()); // IDBRequest 对象
request.onerror = error => console.error('打开数据库失败', error);
request.onsuccess = event => {
console.log('INDEXED_DB OPEN SUCCESS');
Vue.prototype.$db.db = event.target.result;
};
request.onupgradeneeded = event => {
console.log('INDEXED_DB OPEN onupgradeneeded');
Vue.prototype.$db.db = event.target.result;
// 创建表
createCollection(Vue, Vue.prototype.$db.db);
Vue.prototype.$db.create = curriedCreate(Vue.prototype.$db.db); // create 新增数据,颗粒化以后就不用再传db数据了
Vue.prototype.$db.findOne = curriedFindOne(Vue.prototype.$db.db); // 查一条
Vue.prototype.$db.find = curriedFind(Vue.prototype.$db.db); // 查集合里的所有数据
Vue.prototype.$db.update = curriedUpdate(Vue.prototype.$db.db); // 更新某条数据
Vue.prototype.$db.remove = curriedRemove(Vue.prototype.$db.db); // 删除某条数据
// Vue.prototype.$db.createIndex = curriedIndex(Vue.prototype.$db.db); // 创建索引
};
};
export default { install };

4
src/utils/tall.js

@ -1,10 +1,8 @@
import app from '@/config/app.js';
import cache from '@/utils/cache.js';
import cacheAndRequest from '@/utils/cacheAndRequest.js';
import plugin from '@/config/plugin.js';
import storage from '@/utils/storage.js';
import time from '@/utils/time.js';
import timeConfig from '@/config/time';
import ui from '@/utils/ui.js';
import upload from '@/utils/upload.js';
import user from '@/config/user.js';
@ -15,10 +13,8 @@ const gateway = process.env.VUE_APP_API_URL;
const $t = {
zIndex, // 定位元素层级
app, // app级别的相关配置
plugin, // 插件相关配置信息
storage, // 本地存储storage封装
time, // 时间处理
timeConfig, // 时间相关配置
ui, // ui界面提示相关
chooseAndUpload: upload.chooseAndUpload, // 选择并上传单个文件相关的封装
domain: `${gateway}/defaultwbs`,

Loading…
Cancel
Save