Merge branch 'develop' of ssh://git.gezi.vip:6006/gx/dootask

# Conflicts:
#	electron/package.json
#	package.json
#	public/js/app.js
This commit is contained in:
kuaifan 2022-02-24 08:07:40 +08:00
commit 35b1c12bb5
19 changed files with 189 additions and 24 deletions

View File

@ -1747,4 +1747,31 @@ class ProjectController extends AbstractController
//
return Base::retSuccess('success', $list);
}
/**
* @api {get} api/project/top 37. 项目置顶
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup project
* @apiName top
*
* @apiParam {Number} project_id 项目ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function top()
{
$user = User::auth();
$projectId = intval(Request::input('project_id'));
$projectUser = ProjectUser::whereUserid($user->userid)->whereProjectId($projectId)->first();
if (!$projectUser) {
return Base::retError("项目不存在");
}
$projectUser->top_at = $projectUser->top_at ? null : Carbon::now();
$projectUser->save();
return Base::retSuccess("success", $projectId);
}
}

View File

@ -113,6 +113,7 @@ class Project extends AbstractModel
->select([
'projects.*',
'project_users.owner',
'project_users.top_at',
])
->leftJoin('project_users', function ($leftJoin) use ($userid) {
$leftJoin
@ -136,6 +137,7 @@ class Project extends AbstractModel
->select([
'projects.*',
'project_users.owner',
'project_users.top_at',
])
->join('project_users', 'projects.id', '=', 'project_users.project_id')
->where('project_users.userid', $userid);

View File

@ -11,6 +11,7 @@ use App\Module\Base;
* @property int|null $project_id 项目ID
* @property int|null $userid 成员ID
* @property int|null $owner 是否负责人
* @property \Illuminate\Support\Carbon|null $top_at 置顶时间
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Project|null $project

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ProjectUsersAddTopAt extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('project_users', function (Blueprint $table) {
if (!Schema::hasColumn('project_users', 'top_at')) {
$table->timestamp('top_at')->nullable()->after('owner')->comment('置顶时间');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('project_users', function (Blueprint $table) {
$table->dropColumn("top_at");
});
}
}

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -447,5 +447,6 @@
{"CN": "取消置顶","EN": "Cancel","TC": "取消置頂","KM": "លប់ចោល","TH": "ยกเลิก","KO": "취소","JA": "キャンセル"},
{"CN": "置顶该聊天","EN": "Top the chat","TC": "置頂該聊天","KM": "បញ្ចូលការជជែក","TH": "ด้านบนการแชท","KO": "채팅 상단","JA": "トップチャット"},
{"CN": "请输入正确的邀请码","EN": "Please enter the correct invitation code","TC": "請輸入正確的邀請碼","KM": "សូមបញ្ចូលលេខកូដអញ្ជើញត្រឹមត្រូវ","TH": "โปรดป้อนรหัสเชิญที่ถูกต้อง","KO": "올바른 초대 코드를 입력하십시오","JA": "正しい招待状コードを入力してください"},
{"CN": "置顶","EN": "Topping","TC": "置頂","KM": "ការបញ្ចូល","TH": "โรยหน้า","KO": "토핑","JA": "トッピング"},
]
})(window)

View File

@ -54,7 +54,7 @@
<DropdownItem divided name="signout" style="color:#f40">{{$L('退出登录')}}</DropdownItem>
</DropdownMenu>
</Dropdown>
<ul class="overlay-y">
<ul class="overlay-y" @scroll="listScroll()">
<li @click="toggleRoute('dashboard')" :class="classNameRoute('dashboard')">
<i class="taskfont">&#xe6fb;</i>
<div class="menu-title">{{$L('仪表盘')}}</div>
@ -73,13 +73,20 @@
<i class="taskfont">&#xe6f3;</i>
<div class="menu-title">{{$L('文件')}}</div>
</li>
<li class="menu-project">
<li class="menu-project" ref="projectWrapper">
<ul>
<li
v-for="(item, key) in projectLists"
:key="key"
:class="classNameRoute('project/' + item.id, openMenu[item.id])"
@click="toggleRoute('project/' + item.id)">
@click="toggleRoute('project/' + item.id)"
@contextmenu.prevent.stop="handleRightClick($event, item)"
>
<div
:class="{
top: item.top_at,
operate: item.id == topOperateItem.id && topOperateVisible
}">
<div class="project-h1">
<em @click.stop="toggleOpenMenu(item.id)"></em>
<div class="title">{{item.name}}</div>
@ -97,9 +104,24 @@
<Progress :percent="item.task_percent" :stroke-width="6" />
</p>
</div>
</div>
</li>
</ul>
<Loading v-if="loadIng > 0"/>
<div class="top-operate" :style="topOperateStyles">
<Dropdown
trigger="custom"
:visible="topOperateVisible"
transfer-class-name="page-file-dropdown-menu"
@on-clickoutside="handleClickTopOperateOutside"
transfer>
<DropdownMenu slot="list">
<DropdownItem @click.native="handleTopClick">
{{ $L(topOperateItem.top_at ? '取消置顶' : '置顶该项目') }}
</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</li>
</ul>
<div
@ -287,6 +309,9 @@ export default {
reportTabs: "my",
reportUnreadNumber: 0,
topOperateStyles: {},
topOperateVisible: false,
topOperateItem: {},
}
},
@ -411,6 +436,9 @@ export default {
projectLists() {
const {projectKeyValue, cacheProjects} = this;
const data = cacheProjects.sort((a, b) => {
if (a.top_at || b.top_at) {
return $A.Date(b.top_at) - $A.Date(a.top_at);
}
return b.id - a.id;
});
if (projectKeyValue) {
@ -782,6 +810,40 @@ export default {
}
document.addEventListener(visibilityChangeEvent, visibilityChangeListener);
},
handleRightClick(event, item) {
this.handleClickTopOperateOutside();
this.topOperateItem = $A.isJson(item) ? item : {};
this.$nextTick(() => {
const projectWrap = this.$refs.projectWrapper;
const projectBounding = projectWrap.getBoundingClientRect();
this.topOperateStyles = {
left: `${event.clientX - projectBounding.left}px`,
top: `${event.clientY - projectBounding.top}px`
};
this.topOperateVisible = true;
})
},
handleClickTopOperateOutside() {
this.topOperateVisible = false;
},
handleTopClick() {
this.$store.dispatch("call", {
url: 'project/top',
data: {
project_id: this.topOperateItem.id,
},
}).then(() => {
this.$store.dispatch("getProjects").catch(() => {});
this.$Modal.remove();
}).catch(({msg}) => {
$A.modalError(msg, 301);
this.$Modal.remove();
});
},
listScroll() {
this.topOperateVisible = false;
}
}
}
</script>

View File

@ -5,6 +5,7 @@
<div class="project-titbox">
<div class="project-title">
<h1>{{projectData.name}}</h1>
<label v-if="projectData.top_at" class="top-text">{{$L('置顶')}}</label>
<div v-if="projectLoad > 0" class="project-load"><Loading/></div>
</div>
<ul class="project-icons">

View File

@ -19,10 +19,10 @@
<i class="taskfont">&#xe603;</i>
</div>
</li>
<li>
<div class="block-title">{{$L('参与的项目')}}</div>
<li @click="dashboard='all'">
<div class="block-title">{{$L('待完成任务')}}</div>
<div class="block-data">
<div class="block-num">{{cacheProjects.length}}</div>
<div class="block-num">{{dashboardTask.all.length}}</div>
<i class="taskfont">&#xe6f9;</i>
</div>
</li>
@ -62,7 +62,7 @@
<i class="taskfont">&#xe71f;</i>
<em>{{item.sub_complete}}/{{item.sub_num}}</em>
</div>
<ETooltip :content="item.end_at" placement="right">
<ETooltip v-if="item.end_at" :content="item.end_at" placement="right">
<div :class="['item-icon', item.today ? 'today' : '', item.overdue ? 'overdue' : '']">
<i class="taskfont">&#xe71d;</i>
<em>{{expiresFormat(item.end_at)}}</em>
@ -121,6 +121,8 @@ export default {
return this.$L('今日任务');
case 'overdue':
return this.$L('超期任务');
case 'all':
return this.$L('待完成任务');
default:
return '';
}
@ -136,9 +138,12 @@ export default {
case 'overdue':
data = this.transforTasks(this.dashboardTask.overdue);
break
case 'all':
data = this.transforTasks(this.dashboardTask.all);
break
}
return data.sort((a, b) => {
return $A.Date(a.end_at) - $A.Date(b.end_at);
return $A.Date(a.end_at || "2099-12-31 23:59:59") - $A.Date(b.end_at || "2099-12-31 23:59:59");
});
},
},

View File

@ -1116,23 +1116,25 @@ export default {
state.cacheLoading["loadDashboardTasks"] = true;
//
const time = $A.Time()
const {today, overdue} = getters.dashboardTask;
const {today, overdue,all} = getters.dashboardTask;
const currentIds = today.map(({id}) => id)
currentIds.push(...overdue.map(({id}) => id))
currentIds.push(...all.map(({id}) => id))
//
let loadIng = 2;
let loadIng = 3;
let call = () => {
if (loadIng <= 0) {
state.cacheLoading["loadDashboardTasks"] = false;
//
const {today, overdue} = getters.dashboardTask;
const {today, overdue,all} = getters.dashboardTask;
const newIds = today.filter(task => task._time >= time).map(({id}) => id)
newIds.push(...overdue.filter(task => task._time >= time).map(({id}) => id))
newIds.push(...all.filter(task => task._time >= time).map(({id}) => id))
dispatch("forgetTask", currentIds.filter(v => newIds.indexOf(v) == -1))
return;
}
loadIng--;
if (loadIng == 1) {
if (loadIng == 2) {
// 获取今日任务
dispatch("getTasks", {
complete: "no",
@ -1141,12 +1143,17 @@ export default {
$A.formatDate("Y-m-d 23:59:59")
],
}).then(call).catch(call)
} else if (loadIng == 0) {
} else if (loadIng == 1) {
// 获取过期任务
dispatch("getTasks", {
complete: "no",
time_before: $A.formatDate("Y-m-d H:i:s"),
}).then(call).catch(call)
} else if((loadIng == 0)) {
// 获取待处理任务
dispatch("getTasks", {
complete: "no",
}).then(call).catch(call)
}
}
call();

View File

@ -112,7 +112,7 @@ export default {
/**
* 仪表盘任务数据
* @param state
* @returns {{overdue: *, today: *}}
* @returns {{overdue: *, today: *,all:*}}
*/
dashboardTask(state) {
const todayStart = $A.Date($A.formatDate("Y-m-d 00:00:00")),
@ -125,9 +125,6 @@ export default {
if (task.complete_at && chackCompleted === true) {
return false;
}
if (!task.end_at) {
return false;
}
return task.owner;
}
let array = state.cacheTasks.filter(task => filterTask(task));
@ -142,11 +139,13 @@ export default {
return (start <= todayStart && todayStart <= end) || (start <= todayEnd && todayEnd <= end) || (start > todayStart && todayEnd > end);
})
const overdueTasks = array.filter(task => {
return $A.Date(task.end_at) <= todayNow;
return task.end_at && $A.Date(task.end_at) <= todayNow;
})
return {
today: todayTasks,
overdue: overdueTasks,
all: array
}
},
}

