update user page style
This commit is contained in:
@@ -28,7 +28,11 @@
|
||||
<telephone-link :value="managedUser.user.phoneNo" placeholder="未填写" />
|
||||
</template>
|
||||
</t-cell>
|
||||
<t-cell title="上次登录时间" :note="Time.toPassedDateTime(managedUser.user.lastLoginAt)" />
|
||||
<t-cell title="上次登录时间">
|
||||
<template #note>
|
||||
<pass-time-label :timestamp="managedUser.user.lastLoginAt" underline />
|
||||
</template>
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
|
||||
<t-cell-group v-if="currentUser" theme="card">
|
||||
@@ -46,33 +50,23 @@
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
|
||||
<section v-if="currentUser" class="registration-actions bg-white">
|
||||
<header>
|
||||
<h4 class="title">可登记事件</h4>
|
||||
</header>
|
||||
<div class="actions">
|
||||
<t-tag
|
||||
v-for="item in scopeList"
|
||||
:key="item"
|
||||
variant="light-outline"
|
||||
theme="success"
|
||||
:content="item"
|
||||
/>
|
||||
<div v-if="!scopeList.length" class="empty-card">未配置</div>
|
||||
</div>
|
||||
</section>
|
||||
<t-button v-can="permissionCodes.user.update" class="edit" theme="primary" @click="toEdit">编辑用户</t-button>
|
||||
<t-cell-group v-if="currentUser" title="可登记事件" theme="card">
|
||||
<t-cell v-for="item in scopeList" :key="item.id" :title="item.name"/>
|
||||
<t-cell v-if="!scopeList.length" title="未配置" />
|
||||
</t-cell-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Toast } from "tdesign-mobile-vue";
|
||||
import { Time, type User, UserAPI } from "timi-web";
|
||||
import { PassTimeLabel, type User, UserAPI } from "timi-web";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import ManagedUserAPI from "@/api/ManagedUserAPI";
|
||||
import type { ManagedUser } from "@/types/ManagedUser";
|
||||
import type { EventItem } from "@/types/EventItem";
|
||||
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { useUserStore } from "timi-web";
|
||||
import { buildFallbackAvatar } from "@/utils/avatar";
|
||||
import { buildEffectiveRoleCodeSet, getManagedUserRoleList, getRoleDisplayName } from "@/utils/role";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
@@ -83,6 +77,8 @@ defineOptions({
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const navBarStore = useNavBarStore();
|
||||
const userStore = useUserStore();
|
||||
const permissionStore = usePermissionSettingsStore();
|
||||
|
||||
// ---------- 用户数据 ----------
|
||||
@@ -110,9 +106,7 @@ const scopeList = computed(() => {
|
||||
if (!item) {
|
||||
return [];
|
||||
}
|
||||
return eventList.value
|
||||
.filter(action => canEventItem(action, item))
|
||||
.map(item => item.name);
|
||||
return eventList.value.filter(action => canEventItem(action, item));
|
||||
});
|
||||
|
||||
// ---------- 导航 ----------
|
||||
@@ -146,7 +140,7 @@ function isUser(user?: Partial<User>): user is User {
|
||||
// ---------- 事件范围 ----------
|
||||
|
||||
function canEventItem(action: EventItem, item: ManagedUser): boolean {
|
||||
const userRoleCodeSet = buildEffectiveRoleCodeSet(item);
|
||||
const userRoleCodeSet = buildEffectiveRoleCodeSet(item, permissionStore.roleList);
|
||||
const roleCodeList = action.roleCodeList || [];
|
||||
if (!roleCodeList.length) {
|
||||
return false;
|
||||
@@ -158,22 +152,45 @@ function canEventItem(action: EventItem, item: ManagedUser): boolean {
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
const routeId = String(route.params.id || "");
|
||||
const [detail, actions] = await Promise.all([
|
||||
const [detail] = await Promise.all([
|
||||
ManagedUserAPI.detail(routeId),
|
||||
EventAPI.listValid(),
|
||||
permissionStore.loadAll()
|
||||
]);
|
||||
managedUser.value = detail;
|
||||
eventList.value = actions || [];
|
||||
if (!userStore.hasPermissions(permissionCodes.event.read)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await EventAPI.list({ index: 0, size: 1000 }, { storeId: detail.storeId });
|
||||
eventList.value = (result.list || []).filter(isValidEvent);
|
||||
} catch {
|
||||
eventList.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
function isValidEvent(event: EventItem): boolean {
|
||||
if (event.status !== "ACTIVE") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
return (event.beginAt === undefined || event.beginAt <= now)
|
||||
&& (event.endAt === undefined || now <= event.endAt);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await loadData();
|
||||
if (userStore.hasPermissions(permissionCodes.user.update)) {
|
||||
navBarStore.setRightText("编辑", "var(--td-brand-color)", toEdit);
|
||||
}
|
||||
} catch {
|
||||
Toast.error("用户详情加载失败");
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => navBarStore.clearRight());
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -189,34 +206,6 @@ onMounted(async () => {
|
||||
border-radius: .875rem;
|
||||
}
|
||||
|
||||
.registration-actions {
|
||||
gap: .5rem;
|
||||
margin: 0 1rem;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
border-radius: 1rem;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
|
||||
.empty-card {
|
||||
color: #52607a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit {
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
@@ -229,10 +218,6 @@ onMounted(async () => {
|
||||
|
||||
p,
|
||||
.info-list li span,
|
||||
.empty-card {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.action {
|
||||
color: #e2e8f0;
|
||||
background: rgba(15, 23, 42, .72);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
<div class="picker-text" v-text="selectedStoreName" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<t-button class="save" theme="primary" :loading="isSaving" @click="saveUser">保存</t-button>
|
||||
</div>
|
||||
<t-popup v-model="storePickerVisible" placement="bottom">
|
||||
<t-picker
|
||||
@@ -32,6 +31,7 @@ import { Toast } from "tdesign-mobile-vue";
|
||||
import { useUserStore } from "timi-web";
|
||||
import ManagedUserAPI from "@/api/ManagedUserAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { ManagedUser } from "@/types/ManagedUser";
|
||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
|
||||
@@ -43,6 +43,7 @@ defineOptions({
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const navBarStore = useNavBarStore();
|
||||
const userStore = useUserStore();
|
||||
const currentStoreStore = useCurrentStoreStore();
|
||||
|
||||
@@ -205,12 +206,17 @@ function validateForm(): boolean {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (userStore.hasPermissions(savePermissionCode.value)) {
|
||||
navBarStore.setRightText("保存", "var(--td-brand-color)", saveUser);
|
||||
}
|
||||
try {
|
||||
await fillForm();
|
||||
} catch {
|
||||
Toast.error("用户数据加载失败");
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => navBarStore.clearRight());
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -225,12 +231,6 @@ onMounted(async () => {
|
||||
color: var(--td-text-color-primary);
|
||||
}
|
||||
|
||||
.save {
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
|
||||
@@ -25,11 +25,13 @@ const code = String(route.query.code || "");
|
||||
const expireAt = Number(route.query.expireAt || 0);
|
||||
const storeName = String(route.query.storeName || "");
|
||||
const roleName = String(route.query.roleName || "");
|
||||
const inviterName = String(route.query.inviterName || "未命名用户");
|
||||
const registerLink = `${window.location.origin}/gao/invite/register?code=${encodeURIComponent(code)}`;
|
||||
const posterOptions = computed(() => ({
|
||||
code: registerLink,
|
||||
storeName,
|
||||
roleName,
|
||||
inviterName,
|
||||
expireAt
|
||||
}));
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
class="item"
|
||||
v-for="item in pageResult.list"
|
||||
:key="item.id"
|
||||
:description="`可登记事件:${getActionScope(item)}`"
|
||||
arrow
|
||||
@click="toDetail(item)"
|
||||
>
|
||||
@@ -38,14 +37,17 @@
|
||||
<t-tag class="meta" size="small" theme="success" variant="outline" :content="getRoleName(item)" />
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<telephone-link :value="item.user?.phoneNo" />
|
||||
</template>
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
<div v-if="!pageResult.list.length" class="load-state" v-text="isLoading ? '正在加载用户' : '暂无用户'" />
|
||||
<div v-if="pageResult.list.length" class="load-state" v-text="isNoMore ? '已经到底了' : '上拉加载更多'" />
|
||||
</t-pull-down-refresh>
|
||||
<div class="actions">
|
||||
<t-button v-can="permissionCodes.user.create" theme="light" type="button" @click="toCreate">新增用户</t-button>
|
||||
<t-button v-can="permissionCodes.user.create" theme="primary" type="button" @click="inviteVisible = true">邀请注册</t-button>
|
||||
<t-button v-can="permissionCodes.user.create" theme="primary" type="button" @click="toCreate">新增用户</t-button>
|
||||
</div>
|
||||
</div>
|
||||
<user-invite v-model:visible="inviteVisible" :store-id="selectedStoreId === allStoreFilterValue ? undefined : selectedStoreId" />
|
||||
@@ -54,14 +56,12 @@
|
||||
<script setup lang="ts">
|
||||
import { Toast, type DropdownValue } from "tdesign-mobile-vue";
|
||||
import { type Page, type PageResult, UserAPI, type User, useUserStore } from "timi-web";
|
||||
import EventAPI from "@/api/EventAPI";
|
||||
import ManagedUserAPI from "@/api/ManagedUserAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import type { ManagedUser } from "@/types/ManagedUser";
|
||||
import type { EventItem } from "@/types/EventItem";
|
||||
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||
import { buildFallbackAvatar } from "@/utils/avatar";
|
||||
import { buildEffectiveRoleCodeSet, getManagedUserRoleList, getRoleDisplayName } from "@/utils/role";
|
||||
import { getManagedUserRoleList, getRoleDisplayName } from "@/utils/role";
|
||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||
import UserInvite from "@/pages/user/component/UserInvite.vue";
|
||||
|
||||
@@ -103,7 +103,6 @@ const userListOptions = computed(() => {
|
||||
|
||||
// ---------- 列表数据 ----------
|
||||
|
||||
const eventList = ref<EventItem[]>([]);
|
||||
const isRefreshing = ref(false);
|
||||
const isLoading = ref(false);
|
||||
const isNoMore = ref(false);
|
||||
@@ -126,24 +125,6 @@ function getRoleName(item: ManagedUser): string {
|
||||
return getRoleDisplayName(getManagedUserRoleList(item)[0], permissionStore.roleList);
|
||||
}
|
||||
|
||||
function getActionScope(item: ManagedUser): string {
|
||||
const nameList = eventList.value
|
||||
.filter(action => canEventItem(action, item))
|
||||
.map(action => action.name);
|
||||
return nameList.join("、") || "未配置";
|
||||
}
|
||||
|
||||
// ---------- 事件范围 ----------
|
||||
|
||||
function canEventItem(action: EventItem, item: ManagedUser): boolean {
|
||||
const userRoleCodeSet = buildEffectiveRoleCodeSet(item);
|
||||
const roleCodeList = action.roleCodeList || [];
|
||||
if (!roleCodeList.length) {
|
||||
return false;
|
||||
}
|
||||
return roleCodeList.some(code => userRoleCodeSet.has(code));
|
||||
}
|
||||
|
||||
// ---------- 头像 ----------
|
||||
|
||||
function buildUserAvatar(item: ManagedUser): string {
|
||||
@@ -168,16 +149,14 @@ async function loadStoreScope(): Promise<void> {
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
await loadStoreScope();
|
||||
const [userResult, actions] = await Promise.all([
|
||||
const [userResult] = await Promise.all([
|
||||
ManagedUserAPI.list(page, userListOptions.value),
|
||||
EventAPI.listValid(),
|
||||
permissionStore.loadAll()
|
||||
]);
|
||||
pageResult.total = userResult.total;
|
||||
pageResult.list = userResult.list || [];
|
||||
page.index = 1;
|
||||
isNoMore.value = pageResult.list.length >= pageResult.total || pageResult.list.length < page.size;
|
||||
eventList.value = actions || [];
|
||||
}
|
||||
|
||||
async function refresh(): Promise<void> {
|
||||
|
||||
@@ -34,15 +34,6 @@
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
<t-empty v-else-if="!isLoading" description="暂无可授权角色" />
|
||||
<t-button
|
||||
v-can="userRoleWriteAccess"
|
||||
class="save"
|
||||
theme="primary"
|
||||
:loading="isSaving"
|
||||
@click="saveRoleList"
|
||||
>
|
||||
保存授权
|
||||
</t-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -52,6 +43,7 @@ import { Toast } from "tdesign-mobile-vue";
|
||||
import { RoleAPI, type ModuleCode, type Role as UserRole, useUserStore } from "timi-web";
|
||||
import ManagedUserAPI from "@/api/ManagedUserAPI";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { Role } from "@/store/permissionSettingsStore";
|
||||
import type { ManagedUser } from "@/types/ManagedUser";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
@@ -62,6 +54,7 @@ defineOptions({
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const navBarStore = useNavBarStore();
|
||||
const moduleCode: ModuleCode = "GAO";
|
||||
const allStoreFilterValue = "__ALL_STORES__";
|
||||
|
||||
@@ -283,12 +276,20 @@ function setRoleChecked(roleId: string, value: boolean): void {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (
|
||||
userStore.hasPermissions(corePermissionCodes.userRole.create)
|
||||
&& userStore.hasPermissions(corePermissionCodes.userRole.delete)
|
||||
) {
|
||||
navBarStore.setRightText("保存", "var(--td-brand-color)", saveRoleList);
|
||||
}
|
||||
try {
|
||||
await loadData();
|
||||
} catch {
|
||||
Toast.error("用户角色加载失败");
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => navBarStore.clearRight());
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -307,12 +308,6 @@ onMounted(async () => {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.save {
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
|
||||
@@ -130,7 +130,8 @@ async function generateInvite(): Promise<void> {
|
||||
code: result.code,
|
||||
expireAt: String(result.expireAt),
|
||||
storeName: isGlobalManager.value ? selectedStoreName.value : currentStoreStore.currentStoreName,
|
||||
roleName: selectedRoleName.value
|
||||
roleName: selectedRoleName.value,
|
||||
inviterName: getCurrentUserName()
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
@@ -138,6 +139,11 @@ async function generateInvite(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentUserName(): string {
|
||||
const user = userStore.loginUser.user;
|
||||
return user?.nick || user?.name || "未命名用户";
|
||||
}
|
||||
|
||||
watch(() => props.visible, async visible => {
|
||||
if (!visible) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user