feat(F006): 用户列表按企业列表UI标准升级 - 渐变头像单元/两行信息列(姓名工号/部门角色)/状态圆点胶囊(全局st变量暗色自适应)/用户类型下拉筛选/超管徽章/批量删除/清理老式死样式
This commit is contained in:
parent
99bc522b24
commit
90d6561746
@ -1,30 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* F006 用户管理 - 套老 vue user.vue 结构 + v1.8 设计规范
|
||||
* F006 用户管理 - 2026-08-18 按企业列表 UI 标准升级
|
||||
*
|
||||
* 后端端点 (SysUserController):
|
||||
* GET /sys/user/page 分页列表
|
||||
* GET /sys/user/{id} 详情
|
||||
* POST /sys/user 新增
|
||||
* PUT /sys/user 修改
|
||||
* GET /sys/user/page 分页列表 (支持 username/userType 过滤)
|
||||
* PUT /sys/user/updateStatus 启用/停用
|
||||
* PUT /sys/user/password 修改密码(自己)
|
||||
* PUT /sys/user/resetPassword 重置密码(管理员)
|
||||
* DELETE /sys/user 删除
|
||||
* DELETE /sys/user 删除 (支持批量)
|
||||
*
|
||||
* 字段(来自 SysUserDTO):
|
||||
* id / username / password(WRITE_ONLY) / realName / headUrl
|
||||
* gender(0=男/1=女/2=保密) / email / mobile / deptId
|
||||
* superAdmin(0=否/1=是) / status(0=停用/1=正常) / remark
|
||||
* identifier / employeeId(工号) / userType(1=试用/2=正式/3=外包/99=AI公司)
|
||||
* joinDate / invitationUrl / invitationCode / xcxQrCode / operationId
|
||||
* createDate / deptName / roleNames / roleIdList / postIdList
|
||||
* id / username / realName / headUrl / gender / email / mobile / deptId
|
||||
* superAdmin(0=否/1=是) / status(0=停用/1=正常) / employeeId(工号)
|
||||
* userType(1=试用/2=正式/3=外包/99=AI公司) / createDate / deptName / roleNames
|
||||
*
|
||||
* 设计规范:§16 列表页 + §17.7 紧凑弹窗 + §17.8 主题色弹窗 + §17.9 列优化
|
||||
* UI 标准对齐企业列表: 头像渐变单元 / 两行信息列 / 圆点状态胶囊(全局 --st-* 变量暗色自适应) /
|
||||
* 搜索栏补用户类型下拉 / selection 批量删除 / 清理老式死样式
|
||||
*/
|
||||
import { onMounted, ref, reactive } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { httpGet, httpPost, httpPut, httpDelete } from '@/utils/request'
|
||||
import { User, Phone, Clock } from '@element-plus/icons-vue'
|
||||
import { httpGet, httpPut, httpDelete } from '@/utils/request'
|
||||
import DataTable from '@/components/DataTable/DataTable.vue'
|
||||
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
||||
import SearchBar from '@/components/SearchBar/SearchBar.vue'
|
||||
@ -35,42 +30,38 @@ interface SysUser {
|
||||
username: string
|
||||
realName: string
|
||||
headUrl?: string | null
|
||||
gender?: number | null
|
||||
email?: string | null
|
||||
mobile?: string | null
|
||||
deptId?: string | null
|
||||
deptName?: string | null
|
||||
superAdmin?: number
|
||||
status: 0 | 1
|
||||
remark?: string | null
|
||||
identifier?: string | null
|
||||
employeeId?: string | null
|
||||
userType?: number
|
||||
joinDate?: string | number | null
|
||||
createDate: string | number
|
||||
roleNames?: string | null
|
||||
roleIdList?: number[] | null
|
||||
postIdList?: number[] | null
|
||||
raw: any
|
||||
}
|
||||
|
||||
const STATUS_MAP: Record<number, { label: string; type: string }> = {
|
||||
1: { label: '正常', type: 'success' },
|
||||
0: { label: '停用', type: 'danger' }
|
||||
const STATUS_META: Record<number, { label: string; color: string; bg: string }> = {
|
||||
1: { label: '正常', color: 'var(--st-ok-fg)', bg: 'var(--st-ok-bg)' },
|
||||
0: { label: '停用', color: 'var(--st-err-fg)', bg: 'var(--st-err-bg)' }
|
||||
}
|
||||
|
||||
const USER_TYPE_MAP: Record<number, { label: string; type: string }> = {
|
||||
1: { label: '试用期', type: 'info' },
|
||||
2: { label: '正式', type: 'success' },
|
||||
3: { label: '外包', type: 'warning' },
|
||||
99: { label: 'AI 公司', type: 'info' }
|
||||
const USER_TYPE_MAP: Record<number, { label: string; color: string; bg: string }> = {
|
||||
1: { label: '试用期', color: 'var(--st-lv4-fg)', bg: 'var(--st-lv4-bg)' },
|
||||
2: { label: '正式', color: 'var(--st-ok-fg)', bg: 'var(--st-ok-bg)' },
|
||||
3: { label: '外包', color: 'var(--st-warn-fg)', bg: 'var(--st-warn-bg)' },
|
||||
99: { label: 'AI 公司', color: 'var(--st-lv2-fg)', bg: 'var(--st-lv2-bg)' }
|
||||
}
|
||||
|
||||
const GENDER_MAP: Record<number, string> = {
|
||||
0: '男',
|
||||
1: '女',
|
||||
2: '保密'
|
||||
}
|
||||
// 头像品牌渐变色组 (无 headUrl 时交替使用, 与企业列表同源风格)
|
||||
const AVATAR_GRADIENTS = [
|
||||
'linear-gradient(135deg, #0b5394, #6fb1e8)',
|
||||
'linear-gradient(135deg, #2563eb, #93c5fd)',
|
||||
'linear-gradient(135deg, #0e7490, #67e8f9)',
|
||||
'linear-gradient(135deg, #7c3aed, #c4b5fd)',
|
||||
'linear-gradient(135deg, #be123c, #fda4af)'
|
||||
]
|
||||
|
||||
const list = ref<SysUser[]>([])
|
||||
const total = ref(0)
|
||||
@ -78,6 +69,8 @@ const loading = ref(false)
|
||||
const page = ref(1)
|
||||
const limit = ref(10)
|
||||
const loadError = ref<string | null>(null)
|
||||
const selectedIds = ref<string[]>([])
|
||||
const selectedCount = ref(0)
|
||||
|
||||
const filters = reactive({
|
||||
username: '',
|
||||
@ -88,6 +81,14 @@ const resetPwdVisible = ref(false)
|
||||
const resetPwdTarget = ref<SysUser | null>(null)
|
||||
const newPassword = ref('')
|
||||
|
||||
function avatarStyle(row: SysUser, index: number) {
|
||||
return { background: AVATAR_GRADIENTS[index % AVATAR_GRADIENTS.length] }
|
||||
}
|
||||
function initial(row: SysUser): string {
|
||||
const name = row.realName && row.realName !== '--' ? row.realName : row.username
|
||||
return (name || '用').trim().charAt(0)
|
||||
}
|
||||
|
||||
function fmtDate(ts?: string | number | null) {
|
||||
if (!ts) return '--'
|
||||
const n = typeof ts === 'string' ? Date.parse(ts) : ts
|
||||
@ -113,22 +114,14 @@ async function load() {
|
||||
username: it.username || '--',
|
||||
realName: it.realName || '--',
|
||||
headUrl: it.headUrl,
|
||||
gender: it.gender,
|
||||
email: it.email,
|
||||
mobile: it.mobile,
|
||||
deptId: it.deptId ? String(it.deptId) : null,
|
||||
deptName: it.deptName || '--',
|
||||
superAdmin: it.superAdmin ?? 0,
|
||||
status: (it.status ?? 0) as 0 | 1,
|
||||
remark: it.remark,
|
||||
identifier: it.identifier,
|
||||
employeeId: it.employeeId || '--',
|
||||
userType: it.userType,
|
||||
joinDate: it.joinDate,
|
||||
createDate: it.createDate,
|
||||
roleNames: it.roleNames,
|
||||
roleIdList: it.roleIdList,
|
||||
postIdList: it.postIdList,
|
||||
raw: it
|
||||
}))
|
||||
total.value = data.total || 0
|
||||
@ -151,6 +144,7 @@ function onReset() {
|
||||
}
|
||||
function onPageChange(p: number) { page.value = p; load() }
|
||||
function onLimitChange(l: number) { limit.value = l; page.value = 1; load() }
|
||||
function onSelectionChange(rows: any[]) { selectedIds.value = rows.map((r: any) => r.id); selectedCount.value = rows.length }
|
||||
|
||||
async function toggleStatus(row: SysUser) {
|
||||
const newStatus = row.status === 1 ? 0 : 1
|
||||
@ -165,10 +159,7 @@ async function toggleStatus(row: SysUser) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await httpPut('/sys/user/updateStatus', {
|
||||
id: Number(row.id),
|
||||
status: newStatus
|
||||
})
|
||||
await httpPut('/sys/user/updateStatus', { id: Number(row.id), status: newStatus })
|
||||
ElMessage.success(`${action}成功`)
|
||||
load()
|
||||
} catch (e: any) {
|
||||
@ -184,14 +175,8 @@ function resetPwdHandle(row: SysUser) {
|
||||
|
||||
async function resetPwdConfirm() {
|
||||
if (!resetPwdTarget.value) return
|
||||
if (!newPassword.value.trim()) {
|
||||
ElMessage.warning('请输入新密码')
|
||||
return
|
||||
}
|
||||
if (newPassword.value.length < 6) {
|
||||
ElMessage.warning('密码至少 6 位')
|
||||
return
|
||||
}
|
||||
if (!newPassword.value.trim()) { ElMessage.warning('请输入新密码'); return }
|
||||
if (newPassword.value.length < 6) { ElMessage.warning('密码至少 6 位'); return }
|
||||
try {
|
||||
await httpPut('/sys/user/resetPassword', {
|
||||
id: Number(resetPwdTarget.value.id),
|
||||
@ -208,28 +193,35 @@ async function resetPwdConfirm() {
|
||||
|
||||
async function deleteHandle(row: SysUser) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认删除用户 ${row.username} 吗?删除后无法恢复。`,
|
||||
'删除确认',
|
||||
{ type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' }
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
await ElMessageBox.confirm(`确认删除用户 ${row.username} 吗?删除后无法恢复。`, '删除确认',
|
||||
{ type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' })
|
||||
} catch { return }
|
||||
try {
|
||||
await httpDelete(`/sys/user/${row.id}`)
|
||||
ElMessage.success('删除成功')
|
||||
load()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.msg || e?.message || '删除失败')
|
||||
} catch (e: any) { ElMessage.error(e?.msg || e?.message || '删除失败') }
|
||||
}
|
||||
|
||||
async function batchDelete() {
|
||||
if (!selectedIds.value.length) { ElMessage.warning('请先选择用户'); return }
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认删除已选 ${selectedIds.value.length} 个用户?删除后无法恢复。`, '批量删除确认',
|
||||
{ type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' })
|
||||
} catch { return }
|
||||
let ok = 0
|
||||
for (const id of selectedIds.value) {
|
||||
try { await httpDelete(`/sys/user/${id}`); ok++ } catch {/* 单条失败跳过 */}
|
||||
}
|
||||
ElMessage.success(`已删除 ${ok} 个用户`)
|
||||
load()
|
||||
}
|
||||
|
||||
onMounted(() => { load() })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rr-sys-user">
|
||||
<div class="rr-page">
|
||||
<PageHeader title="后台用户" :sub="`共 ${total} 个用户`">
|
||||
<el-button @click="onReset" class="rr-btn-secondary">重置</el-button>
|
||||
</PageHeader>
|
||||
@ -237,68 +229,116 @@ onMounted(() => { load() })
|
||||
<SearchBar
|
||||
v-model:filters="filters"
|
||||
:fields='[
|
||||
{ key: "username", label: "用户名", type: "text", placeholder: "用户名" }
|
||||
{ key: "username", label: "用户名", type: "text", placeholder: "用户名" },
|
||||
{ key: "userType", label: "用户类型", type: "select", placeholder: "全部类型",
|
||||
options: [
|
||||
{ value: 1, label: "试用期" },
|
||||
{ value: 2, label: "正式" },
|
||||
{ value: 3, label: "外包" },
|
||||
{ value: 99, label: "AI 公司" }
|
||||
] }
|
||||
]'
|
||||
@search="onSearch"
|
||||
@reset="onReset"
|
||||
/>
|
||||
|
||||
<div class="rr-list-card">
|
||||
<!-- 批量操作栏 -->
|
||||
<div class="batch-bar" :class="{ 'has-selection': selectedCount > 0 }">
|
||||
<span v-if="selectedCount > 0" class="batch-count">
|
||||
已选 <strong>{{ selectedCount }}</strong> 个用户
|
||||
</span>
|
||||
<span v-else class="batch-hint">勾选用户后可批量删除</span>
|
||||
<span class="batch-actions">
|
||||
<el-button size="small" type="danger" plain :disabled="!selectedCount" @click="batchDelete">批量删除</el-button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
:columns="[
|
||||
{ prop: 'username', label: '用户名', width: 200, slot: 'username' },
|
||||
{ prop: 'realName', label: '姓名', width: 140 },
|
||||
{ prop: 'employeeId', label: '工号', width: 120 },
|
||||
{ prop: 'mobile', label: '手机号', width: 130 },
|
||||
{ prop: 'userType', label: '类型', width: 100, slot: 'userType' },
|
||||
{ prop: 'status', label: '状态', width: 80, slot: 'status' },
|
||||
{ prop: 'createDate', label: '创建时间', width: 170, slot: 'createDate' },
|
||||
{ label: '操作', width: 140, slot: 'action' }
|
||||
{ type: 'selection', width: 48, label: '' },
|
||||
{ label: '用户', minWidth: 220, slot: 'user' },
|
||||
{ prop: 'mobile', label: '手机号', width: 130, slot: 'mobile' },
|
||||
{ label: '部门 / 角色', minWidth: 180, slot: 'deptRole' },
|
||||
{ prop: 'userType', label: '类型', width: 100, slot: 'userType', align: 'center' },
|
||||
{ prop: 'status', label: '状态', width: 90, slot: 'status', align: 'center' },
|
||||
{ label: '创建时间', width: 165, slot: 'createDate' },
|
||||
{ label: '操作', width: 170, slot: 'action', fixed: 'right', align: 'center' }
|
||||
]"
|
||||
:data="list" :borderless="true"
|
||||
:data="list"
|
||||
:borderless="true"
|
||||
:loading="loading"
|
||||
:total="total"
|
||||
:page="page"
|
||||
:limit="limit"
|
||||
:show-pagination="true" :page-sizes="[10, 20, 50, 100]"
|
||||
:show-pagination="true"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:error="loadError"
|
||||
empty-text="暂无用户"
|
||||
row-key="id"
|
||||
@page-change="onPageChange"
|
||||
@limit-change="onLimitChange"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<template #username="{ row }">
|
||||
<code class="rr-username-code">{{ row.username }}</code>
|
||||
<!-- 用户单元: 头像 + 用户名 + 姓名/工号 -->
|
||||
<template #user="{ row, index }">
|
||||
<div class="user-cell">
|
||||
<span class="user-avatar" :style="avatarStyle(row, index)">
|
||||
<el-image v-if="row.headUrl" :src="row.headUrl" fit="cover" class="user-avatar-img" />
|
||||
<template v-else>{{ initial(row) }}</template>
|
||||
</span>
|
||||
<span class="user-text">
|
||||
<span class="user-name">
|
||||
{{ row.username }}
|
||||
<el-tag v-if="row.superAdmin === 1" size="small" type="warning" effect="light" class="super-tag">超管</el-tag>
|
||||
</span>
|
||||
<span class="user-sub">{{ row.realName }}{{ row.employeeId && row.employeeId !== '--' ? ` · ${row.employeeId}` : '' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 手机号 -->
|
||||
<template #mobile="{ row }">
|
||||
<span class="phone-cell"><el-icon><Phone /></el-icon>{{ row.mobile || '--' }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 部门/角色 -->
|
||||
<template #deptRole="{ row }">
|
||||
<div class="two-line">
|
||||
<span class="line-main">{{ row.deptName }}</span>
|
||||
<span class="line-sub">{{ row.roleNames || '未分配角色' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 类型 -->
|
||||
<template #userType="{ row }">
|
||||
<el-tag
|
||||
v-if="row.userType != null && USER_TYPE_MAP[row.userType]"
|
||||
:type="USER_TYPE_MAP[row.userType].type as any"
|
||||
size="small"
|
||||
effect="light"
|
||||
>{{ USER_TYPE_MAP[row.userType].label }}</el-tag>
|
||||
<span v-if="row.userType != null && USER_TYPE_MAP[row.userType]" class="chip"
|
||||
:style="{ color: USER_TYPE_MAP[row.userType].color, background: USER_TYPE_MAP[row.userType].bg }">
|
||||
{{ USER_TYPE_MAP[row.userType].label }}
|
||||
</span>
|
||||
<span v-else class="rr-muted">--</span>
|
||||
</template>
|
||||
|
||||
<!-- 状态: 圆点胶囊 -->
|
||||
<template #status="{ row }">
|
||||
<el-tag
|
||||
:type="STATUS_MAP[row.status].type as any"
|
||||
size="small"
|
||||
effect="light"
|
||||
>{{ STATUS_MAP[row.status].label }}</el-tag>
|
||||
<span class="status-tag"
|
||||
:style="{ color: STATUS_META[row.status].color, background: STATUS_META[row.status].bg }">
|
||||
<span class="status-dot"></span>{{ STATUS_META[row.status].label }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<!-- 创建时间 -->
|
||||
<template #createDate="{ row }">
|
||||
<span class="rr-time">{{ fmtDate(row.createDate) }}</span>
|
||||
<span class="date-cell"><el-icon><Clock /></el-icon>{{ fmtDate(row.createDate) }}</span>
|
||||
</template>
|
||||
|
||||
<!-- 操作 -->
|
||||
<template #action="{ row }">
|
||||
<TableActions
|
||||
:row="row"
|
||||
:actions="[
|
||||
{ label: (r: any) => r.status === 1 ? '停用' : '启用', type: (r: any) => r.status === 1 ? 'warning' : 'success', onClick: toggleStatus },
|
||||
{ label: '重置密码', type: 'primary', onClick: resetPwdHandle },
|
||||
{ label: '重置密码', type: 'info', onClick: resetPwdHandle },
|
||||
{ label: '删除', type: 'danger', onClick: deleteHandle }
|
||||
]"
|
||||
/>
|
||||
@ -311,11 +351,10 @@ onMounted(() => { load() })
|
||||
v-model="resetPwdVisible"
|
||||
title="重置密码"
|
||||
width="480"
|
||||
:style="{ borderRadius: '12px', overflow: 'hidden' }"
|
||||
align-center
|
||||
destroy-on-close
|
||||
>
|
||||
<p v-if="resetPwdTarget" style="margin-bottom: 16px;">
|
||||
<p v-if="resetPwdTarget" style="margin: 0 0 16px; color: var(--ink-2)">
|
||||
用户: <code class="rr-username-code">{{ resetPwdTarget.username }}</code>
|
||||
({{ resetPwdTarget.realName }})
|
||||
</p>
|
||||
@ -330,9 +369,8 @@ onMounted(() => { load() })
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-alert type="warning" :closable="false" style="margin-top: 8px;">
|
||||
<p>重置后请通过安全渠道告知用户</p>
|
||||
<p>用户下次登录需使用新密码</p>
|
||||
<el-alert type="warning" :closable="false" style="margin-top: 8px">
|
||||
<p style="margin: 0">重置后请通过安全渠道告知用户,下次登录需使用新密码</p>
|
||||
</el-alert>
|
||||
<template #footer>
|
||||
<el-button @click="resetPwdVisible = false">取消</el-button>
|
||||
@ -342,78 +380,64 @@ onMounted(() => { load() })
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.rr-sys-user {
|
||||
padding: 16px;
|
||||
<style scoped lang="scss">
|
||||
.rr-page { padding: 16px; }
|
||||
|
||||
/* ===== 批量操作栏 (对齐企业列表) ===== */
|
||||
.batch-bar {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
padding: 12px 16px; border-bottom: 1px solid var(--border);
|
||||
transition: background .2s;
|
||||
}
|
||||
.batch-bar.has-selection { background: var(--primary-soft, #e0ecff); }
|
||||
.batch-count { font-size: 13px; color: var(--ink-2); }
|
||||
.batch-count strong { color: var(--primary); font-size: 15px; }
|
||||
.batch-hint { font-size: 12px; color: var(--ink-3); }
|
||||
.batch-actions { margin-left: auto; }
|
||||
|
||||
/* ===== 用户单元 ===== */
|
||||
.user-cell { display: flex; align-items: center; gap: 10px; }
|
||||
.user-avatar {
|
||||
flex: 0 0 38px; width: 38px; height: 38px; border-radius: 10px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: #fff; font-size: 16px; font-weight: 700; flex-shrink: 0;
|
||||
}
|
||||
.user-avatar-img { width: 100%; height: 100%; border-radius: 10px; }
|
||||
.user-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
||||
.user-name {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
font-size: 14px; font-weight: 600; color: var(--ink);
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.super-tag { margin-left: 2px; }
|
||||
.user-sub {
|
||||
font-size: 12px; color: var(--ink-3);
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.rr-page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
/* ===== 单元格通用 ===== */
|
||||
.two-line { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
||||
.line-main { font-size: 13px; color: var(--ink); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.line-sub { font-size: 12px; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.phone-cell { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; color: var(--ink); font-family: 'SF Mono', Menlo, Consolas, monospace; }
|
||||
.date-cell { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--ink-2); font-family: 'SF Mono', Menlo, Consolas, monospace; }
|
||||
|
||||
.rr-page-titles {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
/* ===== 类型/状态胶囊 (对齐企业列表, 使用全局 --st-* 变量暗色自适应) ===== */
|
||||
.chip {
|
||||
display: inline-flex; align-items: center; width: fit-content;
|
||||
font-size: 12px; font-weight: 500; padding: 2px 12px; border-radius: 20px; white-space: nowrap;
|
||||
}
|
||||
|
||||
.rr-page-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.rr-page-sub {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.rr-page-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.rr-filters-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 14px;
|
||||
padding: 12px 16px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.rr-username-input { width: 240px; }
|
||||
.rr-type-select { width: 140px; }
|
||||
|
||||
.rr-list-card {
|
||||
background: var(--el-bg-color);
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
.status-tag {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
font-size: 12px; font-weight: 500; padding: 3px 12px; border-radius: 20px; white-space: nowrap;
|
||||
}
|
||||
.status-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
|
||||
|
||||
.rr-username-code {
|
||||
font-family: 'SF Mono', Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 4px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.rr-time {
|
||||
font-family: 'SF Mono', Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.rr-muted {
|
||||
color: var(--el-text-color-placeholder);
|
||||
font-size: 12px;
|
||||
font-size: 12px; padding: 2px 6px;
|
||||
background: var(--fill-color-light, var(--surface-2));
|
||||
border-radius: 4px; color: var(--el-color-primary);
|
||||
}
|
||||
.rr-muted { color: var(--ink-3); font-size: 12px; }
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user