View File

@ -29,6 +29,17 @@
height: 22px;
}
}
.top-text{
width: 40px;
height: 24px;
border-radius:4px;
margin-left: 10px;
background-color: #8BCF70;
color:#FFFFFF;
text-align: center;
margin-top: 4px;
padding-top: 2px
}
}
.project-icons {
display: flex;

View File

@ -226,6 +226,12 @@
display: block;
}
}
.top {
background-color: #EEEFF1;
}
.operate {
border:1px solid $primary-color;
}
}
}
.common-loading {
@ -233,6 +239,13 @@
width: 22px;
height: 22px;
}
.top-operate {
position: absolute;
top: 0;
right: 0;
opacity: 0;
display: flex;
}
}
&.active {
background-color: #ffffff;

View File

@ -78,6 +78,7 @@
width: 100%;
overflow-x: hidden;
overflow-y: auto;
padding-right: 9px;
> ul {
&.dialog {
> li {
@ -192,7 +193,7 @@
transform: scale(0.8);
}
&.top {
background-color: #f1f6fb;
background-color: #EEEFF1;
}
&.active {
background-color: #F4F5F7;

View File

@ -447,5 +447,6 @@
{"CN": "取消置顶","EN": "Cancel","TC": "取消置頂","KM": "លប់ចោល","TH": "ยกเลิก","KO": "취소","JA": "キャンセル"},
{"CN": "置顶该聊天","EN": "Top the chat","TC": "置頂該聊天","KM": "បញ្ចូលការជជែក","TH": "ด้านบนการแชท","KO": "채팅 상단","JA": "トップチャット"},
{"CN": "请输入正确的邀请码","EN": "Please enter the correct invitation code","TC": "請輸入正確的邀請碼","KM": "សូមបញ្ចូលលេខកូដអញ្ជើញត្រឹមត្រូវ","TH": "โปรดป้อนรหัสเชิญที่ถูกต้อง","KO": "올바른 초대 코드를 입력하십시오","JA": "正しい招待状コードを入力してください"},
{"CN": "置顶","EN": "Topping","TC": "置頂","KM": "ការបញ្ចូល","TH": "โรยหน้า","KO": "토핑","JA": "トッピング"},
]
})(window)