feat: 完成 Passkey 能力与前后台加载优化
更新说明:\n1. 新增用户端与管理员端 Passkey 登录/注册/设备管理(最多3台,支持设备备注、删除设备)。\n2. 修复 Passkey 注册与登录流程中的浏览器/证书/CSRF相关问题,增强错误提示。\n3. 前台登录页改为独立入口,首屏仅加载必要资源,其他页面按需加载。\n4. 系统配置页改为静默获取金山文档状态,避免首屏阻塞,并优化状态展示为“检测中/已登录/未登录/异常”。\n5. 补充后端接口与页面渲染适配,修复多入口下样式依赖注入问题。\n6. 同步更新前后台构建产物与相关静态资源。
This commit is contained in:
@@ -19,6 +19,9 @@ const userStore = useUserStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const schedules = ref([])
|
||||
const schedulePage = ref(1)
|
||||
const scheduleTotal = ref(0)
|
||||
const schedulePageSize = 12
|
||||
|
||||
const accountsLoading = ref(false)
|
||||
const accountOptions = ref([])
|
||||
@@ -65,6 +68,7 @@ const weekdayOptions = [
|
||||
]
|
||||
|
||||
const canUseSchedule = computed(() => userStore.isVip)
|
||||
const scheduleTotalPages = computed(() => Math.max(1, Math.ceil((scheduleTotal.value || 0) / schedulePageSize)))
|
||||
|
||||
function normalizeTime(value) {
|
||||
const match = String(value || '').match(/^(\d{1,2}):(\d{2})$/)
|
||||
@@ -94,17 +98,37 @@ async function loadAccounts() {
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadSchedulesAfterMutate() {
|
||||
if (schedulePage.value > 1 && schedules.value.length <= 1) {
|
||||
schedulePage.value -= 1
|
||||
}
|
||||
await loadSchedules()
|
||||
}
|
||||
|
||||
async function onSchedulePageChange(page) {
|
||||
schedulePage.value = page
|
||||
await loadSchedules()
|
||||
}
|
||||
|
||||
async function loadSchedules() {
|
||||
loading.value = true
|
||||
try {
|
||||
const list = await fetchSchedules()
|
||||
schedules.value = (Array.isArray(list) ? list : []).map((s) => ({
|
||||
const params = {
|
||||
limit: schedulePageSize,
|
||||
offset: (schedulePage.value - 1) * schedulePageSize,
|
||||
}
|
||||
const payload = await fetchSchedules(params)
|
||||
const rawItems = Array.isArray(payload) ? payload : (Array.isArray(payload?.items) ? payload.items : [])
|
||||
const rawTotal = Array.isArray(payload) ? rawItems.length : Number(payload?.total ?? rawItems.length)
|
||||
schedules.value = rawItems.map((s) => ({
|
||||
...s,
|
||||
browse_type: normalizeBrowseType(s?.browse_type),
|
||||
}))
|
||||
scheduleTotal.value = Number.isFinite(rawTotal) ? Math.max(0, rawTotal) : rawItems.length
|
||||
} catch (e) {
|
||||
if (e?.response?.status === 401) window.location.href = '/login'
|
||||
schedules.value = []
|
||||
scheduleTotal.value = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -172,6 +196,7 @@ async function saveSchedule() {
|
||||
} else {
|
||||
await createSchedule(payload)
|
||||
ElMessage.success('创建成功')
|
||||
schedulePage.value = 1
|
||||
}
|
||||
editorOpen.value = false
|
||||
await loadSchedules()
|
||||
@@ -198,7 +223,7 @@ async function onDelete(schedule) {
|
||||
const res = await deleteSchedule(schedule.id)
|
||||
if (res?.success) {
|
||||
ElMessage.success('已删除')
|
||||
await loadSchedules()
|
||||
await reloadSchedulesAfterMutate()
|
||||
} else {
|
||||
ElMessage.error(res?.error || '删除失败')
|
||||
}
|
||||
@@ -375,6 +400,17 @@ onMounted(async () => {
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<div v-if="scheduleTotal > schedulePageSize" class="pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="schedulePage"
|
||||
:page-size="schedulePageSize"
|
||||
:total="scheduleTotal"
|
||||
layout="prev, pager, next, jumper, ->, total"
|
||||
@current-change="onSchedulePageChange"
|
||||
/>
|
||||
<div class="page-hint app-muted">第 {{ schedulePage }} / {{ scheduleTotalPages }} 页</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
|
||||
@@ -593,6 +629,19 @@ onMounted(async () => {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.page-hint {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.logs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user