20260819173221

This commit is contained in:
oneao committed 2026-08-19 17:32:25 +08:00
1 parent 1ffc8e1ac9
commit a0a4273f4c
326 files changed
+301646 -2314

No files matched your search

+198 -25
View File
@@ -1,7 +1,10 @@
<script setup>
import { computed, ref } from 'vue'
import { LayoutDashboard } from '@lucide/vue'
import { useRouter } from 'vue-router'
import Button from '@/components/ui/button/button.vue'
import Input from '@/components/ui/input/input.vue'
import FmsTable from '@/components/fms-table/FmsTable.vue'
defineOptions({ name: 'Dashboard' })
@@ -11,21 +14,133 @@ const router = useRouter()
function goDetail(id) {
router.push(`/detail/${id}`)
}
/* ---------- fms-table 大数据量 + 单行编辑 demo ---------- */
const TYPE_OPTIONS = [
{ value: 'task', label: '任务' },
{ value: 'job', label: '作业' },
{ value: 'flow', label: '流程' },
{ value: 'alert', label: '告警' },
]
const STATUS_OPTIONS = [
{ value: 'active', label: '启用' },
{ value: 'disabled', label: '禁用' },
{ value: 'pending', label: '待审' },
]
// 额外可编辑文本列数量,与下方业务列合计 20 列
const EXTRA_FIELD_COUNT = 14
function makeColumns() {
const columns = [
{ title: 'ID', dataIndex: 'id', width: 70, editor: { type: 'input', inputType: 'number' } },
{ title: '名称', dataIndex: 'name', width: 120, editor: { type: 'input' } },
{
title: '类型',
dataIndex: 'type',
width: 90,
editor: { type: 'select', options: TYPE_OPTIONS },
},
{
title: '状态',
dataIndex: 'status',
width: 90,
editor: { type: 'select', options: STATUS_OPTIONS },
},
{ title: '启用', dataIndex: 'enabled', width: 64, editor: { type: 'checkbox' } },
{ title: '评分', dataIndex: 'score', width: 80, editor: { type: 'input', inputType: 'number' } },
]
for (let i = 1; i <= EXTRA_FIELD_COUNT; i++) {
columns.push({
title: `字段 ${i}`,
dataIndex: `f${i}`,
width: 80,
editor: { type: 'input' },
})
}
return columns
}
function makeRows(count) {
const rows = []
for (let i = 0; i < count; i++) {
const row = {
id: i + 1,
name: `任务-${String(i + 1).padStart(5, '0')}`,
type: TYPE_OPTIONS[i % TYPE_OPTIONS.length].value,
status: STATUS_OPTIONS[i % STATUS_OPTIONS.length].value,
enabled: i % 2,
score: Math.round(Math.random() * 1000) / 10,
}
for (let j = 1; j <= EXTRA_FIELD_COUNT; j++) row[`f${j}`] = `${i + 1}-${j}`
rows.push(row)
}
return rows
}
const DEFAULT_ROW_COUNT = 5000
const columns = ref(makeColumns())
const rowCount = ref(DEFAULT_ROW_COUNT)
const dataSource = ref(makeRows(DEFAULT_ROW_COUNT))
const editCount = ref(0)
// 修改行数后重新生成全新数据(行对象全部重建,fms-table 编辑状态随之自然重置)
function regenerate() {
const count = Math.max(
1,
Math.min(100000, Math.floor(Number(rowCount.value)) || DEFAULT_ROW_COUNT),
)
rowCount.value = count
dataSource.value = makeRows(count)
editCount.value = 0
}
// fms-table 编辑提交时回传 (row, col, newValue),这里统计编辑次数
function onCellChange() {
editCount.value++
}
const totalCells = computed(() => columns.value.length * dataSource.value.length)
</script>
<template>
<div class="dashboard-page">
<div class="dashboard-icon" aria-hidden="true">
<LayoutDashboard />
</div>
<h1 class="dashboard-title">工作台</h1>
<p class="dashboard-desc">布局壳与业务页面已就绪,点击下方按钮进入详情页验证多参数缓存。</p>
<header class="dashboard-header">
<div class="dashboard-heading">
<div class="dashboard-icon" aria-hidden="true">
<LayoutDashboard />
</div>
<div>
<h1 class="dashboard-title">工作台 · fms-table Demo</h1>
<p class="dashboard-desc">
20 列单行编辑示例:单击单元格进入编辑,Enter 提交,Esc 取消,方向键在单元格间移动。默认
5000 行(虚拟滚动),可修改行数后「重新生成」。
</p>
</div>
</div>
<div class="dashboard-actions">
<Button type="primary" @click="goDetail(1)">打开详情 #1</Button>
<Button type="primary" @click="goDetail(2)">打开详情 #2</Button>
<Button type="primary" @click="goDetail(3)">打开详情 #3</Button>
</div>
<div class="dashboard-actions">
<Button type="primary" @click="goDetail(1)">打开详情 #1</Button>
<Button type="primary" @click="goDetail(2)">打开详情 #2</Button>
</div>
</header>
<section class="demo-card">
<div class="demo-toolbar">
<label class="toolbar-field">
<span class="toolbar-label">行数</span>
<Input v-model="rowCount" type="number" class="toolbar-input" />
</label>
<Button type="primary" @click="regenerate">重新生成</Button>
<span class="toolbar-meta">
{{ dataSource.length.toLocaleString() }} 行 × {{ columns.length }} 列(内置序号列不计入),
共 {{ totalCells.toLocaleString() }} 个单元格 · 已编辑 {{ editCount }} 次
</span>
</div>
<div class="demo-table">
<FmsTable :columns="columns" :data-source="dataSource" row-key="id" @change="onCellChange" />
</div>
</section>
</div>
</template>
@@ -33,48 +148,106 @@ function goDetail(id) {
.dashboard-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
min-height: 100%;
padding: 24px;
/* 页面高度 = fms-content 可视内容高度(100vh - 顶栏40 - 布局gap8 - 页签40 - 内容padding 12+16),
让滚动条落在表格内部而不是整个页面滚动 */
height: calc(100vh - 116px);
color: var(--fms-text);
text-align: center;
}
.dashboard-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
}
.dashboard-heading {
display: flex;
align-items: center;
gap: 12px;
}
.dashboard-icon {
display: flex;
align-items: center;
justify-content: center;
width: 52px;
height: 52px;
margin-bottom: 8px;
border-radius: 12px;
width: 44px;
height: 44px;
flex: none;
border-radius: 10px;
background: color-mix(in srgb, var(--fms-primary) 10%, var(--fms-card));
color: var(--fms-primary);
:deep(svg) {
width: 26px;
height: 26px;
width: 22px;
height: 22px;
}
}
.dashboard-title {
margin: 0;
font-size: 22px;
font-size: 18px;
font-weight: 600;
}
.dashboard-desc {
margin: 0;
font-size: 14px;
margin: 2px 0 0;
font-size: 13px;
color: var(--fms-text-secondary);
}
.dashboard-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
/* ---- demo 卡片:工具栏 + 表格 ---- */
.demo-card {
display: flex;
flex-direction: column;
min-height: 0;
flex: 1;
overflow: hidden;
border: 1px solid var(--fms-border);
border-radius: 8px;
background: var(--fms-card);
}
.demo-toolbar {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
margin-top: 8px;
padding: 10px 12px;
border-bottom: 1px solid var(--fms-border);
}
.toolbar-field {
display: inline-flex;
align-items: center;
gap: 8px;
}
.toolbar-label {
font-size: 13px;
color: var(--fms-text-secondary);
}
.toolbar-input {
width: 120px;
}
.toolbar-meta {
margin-left: auto;
font-size: 12px;
color: var(--fms-text-secondary);
}
.demo-table {
flex: 1;
min-height: 0;
}
</style>
+15 -27
View File
@@ -9,7 +9,6 @@ import Button from '@/components/ui/button/button.vue'
import Message from '@/components/ui/message/message-manager.js'
import { loginApi } from '@/services/api'
import { useAuthStore } from '@/stores/auth'
import { usePermissionStore } from '@/stores/permissions'
const router = useRouter()
const route = useRoute()
@@ -26,35 +25,21 @@ const loginHint = computed(() => {
/* ---------- 表单 ---------- */
const formModel = reactive({
orgId: '',
userId: '',
account: '',
password: '',
})
const formRules = {
orgId: [{ required: true, message: '请输入机构码' }],
userId: [{ required: true, message: '请输入账号' }],
account: [{ required: true, message: '请输入账号' }],
}
const submitting = ref(false)
const LAST_LOGIN_KEY = 'fms-last-login'
onMounted(() => {
// 从独立存储读取上次登录信息回填,与认证状态分离
let lastOrgId = ''
let lastAccount = ''
try {
const raw = localStorage.getItem(LAST_LOGIN_KEY)
if (raw) {
const last = JSON.parse(raw)
lastOrgId = last.orgId || ''
lastAccount = last.account || ''
}
} catch {
// 解析失败不影响登录
}
formModel.orgId = lastOrgId
formModel.userId = lastAccount
// 回填上次登录信息:由 persist 插件在启动时恢复,不手动碰 localStorage
formModel.orgId = authStore.userInfo.orgId || ''
formModel.account = authStore.userInfo.account || ''
formModel.password = ''
// 聚焦机构码输入框(id 落在 Input 根元素上,需定位内部 input)
document.querySelector('#orgId input')?.focus()
@@ -64,13 +49,14 @@ onMounted(() => {
async function onSubmit(values) {
submitting.value = true
try {
const res = await loginApi(values.orgId, values.userId, values.password ?? '')
const res = await loginApi(values.orgId, values.account, values.password ?? '')
authStore.setSession(
{ token: res.data.token, user: { ...res.data.user, account: values.userId } },
{ token: res.data.token, user: { ...res.data.user } },
res.data.orgid ?? values.orgId,
)
// 登录成功后、进入布局前先把菜单/权限数据拉齐,避免 /dashboard 挂载时菜单异步“跳出”
await usePermissionStore().load().catch(() => {})
// 记住本次登录(机构码 + 账号),供下次回填;统一由 persist 插件持久化
authStore.saveLastLogin(values.orgId, values.account)
// 权限数据由路由守卫在跳转 /dashboard 时统一加载,无需在此预加载
Message.success('登录成功')
router.replace(String(route.query.redirect || '/dashboard'))
} catch (e) {
@@ -122,10 +108,11 @@ function onFailed() {
</Input>
</FormItem>
<FormItem name="userId" label="账号">
<FormItem name="account" label="账号">
<Input
id="userId"
v-model="formModel.userId"
id="account"
v-model="formModel.account"
autocomplete="username"
placeholder="请输入账号"
:disabled="submitting"
>
@@ -138,6 +125,7 @@ function onFailed() {
id="password"
v-model="formModel.password"
type="password"
autocomplete="current-password"
placeholder="请输入密码"
:disabled="submitting"
>