Browse Source

feat: 时间轴页面

refact
song 4 years ago
parent
commit
e926b751fb
  1. 158
      App.vue
  2. 13
      CHANGELOG.md
  3. 17
      apis/project.js
  4. 12
      apis/role.js
  5. 20
      apis/task.js
  6. 4
      common/styles/tailwind.scss
  7. 139
      components/Projects/ProjectItem.vue
  8. 236
      components/Roles/Roles.vue
  9. 216
      components/Title/Title.vue
  10. 466
      components/Title/components/CreateTask copy.vue
  11. 583
      components/Title/components/CreateTask.vue
  12. 210
      components/Title/components/ShareProject.vue
  13. 2
      components/Upload/Upload.vue
  14. 17
      config/time.js
  15. 8
      hooks/useGetOptions.js
  16. 12
      main.js
  17. 4
      package.json
  18. 16
      pages.json
  19. 175
      pages/index/index.vue
  20. 480
      pages/project/project.vue
  21. 4
      store/index.js
  22. 17
      store/role/actions.js
  23. 13
      store/role/getters.js
  24. 12
      store/role/index.js
  25. 39
      store/role/mutations.js
  26. 8
      store/role/state.js
  27. 33
      store/task/actions.js
  28. 23
      store/task/getters.js
  29. 12
      store/task/index.js
  30. 238
      store/task/mutations.js
  31. 25
      store/task/state.js
  32. 2
      uni.scss
  33. 235
      utils/cache.js
  34. 58
      utils/task.js

158
App.vue

