归档、还原
This commit is contained in:
parent
7ed30309cd
commit
72ca5579fb
@ -369,20 +369,28 @@ class ProjectController extends AbstractController
|
||||
/**
|
||||
* 归档项目
|
||||
*
|
||||
* @apiParam {Number} project_id 项目ID
|
||||
* @apiParam {Number} project_id 项目ID
|
||||
* @apiParam {String} [type] 类型
|
||||
* - add:归档(默认)
|
||||
* - recovery:还原归档
|
||||
*/
|
||||
public function archived()
|
||||
{
|
||||
User::auth();
|
||||
//
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
$type = Request::input('type', 'add');
|
||||
//
|
||||
$project = Project::userProject($project_id);
|
||||
$project = Project::userProject($project_id, false);
|
||||
if (!$project->owner) {
|
||||
return Base::retError('你不是项目负责人');
|
||||
}
|
||||
//
|
||||
$project->archivedProject(Carbon::now());
|
||||
if ($type == 'recovery') {
|
||||
$project->archivedProject(null);
|
||||
} elseif ($type == 'add') {
|
||||
$project->archivedProject(Carbon::now());
|
||||
}
|
||||
return Base::retSuccess('设置成功', ['id' => $project->id]);
|
||||
}
|
||||
|
||||
@ -550,7 +558,7 @@ class ProjectController extends AbstractController
|
||||
{
|
||||
User::auth();
|
||||
//
|
||||
$builder = ProjectTask::with(['taskUser', 'taskTag'])->whereNull('archived_at');
|
||||
$builder = ProjectTask::with(['taskUser', 'taskTag']);
|
||||
//
|
||||
$parent_id = intval(Request::input('parent_id'));
|
||||
$project_id = intval(Request::input('project_id'));
|
||||
@ -903,20 +911,28 @@ class ProjectController extends AbstractController
|
||||
* 归档任务
|
||||
*
|
||||
* @apiParam {Number} task_id 任务ID
|
||||
* @apiParam {String} [type] 类型
|
||||
* - add:归档(默认)
|
||||
* - recovery:还原归档
|
||||
*/
|
||||
public function task__archived()
|
||||
{
|
||||
User::auth();
|
||||
//
|
||||
$task_id = intval(Request::input('task_id'));
|
||||
$type = Request::input('type', 'add');
|
||||
//
|
||||
$task = ProjectTask::userTask($task_id);
|
||||
$task = ProjectTask::userTask($task_id, [], false);
|
||||
//
|
||||
if ($task->parent_id > 0) {
|
||||
return Base::retError('子任务不支持此功能');
|
||||
}
|
||||
//
|
||||
$task->archivedTask(Carbon::now());
|
||||
if ($type == 'recovery') {
|
||||
$task->archivedTask(null);
|
||||
} elseif ($type == 'add') {
|
||||
$task->archivedTask(Carbon::now());
|
||||
}
|
||||
return Base::retSuccess('设置成功', ['id' => $task->id]);
|
||||
}
|
||||
|
||||
|
@ -298,6 +298,7 @@ class Project extends AbstractModel
|
||||
// 取消归档
|
||||
$this->archived_at = null;
|
||||
$this->addLog("项目取消归档");
|
||||
$this->pushMsg('add', $this->toArray());
|
||||
} else {
|
||||
// 归档任务
|
||||
$this->archived_at = $archived_at;
|
||||
@ -377,15 +378,16 @@ class Project extends AbstractModel
|
||||
/**
|
||||
* 根据用户获取项目信息(用于判断会员是否存在项目内)
|
||||
* @param int $project_id
|
||||
* @param bool $ignoreArchived 排除已归档
|
||||
* @return self
|
||||
*/
|
||||
public static function userProject($project_id)
|
||||
public static function userProject($project_id, $ignoreArchived = true)
|
||||
{
|
||||
$project = self::select(self::projectSelect)
|
||||
->authData()
|
||||
->whereNull('archived_at')
|
||||
->where('projects.id', intval($project_id))
|
||||
->first();
|
||||
$builder = self::select(self::projectSelect)->authData()->where('projects.id', intval($project_id));
|
||||
if ($ignoreArchived) {
|
||||
$builder->whereNull('projects.archived_at');
|
||||
}
|
||||
$project = $builder->first();
|
||||
if (empty($project)) {
|
||||
throw new ApiException('项目不存在或不在成员列表内');
|
||||
}
|
||||
|
@ -602,6 +602,10 @@ class ProjectTask extends AbstractModel
|
||||
// 取消归档
|
||||
$this->archived_at = null;
|
||||
$this->addLog("任务取消归档:" . $this->name);
|
||||
$this->pushMsg('add', [
|
||||
'new_column' => null,
|
||||
'task' => ProjectTask::with(['taskUser', 'taskTag'])->find($this->id)->toArray(),
|
||||
]);
|
||||
} else {
|
||||
// 归档任务
|
||||
$this->archived_at = $archived_at;
|
||||
@ -691,22 +695,22 @@ class ProjectTask extends AbstractModel
|
||||
* 根据会员ID获取任务、项目信息(用于判断会员是否存在项目内)
|
||||
* @param int $task_id
|
||||
* @param array $with
|
||||
* @param bool $ignoreArchived 排除已归档
|
||||
* @return self
|
||||
*/
|
||||
public static function userTask($task_id, $with = [])
|
||||
public static function userTask($task_id, $with = [], $ignoreArchived = true)
|
||||
{
|
||||
$task = self::with($with)->whereId(intval($task_id))->whereNull('archived_at')->first();
|
||||
$builder = self::with($with)->whereId(intval($task_id));
|
||||
if ($ignoreArchived) {
|
||||
$builder->whereNull('archived_at');
|
||||
}
|
||||
$task = $builder->first();
|
||||
if (empty($task)) {
|
||||
throw new ApiException('任务不存在');
|
||||
}
|
||||
//
|
||||
$project = Project::select(Project::projectSelect)
|
||||
->authData()
|
||||
->where('projects.id', $task->project_id)
|
||||
->first();
|
||||
if (empty($project)) {
|
||||
throw new ApiException('项目不存在或不在成员列表内');
|
||||
}
|
||||
Project::userProject($task->project_id, $ignoreArchived);
|
||||
//
|
||||
return $task;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueResizeObserver from "vue-resize-observer";
|
||||
import Vue from 'vue'
|
||||
Vue.use(VueResizeObserver);
|
||||
|
||||
export default {
|
||||
name: 'TableAction',
|
||||
props: {
|
||||
|
@ -13,8 +13,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenu slot="list">
|
||||
<DropdownItem v-for="(item, key) in menu" :key="key" :name="item.path">{{$L(item.name)}}</DropdownItem>
|
||||
<DropdownItem divided name="signout">{{$L('退出登录')}}</DropdownItem>
|
||||
<DropdownItem
|
||||
v-for="(item, key) in menu"
|
||||
v-if="!item.admin||userIsAdmin"
|
||||
:key="key"
|
||||
:name="item.path">{{$L(item.name)}}</DropdownItem>
|
||||
<DropdownItem divided name="signout" style="color:#f40">{{$L('退出登录')}}</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<ul>
|
||||
@ -105,15 +109,24 @@
|
||||
footer-hide>
|
||||
<TaskDetail :open-task="taskData"/>
|
||||
</Modal>
|
||||
|
||||
<!--查看归档项目-->
|
||||
<Drawer
|
||||
v-model="archivedProjectShow"
|
||||
:width="680"
|
||||
:title="$L('归档的项目')">
|
||||
<ProjectArchived v-if="archivedProjectShow"/>
|
||||
</Drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import TaskDetail from "./manage/components/TaskDetail";
|
||||
import ProjectArchived from "./manage/components/ProjectArchived";
|
||||
|
||||
export default {
|
||||
components: {TaskDetail},
|
||||
components: {ProjectArchived, TaskDetail},
|
||||
data() {
|
||||
return {
|
||||
loadIng: 0,
|
||||
@ -130,25 +143,16 @@ export default {
|
||||
columns: [],
|
||||
|
||||
menu: [
|
||||
{
|
||||
path: 'personal',
|
||||
name: '个人设置'
|
||||
},
|
||||
{
|
||||
path: 'password',
|
||||
name: '密码设置'
|
||||
},
|
||||
{
|
||||
path: 'system',
|
||||
name: '系统设置'
|
||||
},
|
||||
{
|
||||
path: 'priority',
|
||||
name: '任务等级'
|
||||
}
|
||||
{path: 'personal', admin: false, name: '个人设置'},
|
||||
{path: 'password', admin: false, name: '密码设置'},
|
||||
{path: 'system', admin: true, name: '系统设置'},
|
||||
{path: 'priority', admin: true, name: '任务等级'},
|
||||
{path: 'archivedProject', admin: false, name: '已归档项目'}
|
||||
],
|
||||
|
||||
openMenu: {}
|
||||
openMenu: {},
|
||||
|
||||
archivedProjectShow: false,
|
||||
}
|
||||
},
|
||||
|
||||
@ -165,6 +169,7 @@ export default {
|
||||
...mapState([
|
||||
'userId',
|
||||
'userInfo',
|
||||
'userIsAdmin',
|
||||
'dialogs',
|
||||
'projects',
|
||||
'taskId',
|
||||
@ -225,6 +230,9 @@ export default {
|
||||
}
|
||||
});
|
||||
return;
|
||||
} else if (path === 'archivedProject') {
|
||||
this.archivedProjectShow = true;
|
||||
return;
|
||||
}
|
||||
this.toggleRoute('setting/' + path);
|
||||
},
|
||||
|
151
resources/assets/js/pages/manage/components/ProjectArchived.vue
Normal file
151
resources/assets/js/pages/manage/components/ProjectArchived.vue
Normal file
@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div class="project-archived">
|
||||
<Table :columns="columns" :data="list" :no-data-text="$L(noText)"></Table>
|
||||
<Page
|
||||
class="page-box"
|
||||
:total="total"
|
||||
:current="page"
|
||||
:disabled="loadIng > 0"
|
||||
simple
|
||||
@on-change="setPage"
|
||||
@on-page-size-change="setPageSize"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ProjectArchived",
|
||||
data() {
|
||||
return {
|
||||
loadIng: 0,
|
||||
|
||||
columns: [],
|
||||
list: [],
|
||||
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
noText: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getLists();
|
||||
},
|
||||
methods: {
|
||||
initLanguage() {
|
||||
this.columns = [
|
||||
{
|
||||
title: this.$L('项目名称'),
|
||||
key: 'name',
|
||||
minWidth: 200,
|
||||
render: (h, {row}) => {
|
||||
return h('AutoTip', row.name);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$L('归档时间'),
|
||||
key: 'archived_at',
|
||||
width: 168,
|
||||
},
|
||||
{
|
||||
title: this.$L('归档会员'),
|
||||
key: 'archived_userid',
|
||||
minWidth: 100,
|
||||
render: (h, {row}) => {
|
||||
return h('UserAvatar', {
|
||||
props: {
|
||||
userid: row.archived_userid,
|
||||
size: 24,
|
||||
showName: true
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$L('操作'),
|
||||
align: 'center',
|
||||
width: 100,
|
||||
render: (h, params) => {
|
||||
let show = h('Poptip', {
|
||||
props: {
|
||||
title: this.$L('你确定要还原归档吗?'),
|
||||
confirm: true,
|
||||
transfer: true,
|
||||
placement: 'left',
|
||||
},
|
||||
style: {
|
||||
fontSize: '13px',
|
||||
cursor: 'pointer',
|
||||
color: '#2d8cf0',
|
||||
},
|
||||
on: {
|
||||
'on-ok': () => {
|
||||
this.recovery(params.row);
|
||||
}
|
||||
},
|
||||
}, this.$L('还原'));
|
||||
return h('TableAction', {
|
||||
props: {
|
||||
column: params.column
|
||||
}
|
||||
}, [
|
||||
show,
|
||||
]);
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
getLists() {
|
||||
this.loadIng++;
|
||||
this.$store.dispatch("call", {
|
||||
url: 'project/lists',
|
||||
data: {
|
||||
archived: 'yes',
|
||||
page: Math.max(this.page, 1),
|
||||
pagesize: Math.max($A.runNum(this.pageSize), 20),
|
||||
},
|
||||
}).then(({data}) => {
|
||||
this.loadIng--;
|
||||
this.page = data.current_page;
|
||||
this.total = data.total;
|
||||
this.list = data.data;
|
||||
this.noText = '没有相关的数据';
|
||||
}).catch(() => {
|
||||
this.loadIng--;
|
||||
this.noText = '数据加载失败';
|
||||
})
|
||||
},
|
||||
|
||||
setPage(page) {
|
||||
this.page = page;
|
||||
this.getLists();
|
||||
},
|
||||
|
||||
setPageSize(pageSize) {
|
||||
this.page = 1;
|
||||
this.pageSize = pageSize;
|
||||
this.getLists();
|
||||
},
|
||||
|
||||
recovery(row) {
|
||||
this.list = this.list.filter(({id}) => id != row.id);
|
||||
this.loadIng++;
|
||||
this.$store.dispatch("call", {
|
||||
url: 'project/archived',
|
||||
data: {
|
||||
project_id: row.id,
|
||||
type: 'recovery'
|
||||
},
|
||||
}).then(() => {
|
||||
this.loadIng--;
|
||||
this.getLists();
|
||||
this.$store.dispatch("getProjectOne", row.id);
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg);
|
||||
this.loadIng--;
|
||||
this.getLists();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -40,7 +40,7 @@
|
||||
</EDropdownMenu>
|
||||
<EDropdownMenu v-else slot="dropdown">
|
||||
<EDropdownItem command="archived_task">{{$L('已归档任务')}}</EDropdownItem>
|
||||
<EDropdownItem command="exit" style="color:#f40">{{$L('退出项目')}}</EDropdownItem>
|
||||
<EDropdownItem command="exit" divided style="color:#f40">{{$L('退出项目')}}</EDropdownItem>
|
||||
</EDropdownMenu>
|
||||
</EDropdown>
|
||||
</li>
|
||||
@ -344,6 +344,14 @@
|
||||
<Button type="primary" :loading="transferLoad > 0" @click="onTransfer">{{$L('移交')}}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<!--查看归档任务-->
|
||||
<Drawer
|
||||
v-model="archivedTaskShow"
|
||||
:width="680"
|
||||
:title="$L('归档的任务')">
|
||||
<TaskArchived v-if="archivedTaskShow" :project-id="projectId"/>
|
||||
</Drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -355,9 +363,10 @@ import {mapGetters, mapState} from "vuex";
|
||||
import UserInput from "../../../components/UserInput";
|
||||
import TaskAddSimple from "./TaskAddSimple";
|
||||
import TaskRow from "./TaskRow";
|
||||
import TaskArchived from "./TaskArchived";
|
||||
export default {
|
||||
name: "ProjectList",
|
||||
components: {TaskRow, Draggable, TaskAddSimple, UserInput, TaskAdd, TaskPriority},
|
||||
components: {TaskArchived, TaskRow, Draggable, TaskAddSimple, UserInput, TaskAdd, TaskPriority},
|
||||
data() {
|
||||
return {
|
||||
nowTime: Math.round(new Date().getTime() / 1000),
|
||||
@ -388,6 +397,8 @@ export default {
|
||||
transferShow: false,
|
||||
transferData: {},
|
||||
transferLoad: 0,
|
||||
|
||||
archivedTaskShow: false,
|
||||
}
|
||||
},
|
||||
|
||||
@ -928,6 +939,10 @@ export default {
|
||||
this.userShow = true;
|
||||
break;
|
||||
|
||||
case "archived_task":
|
||||
this.archivedTaskShow = true;
|
||||
break;
|
||||
|
||||
case "transfer":
|
||||
this.$set(this.transferData, 'owner_userid', [this.projectData.owner_userid]);
|
||||
this.transferShow = true;
|
||||
|
169
resources/assets/js/pages/manage/components/TaskArchived.vue
Normal file
169
resources/assets/js/pages/manage/components/TaskArchived.vue
Normal file
@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div class="task-archived">
|
||||
<Table :columns="columns" :data="list" :no-data-text="$L(noText)"></Table>
|
||||
<Page
|
||||
class="page-box"
|
||||
:total="total"
|
||||
:current="page"
|
||||
:disabled="loadIng > 0"
|
||||
simple
|
||||
@on-change="setPage"
|
||||
@on-page-size-change="setPageSize"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TaskArchived",
|
||||
props: {
|
||||
projectId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loadIng: 0,
|
||||
|
||||
columns: [],
|
||||
list: [],
|
||||
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
noText: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
watch: {
|
||||
projectId: {
|
||||
handler() {
|
||||
this.getLists();
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initLanguage() {
|
||||
this.columns = [
|
||||
{
|
||||
title: this.$L('任务名称'),
|
||||
key: 'name',
|
||||
minWidth: 200,
|
||||
render: (h, {row}) => {
|
||||
return h('AutoTip', row.name);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$L('归档时间'),
|
||||
key: 'archived_at',
|
||||
width: 168,
|
||||
},
|
||||
{
|
||||
title: this.$L('归档会员'),
|
||||
key: 'archived_userid',
|
||||
minWidth: 100,
|
||||
render: (h, {row}) => {
|
||||
return h('UserAvatar', {
|
||||
props: {
|
||||
userid: row.archived_userid,
|
||||
size: 24,
|
||||
showName: true
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
title: this.$L('操作'),
|
||||
align: 'center',
|
||||
width: 100,
|
||||
render: (h, params) => {
|
||||
let show = h('Poptip', {
|
||||
props: {
|
||||
title: this.$L('你确定要还原归档吗?'),
|
||||
confirm: true,
|
||||
transfer: true,
|
||||
placement: 'left',
|
||||
},
|
||||
style: {
|
||||
fontSize: '13px',
|
||||
cursor: 'pointer',
|
||||
color: '#2d8cf0',
|
||||
},
|
||||
on: {
|
||||
'on-ok': () => {
|
||||
this.recovery(params.row);
|
||||
}
|
||||
},
|
||||
}, this.$L('还原'));
|
||||
return h('TableAction', {
|
||||
props: {
|
||||
column: params.column
|
||||
}
|
||||
}, [
|
||||
show,
|
||||
]);
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
getLists() {
|
||||
if (!this.projectId) {
|
||||
return;
|
||||
}
|
||||
this.loadIng++;
|
||||
this.$store.dispatch("call", {
|
||||
url: 'project/task/lists',
|
||||
data: {
|
||||
project_id: this.projectId,
|
||||
archived: 'yes',
|
||||
page: Math.max(this.page, 1),
|
||||
pagesize: Math.max($A.runNum(this.pageSize), 20),
|
||||
},
|
||||
}).then(({data}) => {
|
||||
this.loadIng--;
|
||||
this.page = data.current_page;
|
||||
this.total = data.total;
|
||||
this.list = data.data;
|
||||
this.noText = '没有相关的数据';
|
||||
}).catch(() => {
|
||||
this.loadIng--;
|
||||
this.noText = '数据加载失败';
|
||||
})
|
||||
},
|
||||
|
||||
setPage(page) {
|
||||
this.page = page;
|
||||
this.getLists();
|
||||
},
|
||||
|
||||
setPageSize(pageSize) {
|
||||
this.page = 1;
|
||||
this.pageSize = pageSize;
|
||||
this.getLists();
|
||||
},
|
||||
|
||||
recovery(row) {
|
||||
this.list = this.list.filter(({id}) => id != row.id);
|
||||
this.loadIng++;
|
||||
this.$store.dispatch("call", {
|
||||
url: 'project/task/archived',
|
||||
data: {
|
||||
task_id: row.id,
|
||||
type: 'recovery'
|
||||
},
|
||||
}).then(() => {
|
||||
this.loadIng--;
|
||||
this.getLists();
|
||||
this.$store.dispatch("getTaskOne", row.id);
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg);
|
||||
this.loadIng--;
|
||||
this.getLists();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -13,6 +13,7 @@
|
||||
<ul>
|
||||
<li
|
||||
v-for="(item, key) in menu"
|
||||
v-if="!item.admin||userIsAdmin"
|
||||
:key="key"
|
||||
:class="classNameRoute(item.path)"
|
||||
@click="toggleRoute(item.path)">{{$L(item.name)}}</li>
|
||||
@ -35,30 +36,18 @@ export default {
|
||||
curPath: this.$route.path,
|
||||
|
||||
menu: [
|
||||
{
|
||||
path: 'personal',
|
||||
name: '个人设置'
|
||||
},
|
||||
{
|
||||
path: 'password',
|
||||
name: '密码设置'
|
||||
},
|
||||
{
|
||||
path: 'system',
|
||||
name: '系统设置'
|
||||
},
|
||||
{
|
||||
path: 'priority',
|
||||
name: '任务等级'
|
||||
}
|
||||
]
|
||||
{path: 'personal', admin: false, name: '个人设置'},
|
||||
{path: 'password', admin: false, name: '密码设置'},
|
||||
{path: 'system', admin: true, name: '系统设置'},
|
||||
{path: 'priority', admin: true, name: '任务等级'},
|
||||
],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
computed: {
|
||||
...mapState(['userInfo']),
|
||||
...mapState(['userInfo', 'userIsAdmin']),
|
||||
|
||||
titleNameRoute() {
|
||||
const {curPath, menu} = this;
|
||||
|
18
resources/assets/sass/pages/common.scss
vendored
18
resources/assets/sass/pages/common.scss
vendored
@ -240,12 +240,30 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
.ivu-drawer-content {
|
||||
border-radius: 18px 0 0 18px;
|
||||
.ivu-drawer-header {
|
||||
border-bottom: 1px solid transparent;
|
||||
padding-bottom: 5px;
|
||||
.ivu-drawer-header-inner {
|
||||
font-size: 18px;
|
||||
padding: 0 4px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*[hidden="hidden"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.page-box {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.icon-loading {
|
||||
animation: icon-loading-load 0.6s infinite linear;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user