refine Semi UI account governance
This commit is contained in:
@@ -145,6 +145,54 @@ test('pages and filters large vehicle grants without nesting a vehicle-list scro
|
|||||||
expect(screen.getByText('第 1–1 条,共 1 辆')).toBeInTheDocument();
|
expect(screen.getByText('第 1–1 条,共 1 辆')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('makes draft state explicit, protects bulk removal and preserves edits across a same-revision refresh', async () => {
|
||||||
|
const customer = {
|
||||||
|
id: 7,
|
||||||
|
username: 'customer-east',
|
||||||
|
displayName: '华东客户',
|
||||||
|
userType: 'customer',
|
||||||
|
status: 'enabled',
|
||||||
|
customerRef: '',
|
||||||
|
tenantRef: '',
|
||||||
|
authProvider: 'local',
|
||||||
|
menuKeys: ['monitor', 'vehicles', 'tracks', 'statistics'],
|
||||||
|
vehicleVins: ['VIN001'],
|
||||||
|
vehicles: [{ vin: 'VIN001', plate: '粤A11111', validFrom: '2026-06-05T00:00:00+08:00', sourceSystem: 'manual', grantedBy: '平台管理员' }],
|
||||||
|
grantHistory: [],
|
||||||
|
createdAt: '2026-07-16T00:00:00Z',
|
||||||
|
updatedAt: '2026-07-16T00:00:00Z'
|
||||||
|
};
|
||||||
|
mocks.adminUsers.mockResolvedValue([customer]);
|
||||||
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||||
|
render(<QueryClientProvider client={client}><UsersPage /></QueryClientProvider>);
|
||||||
|
|
||||||
|
fireEvent.click(await screen.findByRole('button', { name: /选择客户 华东客户/ }));
|
||||||
|
expect(screen.getByText('已同步')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: '保存权限' })).toBeDisabled();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('tab', { name: '登录身份' }));
|
||||||
|
const displayName = screen.getByDisplayValue('华东客户');
|
||||||
|
fireEvent.change(displayName, { target: { value: '华东客户(更新)' } });
|
||||||
|
expect(screen.getByText('待保存')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: '保存权限' })).toBeEnabled();
|
||||||
|
|
||||||
|
client.setQueryData(['admin-users'], [{ ...customer }]);
|
||||||
|
await waitFor(() => expect(screen.getByDisplayValue('华东客户(更新)')).toBeInTheDocument());
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('tab', { name: /车辆权限/ }));
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '清空全部 1 辆车辆权限' }));
|
||||||
|
expect(await screen.findByText('清空 1 辆车辆权限?')).toBeInTheDocument();
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '确认清空' }));
|
||||||
|
await waitFor(() => expect(screen.queryByRole('button', { name: '移除 粤A11111' })).not.toBeInTheDocument());
|
||||||
|
expect(screen.getByText('已从草稿移除 1 辆车,保存后生效')).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '关闭账号详情' }));
|
||||||
|
expect(await screen.findByText('放弃未保存的更改?')).toBeInTheDocument();
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'cancel' }));
|
||||||
|
expect(document.querySelector('.v2-user-editor-form')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: '保存权限' })).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
test('keeps the customer directory visible on mobile and opens details on demand', async () => {
|
test('keeps the customer directory visible on mobile and opens details on demand', async () => {
|
||||||
layout.mobile = true;
|
layout.mobile = true;
|
||||||
mocks.adminUsers.mockResolvedValue([{
|
mocks.adminUsers.mockResolvedValue([{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { IconChevronRight, IconDelete, IconPlus, IconRefresh, IconSearch } from '@douyinfe/semi-icons';
|
import { IconChevronRight, IconDelete, IconPlus, IconRefresh, IconSearch } from '@douyinfe/semi-icons';
|
||||||
import { Avatar, Button, Card, Checkbox, Collapse, Empty, Input, List, Select, SideSheet, Spin, Switch, Tabs, Tag, Typography } from '@douyinfe/semi-ui';
|
import { Avatar, Button, Card, Checkbox, Collapse, Empty, Input, List, Modal, Popconfirm, Select, SideSheet, Spin, Switch, Tabs, Tag, Typography } from '@douyinfe/semi-ui';
|
||||||
import { FormEvent, useDeferredValue, useEffect, useMemo, useState } from 'react';
|
import { FormEvent, useDeferredValue, useEffect, useMemo, useState } from 'react';
|
||||||
import { api } from '../../api/client';
|
import { api } from '../../api/client';
|
||||||
import type { AdminUser, CustomerUserInput, CustomerVehicleGrantInput } from '../../api/types';
|
import type { AdminUser, CustomerUserInput, CustomerVehicleGrantInput } from '../../api/types';
|
||||||
@@ -56,6 +56,14 @@ function draftFromUser(user?: AdminUser): Draft {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function draftSignature(draft: Draft) {
|
||||||
|
return JSON.stringify({
|
||||||
|
...draft,
|
||||||
|
menuKeys: [...draft.menuKeys].sort(),
|
||||||
|
vehicleGrants: [...draft.vehicleGrants].sort((left, right) => left.vin.localeCompare(right.vin))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function formatTime(value?: string) {
|
function formatTime(value?: string) {
|
||||||
if (!value) return '尚未登录';
|
if (!value) return '尚未登录';
|
||||||
return new Date(value).toLocaleString('zh-CN', { hour12: false });
|
return new Date(value).toLocaleString('zh-CN', { hour12: false });
|
||||||
@@ -89,6 +97,7 @@ export default function UsersPage() {
|
|||||||
const [activeSection, setActiveSection] = useState<EditorSection>('identity');
|
const [activeSection, setActiveSection] = useState<EditorSection>('identity');
|
||||||
const selected = useMemo(() => customers.find((user) => user.id === selectedID), [customers, selectedID]);
|
const selected = useMemo(() => customers.find((user) => user.id === selectedID), [customers, selectedID]);
|
||||||
const [draft, setDraft] = useState<Draft>(() => draftFromUser());
|
const [draft, setDraft] = useState<Draft>(() => draftFromUser());
|
||||||
|
const [baselineDraft, setBaselineDraft] = useState<Draft>(() => draftFromUser());
|
||||||
const [vehicleKeyword, setVehicleKeyword] = useState('');
|
const [vehicleKeyword, setVehicleKeyword] = useState('');
|
||||||
const deferredVehicleKeyword = useDeferredValue(vehicleKeyword.trim());
|
const deferredVehicleKeyword = useDeferredValue(vehicleKeyword.trim());
|
||||||
const [assignedKeyword, setAssignedKeyword] = useState('');
|
const [assignedKeyword, setAssignedKeyword] = useState('');
|
||||||
@@ -105,15 +114,18 @@ export default function UsersPage() {
|
|||||||
const grantWindowInvalid = Boolean(editingGrant && (!editingGrant.validFrom || (editingGrant.validTo && editingGrant.validTo <= editingGrant.validFrom)));
|
const grantWindowInvalid = Boolean(editingGrant && (!editingGrant.validFrom || (editingGrant.validTo && editingGrant.validTo <= editingGrant.validFrom)));
|
||||||
const editorVisible = creating || Boolean(selected);
|
const editorVisible = creating || Boolean(selected);
|
||||||
const vehicleComposerVisible = !mobileLayout || creating || draft.vehicleGrants.length === 0 || vehicleComposerOpen;
|
const vehicleComposerVisible = !mobileLayout || creating || draft.vehicleGrants.length === 0 || vehicleComposerOpen;
|
||||||
|
const hasUnsavedChanges = useMemo(() => draftSignature(draft) !== draftSignature(baselineDraft), [baselineDraft, draft]);
|
||||||
useSideSheetA11y(editorVisible, '.v2-user-editor-sidesheet', 'v2-user-editor-sheet', '客户账号详情', '关闭账号详情');
|
useSideSheetA11y(editorVisible, '.v2-user-editor-sidesheet', 'v2-user-editor-sheet', '客户账号详情', '关闭账号详情');
|
||||||
useSideSheetA11y(Boolean(editingGrant), '.v2-user-grant-sidesheet', 'v2-user-grant-window', '车辆授权有效期', '关闭车辆授权有效期');
|
useSideSheetA11y(Boolean(editingGrant), '.v2-user-grant-sidesheet', 'v2-user-grant-window', '车辆授权有效期', '关闭车辆授权有效期');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!creating && selected) {
|
if (!creating && selected) {
|
||||||
setDraft(draftFromUser(selected));
|
const nextDraft = draftFromUser(selected);
|
||||||
|
setDraft(nextDraft);
|
||||||
|
setBaselineDraft(nextDraft);
|
||||||
setVehicleLabels(Object.fromEntries(selected.vehicles.map((vehicle) => [vehicle.vin, vehicle.plate])));
|
setVehicleLabels(Object.fromEntries(selected.vehicles.map((vehicle) => [vehicle.vin, vehicle.plate])));
|
||||||
}
|
}
|
||||||
}, [creating, selected]);
|
}, [creating, selected?.id, selected?.updatedAt]);
|
||||||
|
|
||||||
const candidates = useQuery({
|
const candidates = useQuery({
|
||||||
queryKey: ['permission-vehicle-candidates', deferredVehicleKeyword],
|
queryKey: ['permission-vehicle-candidates', deferredVehicleKeyword],
|
||||||
@@ -160,17 +172,23 @@ export default function UsersPage() {
|
|||||||
setFeedback(creating ? '客户账号已创建' : '账号与权限已更新');
|
setFeedback(creating ? '客户账号已创建' : '账号与权限已更新');
|
||||||
setCreating(false);
|
setCreating(false);
|
||||||
setSelectedID(result.id);
|
setSelectedID(result.id);
|
||||||
setDraft((value) => ({ ...value, password: '' }));
|
setDraft((value) => {
|
||||||
|
const savedDraft = { ...value, password: '' };
|
||||||
|
setBaselineDraft(savedDraft);
|
||||||
|
return savedDraft;
|
||||||
|
});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['admin-users'] });
|
await queryClient.invalidateQueries({ queryKey: ['admin-users'] });
|
||||||
},
|
},
|
||||||
onError: (error) => setFeedback(error instanceof Error ? error.message : '保存失败')
|
onError: (error) => setFeedback(error instanceof Error ? error.message : '保存失败')
|
||||||
});
|
});
|
||||||
|
|
||||||
const startCreate = () => {
|
const startCreate = () => {
|
||||||
|
const nextDraft = draftFromUser();
|
||||||
setCreating(true);
|
setCreating(true);
|
||||||
setSelectedID(null);
|
setSelectedID(null);
|
||||||
setActiveSection('identity');
|
setActiveSection('identity');
|
||||||
setDraft(draftFromUser());
|
setDraft(nextDraft);
|
||||||
|
setBaselineDraft(nextDraft);
|
||||||
setVehicleKeyword('');
|
setVehicleKeyword('');
|
||||||
setAssignedKeyword('');
|
setAssignedKeyword('');
|
||||||
setAssignedPage(1);
|
setAssignedPage(1);
|
||||||
@@ -181,9 +199,11 @@ export default function UsersPage() {
|
|||||||
setEditingGrantVIN('');
|
setEditingGrantVIN('');
|
||||||
};
|
};
|
||||||
const closeEditor = () => {
|
const closeEditor = () => {
|
||||||
|
const nextDraft = draftFromUser();
|
||||||
setCreating(false);
|
setCreating(false);
|
||||||
setSelectedID(null);
|
setSelectedID(null);
|
||||||
setDraft(draftFromUser());
|
setDraft(nextDraft);
|
||||||
|
setBaselineDraft(nextDraft);
|
||||||
setVehicleKeyword('');
|
setVehicleKeyword('');
|
||||||
setAssignedKeyword('');
|
setAssignedKeyword('');
|
||||||
setAssignedPage(1);
|
setAssignedPage(1);
|
||||||
@@ -193,11 +213,27 @@ export default function UsersPage() {
|
|||||||
setFeedback('');
|
setFeedback('');
|
||||||
setEditingGrantVIN('');
|
setEditingGrantVIN('');
|
||||||
};
|
};
|
||||||
|
const requestCloseEditor = () => {
|
||||||
|
if (!hasUnsavedChanges) {
|
||||||
|
closeEditor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Modal.confirm({
|
||||||
|
title: '放弃未保存的更改?',
|
||||||
|
content: '登录身份、菜单或车辆权限的修改尚未保存,关闭后将恢复到上次保存状态。',
|
||||||
|
okText: '放弃更改',
|
||||||
|
cancelText: '继续编辑',
|
||||||
|
okType: 'danger',
|
||||||
|
onOk: closeEditor
|
||||||
|
});
|
||||||
|
};
|
||||||
const selectCustomer = (user: AdminUser) => {
|
const selectCustomer = (user: AdminUser) => {
|
||||||
|
const nextDraft = draftFromUser(user);
|
||||||
setCreating(false);
|
setCreating(false);
|
||||||
setSelectedID(user.id);
|
setSelectedID(user.id);
|
||||||
setActiveSection('vehicles');
|
setActiveSection('vehicles');
|
||||||
setDraft(draftFromUser(user));
|
setDraft(nextDraft);
|
||||||
|
setBaselineDraft(nextDraft);
|
||||||
setVehicleKeyword('');
|
setVehicleKeyword('');
|
||||||
setAssignedKeyword('');
|
setAssignedKeyword('');
|
||||||
setAssignedPage(1);
|
setAssignedPage(1);
|
||||||
@@ -207,7 +243,8 @@ export default function UsersPage() {
|
|||||||
setEditingGrantVIN('');
|
setEditingGrantVIN('');
|
||||||
};
|
};
|
||||||
const toggleVIN = (vin: string) => {
|
const toggleVIN = (vin: string) => {
|
||||||
if (!assigned.has(vin)) {
|
const isAssigned = assigned.has(vin);
|
||||||
|
if (!isAssigned) {
|
||||||
setAssignedKeyword('');
|
setAssignedKeyword('');
|
||||||
setAssignedPage(1);
|
setAssignedPage(1);
|
||||||
}
|
}
|
||||||
@@ -217,8 +254,20 @@ export default function UsersPage() {
|
|||||||
? value.vehicleGrants.filter((grant) => grant.vin !== vin)
|
? value.vehicleGrants.filter((grant) => grant.vin !== vin)
|
||||||
: [...value.vehicleGrants, { vin, validFrom: dateTimeInput(new Date().toISOString()), validTo: '' }].sort((left, right) => left.vin.localeCompare(right.vin))
|
: [...value.vehicleGrants, { vin, validFrom: dateTimeInput(new Date().toISOString()), validTo: '' }].sort((left, right) => left.vin.localeCompare(right.vin))
|
||||||
}));
|
}));
|
||||||
|
setFeedback(isAssigned
|
||||||
|
? `${vehicleLabels[vin] || vin} 已从授权草稿移除,保存后生效`
|
||||||
|
: `${vehicleLabels[vin] || vin} 已加入授权草稿,保存后生效`);
|
||||||
if (editingGrantVIN === vin) setEditingGrantVIN('');
|
if (editingGrantVIN === vin) setEditingGrantVIN('');
|
||||||
};
|
};
|
||||||
|
const clearVehicleGrants = () => {
|
||||||
|
const removedCount = draft.vehicleGrants.length;
|
||||||
|
setDraft((value) => ({ ...value, vehicleGrants: [] }));
|
||||||
|
setAssignedKeyword('');
|
||||||
|
setAssignedPage(1);
|
||||||
|
setVehicleComposerOpen(true);
|
||||||
|
setEditingGrantVIN('');
|
||||||
|
setFeedback(`已从草稿移除 ${removedCount} 辆车,保存后生效`);
|
||||||
|
};
|
||||||
const updateGrant = (vin: string, patch: Partial<CustomerVehicleGrantInput>) => setDraft((value) => ({
|
const updateGrant = (vin: string, patch: Partial<CustomerVehicleGrantInput>) => setDraft((value) => ({
|
||||||
...value, vehicleGrants: value.vehicleGrants.map((grant) => grant.vin === vin ? { ...grant, ...patch } : grant)
|
...value, vehicleGrants: value.vehicleGrants.map((grant) => grant.vin === vin ? { ...grant, ...patch } : grant)
|
||||||
}));
|
}));
|
||||||
@@ -371,10 +420,13 @@ export default function UsersPage() {
|
|||||||
<span><strong>{creating ? '创建客户账号' : selected?.displayName}</strong><small>{creating ? '设置登录身份和最小必要权限' : <><span>@{selected?.username} · {draft.menuKeys.length} 个菜单 · {draft.vehicleGrants.length} 辆车</span><span className="v2-user-editor-last-login"> · 最近登录 {formatTime(selected?.lastLoginAt)}</span></>}</small></span>
|
<span><strong>{creating ? '创建客户账号' : selected?.displayName}</strong><small>{creating ? '设置登录身份和最小必要权限' : <><span>@{selected?.username} · {draft.menuKeys.length} 个菜单 · {draft.vehicleGrants.length} 辆车</span><span className="v2-user-editor-last-login"> · 最近登录 {formatTime(selected?.lastLoginAt)}</span></>}</small></span>
|
||||||
<label className="v2-user-status"><Switch aria-label={draft.status === 'enabled' ? '账号启用' : '账号停用'} checked={draft.status === 'enabled'} onChange={(checked) => setDraft((value) => ({ ...value, status: checked ? 'enabled' : 'disabled' }))} /><span>{draft.status === 'enabled' ? '账号启用' : '账号停用'}</span></label>
|
<label className="v2-user-status"><Switch aria-label={draft.status === 'enabled' ? '账号启用' : '账号停用'} checked={draft.status === 'enabled'} onChange={(checked) => setDraft((value) => ({ ...value, status: checked ? 'enabled' : 'disabled' }))} /><span>{draft.status === 'enabled' ? '账号启用' : '账号停用'}</span></label>
|
||||||
</div>}
|
</div>}
|
||||||
onCancel={closeEditor}
|
onCancel={requestCloseEditor}
|
||||||
footer={<div className="v2-user-editor-sheet-footer">
|
footer={<div className="v2-user-editor-sheet-footer">
|
||||||
<Typography.Text className={`v2-user-feedback${save.isError ? ' is-error' : ''}`} role="status" type={save.isError ? 'danger' : 'tertiary'}>{feedback || '权限变更最多 30 秒生效,保存前请核对车辆有效期'}</Typography.Text>
|
<span className="v2-user-save-state" role="status">
|
||||||
<Button theme="solid" htmlType="submit" form="v2-user-editor-form" disabled={save.isPending}>{save.isPending ? '正在保存…' : creating ? '创建账号' : '保存权限'}</Button>
|
<Tag color={save.isError ? 'red' : hasUnsavedChanges ? 'amber' : 'green'} type="light" size="small">{save.isError ? '保存失败' : hasUnsavedChanges ? '待保存' : '已同步'}</Tag>
|
||||||
|
<Typography.Text className={`v2-user-feedback${save.isError ? ' is-error' : ''}`} type={save.isError ? 'danger' : 'tertiary'}>{feedback || (hasUnsavedChanges ? '更改仅保存在当前草稿,保存后最多 30 秒生效' : '账号与权限已和服务器同步')}</Typography.Text>
|
||||||
|
</span>
|
||||||
|
<Button theme="solid" htmlType="submit" form="v2-user-editor-form" disabled={save.isPending || !hasUnsavedChanges}>{save.isPending ? '正在保存…' : creating ? '创建账号' : '保存权限'}</Button>
|
||||||
</div>}
|
</div>}
|
||||||
>
|
>
|
||||||
{editorVisible ? <form id="v2-user-editor-form" className="v2-user-editor-form" onSubmit={submit}>
|
{editorVisible ? <form id="v2-user-editor-form" className="v2-user-editor-form" onSubmit={submit}>
|
||||||
@@ -411,10 +463,17 @@ export default function UsersPage() {
|
|||||||
aria-label={vehicleComposerOpen ? '收起添加车辆' : '添加授权车辆'}
|
aria-label={vehicleComposerOpen ? '收起添加车辆' : '添加授权车辆'}
|
||||||
onClick={() => setVehicleComposerOpen((value) => !value)}
|
onClick={() => setVehicleComposerOpen((value) => !value)}
|
||||||
>{vehicleComposerOpen ? '收起' : '添加'}</Button> : null}
|
>{vehicleComposerOpen ? '收起' : '添加'}</Button> : null}
|
||||||
{draft.vehicleGrants.length ? <Button theme="borderless" type="tertiary" size="small" onClick={() => {
|
{draft.vehicleGrants.length ? <Popconfirm
|
||||||
setDraft((value) => ({ ...value, vehicleGrants: [] }));
|
title={`清空 ${draft.vehicleGrants.length} 辆车辆权限?`}
|
||||||
setVehicleComposerOpen(true);
|
content="车辆会先从当前草稿中移除,点击底部保存后正式生效。"
|
||||||
}}>清空</Button> : null}
|
okText="确认清空"
|
||||||
|
cancelText="取消"
|
||||||
|
okType="danger"
|
||||||
|
position="bottomRight"
|
||||||
|
onConfirm={clearVehicleGrants}
|
||||||
|
>
|
||||||
|
<Button theme="borderless" type="tertiary" size="small" aria-label={`清空全部 ${draft.vehicleGrants.length} 辆车辆权限`}>清空</Button>
|
||||||
|
</Popconfirm> : null}
|
||||||
</div> : null}
|
</div> : null}
|
||||||
>
|
>
|
||||||
{vehicleComposerVisible ? <div id="v2-user-vehicle-composer" className="v2-user-vehicle-composer">
|
{vehicleComposerVisible ? <div id="v2-user-vehicle-composer" className="v2-user-vehicle-composer">
|
||||||
|
|||||||
@@ -10096,6 +10096,369 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Semi UI account governance refinement.
|
||||||
|
* Raise the reading scale to the same level as monitoring and operations,
|
||||||
|
* while making draft/save state and destructive scope changes unmistakable.
|
||||||
|
*/
|
||||||
|
.v2-user-command-bar .v2-workspace-command-copy > .semi-typography:first-child {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-command-bar .v2-workspace-command-copy > .semi-typography:last-child {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-discovery-shell .v2-user-filter-form {
|
||||||
|
min-height: 60px;
|
||||||
|
padding: 9px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-filter-form :is(.semi-input-wrapper, .semi-select),
|
||||||
|
.v2-user-filter-reset.semi-button {
|
||||||
|
min-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-filter-form .semi-input,
|
||||||
|
.v2-user-filter-form .semi-select-selection {
|
||||||
|
min-height: 40px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-directory-header.v2-workspace-panel-header {
|
||||||
|
min-height: 64px;
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-directory-header .v2-workspace-panel-copy strong {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-directory-header .v2-workspace-panel-copy small {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-directory-summary > .semi-tag,
|
||||||
|
.v2-user-directory-summary > .semi-typography {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-directory-columns {
|
||||||
|
min-height: 38px;
|
||||||
|
color: #748397;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-customer-list .v2-user-list-row.semi-list-item,
|
||||||
|
.v2-customer-list .v2-user-list-item.semi-button {
|
||||||
|
min-height: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-customer-list .v2-user-list-item.semi-button {
|
||||||
|
padding-block: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-avatar.semi-avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-customer-list .v2-user-list-item .semi-button-content,
|
||||||
|
.v2-user-directory-columns {
|
||||||
|
grid-template-columns: 40px minmax(180px, 1.45fr) minmax(76px, .45fr) minmax(76px, .45fr) minmax(160px, .9fr) auto;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-list-identity b {
|
||||||
|
color: #20364e;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-list-identity small {
|
||||||
|
color: #728197;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-list-facts b {
|
||||||
|
color: #3f536b;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-list-facts.is-login b {
|
||||||
|
color: #5d7088;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-status-tag.semi-tag {
|
||||||
|
min-width: 46px;
|
||||||
|
min-height: 24px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .semi-sidesheet-inner {
|
||||||
|
width: min(900px, 100vw) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .semi-sidesheet-header {
|
||||||
|
min-height: 82px;
|
||||||
|
padding: 14px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sheet-title strong {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sheet-title small {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sheet-title .v2-user-status {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-editor-tabs > .semi-tabs-bar {
|
||||||
|
min-height: 56px;
|
||||||
|
padding-inline: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-editor-tabs > .semi-tabs-bar .semi-tabs-tab {
|
||||||
|
min-height: 55px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-editor-tabs > .semi-tabs-content {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-editor-section > .semi-card-header {
|
||||||
|
min-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-editor-section > .semi-card-header .semi-card-header-wrapper-title h6,
|
||||||
|
.v2-user-editor-sidesheet .v2-user-editor-section > .semi-card-header .semi-card-header-wrapper-title > span {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-editor-section > .semi-card-body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-fields label,
|
||||||
|
.v2-user-editor-sidesheet .v2-vehicle-permission-tools label {
|
||||||
|
color: #52657d;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-fields .semi-input-wrapper,
|
||||||
|
.v2-user-editor-sidesheet .v2-vehicle-permission-tools .semi-input-wrapper,
|
||||||
|
.v2-user-editor-sidesheet .v2-bulk-vin > .semi-button {
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-fields .semi-input,
|
||||||
|
.v2-user-editor-sidesheet .v2-vehicle-permission-tools .semi-input {
|
||||||
|
height: 40px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-menu-section .v2-menu-permissions label {
|
||||||
|
min-height: 78px;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-menu-section .v2-menu-permissions b {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-menu-section .v2-menu-permissions small {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-toolbar strong {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-toolbar small {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-toolbar .semi-input {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-vins > .v2-assigned-vehicle-card.semi-card {
|
||||||
|
min-height: 108px;
|
||||||
|
border-color: #dfe7f0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 13px;
|
||||||
|
contain-intrinsic-size: auto 108px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-vehicle-card > .semi-card-body > header b {
|
||||||
|
color: #243b54;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-vehicle-card > .semi-card-body > header small {
|
||||||
|
color: #7c8ca0;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-vehicle-interval small {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-vehicle-interval strong {
|
||||||
|
color: #425970;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-save-state {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-save-state > .semi-tag {
|
||||||
|
min-width: 50px;
|
||||||
|
min-height: 24px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-save-state .v2-user-feedback.semi-typography {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 11px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sheet-footer > .semi-button:disabled {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px) and (min-width: 681px) {
|
||||||
|
.v2-customer-list .v2-user-list-item .semi-button-content,
|
||||||
|
.v2-user-directory-columns {
|
||||||
|
grid-template-columns: 40px minmax(160px, 1fr) 76px 76px auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
.v2-user-command-bar .v2-workspace-command-copy > .semi-typography:first-child {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-directory-header .v2-workspace-panel-copy strong {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-customer-list .v2-user-list-row.semi-list-item,
|
||||||
|
.v2-customer-list .v2-user-list-item.semi-button {
|
||||||
|
min-height: 78px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-customer-list .v2-user-list-item .semi-button-content {
|
||||||
|
grid-template-columns: 38px minmax(0, 1fr) 62px auto;
|
||||||
|
gap: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-avatar.semi-avatar {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-list-identity b {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-list-identity small {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-list-facts small {
|
||||||
|
color: #8190a3;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-list-facts b {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .semi-sidesheet-header {
|
||||||
|
min-height: 70px;
|
||||||
|
padding: 10px 11px 10px 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sheet-title strong {
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sheet-title small {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-editor-tabs > .semi-tabs-bar .semi-tabs-tab {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-editor-tabs > .semi-tabs-content {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-user-editor-section > .semi-card-body {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-toolbar strong {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-vins > .v2-assigned-vehicle-card.semi-card {
|
||||||
|
min-height: 106px;
|
||||||
|
padding: 10px 11px;
|
||||||
|
contain-intrinsic-size: auto 106px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-vehicle-card > .semi-card-body > header b {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-vehicle-card > .semi-card-body > header small {
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-vehicle-interval small {
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sidesheet .v2-assigned-vehicle-interval strong {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sheet-footer {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-save-state .v2-user-feedback.semi-typography {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-user-editor-sheet-footer > .semi-button {
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Semi UI workspace migration: global monitor command bar and fleet overview. */
|
/* Semi UI workspace migration: global monitor command bar and fleet overview. */
|
||||||
.v2-filterbar.semi-card {
|
.v2-filterbar.semi-card {
|
||||||
border-color: #dce5ef;
|
border-color: #dce5ef;
|
||||||
|
|||||||
Reference in New Issue
Block a user