mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-10 18:02:55 +00:00
feat: 添加应用排序功能
This commit is contained in:
parent
f6e4ed7c60
commit
d30b38d4b9
@ -36,6 +36,7 @@ use App\Models\UserFavorite;
|
|||||||
use App\Models\UserRecentItem;
|
use App\Models\UserRecentItem;
|
||||||
use App\Models\UserTag;
|
use App\Models\UserTag;
|
||||||
use App\Models\UserTagRecognition;
|
use App\Models\UserTagRecognition;
|
||||||
|
use App\Models\UserAppSort;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use App\Models\UserEmailVerification;
|
use App\Models\UserEmailVerification;
|
||||||
use App\Module\AgoraIO\AgoraTokenGenerator;
|
use App\Module\AgoraIO\AgoraTokenGenerator;
|
||||||
@ -3456,6 +3457,51 @@ class UsersController extends AbstractController
|
|||||||
return Base::retSuccess('删除成功');
|
return Base::retSuccess('删除成功');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} api/users/appsort 获取个人应用排序
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup users
|
||||||
|
* @apiName appsort
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function appsort()
|
||||||
|
{
|
||||||
|
$user = User::auth();
|
||||||
|
$sorts = UserAppSort::getSorts($user->userid);
|
||||||
|
return Base::retSuccess('success', [
|
||||||
|
'sorts' => $sorts,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} api/users/appsort/save 保存个人应用排序
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup users
|
||||||
|
* @apiName appsort__save
|
||||||
|
*
|
||||||
|
* @apiParam {Object} sorts 排序配置,示例:{"base":["micro:calendar"],"admin":["system:ldap"]}
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function appsort__save()
|
||||||
|
{
|
||||||
|
$user = User::auth();
|
||||||
|
$sorts = UserAppSort::normalizeSorts(Request::input('sorts'));
|
||||||
|
$record = UserAppSort::saveSorts($user->userid, $sorts);
|
||||||
|
return Base::retSuccess('保存成功', [
|
||||||
|
'sorts' => $record->sorts ?? $sorts,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} api/users/favorites 获取用户收藏列表
|
* @api {get} api/users/favorites 获取用户收藏列表
|
||||||
*
|
*
|
||||||
@ -3679,4 +3725,5 @@ class UsersController extends AbstractController
|
|||||||
//
|
//
|
||||||
return Base::retSuccess('success', ['favorited' => $isFavorited]);
|
return Base::retSuccess('success', ['favorited' => $isFavorited]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
96
app/Models/UserAppSort.php
Normal file
96
app/Models/UserAppSort.php
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App\Models\UserAppSort
|
||||||
|
*
|
||||||
|
* @property int $id
|
||||||
|
* @property int $userid 用户ID
|
||||||
|
* @property array|null $sorts 排序配置
|
||||||
|
* @property \Illuminate\Support\Carbon|null $created_at
|
||||||
|
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserAppSort newModelQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserAppSort newQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserAppSort query()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserAppSort whereCreatedAt($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserAppSort whereId($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserAppSort whereSorts($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserAppSort whereUpdatedAt($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|UserAppSort whereUserid($value)
|
||||||
|
* @mixin \Eloquent
|
||||||
|
*/
|
||||||
|
class UserAppSort extends AbstractModel
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'userid',
|
||||||
|
'sorts',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'sorts' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户排序配置
|
||||||
|
* @param int $userid
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getSorts(int $userid): array
|
||||||
|
{
|
||||||
|
$record = static::whereUserid($userid)->first();
|
||||||
|
if (!$record) {
|
||||||
|
return self::normalizeSorts([]);
|
||||||
|
}
|
||||||
|
return self::normalizeSorts($record->sorts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存排序配置
|
||||||
|
* @param int $userid
|
||||||
|
* @param array $sorts
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function saveSorts(int $userid, array $sorts): self
|
||||||
|
{
|
||||||
|
return static::updateOrCreate(
|
||||||
|
['userid' => $userid],
|
||||||
|
['sorts' => self::normalizeSorts($sorts)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规范化排序数据
|
||||||
|
* @param mixed $sorts
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function normalizeSorts($sorts): array
|
||||||
|
{
|
||||||
|
$result = [
|
||||||
|
'base' => [],
|
||||||
|
'admin' => [],
|
||||||
|
];
|
||||||
|
if (!is_array($sorts)) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
foreach (['base', 'admin'] as $group) {
|
||||||
|
$list = $sorts[$group] ?? [];
|
||||||
|
if (!is_array($list)) {
|
||||||
|
$list = [];
|
||||||
|
}
|
||||||
|
$normalized = [];
|
||||||
|
foreach ($list as $value) {
|
||||||
|
if (!is_string($value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$value = trim($value);
|
||||||
|
if ($value === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$normalized[] = $value;
|
||||||
|
}
|
||||||
|
$result[$group] = array_values(array_unique($normalized));
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateUserAppSortsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
if (Schema::hasTable('user_app_sorts')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::create('user_app_sorts', function (Blueprint $table) {
|
||||||
|
$table->bigIncrements('id');
|
||||||
|
$table->bigInteger('userid')->unique()->comment('用户ID');
|
||||||
|
$table->json('sorts')->nullable()->comment('排序配置');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('user_app_sorts');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,74 +7,116 @@
|
|||||||
<div class="apply-nav">
|
<div class="apply-nav">
|
||||||
<h1>{{ $L('应用') }}</h1>
|
<h1>{{ $L('应用') }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="apply-nav-actions">
|
||||||
|
<Dropdown trigger="click" placement="bottom-end" transfer @on-click="handleActionMenu">
|
||||||
|
<div class="apply-action-btn">
|
||||||
|
<Icon type="ios-more"/>
|
||||||
|
</div>
|
||||||
|
<DropdownMenu slot="list">
|
||||||
|
<DropdownItem v-if="!sortingMode" name="sort">{{ $L('调整排序') }}</DropdownItem>
|
||||||
|
<DropdownItem v-else name="cancelSort">{{ $L('退出排序') }}</DropdownItem>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="sortingMode" class="apply-sort-bar">
|
||||||
|
<div class="apply-sort-tip">
|
||||||
|
<Icon type="md-move"/>
|
||||||
|
<span>{{ $L('拖动卡片调整顺序,保存后仅自己可见') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="apply-sort-actions">
|
||||||
|
<Button @click="exitSortMode">{{ $L('取消') }}</Button>
|
||||||
|
<Button @click="restoreDefaultSort">{{ $L('恢复默认') }}</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!sortHasChanges"
|
||||||
|
:loading="appSortSaving"
|
||||||
|
@click="submitSort">
|
||||||
|
{{ $L('保存') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="apply-content">
|
<div class="apply-content">
|
||||||
<template v-for="t in applyTypes">
|
<template v-for="t in applyTypes">
|
||||||
<div v-if="isExistAdminList" class="apply-row-title">
|
<template v-if="t === 'base' || adminAppItems.length > 0">
|
||||||
{{ t == 'base' ? $L('常用') : $L('管理员') }}
|
<div
|
||||||
</div>
|
v-if="(t === 'base' && isExistAdminList) || t === 'admin'"
|
||||||
<Row :gutter="16">
|
class="apply-row-title">
|
||||||
<Col
|
{{ t === 'base' ? $L('常用') : $L('管理员') }}
|
||||||
v-for="(item, key) in (t == 'base' ? filterMicroAppsMenus : filterMicroAppsMenusAdmin)"
|
</div>
|
||||||
:key="`micro_` + key"
|
<Draggable
|
||||||
:xs="{ span: 6 }"
|
v-for="cards in [currentCards(t)]"
|
||||||
:sm="{ span: 6 }"
|
:key="`apps_${t}`"
|
||||||
:lg="{ span: 6 }"
|
tag="Row"
|
||||||
:xl="{ span: 6 }"
|
class="apply-sort-list"
|
||||||
:xxl="{ span: 3 }">
|
:list="cards"
|
||||||
<div class="apply-col">
|
:disabled="!sortingMode"
|
||||||
<div class="apply-item" @click="applyClick({value: 'microApp'}, item)">
|
:component-data="{ props: { gutter: 16 } }"
|
||||||
<div class="logo">
|
:options="getDraggableOptions(t)">
|
||||||
<div class="apply-icon no-dark-content" :style="{backgroundImage: `url(${item.icon})`}"></div>
|
<Col
|
||||||
</div>
|
v-for="card in cards"
|
||||||
<p>{{ item.label }}</p>
|
:key="card.sortKey"
|
||||||
</div>
|
class="apply-col-wrapper"
|
||||||
</div>
|
:xs="{ span: 6 }"
|
||||||
</Col>
|
:sm="{ span: 6 }"
|
||||||
<Col
|
:lg="{ span: 6 }"
|
||||||
v-for="(item, key) in applyList"
|
:xl="{ span: 6 }"
|
||||||
:key="key"
|
:xxl="{ span: 3 }">
|
||||||
v-if="((t=='base' && !item.type) || item.type == t) && item.show !== false"
|
<div class="apply-col">
|
||||||
:xs="{ span: 6 }"
|
<template v-if="card.category === 'micro'">
|
||||||
:sm="{ span: 6 }"
|
<div class="apply-item" :class="{'is-sorting': sortingMode}" @click="handleCardClick(card)">
|
||||||
:lg="{ span: 6 }"
|
|
||||||
:xl="{ span: 6 }"
|
|
||||||
:xxl="{ span: 3 }">
|
|
||||||
<div class="apply-col">
|
|
||||||
<template v-if="item.value === 'exportManage'">
|
|
||||||
<EPopover
|
|
||||||
v-model="exportPopoverShow"
|
|
||||||
trigger="click"
|
|
||||||
placement="bottom"
|
|
||||||
popperClass="apply-export-popover"
|
|
||||||
:transfer="true">
|
|
||||||
<div slot="reference" class="apply-item">
|
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<div class="apply-icon no-dark-content" :class="getLogoClass(item.value)"></div>
|
<div class="apply-icon no-dark-content" :style="{backgroundImage: `url(${card.micro.icon})`}"></div>
|
||||||
</div>
|
</div>
|
||||||
<p>{{ $L(item.label) }}</p>
|
<p>{{ card.micro.label }}</p>
|
||||||
</div>
|
</div>
|
||||||
<ul class="apply-export-menu">
|
</template>
|
||||||
<li @click="handleExport('task')">{{ $L('导出任务统计') }}</li>
|
<template v-else>
|
||||||
<li @click="handleExport('overdue')">{{ $L('导出超期任务') }}</li>
|
<template v-if="card.system.value === 'exportManage' && !sortingMode">
|
||||||
<li @click="handleExport('approve')">{{ $L('导出审批数据') }}</li>
|
<EPopover
|
||||||
<li @click="handleExport('checkin')">{{ $L('导出签到数据') }}</li>
|
v-model="exportPopoverShow"
|
||||||
</ul>
|
trigger="click"
|
||||||
</EPopover>
|
placement="bottom"
|
||||||
</template>
|
popperClass="apply-export-popover"
|
||||||
<div v-else class="apply-item" @click="applyClick(item)">
|
:transfer="true">
|
||||||
<div class="logo">
|
<div slot="reference" class="apply-item" :class="{'is-sorting': sortingMode}">
|
||||||
<div class="apply-icon no-dark-content" :class="getLogoClass(item.value)"></div>
|
<div class="logo">
|
||||||
<div @click.stop="applyClick(item, 'badge')" class="apply-box-top-report">
|
<div class="apply-icon no-dark-content" :class="getLogoClass(card.system.value)"></div>
|
||||||
<Badge v-if="showBadge(item,'approve')" :overflow-count="999" :count="approveUnreadNumber"/>
|
</div>
|
||||||
<Badge v-if="showBadge(item,'report')" :overflow-count="999" :count="reportUnreadNumber"/>
|
<p>{{ $L(card.system.label) }}</p>
|
||||||
|
</div>
|
||||||
|
<ul class="apply-export-menu">
|
||||||
|
<li @click="handleExport('task')">{{ $L('导出任务统计') }}</li>
|
||||||
|
<li @click="handleExport('overdue')">{{ $L('导出超期任务') }}</li>
|
||||||
|
<li @click="handleExport('approve')">{{ $L('导出审批数据') }}</li>
|
||||||
|
<li @click="handleExport('checkin')">{{ $L('导出签到数据') }}</li>
|
||||||
|
</ul>
|
||||||
|
</EPopover>
|
||||||
|
</template>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="apply-item"
|
||||||
|
:class="{'is-sorting': sortingMode}"
|
||||||
|
@click="handleCardClick(card)">
|
||||||
|
<div class="logo">
|
||||||
|
<div class="apply-icon no-dark-content" :class="getLogoClass(card.system.value)"></div>
|
||||||
|
<div
|
||||||
|
v-if="!sortingMode"
|
||||||
|
@click.stop="handleCardClick(card, 'badge')"
|
||||||
|
class="apply-box-top-report">
|
||||||
|
<Badge v-if="showBadge(card.system,'approve')" :overflow-count="999" :count="approveUnreadNumber"/>
|
||||||
|
<Badge v-if="showBadge(card.system,'report')" :overflow-count="999" :count="reportUnreadNumber"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>{{ $L(card.system.label) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
<p>{{ $L(item.label) }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Col>
|
||||||
</Col>
|
</Draggable>
|
||||||
</Row>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -289,9 +331,11 @@ import SystemAppPush from "./setting/components/SystemAppPush";
|
|||||||
import emitter from "../../store/events";
|
import emitter from "../../store/events";
|
||||||
import ImgUpload from "../../components/ImgUpload.vue";
|
import ImgUpload from "../../components/ImgUpload.vue";
|
||||||
import {webhookEventOptions} from "../../utils/webhook";
|
import {webhookEventOptions} from "../../utils/webhook";
|
||||||
|
import Draggable from "vuedraggable";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
Draggable,
|
||||||
ImgUpload,
|
ImgUpload,
|
||||||
UserSelect,
|
UserSelect,
|
||||||
DrawerOverlay,
|
DrawerOverlay,
|
||||||
@ -306,6 +350,22 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
applyTypes: ['base', 'admin'],
|
applyTypes: ['base', 'admin'],
|
||||||
|
sortingMode: false,
|
||||||
|
sortLists: {
|
||||||
|
base: [],
|
||||||
|
admin: [],
|
||||||
|
},
|
||||||
|
sortInitialLists: {
|
||||||
|
base: [],
|
||||||
|
admin: [],
|
||||||
|
},
|
||||||
|
appSorts: {
|
||||||
|
base: [],
|
||||||
|
admin: [],
|
||||||
|
},
|
||||||
|
appSortLoaded: false,
|
||||||
|
appSortLoading: false,
|
||||||
|
appSortSaving: false,
|
||||||
//
|
//
|
||||||
mybotShow: false,
|
mybotShow: false,
|
||||||
mybotList: [],
|
mybotList: [],
|
||||||
@ -337,6 +397,9 @@ export default {
|
|||||||
sendType: '',
|
sendType: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchAppSorts();
|
||||||
|
},
|
||||||
activated() {
|
activated() {
|
||||||
this.$store.dispatch("updateMicroAppsStatus")
|
this.$store.dispatch("updateMicroAppsStatus")
|
||||||
},
|
},
|
||||||
@ -349,6 +412,7 @@ export default {
|
|||||||
'approveUnreadNumber',
|
'approveUnreadNumber',
|
||||||
'cacheDialogs',
|
'cacheDialogs',
|
||||||
'windowOrientation',
|
'windowOrientation',
|
||||||
|
'windowPortrait',
|
||||||
'formOptions',
|
'formOptions',
|
||||||
'routeLoading',
|
'routeLoading',
|
||||||
'microAppsIds'
|
'microAppsIds'
|
||||||
@ -359,6 +423,7 @@ export default {
|
|||||||
]),
|
]),
|
||||||
applyList() {
|
applyList() {
|
||||||
const list = [
|
const list = [
|
||||||
|
// 常用应用
|
||||||
{value: "approve", label: "审批中心", sort: 30, show: this.microAppsIds.includes('approve')},
|
{value: "approve", label: "审批中心", sort: 30, show: this.microAppsIds.includes('approve')},
|
||||||
{value: "favorite", label: "我的收藏", sort: 45},
|
{value: "favorite", label: "我的收藏", sort: 45},
|
||||||
{value: "recent", label: "最近打开", sort: 47},
|
{value: "recent", label: "最近打开", sort: 47},
|
||||||
@ -372,6 +437,14 @@ export default {
|
|||||||
{value: "addProject", label: "创建项目", sort: 110},
|
{value: "addProject", label: "创建项目", sort: 110},
|
||||||
{value: "addTask", label: "添加任务", sort: 120},
|
{value: "addTask", label: "添加任务", sort: 120},
|
||||||
{value: "scan", label: "扫一扫", sort: 130, show: $A.isEEUIApp},
|
{value: "scan", label: "扫一扫", sort: 130, show: $A.isEEUIApp},
|
||||||
|
|
||||||
|
// 管理员应用
|
||||||
|
{type: 'admin', value: "ldap", label: "LDAP", sort: 160, show: this.userIsAdmin},
|
||||||
|
{type: 'admin', value: "mail", label: "邮件通知", sort: 170, show: this.userIsAdmin},
|
||||||
|
{type: 'admin', value: "appPush", label: "APP 推送", sort: 180, show: this.userIsAdmin},
|
||||||
|
{type: 'admin', value: "complaint", label: "举报管理", sort: 190, show: this.userIsAdmin},
|
||||||
|
{type: 'admin', value: "exportManage", label: "数据导出", sort: 195, show: this.userIsAdmin},
|
||||||
|
{type: 'admin', value: "allUser", label: "团队管理", sort: 200, show: this.userIsAdmin},
|
||||||
]
|
]
|
||||||
// 竖屏模式
|
// 竖屏模式
|
||||||
if (this.windowPortrait) {
|
if (this.windowPortrait) {
|
||||||
@ -381,25 +454,293 @@ export default {
|
|||||||
{value: "setting", label: "设置", sort: 140},
|
{value: "setting", label: "设置", sort: 140},
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
// 管理员
|
|
||||||
if (this.userIsAdmin) {
|
|
||||||
list.push(...[
|
|
||||||
{type: 'admin', value: "ldap", label: "LDAP", sort: 160},
|
|
||||||
{type: 'admin', value: "mail", label: "邮件通知", sort: 170},
|
|
||||||
{type: 'admin', value: "appPush", label: "APP 推送", sort: 180},
|
|
||||||
{type: 'admin', value: "complaint", label: "举报管理", sort: 190},
|
|
||||||
{type: 'admin', value: "exportManage", label: "数据导出", sort: 195},
|
|
||||||
{type: 'admin', value: "allUser", label: "团队管理", sort: 200},
|
|
||||||
])
|
|
||||||
}
|
|
||||||
//
|
//
|
||||||
return list.sort((a, b) => a.sort - b.sort);
|
return list.sort((a, b) => a.sort - b.sort);
|
||||||
},
|
},
|
||||||
isExistAdminList() {
|
isExistAdminList() {
|
||||||
return this.filterMicroAppsMenusAdmin.length > 0 || this.applyList.map(h => h.type).indexOf('admin') !== -1;
|
return this.adminAppItems.length > 0;
|
||||||
|
},
|
||||||
|
baseAppItems() {
|
||||||
|
return this.applySavedSort(this.collectAppItems('base'), 'base');
|
||||||
|
},
|
||||||
|
adminAppItems() {
|
||||||
|
return this.applySavedSort(this.collectAppItems('admin'), 'admin');
|
||||||
|
},
|
||||||
|
sortHasChanges() {
|
||||||
|
if (!this.sortingMode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const groups = ['base', 'admin'];
|
||||||
|
return groups.some(group => {
|
||||||
|
const current = (this.sortLists[group] || []).map(item => item.sortKey);
|
||||||
|
const initial = this.sortInitialLists[group] || [];
|
||||||
|
if (current.length !== initial.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return current.some((key, index) => key !== initial[index]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
sortingMode(val) {
|
||||||
|
if (val) {
|
||||||
|
this.bootstrapSortLists();
|
||||||
|
} else {
|
||||||
|
this.resetSortState();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
baseAppItems() {
|
||||||
|
if (this.sortingMode) {
|
||||||
|
this.mergeSortListWithSource('base');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
adminAppItems() {
|
||||||
|
if (this.sortingMode) {
|
||||||
|
this.mergeSortListWithSource('admin');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
handleActionMenu(action) {
|
||||||
|
if (action === 'sort') {
|
||||||
|
this.enterSortMode();
|
||||||
|
} else if (action === 'cancelSort') {
|
||||||
|
this.exitSortMode();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currentCards(type) {
|
||||||
|
return this.sortingMode ? (this.sortLists[type] || []) : this.getDisplayItems(type);
|
||||||
|
},
|
||||||
|
getDisplayItems(type) {
|
||||||
|
return type === 'admin' ? this.adminAppItems : this.baseAppItems;
|
||||||
|
},
|
||||||
|
collectAppItems(group) {
|
||||||
|
const items = [];
|
||||||
|
const microSource = group === 'admin' ? this.filterMicroAppsMenusAdmin : this.filterMicroAppsMenus;
|
||||||
|
microSource.forEach(menu => {
|
||||||
|
if (!menu || menu.show === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
items.push(this.createMicroCard(menu, group));
|
||||||
|
});
|
||||||
|
this.applyList.forEach(item => {
|
||||||
|
if (item.show === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isAdminItem = item.type === 'admin';
|
||||||
|
if (group === 'admin') {
|
||||||
|
if (!isAdminItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (isAdminItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
items.push(this.createSystemCard(item, group));
|
||||||
|
});
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
createMicroCard(menu, group) {
|
||||||
|
const fallback = menu?.id || menu?.value || menu?.url || menu?.label || 'unknown';
|
||||||
|
const key = menu?.name || fallback;
|
||||||
|
return {
|
||||||
|
sortKey: `micro:${key}`,
|
||||||
|
category: 'micro',
|
||||||
|
group,
|
||||||
|
micro: menu,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
createSystemCard(item, group) {
|
||||||
|
return {
|
||||||
|
sortKey: `system:${item.value}`,
|
||||||
|
category: 'system',
|
||||||
|
group,
|
||||||
|
system: item,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
applySavedSort(items, group) {
|
||||||
|
const saved = this.appSorts[group] || [];
|
||||||
|
if (!saved.length) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
const map = {};
|
||||||
|
items.forEach(card => {
|
||||||
|
map[card.sortKey] = card;
|
||||||
|
});
|
||||||
|
const ordered = [];
|
||||||
|
saved.forEach(key => {
|
||||||
|
if (map[key]) {
|
||||||
|
ordered.push(map[key]);
|
||||||
|
delete map[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
items.forEach(card => {
|
||||||
|
if (map[card.sortKey]) {
|
||||||
|
ordered.push(card);
|
||||||
|
delete map[card.sortKey];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ordered;
|
||||||
|
},
|
||||||
|
async enterSortMode() {
|
||||||
|
if (this.sortingMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.appSortLoaded && !this.appSortLoading) {
|
||||||
|
await this.fetchAppSorts();
|
||||||
|
}
|
||||||
|
this.sortingMode = true;
|
||||||
|
},
|
||||||
|
exitSortMode() {
|
||||||
|
this.sortingMode = false;
|
||||||
|
},
|
||||||
|
bootstrapSortLists() {
|
||||||
|
const base = this.cloneAppItems(this.baseAppItems);
|
||||||
|
const admin = this.cloneAppItems(this.adminAppItems);
|
||||||
|
this.$set(this.sortLists, 'base', base);
|
||||||
|
this.$set(this.sortLists, 'admin', admin);
|
||||||
|
this.$set(this.sortInitialLists, 'base', base.map(item => item.sortKey));
|
||||||
|
this.$set(this.sortInitialLists, 'admin', admin.map(item => item.sortKey));
|
||||||
|
},
|
||||||
|
resetSortState() {
|
||||||
|
this.$set(this.sortLists, 'base', []);
|
||||||
|
this.$set(this.sortLists, 'admin', []);
|
||||||
|
this.$set(this.sortInitialLists, 'base', []);
|
||||||
|
this.$set(this.sortInitialLists, 'admin', []);
|
||||||
|
},
|
||||||
|
mergeSortListWithSource(group) {
|
||||||
|
const source = this.cloneAppItems(this.getDisplayItems(group));
|
||||||
|
if (!source.length) {
|
||||||
|
this.$set(this.sortLists, group, []);
|
||||||
|
this.$set(this.sortInitialLists, group, []);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sourceMap = new Map(source.map(item => [item.sortKey, item]));
|
||||||
|
const next = [];
|
||||||
|
(this.sortLists[group] || []).forEach(item => {
|
||||||
|
if (sourceMap.has(item.sortKey)) {
|
||||||
|
next.push(sourceMap.get(item.sortKey));
|
||||||
|
sourceMap.delete(item.sortKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sourceMap.forEach(item => next.push(item));
|
||||||
|
this.$set(this.sortLists, group, this.cloneAppItems(next));
|
||||||
|
const snapshot = this.sortInitialLists[group] ? [...this.sortInitialLists[group]] : [];
|
||||||
|
next.forEach(item => {
|
||||||
|
if (!snapshot.includes(item.sortKey)) {
|
||||||
|
snapshot.push(item.sortKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.$set(this.sortInitialLists, group, snapshot);
|
||||||
|
},
|
||||||
|
cloneAppItems(items = []) {
|
||||||
|
return items.map(item => Object.assign({}, item));
|
||||||
|
},
|
||||||
|
getDraggableOptions(type) {
|
||||||
|
return {
|
||||||
|
animation: 200,
|
||||||
|
draggable: '.apply-col-wrapper',
|
||||||
|
group: {
|
||||||
|
name: `${type}-apps`,
|
||||||
|
pull: false,
|
||||||
|
put: false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async fetchAppSorts() {
|
||||||
|
if (this.appSortLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.appSortLoading = true;
|
||||||
|
try {
|
||||||
|
const {data} = await this.$store.dispatch("call", {
|
||||||
|
url: 'users/appsort',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
this.appSorts = this.normalizeSortPayload(data?.sorts);
|
||||||
|
} catch (error) {
|
||||||
|
const msg = error?.msg || error?.message;
|
||||||
|
msg && console.warn(msg);
|
||||||
|
} finally {
|
||||||
|
this.appSortLoading = false;
|
||||||
|
this.appSortLoaded = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
normalizeSortPayload(raw) {
|
||||||
|
const result = {base: [], admin: []};
|
||||||
|
if (!raw || typeof raw !== 'object') {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
['base', 'admin'].forEach(group => {
|
||||||
|
const list = Array.isArray(raw[group]) ? raw[group] : [];
|
||||||
|
result[group] = list
|
||||||
|
.filter(item => typeof item === 'string')
|
||||||
|
.map(item => item.trim())
|
||||||
|
.filter(item => item.length > 0);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
submitSort() {
|
||||||
|
if (!this.sortHasChanges) {
|
||||||
|
this.exitSortMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const payload = this.buildSortPayload();
|
||||||
|
this.appSortSaving = true;
|
||||||
|
this.$store.dispatch("call", {
|
||||||
|
url: 'users/appsort/save',
|
||||||
|
method: 'post',
|
||||||
|
data: {
|
||||||
|
sorts: payload,
|
||||||
|
}
|
||||||
|
}).then(({data, msg}) => {
|
||||||
|
this.appSorts = this.normalizeSortPayload(data?.sorts || payload);
|
||||||
|
this.exitSortMode();
|
||||||
|
$A.messageSuccess(msg || this.$L('保存成功'));
|
||||||
|
}).catch(({msg}) => {
|
||||||
|
$A.modalError(msg || this.$L('保存失败'));
|
||||||
|
}).finally(() => {
|
||||||
|
this.appSortSaving = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
restoreDefaultSort() {
|
||||||
|
if (!this.sortingMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
['base', 'admin'].forEach(group => {
|
||||||
|
this.$set(this.sortLists, group, this.cloneAppItems(this.collectAppItems(group)));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
buildSortPayload() {
|
||||||
|
const payload = {base: [], admin: []};
|
||||||
|
['base', 'admin'].forEach(group => {
|
||||||
|
const keys = (this.sortLists[group] || []).map(item => item.sortKey);
|
||||||
|
const defaults = this.getDefaultSortKeys(group);
|
||||||
|
payload[group] = this.arraysEqual(keys, defaults) ? [] : keys;
|
||||||
|
});
|
||||||
|
return payload;
|
||||||
|
},
|
||||||
|
getDefaultSortKeys(group) {
|
||||||
|
return this.collectAppItems(group).map(item => item.sortKey);
|
||||||
|
},
|
||||||
|
arraysEqual(a = [], b = []) {
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return a.every((item, index) => item === b[index]);
|
||||||
|
},
|
||||||
|
handleCardClick(card, params = '') {
|
||||||
|
if (this.sortingMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!card) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (card.category === 'micro') {
|
||||||
|
this.applyClick({value: 'microApp'}, card.micro);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.applyClick(card.system, params);
|
||||||
|
},
|
||||||
normalizeWebhookEvents(events = [], useFallback = false) {
|
normalizeWebhookEvents(events = [], useFallback = false) {
|
||||||
if (!Array.isArray(events)) {
|
if (!Array.isArray(events)) {
|
||||||
events = events ? [events] : [];
|
events = events ? [events] : [];
|
||||||
|
|||||||
65
resources/assets/sass/pages/page-apply.scss
vendored
65
resources/assets/sass/pages/page-apply.scss
vendored
@ -30,6 +30,52 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.apply-nav-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.apply-action-btn {
|
||||||
|
font-size: 26px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px;
|
||||||
|
color: #6f6f6f;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all .2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $primary-title-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.apply-sort-bar {
|
||||||
|
margin: 16px 32px 0;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px dashed rgba($primary-color, 0.4);
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: mix(#ffffff, $primary-color, 92%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.apply-sort-tip {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: $primary-color;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apply-sort-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.apply-content {
|
.apply-content {
|
||||||
@ -39,7 +85,6 @@
|
|||||||
|
|
||||||
.apply-row-title {
|
.apply-row-title {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> div.apply-row-title:nth-last-child(2) {
|
> div.apply-row-title:nth-last-child(2) {
|
||||||
@ -83,11 +128,23 @@
|
|||||||
top: -16px;
|
top: -16px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.is-sorting {
|
||||||
|
border-style: dashed;
|
||||||
|
border-color: $primary-color;
|
||||||
|
background: rgba($primary-color, 0.05);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width <= 510px) {
|
@media (width <= 510px) {
|
||||||
|
.apply-sort-bar {
|
||||||
|
margin: 12px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.apply-row-title {
|
.apply-row-title {
|
||||||
margin-bottom: 8px !important;
|
margin-bottom: 8px !important;
|
||||||
}
|
}
|
||||||
@ -531,7 +588,11 @@ body.window-portrait {
|
|||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
|
|
||||||
.apply-head {
|
.apply-head {
|
||||||
margin: 24px 24px 0 24px;
|
margin: 24px 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apply-sort-bar {
|
||||||
|
margin: 16px 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.apply-content {
|
.apply-content {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user