21 changed files with 1281 additions and 231 deletions
@ -0,0 +1,126 @@ |
|||||
|
<template> |
||||
|
<!-- 时间间隔栏 --> |
||||
|
<!-- <Barrier /> --> |
||||
|
|
||||
|
<scroll-view |
||||
|
:lower-threshold="300" |
||||
|
scroll-y="true" |
||||
|
:upper-threshold="300" |
||||
|
:scroll-into-view="scrollToTaskId" |
||||
|
@scroll="scroll" |
||||
|
@scrolltolower="handleScrollBottom" |
||||
|
@scrolltoupper="handleScrollTop" |
||||
|
id="scroll" |
||||
|
> |
||||
|
<!-- 时间轴 --> |
||||
|
<!-- <u-divider bg-color="#f3f4f6" class="pt-5" fontSize="14px" v-if="topEnd">到顶啦</u-divider> --> |
||||
|
|
||||
|
<TimeBox /> |
||||
|
|
||||
|
<!-- <u-divider bg-color="#f3f4f6" class="pb-5" fontSize="14px" v-if="bottomEnd">到底啦</u-divider> --> |
||||
|
</scroll-view> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { reactive, computed } from 'vue'; |
||||
|
import { useStore } from 'vuex'; |
||||
|
// import Barrier from './component/Barrier.vue'; |
||||
|
import dayjs from 'dayjs'; |
||||
|
import TimeBox from './component/TimeBox.vue'; |
||||
|
|
||||
|
const store = useStore(); |
||||
|
// const visibleRoles = computed(() => store.state.role.visibleRoles); |
||||
|
// const scrollTop = computed(() => store.state.task.scrollTop); |
||||
|
const tasks = computed(() => store.state.task.tasks); |
||||
|
const topEnd = computed(() => store.state.task.topEnd); |
||||
|
const bottomEnd = computed(() => store.state.task.bottomEnd); |
||||
|
const showSkeleton = computed(() => store.state.task.showSkeleton); |
||||
|
const timeNode = computed(() => store.state.task.timeNode); |
||||
|
const scrollToTaskId = computed(() => store.state.task.scrollToTaskId); |
||||
|
const timeGranularity = computed(() => store.getters['task/timeGranularity']); |
||||
|
const emit = defineEmits(['getTasks']); |
||||
|
|
||||
|
const data = reactive({ top: 0 }); |
||||
|
|
||||
|
// 滚动 |
||||
|
function scroll(e) { |
||||
|
data.top = e.detail.scrollTop; |
||||
|
store.commit('task/setShrink', data.top > data.scrollTop); |
||||
|
store.commit('task/setScrollTop', data.top); |
||||
|
} |
||||
|
|
||||
|
// 滚动到顶部 |
||||
|
async function handleScrollTop() { |
||||
|
if (!tasks.value || !tasks.value.length || showSkeleton.value) return; |
||||
|
const startTime = tasks.value[0].planStart - 0; |
||||
|
if (topEnd.value) { |
||||
|
// 没有数据时 自动加载数据 |
||||
|
console.warn('滚动到顶部没有数据时: '); |
||||
|
const addTasks = uni.$task.setPlaceholderTasks(startTime, true, timeGranularity.value); |
||||
|
store.commit('task/setUpTasks', addTasks); |
||||
|
} else { |
||||
|
// 有数据时 |
||||
|
console.warn('滚动到顶部有数据时: '); |
||||
|
const detailId = tasks.value.findIndex(task => task.detailId); |
||||
|
const timeNodeValue = tasks.value[detailId].planStart - 0; |
||||
|
const upQuery = { |
||||
|
timeNode: timeNodeValue, |
||||
|
queryType: 0, |
||||
|
queryNum: 6, |
||||
|
}; |
||||
|
await emit('getTasks', upQuery); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 滚动到底部 |
||||
|
async function handleScrollBottom() { |
||||
|
if (!tasks.value || !tasks.value.length || showSkeleton.value) return; |
||||
|
const startTime = tasks.value[tasks.value.length - 1].planStart - 0; |
||||
|
if (bottomEnd.value) { |
||||
|
// 没有数据时 自动加载数据 |
||||
|
console.warn('滚动到底部没有数据时: '); |
||||
|
const addTasks = uni.$task.setPlaceholderTasks(startTime, false, timeGranularity.value); |
||||
|
store.commit('task/setDownTasks', addTasks); |
||||
|
} else { |
||||
|
// 时间基准点=最后一个任务的开始时间+当前时间颗粒度 |
||||
|
console.warn('滚动到底部有数据时: '); |
||||
|
const arr = []; |
||||
|
tasks.value.forEach(task => { |
||||
|
if (task.detailId) { |
||||
|
arr.push(task); |
||||
|
} |
||||
|
}); |
||||
|
const nextQueryTime = +uni.$time.add(+arr[arr.length - 1].planStart, 1, timeGranularity.value); |
||||
|
const downQuery = { |
||||
|
timeNode: nextQueryTime, |
||||
|
queryType: 1, |
||||
|
queryNum: 6, |
||||
|
}; |
||||
|
await emit('getTasks', downQuery); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 设置自动滚动位置 |
||||
|
function setScrollPosition() { |
||||
|
// 如果storage里有taskId 滚动到这个id的任务 |
||||
|
const taskId = uni.$storage.getStorageSync('taskId'); |
||||
|
if (taskId) { |
||||
|
store.commit('task/setScrollToTaskId', `a${taskId}`); |
||||
|
uni.$storage.setStorageSync('taskId', ''); // 记录后即刻清除本地存储 |
||||
|
} else { |
||||
|
const item = tasks.value.find(task => task.detailId); |
||||
|
if (item) { |
||||
|
store.commit('task/setScrollToTaskId', `a${item.id}`); |
||||
|
} else { |
||||
|
// 没有本地记录的taskId |
||||
|
// 找到当前时间基准线的任务id 记录 并滚动到当前时间基准线 |
||||
|
const task = tasks.value.find(item => dayjs(+item.planStart).isSame(timeNode.value, timeGranularity.value)); |
||||
|
task && store.commit('task/setScrollToTaskId', `a${task.id}`); // 有这个task 就记录他的id |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
defineExpose({ |
||||
|
setScrollPosition |
||||
|
}) |
||||
|
</script> |
@ -0,0 +1,38 @@ |
|||||
|
<!-- |
||||
|
* @Author: aBin |
||||
|
* @email: binbin0314@126.com |
||||
|
* @Date: 2021-07-19 14:22:54 |
||||
|
* @LastEditors: aBin |
||||
|
* @LastEditTime: 2021-07-20 11:46:04 |
||||
|
--> |
||||
|
<template> |
||||
|
<view class> |
||||
|
<!-- :class="{ active: cycleTasks.time.start === filter.startTime }" --> |
||||
|
<view class="cycle-time active"> |
||||
|
<!-- {{ $util.formatStartTimeToCycleTime(filter.time, cycleTasks.time.start) }} --> |
||||
|
2021年30周 |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
|
||||
|
</script> |
||||
|
<style scoped lang="scss"> |
||||
|
.cycle-time { |
||||
|
padding: 8rpx 16rpx; |
||||
|
margin-bottom: 16rpx; |
||||
|
background: #fafafc; |
||||
|
color: $uni-text-color; |
||||
|
font-size: 28rpx; |
||||
|
position: sticky; |
||||
|
top: -1px; |
||||
|
left: 0; |
||||
|
z-index: 99; |
||||
|
|
||||
|
&.active { |
||||
|
background: $uni-color-primary; |
||||
|
color: $uni-text-color-inverse; |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,177 @@ |
|||||
|
<template> |
||||
|
<view> |
||||
|
<view class="flex justify-between" style="min-width: 90px; position: relative"> |
||||
|
<u-icon custom-prefix="custom-icon" name="C-bxl-redux" size="17px"></u-icon> |
||||
|
<u-icon custom-prefix="custom-icon" name="attachment" size="21px"></u-icon> |
||||
|
<!-- <u-icon custom-prefix="custom-icon" name="moneycollect" size="20px"></u-icon> --> |
||||
|
<u-icon name="xuanxiang" custom-prefix="custom-icon" size="21px" @click="operation"></u-icon> |
||||
|
|
||||
|
<!-- 右上角 ... 弹窗 --> |
||||
|
<view class="popup border shadow-md" v-if="data.show"> |
||||
|
<!-- <view class="flex justify-center pb-3 border-b-1"> |
||||
|
<span>添加插件</span> |
||||
|
</view> --> |
||||
|
<view class="flex justify-center pb-3 border-b-1"> |
||||
|
<span @click="createTask">新建任务</span> |
||||
|
</view> |
||||
|
<view class="flex pt-3 justify-center"> |
||||
|
<span>克隆任务</span> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 遮罩 --> |
||||
|
<view class="mask" v-if="data.maskShow" @click="closeMask"></view> |
||||
|
<!-- 新建任务弹窗 --> |
||||
|
<CreateTask |
||||
|
:startTime="data.startTime" |
||||
|
:endTime="data.endTime" |
||||
|
:task="task" |
||||
|
:source="'regular'" |
||||
|
@showTime="showTime" |
||||
|
@closeMask="closeMask" |
||||
|
class="thirdPopup flex transition-transform" |
||||
|
v-if="data.createTaskShow" |
||||
|
/> |
||||
|
|
||||
|
<u-picker title="开始时间" mode="time" v-model="data.showStart" :params="data.params" @confirm="confirmStartTime"></u-picker> |
||||
|
<u-picker title="结束时间" mode="time" v-model="data.showEnd" :params="data.params" @confirm="confirmEndTime"></u-picker> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { reactive } from 'vue'; |
||||
|
import CreateTask from '@/components/Title/components/CreateTask.vue'; |
||||
|
|
||||
|
defineProps({ task: { type: Object, default: () => {} } }); |
||||
|
|
||||
|
const data = reactive({ |
||||
|
show: false, // 右上角 ... 显示 |
||||
|
createTaskShow: false, // 新建项目显示 |
||||
|
secondShow: false, // 分享项目显示 |
||||
|
maskShow: false, // 遮罩显示 |
||||
|
showStart: false, |
||||
|
showEnd: false, |
||||
|
startTime: '', // 新建任务的开始时间 |
||||
|
endTime: '', // 新建任务的截止时间 |
||||
|
params: { |
||||
|
year: true, |
||||
|
month: true, |
||||
|
day: true, |
||||
|
hour: true, |
||||
|
minute: true, |
||||
|
second: true, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
// 操作 |
||||
|
function operation() { |
||||
|
// this.$t.ui.showToast('操作'); |
||||
|
data.show = !data.show; |
||||
|
} |
||||
|
|
||||
|
// 新建项目 |
||||
|
function createTask() { |
||||
|
// 关闭 ... 弹窗 |
||||
|
data.show = false; |
||||
|
// 打开遮罩 |
||||
|
data.maskShow = true; |
||||
|
// 打开新建项目弹窗 |
||||
|
data.createTaskShow = true; |
||||
|
} |
||||
|
|
||||
|
// 点击遮罩,关闭弹窗 |
||||
|
function closeMask() { |
||||
|
// 关闭遮罩 |
||||
|
data.maskShow = false; |
||||
|
// 关闭分享项目弹窗 |
||||
|
data.secondShow = false; |
||||
|
// 关闭新建项目弹窗 |
||||
|
data.createTaskShow = false; |
||||
|
} |
||||
|
|
||||
|
function showTime() { |
||||
|
data.showStart = !data.showStart; |
||||
|
} |
||||
|
|
||||
|
// 选择开始时间 |
||||
|
function confirmStartTime(e) { |
||||
|
data.startTime = `${e.year}-${e.month}-${e.day} ${e.hour}:${e.minute}:${e.second}`; |
||||
|
data.showEnd = true; |
||||
|
} |
||||
|
|
||||
|
// 选择结束时间 |
||||
|
function confirmEndTime(e) { |
||||
|
data.endTime = `${e.year}-${e.month}-${e.day} ${e.hour}:${e.minute}:${e.second}`; |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.mask { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
z-index: 21; |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
background: rgba(0, 0, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.thirdPopup { |
||||
|
background: #ffffff; |
||||
|
position: fixed; |
||||
|
left: 50%; |
||||
|
top: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
z-index: 33; |
||||
|
border-radius: 5px; |
||||
|
width: 90%; |
||||
|
} |
||||
|
|
||||
|
.popup { |
||||
|
width: 110px; |
||||
|
background: #fff; |
||||
|
position: absolute; |
||||
|
right: 0; |
||||
|
top: 35px; |
||||
|
z-index: 99; |
||||
|
padding: 15px 0; |
||||
|
color: black; |
||||
|
animation: opacity 1s ease-in; |
||||
|
} |
||||
|
|
||||
|
@keyframes opacity { |
||||
|
0% { |
||||
|
opacity: 0; |
||||
|
} |
||||
|
50% { |
||||
|
opacity: 0.8; |
||||
|
} |
||||
|
100% { |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
::v-deep .u-slot-content { |
||||
|
min-width: 0; |
||||
|
} |
||||
|
::v-deep .u-dropdown__content { |
||||
|
min-height: 120px !important; |
||||
|
height: auto !important; |
||||
|
overflow-y: auto; |
||||
|
background: #fff !important; |
||||
|
transition: none !important; |
||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
::v-deep .u-dropdown__menu__item .u-flex { |
||||
|
justify-content: space-between; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
flex-wrap: nowrap; |
||||
|
border: 1px solid #afbed1; |
||||
|
padding: 0 8px; |
||||
|
} |
||||
|
::v-deep .u-dropdown__content__mask { |
||||
|
display: none; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,132 @@ |
|||||
|
<template> |
||||
|
<!-- v-finger:pinch="pinchHandler" --> |
||||
|
<view class="column"> |
||||
|
<view v-if="tasks && tasks.length"> |
||||
|
<view :key="task.id" v-for="task in tasks" :id="`a${task.id}`"> |
||||
|
<view class="flex"> |
||||
|
<TimeStatus :task="task" /> |
||||
|
<view class="flex items-center justify-between flex-1 ml-2 task-column"> |
||||
|
<view v-if="task.process !== 4">{{ $moment(+task.planStart).format(startTimeFormat) }}</view> |
||||
|
<view v-else>{{ $moment(+task.planStart).format('D日') }}</view> |
||||
|
|
||||
|
<!-- 任务功能菜单 --> |
||||
|
<TaskTools v-if="task.process !== 4" :task="task" /> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="plugin"> |
||||
|
<view class="h-3" v-if="task.process === 4"></view> |
||||
|
<!-- <view class="ml-3 overflow-hidden shadow-lg task-box"> --> |
||||
|
<view class="ml-3"> |
||||
|
<u-card :show-foot="false" :show-head="false" :style="{ height: setHeight(task.panel) }" class="h-16" margin="0" v-if="showSkeleton"> |
||||
|
<view slot="body"> |
||||
|
<view><skeleton :banner="false" :loading="true" :row="4" animate class="mt-2 u-line-2 skeleton"></skeleton></view> |
||||
|
</view> |
||||
|
</u-card> |
||||
|
<!-- <u-card |
||||
|
@click="onClickTask(task.planStart - 0, task.id)" |
||||
|
:style="{ height: setHeight(task.panel) }" |
||||
|
:show-foot="false" |
||||
|
:show-head="false" |
||||
|
class="h-16" |
||||
|
margin="0" |
||||
|
v-if="tasks && tasks.length && task.process !== 4 && !showSkeleton" |
||||
|
> |
||||
|
<template v-slot:body> --> |
||||
|
<view class="h-16" v-if="tasks && tasks.length && task.process !== 4 && !showSkeleton" @click="onClickTask(task.planStart - 0, task.id)"> |
||||
|
<view class="p-0 u-col-between grid gap-3"> |
||||
|
<view :key="pIndex" v-for="(row, pIndex) in task.plugins"> |
||||
|
<view class="grid gap-2 grid-cols-1" v-if="row.length"> |
||||
|
<Plugin |
||||
|
:class="[`row-span-${plugin.row}`, `col-span-${plugin.col}`]" |
||||
|
:task="task" |
||||
|
:key="plugin.pluginTaskId" |
||||
|
:plugin-task-id="plugin.pluginTaskId" |
||||
|
:plugin-id="plugin.pluginId" |
||||
|
:param="plugin.param" |
||||
|
:style-type="data.styleType || 0" |
||||
|
v-for="plugin in row" |
||||
|
/> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<!-- </template> |
||||
|
</u-card> --> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<!-- 局部弹框操作栏 --> |
||||
|
<Tips /> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { useStore } from 'vuex'; |
||||
|
import { computed, reactive } from 'vue'; |
||||
|
import TimeStatus from './TimeStatus.vue'; |
||||
|
import TaskTools from './TaskTools.vue'; |
||||
|
import Skeleton from '@/components/Skeleton/Skeleton.vue'; |
||||
|
|
||||
|
const data = reactive({ |
||||
|
currentComponent: '', |
||||
|
styleType: 0, |
||||
|
}); |
||||
|
|
||||
|
const store = useStore(); |
||||
|
const roleId = computed(() => store.state.role.roleId); |
||||
|
const timeUnit = computed(() => store.state.task.timeUnit); |
||||
|
const tasks = computed(() => store.state.task.tasks); |
||||
|
const showSkeleton = computed(() => store.state.task.showSkeleton); |
||||
|
const startTimeFormat = computed(() => store.getters['task/startTimeFormat']); |
||||
|
|
||||
|
// 设置任务面板高度 |
||||
|
function setHeight(panel) { |
||||
|
if (panel && panel.height) { |
||||
|
return `${panel.height}px`; |
||||
|
} |
||||
|
return 'auto'; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 点击了定期任务的面板 更新可变的日常任务 |
||||
|
* @param {number} planStart 任务计划开始时间 |
||||
|
* @param {string} taskId 任务id |
||||
|
*/ |
||||
|
function onClickTask(planStart, taskId) { |
||||
|
const param = { roleId: roleId.value, timeNode: planStart, timeUnit: timeUnit.value }; |
||||
|
store.dispatch('task/getGlobal', param); |
||||
|
uni.$storage.setStorageSync('taskId', taskId); |
||||
|
uni.$storage.setStorageSync('roleId', roleId.value); |
||||
|
} |
||||
|
|
||||
|
function pinchHandler(evt) { |
||||
|
// evt.scale代表两个手指缩放的比例 |
||||
|
console.log(`缩放:${evt.zoom}`); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.task-box { |
||||
|
border-radius: 24rpx; |
||||
|
} |
||||
|
.column { |
||||
|
padding: 24px 14px; |
||||
|
} |
||||
|
.task-column { |
||||
|
height: 33px; |
||||
|
} |
||||
|
.plugin { |
||||
|
margin-top: 8px; |
||||
|
margin-bottom: 8px; |
||||
|
margin-left: 15px; |
||||
|
border-left: 2px solid #d1d5db; |
||||
|
} |
||||
|
::v-deep .ml-2 { |
||||
|
margin-left: 16px; |
||||
|
} |
||||
|
|
||||
|
::v-deep .ml-3 { |
||||
|
margin-left: 20px; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,317 @@ |
|||||
|
<template> |
||||
|
<view class="u-font-14"> |
||||
|
<view |
||||
|
class="flex items-center justify-center rounded-full icon-column" |
||||
|
:style="{ color: orderStyle.color }" |
||||
|
@click="changeStatus(task.process, $event)" |
||||
|
> |
||||
|
<!-- 1进行中 2暂停中 3已完成 --> |
||||
|
<u-circle-progress |
||||
|
:percent="orderStyle.persent - 0" |
||||
|
:active-color="orderStyle.color" |
||||
|
bg-color="rgba(255,255,255,0)" |
||||
|
border-width="4" |
||||
|
:width="task.process !== 4 ? 66 : 50" |
||||
|
v-if="task.process === 1 || task.process === 2 || task.process === 3" |
||||
|
> |
||||
|
<view class="u-progress-content"> |
||||
|
<view class="u-progress-dot"></view> |
||||
|
<view class="u-progress-info"> |
||||
|
<u-icon :name="orderStyle.icon" v-if="orderStyle.icon" size="15px"></u-icon> |
||||
|
<template v-else>{{ data.durationText }}</template> |
||||
|
</view> |
||||
|
</view> |
||||
|
</u-circle-progress> |
||||
|
<!-- 0未开始 4添加任务 --> |
||||
|
<view class="flex items-center justify-center rounded-full progress-box" v-else :class="task.process === 4 ? 'progress-box-4' : ''"> |
||||
|
<view class="u-progress-content"> |
||||
|
<view class="u-progress-dot"></view> |
||||
|
<view class="u-progress-info"> |
||||
|
<span v-if="orderStyle.icon"> |
||||
|
<u-icon :name="orderStyle.icon" v-if="task.process !== 4" size="15px"></u-icon> |
||||
|
<u-icon :name="orderStyle.icon" v-else size="15px"></u-icon> |
||||
|
</span> |
||||
|
<template v-else>{{ data.durationText }}</template> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 遮罩 --> |
||||
|
<view class="mask" v-if="data.maskShow" @click="closeMask"></view> |
||||
|
<!-- 新建任务弹窗 --> |
||||
|
<CreateTask |
||||
|
:startTime="data.startTime" |
||||
|
:endTime="data.endTime" |
||||
|
:task="task" |
||||
|
:source="'regular'" |
||||
|
@showTime="showTime" |
||||
|
@closeMask="closeMask" |
||||
|
class="thirdPopup flex transition-transform" |
||||
|
v-if="data.createTaskShow" |
||||
|
/> |
||||
|
|
||||
|
<u-picker title="开始时间" mode="time" v-model="data.showStart" :params="data.params" @confirm="confirmStartTime"></u-picker> |
||||
|
<u-picker title="结束时间" mode="time" v-model="data.showEnd" :params="data.params" @confirm="confirmEndTime"></u-picker> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { reactive, onMounted, computed } from 'vue'; |
||||
|
import { useStore } from 'vuex'; |
||||
|
import dayjs from 'dayjs'; |
||||
|
import CreateTask from '../../Title/components/CreateTask.vue'; |
||||
|
|
||||
|
const props = defineProps({ task: { type: Object, default: () => {} } }); |
||||
|
|
||||
|
const data = reactive({ |
||||
|
time: '', |
||||
|
start: [{ text: '确认开始任务', color: 'blue' }], |
||||
|
pause: [{ text: '继续' }, { text: '重新开始任务', color: 'blue' }, { text: '结束' }], |
||||
|
proceed: [{ text: '暂停' }, { text: '重新开始任务', color: 'blue' }, { text: '结束' }], |
||||
|
again: [{ text: '重新开始任务', color: 'blue' }], |
||||
|
timer: null, |
||||
|
durationText: 0, |
||||
|
maskShow: false, // 遮罩显示 |
||||
|
createTaskShow: false, // 新建项目显示 |
||||
|
startTime: '', // 新建任务的开始时间 |
||||
|
endTime: '', // 新建任务的截止时间 |
||||
|
showStart: false, |
||||
|
showEnd: false, |
||||
|
params: { |
||||
|
year: true, |
||||
|
month: true, |
||||
|
day: true, |
||||
|
hour: true, |
||||
|
minute: true, |
||||
|
second: true, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
const store = useStore(); |
||||
|
const tip = computed(() => store.state.task.tip); |
||||
|
const status = computed(() => props.task ? props.task.process : 0); |
||||
|
const taskName = computed(() => props.task ? props.task.name : ''); |
||||
|
const taskId = computed(() => props.task ? props.task.id : ''); |
||||
|
|
||||
|
// 计算圆环的弧度百分比 |
||||
|
function computeCyclePersent() { |
||||
|
if (!props.task || !props.task.realStart || !props.task.planDuration) return 100; |
||||
|
const { realStart, planDuration } = props.task; |
||||
|
return (((Date.now() - +realStart) * 100) / +planDuration).toFixed(2); |
||||
|
} |
||||
|
|
||||
|
const orderStyle = computed(() => { |
||||
|
// 图标文本颜色 |
||||
|
// 任务状态 0未开始 1进行中 2暂停中 3已完成 |
||||
|
let color = '#9CA3AF'; |
||||
|
let icon = 'play-right-fill'; |
||||
|
let persent = 100; |
||||
|
switch (status.value) { |
||||
|
case 1: // 进行中 |
||||
|
color = '#60A5FA'; |
||||
|
icon = ''; |
||||
|
if (+computeCyclePersent() > 100) { |
||||
|
persent = 96; |
||||
|
} else { |
||||
|
persent = computeCyclePersent(); |
||||
|
} |
||||
|
break; |
||||
|
case 2: // 暂停中 |
||||
|
color = '#F87171'; |
||||
|
icon = 'pause'; |
||||
|
persent = 50; // TODO: 暂时这样 暂停状态没有计算剩余多少时间 |
||||
|
break; |
||||
|
case 3: // 已结束 |
||||
|
color = '#34D399'; |
||||
|
icon = 'checkmark'; |
||||
|
persent = 100; |
||||
|
break; |
||||
|
case 4: // 添加任务 |
||||
|
color = '#60A5FA'; |
||||
|
icon = 'plus'; |
||||
|
persent = 100; |
||||
|
break; |
||||
|
default: |
||||
|
// 未开始 |
||||
|
color = '#9CA3AF'; |
||||
|
icon = 'play-right'; |
||||
|
persent = 100; |
||||
|
break; |
||||
|
} |
||||
|
return { color, icon, persent }; |
||||
|
}); |
||||
|
|
||||
|
// unMounted(() => { |
||||
|
// if (data.timer) { |
||||
|
// clearInterval(data.timer); |
||||
|
// data.timer = null; |
||||
|
// } |
||||
|
// }); |
||||
|
|
||||
|
/** |
||||
|
* 计算tip的标题内容 |
||||
|
*/ |
||||
|
function genetateTips(type, content) { |
||||
|
if (type === 0) { |
||||
|
return `确认开始任务"${content}"吗?`; |
||||
|
} |
||||
|
if (type === 3) { |
||||
|
return '是否要重新开始此任务'; |
||||
|
} |
||||
|
return '请选择要执行的操作'; |
||||
|
} |
||||
|
|
||||
|
// 新建任务 |
||||
|
function addTask() { |
||||
|
// uni.$ui.showToast('新建任务'); |
||||
|
// 打开遮罩 |
||||
|
data.maskShow = true; |
||||
|
// 打开新建项目弹窗 |
||||
|
data.createTaskShow = true; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 点击了图标 修改任务状态 |
||||
|
* @param {object} event |
||||
|
*/ |
||||
|
function changeStatus(process, event) { |
||||
|
if (process === 4) { |
||||
|
addTask(); |
||||
|
return; |
||||
|
} |
||||
|
// return false; |
||||
|
tip.status = status; |
||||
|
tip.taskId = taskId; |
||||
|
tip.left = event.target.x; |
||||
|
tip.top = event.target.y; |
||||
|
tip.show = true; |
||||
|
tip.text = genetateTips(status, taskName); |
||||
|
store.commit('task/setTip', tip); |
||||
|
} |
||||
|
|
||||
|
// 点击遮罩,关闭弹窗 |
||||
|
function closeMask() { |
||||
|
// 关闭遮罩 |
||||
|
data.maskShow = false; |
||||
|
// 关闭新建项目弹窗 |
||||
|
data.createTaskShow = false; |
||||
|
} |
||||
|
|
||||
|
function showTime(type) { |
||||
|
if (type === 1) { |
||||
|
data.showStart = !data.showStart; |
||||
|
} else { |
||||
|
data.showEnd = !data.showEnd; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 选择开始时间 |
||||
|
function confirmStartTime(e) { |
||||
|
data.startTime = `${e.year}-${e.month}-${e.day} ${e.hour}:${e.minute}:${e.second}`; |
||||
|
data.showEnd = true; |
||||
|
} |
||||
|
|
||||
|
// 选择结束时间 |
||||
|
function confirmEndTime(e) { |
||||
|
data.endTime = `${e.year}-${e.month}-${e.day} ${e.hour}:${e.minute}:${e.second}`; |
||||
|
} |
||||
|
|
||||
|
// 计算进行中状态剩余时间 |
||||
|
// 预计结束时间 = realStart(实际开始) + planDuration(计划时长) |
||||
|
// 剩余时间 = 预计结束时间 - 当前时间 |
||||
|
// 剩余时间 = realStart + planDuration - Date.now() |
||||
|
function computeDurationText() { |
||||
|
const { realStart, planDuration } = props.task; |
||||
|
const leftTime = (realStart-0 || 0) + (planDuration-0 || 0) - Date.now(); // 剩余时间 |
||||
|
const { num, time } = uni.$time.computeDurationText(leftTime); |
||||
|
if (num <= 0) { |
||||
|
clearInterval(data.timer); |
||||
|
data.timer = null; |
||||
|
} |
||||
|
data.durationText = num; |
||||
|
return time; |
||||
|
} |
||||
|
|
||||
|
function updateDurationText(time) { |
||||
|
if (data.timer) { |
||||
|
clearInterval(data.timer); |
||||
|
data.timer = null; |
||||
|
} |
||||
|
if (!time) return; |
||||
|
setInterval(() => { |
||||
|
computeDurationText(); |
||||
|
}, time); |
||||
|
} |
||||
|
|
||||
|
onMounted(() => { |
||||
|
// TODO: 计算在不在窗口内显示 |
||||
|
const time = computeDurationText(); |
||||
|
updateDurationText(time); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style scoped lang="scss"> |
||||
|
.icon-column { |
||||
|
height: 33px; |
||||
|
width: 33px; |
||||
|
} |
||||
|
.one { |
||||
|
height: 33px; |
||||
|
width: 33px; |
||||
|
} |
||||
|
|
||||
|
.progress-box { |
||||
|
background: rgba(255, 255, 255, 0); |
||||
|
width: 33px; |
||||
|
height: 33px; |
||||
|
border: 2px solid #9ca3af; |
||||
|
} |
||||
|
|
||||
|
.progress-box-4 { |
||||
|
width: 25px; |
||||
|
height: 25px; |
||||
|
border: 2px solid #60a5fa; |
||||
|
} |
||||
|
|
||||
|
.mask { |
||||
|
width: 100%; |
||||
|
height: 100vh; |
||||
|
z-index: 21; |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
background: rgba(0, 0, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.thirdPopup { |
||||
|
background: #ffffff; |
||||
|
position: fixed; |
||||
|
left: 50%; |
||||
|
top: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
z-index: 33; |
||||
|
border-radius: 5px; |
||||
|
width: 90%; |
||||
|
} |
||||
|
|
||||
|
::v-deep .u-dropdown__content { |
||||
|
min-height: 120px !important; |
||||
|
height: auto !important; |
||||
|
overflow-y: auto; |
||||
|
background: #fff !important; |
||||
|
transition: none !important; |
||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
::v-deep .u-dropdown__menu__item .u-flex { |
||||
|
justify-content: space-between; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
flex-wrap: nowrap; |
||||
|
border: 1px solid #afbed1; |
||||
|
padding: 0 8px; |
||||
|
} |
||||
|
::v-deep .u-dropdown__content__mask { |
||||
|
display: none; |
||||
|
} |
||||
|
</style> |
@ -1,148 +1,148 @@ |
|||||
<template> |
<template> |
||||
<!-- <view class="flex flex-col h-full bg-gray-50" @click="openAuth"> --> |
<!-- <view class="flex flex-col h-full bg-gray-50" @click="openAuth"> --> |
||||
<view class="flex flex-col h-full bg-gray-50"> |
<view class="flex flex-col h-full bg-gray-50"> |
||||
<view class="relative"> |
<view class="relative"> |
||||
<!-- <view class="relative" @touchmove="onMove"> --> |
<!-- <view class="relative" @touchmove="onMove"> --> |
||||
<!-- 日历 --> |
<!-- 日历 --> |
||||
<Calendar @selected-change="onDateChange" :show-back="true" ref="calendar" @handleFindPoint="handleFindPoint" /> |
<Calendar @selected-change="onDateChange" :show-back="true" ref="calendar" @handleFindPoint="handleFindPoint" /> |
||||
<!-- 上传 导入wbs --> |
<!-- 上传 导入wbs --> |
||||
<Upload @success="onUploadSuccess" @error="onUploadError" /> |
<Upload @success="onUploadSuccess" @error="onUploadError" /> |
||||
</view> |
</view> |
||||
|
|
||||
<u-button class="mt-4" @click="toLogin">登录</u-button> |
<u-button class="mt-4" @click="toLogin">登录</u-button> |
||||
<!-- 项目列表 --> |
<!-- 项目列表 --> |
||||
<Projects @getProjects="getProjects" class="flex-1 overflow-y-auto" /> |
<Projects @getProjects="getProjects" class="flex-1 overflow-y-auto" /> |
||||
|
|
||||
<!-- 全局提示框 --> |
<!-- 全局提示框 --> |
||||
<u-top-tips ref="uTips"></u-top-tips> |
<u-top-tips ref="uTips"></u-top-tips> |
||||
</view> |
</view> |
||||
</template> |
</template> |
||||
|
|
||||
<script setup> |
<script setup> |
||||
import { reactive, computed, watchEffect, ref } from 'vue'; |
import { reactive, computed, watchEffect, ref } from 'vue'; |
||||
import { useStore } from 'vuex'; |
import { useStore } from 'vuex'; |
||||
import dayjs from 'dayjs'; |
import dayjs from 'dayjs'; |
||||
|
|
||||
const store = useStore(); |
const store = useStore(); |
||||
const token = computed(() => store.state.user.token); |
const token = computed(() => store.state.user.token); |
||||
const uTips = ref(null); |
const uTips = ref(null); |
||||
|
|
||||
const data = reactive({ |
const data = reactive({ |
||||
calendar: null, |
calendar: null, |
||||
// days: [], |
// days: [], |
||||
}); |
}); |
||||
|
|
||||
getProjects(); |
getProjects(); |
||||
handleFindPoint(); |
handleFindPoint(); |
||||
|
|
||||
// 监听token |
// 监听token |
||||
// watchEffect(() => { |
// watchEffect(() => { |
||||
// if (!token.value) return; |
// if (!token.value) return; |
||||
// if (token.value) { |
// if (token.value) { |
||||
// getProjects(); |
// getProjects(); |
||||
// handleFindPoint(); |
// handleFindPoint(); |
||||
// } |
// } |
||||
// }); |
// }); |
||||
|
|
||||
// 获取项目列表 |
// 获取项目列表 |
||||
function getProjects(start = dayjs().startOf('day').valueOf(), end = dayjs().endOf('day').valueOf()) { |
function getProjects(start = dayjs().startOf('day').valueOf(), end = dayjs().endOf('day').valueOf()) { |
||||
uni.$catchReq.getProjects(start, end, (err, data) => { |
uni.$catchReq.getProjects(start, end, (err, data) => { |
||||
if (err) { |
if (err) { |
||||
console.error('err: ', err); |
console.error('err: ', err); |
||||
} else { |
} else { |
||||
data.forEach(item => { |
data.forEach(item => { |
||||
item.show = false; |
item.show = false; |
||||
}); |
}); |
||||
store.commit('project/setProjects', data); |
store.commit('project/setProjects', data); |
||||
} |
} |
||||
}); |
}); |
||||
} |
} |
||||
|
|
||||
async function handleFindPoint(start, end) { |
async function handleFindPoint(start, end) { |
||||
try { |
try { |
||||
const startTime = start || dayjs().startOf('month').valueOf(); |
const startTime = start || dayjs().startOf('month').valueOf(); |
||||
const endTime = end || dayjs().endOf('month').valueOf(); |
const endTime = end || dayjs().endOf('month').valueOf(); |
||||
const res = await uni.$u.api.findRedPoint(startTime, endTime); |
const res = await uni.$u.api.findRedPoint(startTime, endTime); |
||||
store.commit('project/setDotList', res); |
store.commit('project/setDotList', res); |
||||
} catch (error) { |
} catch (error) { |
||||
console.log('error: ', error); |
console.log('error: ', error); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
// 点击了某个日期 |
// 点击了某个日期 |
||||
const onDateChange = event => { |
const onDateChange = event => { |
||||
const day = dayjs(event.fullDate); |
const day = dayjs(event.fullDate); |
||||
const start = day.startOf('date').valueOf(); |
const start = day.startOf('date').valueOf(); |
||||
const end = day.endOf('date').valueOf(); |
const end = day.endOf('date').valueOf(); |
||||
getProjects(start, end); |
getProjects(start, end); |
||||
}; |
}; |
||||
|
|
||||
// 导入成功 |
// 导入成功 |
||||
const onUploadSuccess = () => { |
const onUploadSuccess = () => { |
||||
uni.$ui.showToast('导入成功,即将打开新项目', 3000); |
uni.$ui.showToast('导入成功,即将打开新项目', 3000); |
||||
// uTips.show({ |
// uTips.show({ |
||||
// title: '导入成功,即将打开新项目', |
// title: '导入成功,即将打开新项目', |
||||
// type: 'success', |
// type: 'success', |
||||
// duration: '3000', |
// duration: '3000', |
||||
// }); |
// }); |
||||
}; |
}; |
||||
|
|
||||
// 导入失败 |
// 导入失败 |
||||
const onUploadError = error => { |
const onUploadError = error => { |
||||
uni.$ui.showToast('导入失败', 6000); |
uni.$ui.showToast('导入失败', 6000); |
||||
// uTips.show({ |
// uTips.show({ |
||||
// title: error || '导入失败', |
// title: error || '导入失败', |
||||
// type: 'error', |
// type: 'error', |
||||
// duration: '6000', |
// duration: '6000', |
||||
// }); |
// }); |
||||
}; |
}; |
||||
|
|
||||
// 监听触摸滑动 切换日历的模式 月/周 |
// 监听触摸滑动 切换日历的模式 月/周 |
||||
// function onMove(event) { |
// function onMove(event) { |
||||
// const y = event.changedTouches[0].pageY; |
// const y = event.changedTouches[0].pageY; |
||||
// const prevY = 0; |
// const prevY = 0; |
||||
// if (y - prevY > 0) { |
// if (y - prevY > 0) { |
||||
// // 向下滑动 如果是周视图weekMode=true 就 变成 月视图weekMode=false |
// // 向下滑动 如果是周视图weekMode=true 就 变成 月视图weekMode=false |
||||
// data.calendar.weekMode && (data.calendar.weekMode = false); |
// data.calendar.weekMode && (data.calendar.weekMode = false); |
||||
// } else if (y - prevY < 0) { |
// } else if (y - prevY < 0) { |
||||
// // 向上滑动 如果是月视图weekMode=false 就变成 周视图weekMode=true |
// // 向上滑动 如果是月视图weekMode=false 就变成 周视图weekMode=true |
||||
// !data.calendar.weekMode && (data.calendar.weekMode = true); |
// !data.calendar.weekMode && (data.calendar.weekMode = true); |
||||
// } |
// } |
||||
// prevY = y; |
// prevY = y; |
||||
// data.calendar.initDate(); |
// data.calendar.initDate(); |
||||
// } |
// } |
||||
|
|
||||
function toLogin() { |
function toLogin() { |
||||
uni.navigateTo({ |
uni.navigateTo({ |
||||
url: '/pages/user/login' |
url: '/pages/user/login' |
||||
}) |
}) |
||||
} |
} |
||||
</script> |
</script> |
||||
|
|
||||
<style> |
<style> |
||||
.content { |
.content { |
||||
display: flex; |
display: flex; |
||||
flex-direction: column; |
flex-direction: column; |
||||
align-items: center; |
align-items: center; |
||||
justify-content: center; |
justify-content: center; |
||||
} |
} |
||||
|
|
||||
.logo { |
.logo { |
||||
height: 200rpx; |
height: 200rpx; |
||||
width: 200rpx; |
width: 200rpx; |
||||
margin-top: 200rpx; |
margin-top: 200rpx; |
||||
margin-left: auto; |
margin-left: auto; |
||||
margin-right: auto; |
margin-right: auto; |
||||
margin-bottom: 50rpx; |
margin-bottom: 50rpx; |
||||
} |
} |
||||
|
|
||||
.text-area { |
.text-area { |
||||
display: flex; |
display: flex; |
||||
justify-content: center; |
justify-content: center; |
||||
} |
} |
||||
|
|
||||
.title { |
.title { |
||||
font-size: 36rpx; |
font-size: 36rpx; |
||||
color: #8f8f94; |
color: #8f8f94; |
||||
} |
} |
||||
</style> |
</style> |
||||
|
@ -0,0 +1,142 @@ |
|||||
|
<template> |
||||
|
<theme :style="{ height: height }" class="flex flex-col overflow-hidden u-font-14"> |
||||
|
<!-- 标题栏 --> |
||||
|
<Title /> |
||||
|
|
||||
|
<view class="container flex flex-col flex-1 mx-auto overflow-hidden bg-gray-100"> |
||||
|
<!-- 角色栏 --> |
||||
|
<Roles /> |
||||
|
|
||||
|
<!-- 日常任务面板 --> |
||||
|
<Globals /> |
||||
|
|
||||
|
<!-- 定期任务面板 --> |
||||
|
<TimeLine @getTasks="getTasks" class="flex-1 overflow-hidden" ref="timeLine" /> |
||||
|
|
||||
|
<!-- TODO: DEBUG: --> |
||||
|
<u-button @click="$store.commit('setTheme', 'theme-test')">测试切换主题</u-button> |
||||
|
</view> |
||||
|
</theme> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { ref, computed, watch, onMounted } from 'vue'; |
||||
|
import { useStore } from 'vuex'; |
||||
|
import useInit from '@/hooks/project/useInit'; |
||||
|
import useGetTasks from '@/hooks/project/useGetTasks'; |
||||
|
|
||||
|
const initHook = useInit(); |
||||
|
const getTasksHook = useGetTasks(); |
||||
|
const store = useStore(); |
||||
|
const roleId = computed(() => store.state.role.roleId); |
||||
|
const timeNode = computed(() => store.state.task.timeNode); |
||||
|
const timeUnit = computed(() => store.state.task.timeUnit); |
||||
|
const projectId = computed(() => store.getters['project/projectId']); |
||||
|
const userId = computed(() => store.getters['user/userId']); |
||||
|
const newProjectInfo = computed(() => store.state.task.newProjectInfo); |
||||
|
const showScrollTo = computed(() => store.state.task.showScrollTo); |
||||
|
const height = ref(null); |
||||
|
const timeLine = ref(null); |
||||
|
|
||||
|
onMounted(() => { |
||||
|
const system = uni.getSystemInfoSync(); |
||||
|
height.value = `${system.windowHeight}px`; |
||||
|
}); |
||||
|
|
||||
|
// 获取可变全局任务 |
||||
|
function getGlobalData() { |
||||
|
const param = { |
||||
|
roleId: roleId.value, |
||||
|
timeNode: timeNode.value, |
||||
|
timeUnit: timeUnit.value, |
||||
|
projectId: projectId.value, |
||||
|
}; |
||||
|
store.dispatch('task/getGlobal', param); |
||||
|
} |
||||
|
|
||||
|
// 清除已有的任务数据 |
||||
|
function clearTasksData() { |
||||
|
// 清空日常任务的数据 |
||||
|
store.commit('task/setPermanents', []); |
||||
|
store.commit('task/setDailyTasks', []); |
||||
|
// 清空定期任务数据 |
||||
|
store.commit('task/clearTasks'); |
||||
|
// 到顶的标志复位 |
||||
|
// 到底的标志复位 |
||||
|
store.commit('task/clearEndFlag'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 当时间基准点发生变化时 |
||||
|
* 重新根据时间和角色查询普通日常任务 |
||||
|
* 永久日常任务不发生 改变 |
||||
|
*/ |
||||
|
watch(timeNode, newValue => { |
||||
|
if (newValue && roleId.value) { |
||||
|
console.log('当时间基准点发生变化时'); |
||||
|
clearTasksData(); |
||||
|
getGlobalData(); // 查可变日常任务 |
||||
|
getTasksHook.initPlanTasks(); // 处理定期任务 |
||||
|
|
||||
|
// 滚动到对应位置 |
||||
|
let timer = null; |
||||
|
timer = setInterval(() => { |
||||
|
if (showScrollTo.value) { |
||||
|
clearInterval(timer); |
||||
|
console.log('timeLine: ', timeLine); |
||||
|
timeLine.value.setScrollPosition(); |
||||
|
} |
||||
|
}, 500); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* 当角色发生变化时 |
||||
|
* 重新查询永久日常任务和普通日常任务 |
||||
|
* 注意: 切换角色后 重新设置了时间基准点 时间基准点一定会变 |
||||
|
* 所以监听时间基准点获取 可变日常任务即可 这里不用获取 避免重复获取 |
||||
|
*/ |
||||
|
watch(roleId, newValue => { |
||||
|
if (newValue) { |
||||
|
console.log('当角色发生变化时', newValue); |
||||
|
store.commit('task/setTimeNode', Date.now()); |
||||
|
// 根据角色查找永久的日常任务 |
||||
|
const params = { |
||||
|
roleId: newValue, |
||||
|
projectId: projectId.value, |
||||
|
}; |
||||
|
store.dispatch('task/getPermanent', params); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
/** |
||||
|
* 当时间基准点发生变化时 |
||||
|
* 重新根据时间和角色查询普通日常任务 |
||||
|
* 永久日常任务不发生改变 |
||||
|
*/ |
||||
|
watch(newProjectInfo, newValue => { |
||||
|
console.log('当时间基准点发生变化时'); |
||||
|
if (newValue && newValue.value.projectId && newValue.value.url) { |
||||
|
uni.$u.route('/', { |
||||
|
u: userId.value, |
||||
|
p: newValue.value.projectId, |
||||
|
url: newValue.value.url, |
||||
|
}); |
||||
|
clearTasksData(); |
||||
|
store.commit('role/setRoleId', ''); |
||||
|
const options = uni.$route.query; |
||||
|
|
||||
|
initHook.init(options); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
function getTasks(params) { |
||||
|
getTasksHook.initPlanTasks(params); // 处理定期任务 |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.border-b { |
||||
|
border-bottom: 1px solid #e4e7ed; |
||||
|
} |
||||
|
</style> |
Loading…
Reference in new issue