@ -1,96 +1,90 @@
<script>
import { ref, computed } from 'vue';
import store from '@/store/index.js';
import { ref, computed } from 'vue';
import store from '@/store/index.js';
export default {
setup() {
export default {
setup() {
return {};
},
async onLaunch(options) {
console.log('options: ', options);
this.checkNetwork(); //
this.getSystemInfo(); //
return {
// - H5APP
/* #ifndef MP-WEIXIN */
if (!store.state.user.token) {
// tokenuserIdtoken
// token userId
if (!options.query || !options.query.u) {
// u (userId)
this.$ui.showToast('缺少用户信息参数');
} else {
const data = await store.dispatch('user/getTokenByUserId', options.query.u);
this.noPhone(data.phone);
}
}
/* #endif */
}
},
store.dispatch('socket/initSocket');
},
async onLaunch(options) {
console.log('options: ', options);
this.checkNetwork(); //
this.getSystemInfo(); //
methods: {
// store
// 2g 3g ;
checkNetwork() {
uni.getNetworkType({
success: ({ networkType }) => {
store.commit('setNetworkConnected', !(networkType === 'none' || networkType === '2g' || networkType === '3g'));
},
});
//
uni.onNetworkStatusChange(({ isConnected, networkType }) => {
store.commit('setNetworkConnected', isConnected && !(networkType === '2g' || networkType === '3g'));
});
},
// - H5APP
/* #ifndef MP-WEIXIN */
if (!store.state.user.token) {
// tokenuserIdtoken
// token userId
if (!options.query || !options.query.u) {
// u (userId)
this.$ui.showToast('缺少用户信息参数');
} else {
const data = await store.dispatch('user/getTokenByUserId', options.query.u);
this.noPhone(data.phone);
}
}
/* #endif */
//
getSystemInfo() {
uni.getSystemInfo({
success: result => {
store.commit('setSystemInfo', result);
},
fail: error => {
console.error('getSystemInfo fail:', error);
},
});
},
store.dispatch('socket/initSocket');
},
//
async signin() {
try {
const data = await uni.$u.api.signin();
if (data && data.token) {
store.commit('user/setUser', data);
store.commit('user/setToken', data.token);
noPhone(data.phone);
} else {
uni.$ui.showToast('返回数据异常');
}
} catch (error) {
console.error('error: ', error);
uni.$ui.showToast(error || '登录失败');
}
},
methods: {
// store
// 2g 3g ;
checkNetwork() {
uni.getNetworkType({
success: ({ networkType }) => {
store.commit('setNetworkConnected', !(networkType === 'none' || networkType === '2g' || networkType === '3g'));
},
});
//
uni.onNetworkStatusChange(({ isConnected, networkType }) => {
store.commit('setNetworkConnected', isConnected && !(networkType === '2g' || networkType === '3g'));
});
},
//
getSystemInfo() {
uni.getSystemInfo({
success: result => {
store.commit('setSystemInfo', result);
},
fail: error => {
console.error('getSystemInfo fail:', error);
},
});
},
//
async signin() {
try {
const data = await uni.$u.api.signin();
if (data && data.token) {
store.commit('user/setUser', data);
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) {
uni.navigateTo({
title: '/pages/phone-bind/phone-bind'
});
}
},
}
}
async noPhone(phone) {
if (!phone) {
uni.navigateTo({ title: '/pages/phone-bind/phone-bind' });
}
},
},
};
</script>
<style lang="scss">
@ -99,7 +93,7 @@
@import '@/common/styles/iconfont.scss';
@import '@/common/styles/app.scss';
@import '@/common/styles/tailwind.scss';
page {
height: 100%;
}

13
CHANGELOG.md

@ -1,16 +1,17 @@
# 1.0.0 (2022-01-05)
# 1.0.0 (2022-01-06)
### 🌟 新功能
范围|描述|commitId
--|--|--
- | 更新代码 | [392c8cc](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/392c8cc)
- | 日历页首页 | [561c8e6](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/561c8e6)
- | 日历页添加 | [1b46a91](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/1b46a91)
- | 使用uview完成api请求 | [1b3efd8](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/1b3efd8)
- | 项目列表 | [a52e6d5](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/a52e6d5)
- | app.vue | [970cf9a](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/970cf9a)
- | first commit | [8dc26de](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/8dc26de)
- | vue3 | [12ed2ad](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/12ed2ad)
- | 使用uview完成api请求 | [1b3efd8](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/1b3efd8)
- | 日历页添加 | [1b46a91](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/1b46a91)
- | 日历页首页 | [561c8e6](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/561c8e6)
- | 更新代码 | [392c8cc](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/392c8cc)
- | 项目列表 | [a52e6d5](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/a52e6d5)
- | 项目操作面板 | [3beb05e](https://101.201.226.163:50022/TALL/TALL-MUI-4/commits/3beb05e)
### 🎨 代码样式

17
apis/project.js

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

12
apis/role.js

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

20
apis/task.js

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

4
common/styles/tailwind.scss

@ -1,3 +1,7 @@
.container {
width: 100%;
}
.fixed {
position: fixed;
}

139
components/Projects/ProjectItem.vue

@ -20,29 +20,29 @@
<!-- 箭头 -->
<view v-if="item.sonProjectList && item.sonProjectList.length">
<u-icon
@click="$emit('openSubProject', item.sonProjectList.length, index)"
<u-icon
@click="$emit('openSubProject', item.sonProjectList.length, index)"
class="text-gray-400"
name="arrow-up"
size="14px"
name="arrow-up"
size="14px"
v-if="item.show"
></u-icon>
<u-icon
@click="$emit('openSubProject', item.sonProjectList.length, index)"
<u-icon
@click="$emit('openSubProject', item.sonProjectList.length, index)"
class="text-gray-400"
name="arrow-down"
size="14px"
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"
<view
:id="'cu-' + index + '-' + subIndex"
:key="subIndex"
class="cu-item flex-col"
v-for="(subItem, subIndex) in item.sonProjectList"
>
@ -54,7 +54,7 @@
<view class="flex items-center">
<view class="mr-2">{{ subItem.name }}</view>
<!-- 状态 -->
<view
<view
:class="
subItem.status === 0
? 'text-blue-400 bg-blue-100'
@ -63,7 +63,7 @@
: 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 ? '暂停' : '已完成' }}
@ -80,63 +80,62 @@
</template>
<script setup>
import { ref, computed } from 'vue';
import dayjs from 'dayjs';
defineProps({
item: {
type: Object,
default: () => {},
},
index: {
type: Number,
default: 0,
},
menuList: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(['setData']);
const userId = computed(() => store.getters['user/userId']);
const data = ref({
showMenu: false,
tips: {
text: '',
color: '#909399',
fontSize: 28,
},
// show: false,
// border: 'border border-blue-500 shadow rounded-md',
// showBorder: false,
projectId: 0,
})
//
function openProject(project) {
const { name, id, url } = project;
url && (uni.$t.domain = url);
this.$u.route('pages/project-webview/project-webview', {
u: userId.value,
p: id,
pname: name,
url: encodeURIComponent(url),
});
}
/**
import { ref, computed } from 'vue';
import dayjs from 'dayjs';
import { useStore } from 'vuex';
import config from '@/common/js/config.js';
defineProps({
item: {
type: Object,
default: () => {},
},
index: {
type: Number,
default: 0,
},
menuList: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(['setData']);
const store = useStore();
const userId = computed(() => store.getters['user/userId']);
const data = ref({
showMenu: false,
tips: {
text: '',
color: '#909399',
fontSize: 28,
},
// show: false,
// border: 'border border-blue-500 shadow rounded-md',
// showBorder: false,
projectId: 0,
});
//
function openProject(project) {
const gateway = config.apiUrl;
const url = `${gateway}/defaultwbs`;
const { name, id } = project;
uni.navigateTo({ url: `/pages/project/project?u=${userId.value}&p=${id}&pname=${name}&url=${encodeURIComponent(url)}` });
}
/**
* 弹出项目操作面板
*/
function openMenu(project) {
data.value.showMenu = true;
data.value.projectId = project.id;
data.value.tips.text = project.name;
emit('setData', data.value.showMenu, data.value.projectId, data.value.tips);
// this.$emit('setData', data.value.showMenu, data.value.projectId, data.value.tips);
}
function openMenu(project) {
data.value.showMenu = true;
data.value.projectId = project.id;
data.value.tips.text = project.name;
emit('setData', data.value.showMenu, data.value.projectId, data.value.tips);
// this.$emit('setData', data.value.showMenu, data.value.projectId, data.value.tips);
}
</script>
<style lang="scss" scoped>
@ -144,7 +143,7 @@
height: 4rpx;
margin: 0 20rpx;
}
.border-80 {
height: 4rpx;
margin: 0 20rpx 0 90rpx;

236
components/Roles/Roles.vue

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

216
components/Title/Title.vue

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

466
components/Title/components/CreateTask copy.vue

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

583
components/Title/components/CreateTask.vue

@ -0,0 +1,583 @@
<template>
<div class="new-projects-box">
<div class="form">
<!-- 项目名称 -->
<view class="new-projects-title font-bold text-base flex justify-center items-center text-black">新建任务</view>
<div class="form-item flex items-center">
<div class="mr-4">名称<span class="text-red-500">*</span></div>
<u-input :maxlength="8" v-model="name" type="text" :inputAlign="'right'" :clearable="false" placeholder="请输入任务名称" />
</div>
<!-- 开始时间 -->
<div class="form-item flex items-center">
<div class="mr-4">开始时间<span class="text-red-500" v-if="source === 'regular'">*</span></div>
<div class="flex justify-end items-center flex-1">
<u-input
placeholder="请选择开始时间"
v-model="startTime"
type="text"
:inputAlign="'right'"
:clearable="false"
@click="$emit('showTime', 1)"
/>
<u-icon class="ml-1" name="arrow-right" color="#969799" size="28"></u-icon>
</div>
</div>
<!-- 结束时间 -->
<div class="form-item flex items-center">
<div class="mr-4">结束时间<span class="text-red-500" v-if="source === 'regular'">*</span></div>
<div class="flex justify-end items-center flex-1">
<u-input
placeholder="请选择结束时间"
v-model="endTime"
type="text"
:inputAlign="'right'"
:clearable="false"
@click="$emit('showTime', 2)"
/>
<u-icon class="ml-1" name="arrow-right" color="#969799" size="28"></u-icon>
</div>
</div>
<!-- 多选框 -->
<div class="form-item flex justify-between items-center">
<div class="mr-4">负责人<span class="text-red-500">*</span></div>
<div class="flex-1 text-right" v-if="hasRole">{{ roleName }}</div>
<div label="负责人" class="flex-1" v-else>
<u-dropdown disabled ref="uDropdown" placeholder="请选择负责人">
<u-dropdown-item :title="roleList">
<view class="slot-content bg-white">
<div
class="flex flex-row justify-between mb-1 drop-item"
v-for="(role, roleIndex) in roleOptions"
:key="roleIndex"
@click="change(roleIndex)"
>
<view 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="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 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="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>

2
components/Upload/Upload.vue

@ -22,7 +22,7 @@ const handleUpload = async cur => {
emit('success');
res.url && (uni.$t.domain = res.url);
setTimeout(() => {
uni.navigateTo({ url: `/pages/project-webview/project-webview?u=${userId.value}&p=${res.id}&pname=${res.pname}&url=${res.url}` });
uni.navigateTo({ url: `/pages/project/project?u=${userId.value}&p=${res.id}&pname=${res.pname}&url=${res.url}` });
}, 2000);
} catch (error) {
console.error('error: ', error);

17
config/time.js

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

8
hooks/useGetOptions.js

@ -0,0 +1,8 @@
import qs from 'qs';
export default function useGetOptions(){
const options = getCurrentPages();
const param = options[0].$page.fullPath.split('?')[1];
const prefixed = qs.parse(param, { ignoreQueryPrefix: true });
return prefixed
}

12
main.js

@ -4,6 +4,9 @@ import uView from './uni_modules/vk-uview-ui'; // 引入 uView UI
import store from "./store";
import { setupHttp } from '@/utils/request.js';
import { setupTall } from '@/apis/tall.js';
import { setupProject } from '@/apis/project.js';
import { setupRole } from '@/apis/role.js';
import { setupTask } from '@/apis/task.js';
import { setupWbs } from '@/apis/wbs.js'
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
@ -14,6 +17,8 @@ import storage from '@/utils/storage.js';
import time from '@/utils/time.js';
import ui from '@/utils/ui.js';
import upload from '@/utils/upload.js';
import task from '@/utils/task.js';
import timeConfig from '@/config/time';
export function createApp() {
const app = createSSRApp(App)
@ -22,6 +27,9 @@ export function createApp() {
app.use(store);
setupHttp(app);
setupTall(app);
setupProject(app);
setupRole(app);
setupTask(app);
setupWbs(app);
dayjs.locale('zh-cn');
@ -32,6 +40,8 @@ export function createApp() {
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;
uni.$cache = cache;
uni.$catchReq = cacheAndRequest;
@ -39,6 +49,8 @@ export function createApp() {
uni.$time = time;
uni.$ui = ui;
uni.$upload = upload;
uni.$task = task;
uni.$timeConfig = timeConfig;
return {
app

4
package.json

@ -5,7 +5,9 @@
"main": "main.js",
"dependencies": {
"axios": "^0.24.0",
"dayjs": "^1.10.7"
"dayjs": "^1.10.7",
"lodash": "^4.17.21",
"qs": "^6.10.2"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.8.1",

16
pages.json

@ -5,14 +5,14 @@
"style": {
"navigationBarText": "TALL"
}
}
// {
// "path": "pages/project/project",
// "style": {
// "navigationStyle": "custom",
// "navigationBarTextStyle": "white"
// }
// }
},
{
"path": "pages/project/project",
"style": {
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",

175
pages/index/index.vue

@ -4,7 +4,8 @@
<view class="relative" @touchmove="onMove">
<!-- 日历 -->
<Calendar @selected-change="onDateChange" :show-back="true" ref="calendar"
@handleFindPoint="handleFindPoint" />
@handleFindPoint="handleFindPoint"
/>
<!-- 上传 导入wbs -->
<Upload @success="onUploadSuccess" @error="onUploadError" />
</view>
@ -18,92 +19,92 @@
</template>
<script setup>
import { reactive, ref, computed, watchEffect } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
const store = useStore();
const token = computed(() => store.state.user.token);
const data = reactive({
calendar: null,
days: [],
});
// token
watchEffect(() => {
if (!token.value) return;
if (token.value) {
getProjects();
handleFindPoint();
}
});
//
function getProjects(start = dayjs().startOf('day').valueOf(), end = dayjs().endOf('day').valueOf()) {
// const data = await this.$u.api.getProjects(start, end);
uni.$catchReq.getProjects(start, end, (err, data) => {
if (err) {
console.error('err: ', err);
} else {
data.forEach(item => {
item.show = false;
});
store.commit('project/setProjects', data);
}
});
};
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.$refs.uTips.show({
title: '导入成功,即将打开新项目',
type: 'success',
duration: '3000',
});
};
//
const onUploadError = error => {
uni.$refs.uTips.show({
title: error || '导入失败',
type: 'error',
duration: '6000',
});
};
// /
function onMove(event) {
const y = event.changedTouches[0].pageY;
if (y - prevY > 0) {
// weekMode=true weekMode=false
data.value.calendar.weekMode && (data.value.calendar.weekMode = false);
} else if (y - prevY < 0) {
// weekMode=false weekMode=true
!data.value.calendar.weekMode && (data.value.calendar.weekMode = true);
}
prevY = y;
data.value.calendar.initDate();
}
import { reactive, computed, watchEffect } from 'vue';
import { useStore } from 'vuex';
import dayjs from 'dayjs';
const store = useStore();
const token = computed(() => store.state.user.token);
const data = reactive({
calendar: null,
days: [],
});
// token
watchEffect(() => {
if (!token.value) return;
if (token.value) {
getProjects();
handleFindPoint();
}
});
//
function getProjects(start = dayjs().startOf('day').valueOf(), end = dayjs().endOf('day').valueOf()) {
// const data = await this.$u.api.getProjects(start, end);
uni.$catchReq.getProjects(start, end, (err, data) => {
if (err) {
console.error('err: ', err);
} else {
data.forEach(item => {
item.show = false;
});
store.commit('project/setProjects', data);
}
});
}
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.$refs.uTips.show({
title: '导入成功,即将打开新项目',
type: 'success',
duration: '3000',
});
};
//
const onUploadError = error => {
uni.$refs.uTips.show({
title: error || '导入失败',
type: 'error',
duration: '6000',
});
};
// /
function onMove(event) {
const y = event.changedTouches[0].pageY;
if (y - prevY > 0) {
// weekMode=true weekMode=false
data.value.calendar.weekMode && (data.value.calendar.weekMode = false);
} else if (y - prevY < 0) {
// weekMode=false weekMode=true
!data.value.calendar.weekMode && (data.value.calendar.weekMode = true);
}
prevY = y;
data.value.calendar.initDate();
}
</script>
<style>

480
pages/project/project.vue

@ -1,64 +1,436 @@
<template>
<view :style="{ height: height }" class="flex flex-col overflow-hidden u-font-14">
<!-- 标题栏 -->
<Title />
<view class="container flex flex-col flex-1 mx-auto overflow-hidden bg-gray-100">
<!-- 角色栏 -->
<Roles />
<!-- 日常任务面板 -->
<Globals />
<!-- 定期任务面板 -->
<TimeLine @getTasks="getTasks" class="flex-1 overflow-hidden" ref="timeLine" />
</view>
</view>
<view :style="{ height: height }" class="flex flex-col overflow-hidden u-font-14">
<!-- 标题栏 -->
<Title />
<view class="container flex flex-col flex-1 mx-auto overflow-hidden bg-gray-100">
<!-- 角色栏 -->
<Roles />
<!-- 日常任务面板 -->
<Globals />
<!-- 定期任务面板 -->
<TimeLine @getTasks="getTasks" class="flex-1 overflow-hidden" ref="timeLine" />
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import Navbar from '@/components/Title/Title.vue';
import Roles from '@/components/Roles/Roles.vue';
import Globals from '@/components/Globals/Globals.vue';
import TimeLine from '@/components/TimeLine/TimeLine.vue';
<script>
import { defineComponent, ref, onMounted, computed, watch, nextTick } from 'vue';
import { useStore } from 'vuex';
import { flatten } from 'lodash';
import useGetOptions from '@/hooks/useGetOptions';
export default defineComponent({
setup(options) {
const store = useStore();
const height = ref(null);
const timeLine = ref(null);
const token = computed(() => store.state.user.token);
const roleId = computed(() => store.state.role.roleId);
const timeNode = computed(() => store.state.task.timeNode);
const timeUnit = computed(() => store.state.task.timeUnit);
const tasks = computed(() => store.state.task.tasks);
const newProjectInfo = computed(() => store.state.task.newProjectInfo);
const showScrollTo = computed(() => store.state.task.showScrollTo);
const timeGranularity = computed(() => store.getters['task/timeGranularity']);
const projectId = computed(() => store.getters['project/projectId']);
const userId = computed(() => store.getters['user/userId']);
/**
* 当时间基准点发生变化时
* 重新根据时间和角色查询普通日常任务
* 永久日常任务不发生改变
*/
watch(timeNode, newValue => {
if (newValue && roleId.value) {
clearTasksData();
getGlobalData(); //
initPlanTasks(); //
}
});
/**
* 当角色发生变化时
* 重新查询永久日常任务和普通日常任务
* 注意: 切换角色后 重新设置了时间基准点 时间基准点一定会变
* 所以监听时间基准点获取 可变日常任务即可 这里不用获取 避免重复获取
*/
watch(roleId, newValue => {
if (newValue) {
store.commit('task/setTimeNode', Date.now());
//
const params = { roleId: newValue, projectId: projectId.value };
store.dispatch('task/getPermanent', params);
}
});
/**
* 当时间基准点发生变化时
* 重新根据时间和角色查询普通日常任务
* 永久日常任务不发生改变
*/
watch(newProjectInfo, newValue => {
if (newValue && newValue.projectId && newValue.url) {
uni.$u.route('/', { u: userId.value, p: newValue.projectId, url: newValue.url });
clearTasksData();
store.commit('role/setRoleId', '');
const options = uni.$route.query;
init(options);
}
});
onMounted(() => {
const system = uni.getSystemInfoSync();
height.value = `${system.windowHeight}px`;
});
//
async function initPlanTasks() {
setPrevPlaceholderTasks(); //
setNextPlaceholderTasks(); //
await getInitTasks(); //
//
let timer = null;
timer = setInterval(() => {
if (showScrollTo.value) {
clearInterval(timer);
// nextTick(() => timeLine.setScrollPosition());
}
}, 500);
}
// ||
function getInitTasks() {
//
function preloadFn(that) {
const detailId = tasks.value.findIndex(task => task.detailId);
const arr = [];
tasks.value.forEach(task => {
if (task.detailId) {
arr.push(task);
}
});
if (detailId !== -1) {
// 1
const { pageCount } = uni.$task;
nextTick(() => {
//
getTasks({ timeNode: +tasks.value[detailId].planStart, queryType: 0, queryNum: pageCount });
//
const nextQueryTime = +uni.$time.add(+arr[arr.length - 1].planStart, 1, timeGranularity.value);
getTasks({ timeNode: nextQueryTime, queryType: 1, queryNum: pageCount });
});
} else {
//
//
setPrevPlaceholderTasks();
// //
setNextPlaceholderTasks();
}
}
//
getTasks({ queryType: 0 }); //
// id
getTasks({ queryType: 1 }, preloadFn); //
}
/**
* 根据时间基准点和角色查找定期任务
* @param {object} query
* @param {string} query.roleId 角色id
* @param {string} query.timeNode 时间基准点 默认当前
* @param {string} query.timeUnit 时间颗粒度 默认天
* @param {string} query.queryNum 查找颗粒度数量 默认3个
* @param {number} query.queryType 0向上查找 1向下查找(默认) 下查包含自己上查不包含
*/
function getTasks(query, fn) {
store.commit('task/setShowSkeleton', false);
const params = generateGetTaskParam(query);
uni.$catchReq.getRegularTask(params, (err, data) => {
store.commit('task/setShowSkeleton', false);
if (err) {
// TODO:
console.error('err: ', err);
} else {
store.commit('task/setShowScrollTo', true);
//
//
if (data && data.length) {
replacePrevData(data, params.queryType);
params.queryType === 0 ? store.commit('task/setTopEnd', false) : store.commit('task/setBottomEnd', false);
} else {
// TODO: 0 -> 1 ->
params.queryType === 0 ? setPrevPlaceholderTasks() : setNextPlaceholderTasks();
}
if (tasks.value.length && fn) {
fn(this);
}
}
});
}
/**
* 生成getTasks所用的参数
* @param {object} query getTasks传递的参数
*/
function generateGetTaskParam(query) {
return {
roleId: roleId.value,
timeNode: query.timeNode || timeNode.value,
timeUnit: query.timeUnit || timeUnit.value,
queryNum: query.queryNum || 3,
queryType: query.queryType,
projectId: projectId.value,
};
}
//
function setPrevPlaceholderTasks() {
store.commit('task/setTopEnd', true);
let startTime = '';
if (!tasks.value || !tasks.value.length) {
startTime = Date.now(); //
} else {
startTime = tasks[0].planStart - 0; //
}
const placeholderTasks = uni.$task.setPlaceholderTasks(startTime, true, timeGranularity.value);
store.commit('task/setUpTasks', placeholderTasks);
}
//
function setNextPlaceholderTasks() {
store.commit('task/setBottomEnd', true);
let startTime = '';
if (!tasks.value || !tasks.value.length) {
startTime = Date.now();
} else {
startTime = +tasks.value[tasks.value.length - 1].planStart;
}
const initData = uni.$task.setPlaceholderTasks(startTime, false, timeGranularity.value);
store.commit('task/setDownTasks', initData);
}
/**
* 用拿到的新数据 替换 时间刻度/旧数据
* 先对比 新旧数据的 始末时间 补齐刻度
* 再遍历对比 用任务替换刻度
* @param {array} data 服务端返回的新数据 上边已经处理过空值
* @param {number} type 0 -> 向上 1->向下
*/
function replacePrevData(data, type) {
let oldTasks = fillPlaceholderTask({ tasks: tasks.value, data, timeGranularity: timeGranularity.value }); //
//
// 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({ tasks, data, timeGranularity }) {
const { prev, next } = uni.$task.computeFillPlaceholderTaskCount({ tasks, data, timeGranularity });
if (prev) {
const newTasks = uni.$task.setPlaceholderTasks(+tasks[0].planStart, true, timeGranularity, prev);
store.commit('task/setUpTasks', newTasks);
}
if (next) {
const newTasks = uni.$task.setPlaceholderTasks(+tasks[tasks.length - 1].planStart, false, timeGranularity, next);
store.commit('task/setDownTasks', newTasks);
}
return tasks.value;
}
/**
* 初始化
* @param {object | null} options
*/
function init(options) {
if (!token.value) {
// tokenuserIdtoken
// token userId
if (!options || !options.u) {
uni.$ui.showToast('缺少用户信息参数'); // u (userId)
} else {
store.dispatch('user/getToken', options.u);
}
}
//
options && options.pname && store.commit('project/setProjectName', options.pname);
if (!options || !options.p) {
uni.$ui.showToast('缺少项目信息参数'); // id
} else {
if (options.p !== uni.$storage.getStorageSync('projectId')) {
console.log('切项目了');
uni.$storage.setStorageSync('roleId', '');
}
// TODO
getProjectById({ projectId: options.p, num: 0 }); // id
//
// this.handleQueryNotWrite(options.p);
// id
store.dispatch('role/getAllMembers', { projectId: options.p });
}
}
//
async function shareInit(options) {
const storageUser = uni.$storage.getStorageSync('user');
const user = storageUser ? JSON.parse(storageUser) : null;
if (user && user.id) {
await store.dispatch('user/getToken', user.id);
const res = await clickShare({ code: options.shareId });
if (res && res.projectId) {
let query = { ...uni.$route.query };
query = {
u: user.id,
p: res.projectId,
};
uni.$router.push({ path: uni.$route.path, query });
init(query);
}
} else {
uni.$ui.showToast('缺少用户信息参数,请登录');
}
}
function setOptions(options) {
if (options.share && options.share === '1') {
shareInit(options);
} else {
init(options);
}
}
setOptions(options);
/**
* 点击分享连接
* @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 || '获取失败');
}
}
/**
* 通过项目id获取项目信息
* @param {object} params 提交的参数
*/
async function getProjectById(params) {
try {
const data = await uni.$u.api.findProjectById(params);
store.commit('project/setProject', data);
// id
getRoles(params);
} catch (error) {
console.log('error: ', error || '获取项目信息失败');
}
}
/**
* 通过项目id获取角色信息
* @param {string} projectId
* @param {object} params 提交的参数
*/
function getRoles(params) {
uni.$catchReq.findShowRole(params, (err, data) => {
if (err) {
console.error('err: ', err || '获取角色信息失败');
} else {
store.commit('role/setInvisibleRoles', data ? data.invisibleList : []);
store.commit('role/setVisibleRoles', data ? data.visibleList : []);
setInitialRoleId(data ? data.visibleList : []);
}
});
}
let height = ref(null);
//
function setInitialRoleId(visibleList) {
if (!visibleList || !visibleList.length) return;
const index = visibleList.findIndex(item => +item.mine === 1);
const currentRole = index > 0 ? visibleList[index] : visibleList[0];
const storageRoleId = uni.$storage.getStorageSync('roleId');
const currentRoleId = storageRoleId || (currentRole ? currentRole.id : '');
store.commit('role/setRoleId', currentRoleId);
// storage
uni.$storage.setStorageSync('roleId', '');
}
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 getTasks() {
//
function clearTasksData() {
//
store.commit('task/setPermanents', []);
store.commit('task/setDailyTasks', []);
//
store.commit('task/clearTasks');
//
//
store.commit('task/clearEndFlag');
}
return {
height,
timeLine,
roleId,
timeNode,
timeUnit,
tasks,
newProjectInfo,
showScrollTo,
timeGranularity,
projectId,
userId,
initPlanTasks,
getInitTasks,
getTasks,
generateGetTaskParam,
setPrevPlaceholderTasks,
setNextPlaceholderTasks,
replacePrevData,
fillPlaceholderTask,
init,
shareInit,
setOptions,
clickShare,
getProjectById,
getRoles,
setInitialRoleId,
getGlobalData,
clearTasksData,
};
},
});
}
</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>
.border-b {
border-bottom: 1px solid #e4e7ed;
}
</style>

4
store/index.js

@ -2,6 +2,8 @@ import { createStore } from 'vuex';
import user from './user/index.js';
import socket from './socket/index.js';
import project from './project/index.js';
import role from './role/index.js';
import task from './task/index.js';
// 不属于具体模块的 应用级的 store内容
const state = {
@ -42,5 +44,5 @@ export default createStore({
state,
getters,
mutations,
modules: {user, socket, project}
modules: {user, socket, project, role, task}
});

17
store/role/actions.js

@ -0,0 +1,17 @@
const actions = {
/**
* 根据项目id查找所有成员信息
* @param {*} commit
* @param {object} params
*/
async getAllMembers({ commit }, params) {
try {
const data = await uni.$catchReq.queryChecker(params);
commit('setMembers', data);
} catch (error) {
uni.$ui.showToast(error.msg || '成员查询失败');
}
},
};
export default actions;

13
store/role/getters.js

@ -0,0 +1,13 @@
const getters = {
// 是不是负责人
isMine({ roleId, invisibleRoles, visibleRoles }) {
if (!visibleRoles || !visibleRoles.length) return false;
const visible = visibleRoles.find(visible => visible.id === roleId);
if (visible) return visible.mine;
const invisible = invisibleRoles.find(invisible => invisible.id === roleId);
if (invisible) return visible.mine;
return false;
},
};
export default getters;

12
store/role/index.js

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

39
store/role/mutations.js

@ -0,0 +1,39 @@
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;
},
/**
* 设置项目下所有成员信息
* @param {Object} state
* @param {Array} data 服务端返回的模板数组
*/
setMembers(state, data) {
state.members = data || [];
},
};
export default mutations;

8
store/role/state.js

@ -0,0 +1,8 @@
const state = {
invisibleRoles: [], // 不展示的角色信息
visibleRoles: [], // 展示的角色信息
roleId: '', // 当前展示查看的角色id
members: [], // 项目下所有成员
};
export default state;

33
store/task/actions.js

@ -0,0 +1,33 @@
const actions = {
/**
* 根据角色查找永久的日常任务
* @param {*} commit
* @param {string} roleId 角色id
*/
getPermanent({ commit }, param) {
uni.$catchReq.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.$catchReq.getGlobal(param, (err, data) => {
if (err) {
console.error('err: ', err);
} else {
commit('setDailyTasks', data);
}
});
},
};
export default actions;

23
store/task/getters.js

@ -0,0 +1,23 @@
const getters = {
// 所有的日常任务 永久 + 可变 日常任务
globals({ dailyTasks, permanents }) {
return [...permanents, ...dailyTasks];
},
unitConfig({ timeUnit }) {
const target = uni.$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
store/task/index.js

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

238
store/task/mutations.js

@ -0,0 +1,238 @@
const mutations = {
/**
* 记录时间轴向上滚动的距离
* @param { object } state
* @param { number } num
*/
setScrollTop(state, num) {
state.scrollTop = num;
},
/**
* 记录时间轴向上滚动的距离
* @param { object } state
* @param {string} taskId
*/
setScrollToTaskId(state, taskId) {
state.scrollToTaskId = taskId;
},
/**
* 设置日常任务当前是否应该处于收缩状态
* @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, ...state.tasks];
let arr = [],
flag = false;
state.tasks.forEach(task => {
arr.forEach(item => {
if (task.id == item.id) {
flag = true;
}
});
if (!flag) {
arr.push(task);
}
});
state.tasks = [...arr];
// 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, ...data];
let arr = [],
flag = false;
state.tasks.forEach(task => {
arr.forEach(item => {
if (task.id == item.id) {
flag = true;
}
});
if (!flag) {
arr.push(task);
}
});
state.tasks = [...arr];
// state.tasks = [...state.tasks.concat(data)];
}
},
/**
* 添加任务后更新tasks
* @param {Object} state
* @param {Array} data 新添加的task
*/
updateTasks(state, data) {
state.tasks = [...data];
},
/**
* 设置添加任务的位置
* @param {*} state
* @param {*} data
*/
setAddPosition(state, data) {
console.log('data: ', 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 {Array} data 服务端返回的模板数组
*/
setNewProjectInfo(state, data) {
state.newProjectInfo = data;
},
/**
* 设置骨架屏是否显示
* @param {Object} state
* @param {Boolean} show
*/
setShowSkeleton(state, show) {
state.showSkeleton = show;
},
/**
* 是否设置时间轴自动滚动的位置
* @param {Object} state
* @param {Boolean} show
*/
setShowScrollTo(state, show) {
state.showScrollTo = show;
},
};
export default mutations;

25
store/task/state.js

@ -0,0 +1,25 @@
const state = {
scrollTop: 0,
scrollToTaskId: '', // 时间轴自动滚动的位置
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, // 定期任务骨架屏
newProjectInfo: {},
showScrollTo: false, // 是否可以设置时间轴自动滚动的位置
};
export default state;

2
uni.scss

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

235
utils/cache.js

@ -59,4 +59,239 @@ export default {
uni.$storage.setStorage('projects', []);
}
},
/**
* 当前显示的角色信息 获取
* @param {object} params
* @returns
*/
async getShowRole(projectId) {
try {
const data = await uni.$storage.getStorage(`roles_${projectId}`);
return filter.roles(JSON.parse(data));
} catch (error) {
return null;
}
},
/**
* 当前显示的角色信息
* @param {array} data
*/
putShowRole(projectId, data) {
try {
if (!data || !data.visibleList || !data.visibleList.length) return; // 服务端没数据不做操作
let value = uni.$storage.getStorageSync(`roles_${projectId}`);
let locals = value ? JSON.parse(value) : null;
if (!locals || !locals.length) {
// 本地没数据
locals = data || null;
} else {
// 本地有数据
data.invisibleList.forEach(item => {
let invisibleListLocalData = locals.invisibleList.find(local => item.id === local.id);
if (invisibleListLocalData) {
// 有相同数据 就用新的data里的数据
invisibleListLocalData = item;
} else {
// 没有就直接存本地
locals.invisibleList.push(item);
}
});
data.visibleList.forEach(item => {
let localData = locals.visibleList.find(local => item.id === local.id);
if (localData) {
// 有相同数据 就用新的data里的数据
localData = item;
} else {
// 没有就直接存本地
locals.visibleList.push(item);
}
});
}
uni.$storage.setStorage(`roles_${projectId}`, locals);
} catch (error) {
console.error('error: ', error);
uni.$storage.setStorage(`roles_${projectId}`, []);
}
},
/**
* 定期任务 获取
* @param {number} startTime
* @param {number} endTime
* @returns
*/
async getStorageRegularTask(params) {
try {
const data = await uni.$storage.getStorage(`plan_task_${params.projectId}_${params.roleId}`);
return filter.planTask(JSON.parse(data), params.timeNode, params.queryNum, params.timeUnit, params.queryType);
} catch (error) {
return [];
}
},
/**
* 定期任务
* @param {array} data
*/
putStorageRegularTask(params, data) {
try {
if (!data || !data.length) return; // 服务端没数据不做操作
let value = uni.$storage.getStorageSync(`plan_task_${params.projectId}_${params.roleId}`);
let locals = value ? JSON.parse(value) : [];
if (!locals || !locals.length) {
// 本地没数据
locals = data || [];
} else {
// 本地有数据
data.forEach(item => {
let localData = locals.find(local => item.id === local.id);
if (localData) {
// 有相同数据 就用新的data里的数据
localData = item;
} else {
// 没有就直接存本地
locals.push(item);
}
});
}
uni.$storage.setStorage(`plan_task_${params.projectId}_${params.roleId}`, locals);
} catch (error) {
console.error('error: ', error);
uni.$storage.setStorage(`plan_task_${params.projectId}_${params.roleId}`, []);
}
},
/**
* 永久的日常任务 获取
* @param {number} startTime
* @param {number} endTime
* @returns
*/
async getStoragePermanent(params) {
try {
const data = await uni.$storage.getStorage(`fixed_tasks_${params.projectId}_${params.roleId}`);
return filter.fixedTasks(JSON.parse(data));
} catch (error) {
return [];
}
},
/**
* 永久的日常任务
* @param {array} data
*/
putStoragePermanent(params, data) {
try {
if (!data || !data.length) return; // 服务端没数据不做操作
let value = uni.$storage.getStorageSync(`fixed_tasks_${params.projectId}_${params.roleId}`);
let locals = value ? JSON.parse(value) : [];
if (!locals || !locals.length) {
// 本地没数据
locals = data || [];
} else {
// 本地有数据
data.forEach((item, index) => {
let localData = locals.find(local => item.detailId === local.detailId);
if (localData) {
// 有相同数据 就用新的data里的数据
localData = item;
} else {
locals.splice(index, 1);
// 没有就直接存本地
locals.push(item);
}
});
}
uni.$storage.setStorage(`fixed_tasks_${params.projectId}_${params.roleId}`, locals);
} catch (error) {
console.error('error: ', error);
uni.$storage.setStorage(`fixed_tasks_${params.projectId}_${params.roleId}`, []);
}
},
/**
* 日常任务 获取
* @param {number} timeNode
* @returns
*/
async getDailyTask(params) {
try {
const data = await uni.$storage.getStorage(`variable_tasks_${params.projectId}_${params.roleId}`);
return filter.dailyTask(JSON.parse(data), params.timeNode);
} catch (error) {
return [];
}
},
/**
* 日常任务
* @param {array} data
*/
putDailyTask(params, data) {
try {
if (!data || !data.length) return; // 服务端没数据不做操作
let value = uni.$storage.getStorageSync(`variable_tasks_${params.projectId}_${params.roleId}`);
let locals = value ? JSON.parse(value) : [];
if (!locals || !locals.length) {
// 本地没数据
locals = data || [];
} else {
// 本地有数据
data.forEach(item => {
let localData = locals.find(local => item.detailId === local.detailId);
if (localData) {
// 有相同数据 就用新的data里的数据
localData = item;
} else {
// 没有就直接存本地
locals.push(item);
}
});
}
uni.$storage.setStorage(`variable_tasks_${params.projectId}_${params.roleId}`, locals);
} catch (error) {
console.error('error: ', error);
uni.$storage.setStorage(`variable_tasks_${params.projectId}_${params.roleId}`, []);
}
},
/**
* 插件信息 获取
* @param {string} pluginId
* @returns
*/
async getPlugin(pluginId) {
try {
const data = await uni.$storage.getStorage(`plugin_${pluginId}`);
return filter.plugin(JSON.parse(data));
} catch (error) {
return null;
}
},
/**
* 插件信息
* @param {string} pluginId
* @param {object} data
*/
putPlugin(pluginId, data) {
try {
if (!data || !data.id) return; // 服务端没数据不做操作
let value = uni.$storage.getStorageSync(`plugin_${pluginId}`);
let locals = value ? JSON.parse(value) : null;
if (!locals || !locals.length) {
// 本地没数据
locals = data || null;
} else {
// 本地有数据
locals = data;
}
uni.$storage.setStorage(`plugin_${pluginId}`, locals);
} catch (error) {
console.error('error: ', error);
uni.$storage.setStorage(`plugin_${pluginId}`, null);
}
},
};

58
utils/task.js

@ -0,0 +1,58 @@
import dayjs from 'dayjs';
/**
* 设置时间轴空数据
* @param {number} startTime
* @param {boolean} isUp true 向上加载,false 向下加载
* @param {string} timeGranularity 颗粒度
* @param {number} pageCount 加载的颗粒度数量 默认值是10
*/
const setPlaceholderTasks = (startTime, isUp, timeGranularity, pageCount) => {
let result = [];
pageCount = pageCount || uni.$task.pageCount;
for (let i = 0; i < pageCount; i++) {
const delta = isUp ? `-${i + 1}` - 0 : i + 1;
let item = {
id: uni.$u.guid(20, false, 10),
panel: {},
plugins: [],
process: 4,
planStart: uni.$moment(startTime).add(delta, timeGranularity).valueOf(),
};
// console.log('isup: ', isUp, 'result:', new Date(item.planStart).toLocaleDateString());
isUp ? result.unshift(item) : result.push(item);
}
return result;
};
/**
* 超出旧数据上下限 补齐时间刻度到新数据的起始时间颗粒度
* @param {object} option
* @param {array} option.tasks 旧的已有的任务书籍
* @param {array} option.data 新拿到的任务数据 空值已经过滤过了
* @param {string} option.timeGranularity 颗粒度
*/
const computeFillPlaceholderTaskCount = ({ tasks, data, timeGranularity }) => {
const result = { prev: 0, next: 0 };
// 新数据的开始时间 < 旧数据的开始时间
// 超出了上限 补上限的时间刻度
// 补上 新数据开始时间 到 旧数据开始时间 的刻度
if (+data[0].planStart < +tasks[0].planStart) {
// 找出来需要补几组颗粒度
result.prev = dayjs(+tasks[0].planStart).diff(+data[0].planStart, timeGranularity) + 1;
}
// 新数据的结束时间 > 旧数据的结束时间
// 超出了下线 补下限的时间刻度
// 补上 旧数据截止时间 到 新数据截止时间 的刻度
if (+data[data.length - 1].planStart > +tasks[tasks.length - 1].planStart) {
result.next = dayjs(+data[data.length - 1].planStart).diff(+tasks[tasks.length - 1].planStart, timeGranularity) + 1;
}
return result;
};
export default {
setPlaceholderTasks,
computeFillPlaceholderTaskCount
};
Loading…
Cancel
Save