优化考核调研移动端问卷体验
This commit is contained in:
parent
3e42ca3104
commit
ab2e31f01a
@ -6,6 +6,61 @@ import { buildDingtalkAutoLoginPath } from '@/lib/safe-redirect'
|
||||
export const runtime = 'nodejs'
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const SURVEY_OPENING_PAGE_STYLE = `
|
||||
html,body{margin:0;min-height:100%;background:#f7fbff;color:#111827;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||
body{min-height:100vh;min-height:100dvh;overflow:hidden}
|
||||
.topbar{height:56px;display:flex;align-items:center;justify-content:space-between;padding:0 20px;color:#fff;background:linear-gradient(90deg,#1677ff,#4f9cff);box-shadow:0 10px 24px rgba(22,119,255,.18)}
|
||||
.topbar span:first-child{font-size:28px;line-height:1}
|
||||
.topbar span:nth-child(2){font-size:15px;font-weight:600}
|
||||
.topbar span:last-child{font-size:20px;line-height:1}
|
||||
main{box-sizing:border-box;width:min(390px,100%);margin:0 auto;padding:48px 28px 0}
|
||||
.brand{text-align:center}
|
||||
.brand img{width:42px;height:42px;border-radius:10px;object-fit:cover}
|
||||
.brand h1{margin:12px 0 0;font-size:23px;font-weight:600;line-height:1}
|
||||
.brand p{margin:8px 0 0;color:#8a94a6;font-size:14px;font-weight:500}
|
||||
.open-pill{min-height:58px;margin-top:40px;padding:0 20px;display:flex;align-items:center;justify-content:space-between;border-radius:999px;background:#fff;box-shadow:0 12px 30px rgba(15,23,42,.12)}
|
||||
.open-pill strong{display:flex;align-items:center;gap:12px;font-size:16px;font-weight:600}
|
||||
.open-pill em{font-style:normal;color:#1677ff;font-size:15px;font-weight:600}
|
||||
.spinner{width:28px;height:28px;border:4px solid #d7e8ff;border-top-color:#1677ff;border-radius:999px;animation:spin .85s linear infinite}
|
||||
.message{margin:28px 0 0;text-align:center;color:#8a94a6;font-size:13px;font-weight:500;line-height:1.7}
|
||||
.ghost-card{margin-top:44px;padding:20px;border-radius:18px;background:rgba(255,255,255,.9);box-shadow:0 18px 42px rgba(15,23,42,.08)}
|
||||
.ghost-title{font-size:15px;font-weight:600;color:#7b8494}
|
||||
.ghost-line{height:12px;border-radius:999px;background:#eef3f9}
|
||||
.ghost-line.one{margin-top:24px}
|
||||
.ghost-line.two{width:74%;margin-top:12px}
|
||||
.ghost-list{margin-top:32px;display:grid;gap:20px}
|
||||
.ghost-row{display:flex;align-items:center;gap:12px}
|
||||
.ghost-dot{width:16px;height:16px;border-radius:999px;border:1px solid #e3eaf3}
|
||||
.ghost-row .ghost-line{flex:1;margin:0}
|
||||
.ghost-row:last-child .ghost-line{flex:.67}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
`
|
||||
|
||||
const SURVEY_OPENING_PAGE_MARKUP = `
|
||||
<div class="topbar"><span>‹</span><span>素材库</span><span>•••</span></div>
|
||||
<main>
|
||||
<div class="brand">
|
||||
<img src="/api/favicon" alt="素材库" />
|
||||
<h1>素材库</h1>
|
||||
<p>内部素材协作平台</p>
|
||||
</div>
|
||||
<div class="open-pill">
|
||||
<strong><span class="spinner" aria-hidden="true"></span>正在打开问卷</strong>
|
||||
<em>马上进入</em>
|
||||
</div>
|
||||
<p class="message">正在打开问卷...</p>
|
||||
<div class="ghost-card" aria-hidden="true">
|
||||
<div class="ghost-title">绩效考核问卷</div>
|
||||
<div class="ghost-line one"></div>
|
||||
<div class="ghost-line two"></div>
|
||||
<div class="ghost-list">
|
||||
<div class="ghost-row"><span class="ghost-dot"></span><span class="ghost-line"></span></div>
|
||||
<div class="ghost-row"><span class="ghost-dot"></span><span class="ghost-line"></span></div>
|
||||
<div class="ghost-row"><span class="ghost-dot"></span><span class="ghost-line"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>`
|
||||
|
||||
function appendLoginSetCookie(target: NextResponse, source: Response) {
|
||||
const getSetCookie = (source.headers as any).getSetCookie
|
||||
const cookies = typeof getSetCookie === 'function'
|
||||
@ -42,17 +97,10 @@ function createCookieLandingResponse(redirectPath: string, loginResponse: Respon
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>正在打开问卷</title>
|
||||
<meta http-equiv="Cache-Control" content="no-store" />
|
||||
<style>
|
||||
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f8fafc;color:#0f172a;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||
main{padding:28px;text-align:center}
|
||||
p{margin:8px 0 0;color:#64748b}
|
||||
</style>
|
||||
<style>${SURVEY_OPENING_PAGE_STYLE}</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>正在打开问卷</h1>
|
||||
<p>正在打开问卷...</p>
|
||||
</main>
|
||||
${SURVEY_OPENING_PAGE_MARKUP}
|
||||
<script>
|
||||
window.location.replace(${escapedPath});
|
||||
</script>
|
||||
|
||||
@ -10,6 +10,61 @@ import { normalizeInternalRedirectPath } from '@/lib/safe-redirect'
|
||||
export const runtime = 'nodejs'
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const SURVEY_OPENING_PAGE_STYLE = `
|
||||
html,body{margin:0;min-height:100%;background:#f7fbff;color:#111827;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||
body{min-height:100vh;min-height:100dvh;overflow:hidden}
|
||||
.topbar{height:56px;display:flex;align-items:center;justify-content:space-between;padding:0 20px;color:#fff;background:linear-gradient(90deg,#1677ff,#4f9cff);box-shadow:0 10px 24px rgba(22,119,255,.18)}
|
||||
.topbar span:first-child{font-size:28px;line-height:1}
|
||||
.topbar span:nth-child(2){font-size:15px;font-weight:600}
|
||||
.topbar span:last-child{font-size:20px;line-height:1}
|
||||
main{box-sizing:border-box;width:min(390px,100%);margin:0 auto;padding:48px 28px 0}
|
||||
.brand{text-align:center}
|
||||
.brand img{width:42px;height:42px;border-radius:10px;object-fit:cover}
|
||||
.brand h1{margin:12px 0 0;font-size:23px;font-weight:600;line-height:1}
|
||||
.brand p{margin:8px 0 0;color:#8a94a6;font-size:14px;font-weight:500}
|
||||
.open-pill{min-height:58px;margin-top:40px;padding:0 20px;display:flex;align-items:center;justify-content:space-between;border-radius:999px;background:#fff;box-shadow:0 12px 30px rgba(15,23,42,.12)}
|
||||
.open-pill strong{display:flex;align-items:center;gap:12px;font-size:16px;font-weight:600}
|
||||
.open-pill em{font-style:normal;color:#1677ff;font-size:15px;font-weight:600}
|
||||
.spinner{width:28px;height:28px;border:4px solid #d7e8ff;border-top-color:#1677ff;border-radius:999px;animation:spin .85s linear infinite}
|
||||
#message{margin:28px 0 0;text-align:center;color:#8a94a6;font-size:13px;font-weight:500;line-height:1.7}
|
||||
.ghost-card{margin-top:44px;padding:20px;border-radius:18px;background:rgba(255,255,255,.9);box-shadow:0 18px 42px rgba(15,23,42,.08)}
|
||||
.ghost-title{font-size:15px;font-weight:600;color:#7b8494}
|
||||
.ghost-line{height:12px;border-radius:999px;background:#eef3f9}
|
||||
.ghost-line.one{margin-top:24px}
|
||||
.ghost-line.two{width:74%;margin-top:12px}
|
||||
.ghost-list{margin-top:32px;display:grid;gap:20px}
|
||||
.ghost-row{display:flex;align-items:center;gap:12px}
|
||||
.ghost-dot{width:16px;height:16px;border-radius:999px;border:1px solid #e3eaf3}
|
||||
.ghost-row .ghost-line{flex:1;margin:0}
|
||||
.ghost-row:last-child .ghost-line{flex:.67}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
`
|
||||
|
||||
const SURVEY_OPENING_PAGE_MARKUP = `
|
||||
<div class="topbar"><span>‹</span><span>素材库</span><span>•••</span></div>
|
||||
<main>
|
||||
<div class="brand">
|
||||
<img src="/api/favicon" alt="素材库" />
|
||||
<h1>素材库</h1>
|
||||
<p>内部素材协作平台</p>
|
||||
</div>
|
||||
<div class="open-pill">
|
||||
<strong><span class="spinner" aria-hidden="true"></span>正在打开问卷</strong>
|
||||
<em>马上进入</em>
|
||||
</div>
|
||||
<p id="message">正在打开问卷...</p>
|
||||
<div class="ghost-card" aria-hidden="true">
|
||||
<div class="ghost-title">绩效考核问卷</div>
|
||||
<div class="ghost-line one"></div>
|
||||
<div class="ghost-line two"></div>
|
||||
<div class="ghost-list">
|
||||
<div class="ghost-row"><span class="ghost-dot"></span><span class="ghost-line"></span></div>
|
||||
<div class="ghost-row"><span class="ghost-dot"></span><span class="ghost-line"></span></div>
|
||||
<div class="ghost-row"><span class="ghost-dot"></span><span class="ghost-line"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>`
|
||||
|
||||
function getLoginSetCookies(source: Response): string[] {
|
||||
const getSetCookie = (source.headers as any).getSetCookie
|
||||
const cookies = typeof getSetCookie === 'function'
|
||||
@ -78,21 +133,10 @@ function createSessionTicketTransferPage({
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>正在打开问卷</title>
|
||||
<meta http-equiv="Cache-Control" content="no-store" />
|
||||
<style>
|
||||
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0f172a;color:#f8fafc;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||
main{width:min(420px,calc(100vw - 48px));padding:32px 24px;text-align:center}
|
||||
h1{margin:0 0 12px;font-size:22px}
|
||||
p{margin:0;color:#cbd5e1;line-height:1.7}
|
||||
.spinner{width:38px;height:38px;margin:0 auto 20px;border:4px solid rgba(255,255,255,.24);border-top-color:#60a5fa;border-radius:999px;animation:spin .85s linear infinite}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
</style>
|
||||
<style>${SURVEY_OPENING_PAGE_STYLE}</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="spinner" aria-hidden="true"></div>
|
||||
<h1>正在打开问卷</h1>
|
||||
<p id="message">正在打开问卷...</p>
|
||||
</main>
|
||||
${SURVEY_OPENING_PAGE_MARKUP}
|
||||
<script>
|
||||
(function () {
|
||||
var targetUrl = ${stringifyForInlineScript(redirectUrl)};
|
||||
@ -185,21 +229,10 @@ function createPerformanceSurveySessionTransferPage({
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>正在打开问卷</title>
|
||||
<meta http-equiv="Cache-Control" content="no-store" />
|
||||
<style>
|
||||
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0f172a;color:#f8fafc;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||
main{width:min(420px,calc(100vw - 48px));padding:32px 24px;text-align:center}
|
||||
h1{margin:0 0 12px;font-size:22px}
|
||||
p{margin:0;color:#cbd5e1;line-height:1.7}
|
||||
.spinner{width:38px;height:38px;margin:0 auto 20px;border:4px solid rgba(255,255,255,.24);border-top-color:#60a5fa;border-radius:999px;animation:spin .85s linear infinite}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
</style>
|
||||
<style>${SURVEY_OPENING_PAGE_STYLE}</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="spinner" aria-hidden="true"></div>
|
||||
<h1>正在打开问卷</h1>
|
||||
<p id="message">正在打开问卷...</p>
|
||||
</main>
|
||||
${SURVEY_OPENING_PAGE_MARKUP}
|
||||
<script>
|
||||
(function () {
|
||||
var targetUrl = ${stringifyForInlineScript(redirectUrl)};
|
||||
|
||||
@ -5,6 +5,61 @@ import { consumeDingtalkSessionTicket } from '@/lib/dingtalk-session-ticket'
|
||||
export const runtime = 'nodejs'
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const SURVEY_OPENING_PAGE_STYLE = `
|
||||
html,body{margin:0;min-height:100%;background:#f7fbff;color:#111827;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||
body{min-height:100vh;min-height:100dvh;overflow:hidden}
|
||||
.topbar{height:56px;display:flex;align-items:center;justify-content:space-between;padding:0 20px;color:#fff;background:linear-gradient(90deg,#1677ff,#4f9cff);box-shadow:0 10px 24px rgba(22,119,255,.18)}
|
||||
.topbar span:first-child{font-size:28px;line-height:1}
|
||||
.topbar span:nth-child(2){font-size:15px;font-weight:600}
|
||||
.topbar span:last-child{font-size:20px;line-height:1}
|
||||
main{box-sizing:border-box;width:min(390px,100%);margin:0 auto;padding:48px 28px 0}
|
||||
.brand{text-align:center}
|
||||
.brand img{width:42px;height:42px;border-radius:10px;object-fit:cover}
|
||||
.brand h1{margin:12px 0 0;font-size:23px;font-weight:600;line-height:1}
|
||||
.brand p{margin:8px 0 0;color:#8a94a6;font-size:14px;font-weight:500}
|
||||
.open-pill{min-height:58px;margin-top:40px;padding:0 20px;display:flex;align-items:center;justify-content:space-between;border-radius:999px;background:#fff;box-shadow:0 12px 30px rgba(15,23,42,.12)}
|
||||
.open-pill strong{display:flex;align-items:center;gap:12px;font-size:16px;font-weight:600}
|
||||
.open-pill em{font-style:normal;color:#1677ff;font-size:15px;font-weight:600}
|
||||
.spinner{width:28px;height:28px;border:4px solid #d7e8ff;border-top-color:#1677ff;border-radius:999px;animation:spin .85s linear infinite}
|
||||
#message{margin:28px 0 0;text-align:center;color:#8a94a6;font-size:13px;font-weight:500;line-height:1.7}
|
||||
.ghost-card{margin-top:44px;padding:20px;border-radius:18px;background:rgba(255,255,255,.9);box-shadow:0 18px 42px rgba(15,23,42,.08)}
|
||||
.ghost-title{font-size:15px;font-weight:600;color:#7b8494}
|
||||
.ghost-line{height:12px;border-radius:999px;background:#eef3f9}
|
||||
.ghost-line.one{margin-top:24px}
|
||||
.ghost-line.two{width:74%;margin-top:12px}
|
||||
.ghost-list{margin-top:32px;display:grid;gap:20px}
|
||||
.ghost-row{display:flex;align-items:center;gap:12px}
|
||||
.ghost-dot{width:16px;height:16px;border-radius:999px;border:1px solid #e3eaf3}
|
||||
.ghost-row .ghost-line{flex:1;margin:0}
|
||||
.ghost-row:last-child .ghost-line{flex:.67}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
`
|
||||
|
||||
const SURVEY_OPENING_PAGE_MARKUP = `
|
||||
<div class="topbar"><span>‹</span><span>素材库</span><span>•••</span></div>
|
||||
<main>
|
||||
<div class="brand">
|
||||
<img src="/api/favicon" alt="素材库" />
|
||||
<h1>素材库</h1>
|
||||
<p>内部素材协作平台</p>
|
||||
</div>
|
||||
<div class="open-pill">
|
||||
<strong><span class="spinner" aria-hidden="true"></span>正在打开问卷</strong>
|
||||
<em>马上进入</em>
|
||||
</div>
|
||||
<p id="message">正在打开问卷...</p>
|
||||
<div class="ghost-card" aria-hidden="true">
|
||||
<div class="ghost-title">绩效考核问卷</div>
|
||||
<div class="ghost-line one"></div>
|
||||
<div class="ghost-line two"></div>
|
||||
<div class="ghost-list">
|
||||
<div class="ghost-row"><span class="ghost-dot"></span><span class="ghost-line"></span></div>
|
||||
<div class="ghost-row"><span class="ghost-dot"></span><span class="ghost-line"></span></div>
|
||||
<div class="ghost-row"><span class="ghost-dot"></span><span class="ghost-line"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>`
|
||||
|
||||
function stringifyForInlineScript(value: string) {
|
||||
return JSON.stringify(value).replace(/</g, '\\u003c')
|
||||
}
|
||||
@ -94,21 +149,10 @@ function createSessionRedirectResponse({
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>正在打开问卷</title>
|
||||
<meta http-equiv="Cache-Control" content="no-store" />
|
||||
<style>
|
||||
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0f172a;color:#f8fafc;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||
main{width:min(420px,calc(100vw - 48px));padding:32px 24px;text-align:center}
|
||||
h1{margin:0 0 12px;font-size:22px}
|
||||
p{margin:0;color:#cbd5e1;line-height:1.7}
|
||||
.spinner{width:38px;height:38px;margin:0 auto 20px;border:4px solid rgba(255,255,255,.24);border-top-color:#60a5fa;border-radius:999px;animation:spin .85s linear infinite}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
</style>
|
||||
<style>${SURVEY_OPENING_PAGE_STYLE}</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="spinner" aria-hidden="true"></div>
|
||||
<h1>正在打开问卷</h1>
|
||||
<p id="message">正在打开问卷...</p>
|
||||
</main>
|
||||
${SURVEY_OPENING_PAGE_MARKUP}
|
||||
<script>
|
||||
(function () {
|
||||
var targetUrl = ${stringifyForInlineScript(redirectUrl)};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import Image from 'next/image'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
declare global {
|
||||
@ -132,12 +133,12 @@ export default function DingtalkEntryClient({ corpId, redirectPath }: DingtalkEn
|
||||
|
||||
const run = async () => {
|
||||
if (!corpId) {
|
||||
setMessage('暂时无法打开问卷,请联系管理员检查钉钉应用配置。')
|
||||
setMessage('请返回钉钉,从最新通知重新打开问卷。')
|
||||
return
|
||||
}
|
||||
|
||||
if (!/DingTalk/i.test(window.navigator.userAgent || '')) {
|
||||
setMessage('请在钉钉客户端内打开这条通知。')
|
||||
setMessage('请在钉钉客户端内打开通知。')
|
||||
return
|
||||
}
|
||||
|
||||
@ -155,7 +156,7 @@ export default function DingtalkEntryClient({ corpId, redirectPath }: DingtalkEn
|
||||
navigateDingtalkH5Login(authCode, redirectPath)
|
||||
} catch (error) {
|
||||
console.error('[Dingtalk Entry] 打开问卷失败:', error)
|
||||
setMessage('暂时无法打开问卷,请返回钉钉从最新通知重新进入。')
|
||||
setMessage('请返回钉钉,从最新通知重新打开问卷。')
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,11 +164,55 @@ export default function DingtalkEntryClient({ corpId, redirectPath }: DingtalkEn
|
||||
}, [corpId, redirectPath])
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen items-center justify-center bg-slate-950 px-6 text-slate-50">
|
||||
<section className="w-full max-w-sm text-center">
|
||||
<div className="mx-auto mb-5 h-10 w-10 animate-spin rounded-full border-4 border-white/25 border-t-sky-400" />
|
||||
<h1 className="mb-3 text-2xl font-semibold">正在打开问卷</h1>
|
||||
<p className="text-sm leading-7 text-slate-300">{message}</p>
|
||||
<main className="fixed inset-0 min-h-[100dvh] overflow-hidden bg-[#f7fbff] text-[#111827]">
|
||||
<div className="flex h-14 items-center justify-between bg-gradient-to-r from-[#1677ff] to-[#4f9cff] px-5 text-white shadow-[0_10px_24px_rgba(22,119,255,0.18)]">
|
||||
<span className="text-2xl leading-none">‹</span>
|
||||
<span className="text-[15px] font-semibold">素材库</span>
|
||||
<span className="text-xl leading-none">•••</span>
|
||||
</div>
|
||||
<section className="mx-auto max-w-[390px] px-7 pt-12">
|
||||
<div className="text-center">
|
||||
<Image
|
||||
src="/api/favicon"
|
||||
alt="素材库"
|
||||
width={42}
|
||||
height={42}
|
||||
unoptimized
|
||||
className="mx-auto h-[42px] w-[42px] rounded-[10px] object-cover"
|
||||
/>
|
||||
<h1 className="mt-3 text-[23px] font-semibold leading-none">素材库</h1>
|
||||
<p className="mt-2 text-[14px] font-medium text-[#8a94a6]">内部素材协作平台</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-10 flex min-h-[58px] items-center justify-between rounded-full bg-white px-5 shadow-[0_12px_30px_rgba(15,23,42,0.12)]">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="h-7 w-7 animate-spin rounded-full border-[4px] border-[#d7e8ff] border-t-[#1677ff]" />
|
||||
<span className="text-[16px] font-semibold">正在打开问卷</span>
|
||||
</div>
|
||||
<span className="text-[15px] font-semibold text-[#1677ff]">马上进入</span>
|
||||
</div>
|
||||
|
||||
<p className="mt-7 text-center text-[13px] font-medium leading-6 text-[#8a94a6]">{message}</p>
|
||||
|
||||
<div className="mt-11 rounded-[18px] bg-white/90 p-5 shadow-[0_18px_42px_rgba(15,23,42,0.08)]">
|
||||
<div className="text-[15px] font-semibold text-[#7b8494]">绩效考核问卷</div>
|
||||
<div className="mt-6 h-3 rounded-full bg-[#eef3f9]" />
|
||||
<div className="mt-3 h-3 w-3/4 rounded-full bg-[#eef3f9]" />
|
||||
<div className="mt-8 space-y-5">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="h-4 w-4 rounded-full border border-[#e3eaf3]" />
|
||||
<span className="h-3 flex-1 rounded-full bg-[#f1f5f9]" />
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="h-4 w-4 rounded-full border border-[#e3eaf3]" />
|
||||
<span className="h-3 flex-1 rounded-full bg-[#f1f5f9]" />
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="h-4 w-4 rounded-full border border-[#e3eaf3]" />
|
||||
<span className="h-3 w-2/3 rounded-full bg-[#f1f5f9]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { useParams, useRouter, useSearchParams } from 'next/navigation'
|
||||
import { useSession } from 'next-auth/react'
|
||||
@ -258,14 +259,23 @@ export default function PerformanceSurveyPage() {
|
||||
},
|
||||
} as any)
|
||||
|
||||
const isSurveySubmitPage = path[0] === 'survey' && Boolean(path[1])
|
||||
let content = <Dashboard />
|
||||
if (path[0] === 'survey' && path[1]) {
|
||||
content = <SurveySubmit surveyId={path[1]} />
|
||||
if (isSurveySubmitPage && path[1]) {
|
||||
content = <SurveySubmit surveyId={path[1]} session={effectiveSession} />
|
||||
}
|
||||
if (path[0] === 'results' && path[1]) {
|
||||
content = <SurveyResults surveyId={path[1]} />
|
||||
}
|
||||
|
||||
if (isSurveySubmitPage) {
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
{content}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
<Header session={effectiveSession} />
|
||||
@ -1211,7 +1221,7 @@ function NotificationList({
|
||||
)
|
||||
}
|
||||
|
||||
function SurveySubmit({ surveyId }: { surveyId: string }) {
|
||||
function SurveySubmit({ surveyId, session }: { surveyId: string; session: any }) {
|
||||
const searchParams = useSearchParams()
|
||||
const targetIdParam = searchParams.get('targetId')
|
||||
const detail = useApi<any>(`/surveys/${surveyId}`, [surveyId])
|
||||
@ -1260,7 +1270,7 @@ function SurveySubmit({ surveyId }: { surveyId: string }) {
|
||||
}
|
||||
}
|
||||
|
||||
if (detail.loading) return <MainPanel>正在打开问卷...</MainPanel>
|
||||
if (detail.loading) return <SurveyOpeningScreen />
|
||||
if (detail.error) return <MainPanel><ErrorPanel message={detail.error} /></MainPanel>
|
||||
if (!detail.data) return null
|
||||
|
||||
@ -1268,53 +1278,50 @@ function SurveySubmit({ surveyId }: { surveyId: string }) {
|
||||
const submitted = activeTarget?.currentAssignment?.status === 'submitted'
|
||||
const scoreValues = questions.map((question) => Number(scores[question.id] ?? 16))
|
||||
const scoreTotal = scoreValues.reduce((sum, value) => sum + value, 0)
|
||||
const averageScore = questions.length > 0 ? (scoreTotal / questions.length).toFixed(1) : '-'
|
||||
const ratedCount = questions.filter((question) => typeof scores[question.id] === 'number').length
|
||||
const progressPercent = questions.length > 0 ? Math.round((ratedCount / questions.length) * 100) : 0
|
||||
const viewer = session?.user || {}
|
||||
const viewerName = String(viewer.name || '当前用户')
|
||||
const viewerDepartment = String(viewer.department || viewer.managedDepartment || '素材库成员')
|
||||
const viewerAvatar = typeof viewer.avatar === 'string' && viewer.avatar
|
||||
? viewer.avatar
|
||||
: typeof viewer.image === 'string'
|
||||
? viewer.image
|
||||
: ''
|
||||
const surveyStatusLabel = getSurveyStatusLabel(detail.data.status)
|
||||
const dueLabel = detail.data.dueAt ? `截止 ${formatDate(detail.data.dueAt)}` : '无截止时间'
|
||||
|
||||
return (
|
||||
<main className="mx-auto max-w-3xl px-3 pb-24 pt-4 sm:px-4 sm:pb-8 sm:pt-6">
|
||||
<div className="mb-4">
|
||||
<Link href="/performance-survey" className="text-sm text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white">返回考核调研</Link>
|
||||
<h1 className="mt-3 text-xl font-semibold leading-snug text-gray-900 dark:text-white sm:text-2xl">{detail.data.title}</h1>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<StatusBadge status={detail.data.status} />
|
||||
{detail.data.dueAt && <span>截止 {formatDate(detail.data.dueAt)}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="mb-4 rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">评价对象</div>
|
||||
<div className="mt-1 truncate text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{activeTarget?.targetName || '待选择'}
|
||||
<main className="min-h-[100dvh] bg-white text-[#111827]">
|
||||
<MobileSurveyRangeStyles />
|
||||
<div className="mx-auto flex min-h-[100dvh] max-w-[430px] flex-col bg-white px-5 pb-[calc(92px+env(safe-area-inset-bottom))] pt-6 sm:my-6 sm:min-h-0 sm:rounded-[28px] sm:border sm:border-[#e5eaf2] sm:shadow-[0_24px_80px_rgba(15,23,42,0.14)]">
|
||||
<div className="mb-6 flex items-center justify-between gap-4">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<SurveyUserAvatar src={viewerAvatar} name={viewerName} />
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-[15px] font-semibold leading-5 text-[#111827]">{viewerName}</div>
|
||||
<div className="mt-0.5 truncate text-[12px] font-medium leading-4 text-[#9aa3b2]">{viewerDepartment}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-gray-50 px-3 py-2 text-right dark:bg-gray-900/40">
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">平均分</div>
|
||||
<div className="mt-0.5 text-lg font-semibold text-gray-900 dark:text-white">{averageScore}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<div className="mb-1.5 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>评分进度</span>
|
||||
<span>{ratedCount}/{questions.length || 0}</span>
|
||||
</div>
|
||||
<div className="h-2 overflow-hidden rounded-full bg-gray-100 dark:bg-gray-700">
|
||||
<div
|
||||
className="h-full rounded-full bg-gray-900 transition-all dark:bg-white"
|
||||
style={{ width: `${progressPercent}%` }}
|
||||
/>
|
||||
<span className="shrink-0 rounded-[7px] bg-[#eaf3ff] px-3 py-1.5 text-[13px] font-semibold leading-none text-[#1677ff]">
|
||||
{surveyStatusLabel}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mb-7">
|
||||
<h1 className="text-[22px] font-semibold leading-[1.25] tracking-[-0.01em] text-[#111827]">{detail.data.title}</h1>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[13px] font-medium leading-5 text-[#7b8494]">
|
||||
<span>评价对象:{activeTarget?.targetName || '待选择'}</span>
|
||||
<span>{dueLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{targets.length > 1 && (
|
||||
<label className="mt-4 grid gap-1.5 text-sm">
|
||||
<span className="font-medium text-gray-700 dark:text-gray-300">评价对象</span>
|
||||
<label className="mb-4 grid gap-2 text-sm">
|
||||
<span className="font-semibold text-[#4b5565]">切换评价对象</span>
|
||||
<select
|
||||
value={activeTargetId}
|
||||
onChange={(event) => setTargetId(event.target.value)}
|
||||
className="min-h-11 rounded-lg border border-gray-300 bg-white px-3 py-2 text-base text-gray-900 dark:border-gray-600 dark:bg-gray-900 dark:text-white sm:text-sm"
|
||||
className="min-h-11 rounded-[10px] border border-[#d9e0ea] bg-white px-3 py-2 text-base font-medium text-[#111827] outline-none transition focus:border-[#1677ff] focus:ring-4 focus:ring-[#1677ff]/10"
|
||||
>
|
||||
{targets.map((target: any) => (
|
||||
<option key={target.id} value={target.id}>{target.targetName}</option>
|
||||
@ -1322,70 +1329,152 @@ function SurveySubmit({ surveyId }: { surveyId: string }) {
|
||||
</select>
|
||||
</label>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{submitted ? (
|
||||
<EmptyState title="你已提交该评价" />
|
||||
) : detail.data.status !== 'open' ? (
|
||||
<EmptyState title="该调研未开放或已关闭" />
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{questions.map((question, index) => (
|
||||
<section key={question.id} className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
|
||||
<div className="mb-3 flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs font-medium text-gray-400 dark:text-gray-500">问题 {index + 1}</div>
|
||||
<div className="mt-1 font-medium leading-snug text-gray-900 dark:text-white">{question.title}</div>
|
||||
{question.description && <div className="mt-1 text-sm leading-6 text-gray-500 dark:text-gray-400">{question.description}</div>}
|
||||
</div>
|
||||
<span className="shrink-0 rounded-md bg-gray-900 px-2.5 py-1 text-sm font-semibold text-white dark:bg-white dark:text-gray-900">{scores[question.id] ?? 16}/20</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={20}
|
||||
value={scores[question.id] ?? 16}
|
||||
onChange={(event) => setScores((current) => ({ ...current, [question.id]: Number(event.target.value) }))}
|
||||
className="h-8 w-full accent-gray-900 dark:accent-white"
|
||||
/>
|
||||
<div className="mt-1 flex justify-between text-[11px] text-gray-400 dark:text-gray-500">
|
||||
<span>0</span>
|
||||
<span>10</span>
|
||||
<span>20</span>
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
|
||||
<section className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800">
|
||||
<label className="grid gap-1.5 text-sm">
|
||||
<span className="font-medium text-gray-700 dark:text-gray-300">补充评价</span>
|
||||
<textarea
|
||||
value={comment}
|
||||
onChange={(event) => setComment(event.target.value)}
|
||||
rows={4}
|
||||
placeholder="可以补充具体建议或说明"
|
||||
className="min-h-28 rounded-lg border border-gray-300 px-3 py-2 text-base text-gray-900 dark:border-gray-600 dark:bg-gray-900 dark:text-white sm:text-sm"
|
||||
/>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
{message && <div className="rounded-lg bg-gray-100 px-3 py-2 text-sm text-gray-700 dark:bg-gray-800 dark:text-gray-200">{message}</div>}
|
||||
|
||||
<div className="sticky bottom-0 -mx-3 border-t border-gray-200 bg-gray-50/95 p-3 backdrop-blur dark:border-gray-700 dark:bg-gray-900/95 sm:static sm:mx-0 sm:border-0 sm:bg-transparent sm:p-0 sm:backdrop-blur-none">
|
||||
<button
|
||||
disabled={submitting || !activeTargetId}
|
||||
onClick={submit}
|
||||
className="min-h-11 w-full rounded-lg bg-gray-900 px-4 py-2.5 text-base font-medium text-white hover:bg-gray-700 disabled:opacity-50 dark:bg-white dark:text-gray-900 sm:w-auto sm:text-sm"
|
||||
>
|
||||
{submitting ? '提交中...' : '提交评价'}
|
||||
</button>
|
||||
{submitted ? (
|
||||
<div className="rounded-[12px] border border-[#dce6f2] bg-[#f8fbff] px-4 py-10 text-center">
|
||||
<div className="text-[18px] font-semibold text-[#111827]">你已提交该评价</div>
|
||||
<Link href="/performance-survey" className="mt-4 inline-flex rounded-[10px] bg-[#1677ff] px-4 py-2 text-sm font-semibold text-white">
|
||||
返回考核调研
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
) : detail.data.status !== 'open' ? (
|
||||
<div className="rounded-[12px] border border-[#dce6f2] bg-[#f8fbff] px-4 py-10 text-center text-[16px] font-semibold text-[#4b5565]">
|
||||
该调研未开放或已关闭
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{questions.map((question, index) => {
|
||||
const value = scores[question.id] ?? 16
|
||||
const scorePercent = Math.max(0, Math.min(100, (value / 20) * 100))
|
||||
const accentColor = getQuestionAccentColor(index)
|
||||
return (
|
||||
<section key={question.id} className="rounded-[10px] border border-[#dfe6f0] bg-white px-3.5 py-4 shadow-[0_8px_24px_rgba(15,23,42,0.04)]">
|
||||
<div className="grid grid-cols-[32px_minmax(0,1fr)_auto] items-start gap-3">
|
||||
<span
|
||||
className="mt-0.5 flex h-7 w-7 items-center justify-center rounded-full text-[14px] font-semibold leading-none text-white"
|
||||
style={{ backgroundColor: accentColor }}
|
||||
>
|
||||
{index + 1}
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-[15px] font-semibold leading-6 text-[#111827]">
|
||||
{question.title}
|
||||
<span className="ml-1 text-[11px] font-medium text-[#98a2b3]">满分20分</span>
|
||||
</h2>
|
||||
{question.description && (
|
||||
<p className="mt-1 text-[13px] font-medium leading-5 text-[#7b8494]">{question.description}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="pt-1 text-right">
|
||||
<span className="text-[18px] font-semibold leading-none" style={{ color: accentColor }}>{value}</span>
|
||||
<span className="ml-1 text-[11px] font-medium text-[#98a2b3]">/ 20分</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={20}
|
||||
value={value}
|
||||
onChange={(event) => setScores((current) => ({ ...current, [question.id]: Number(event.target.value) }))}
|
||||
className="survey-score-range mt-4 w-full"
|
||||
style={{ '--score-percent': `${scorePercent}%`, '--score-color': accentColor } as CSSProperties}
|
||||
/>
|
||||
<div className="mt-2 grid grid-cols-5 text-[11px] font-medium text-[#9aa3b2]">
|
||||
<span>0</span>
|
||||
<span className="text-center">5</span>
|
||||
<span className="text-center">10</span>
|
||||
<span className="text-center">15</span>
|
||||
<span className="text-right">20</span>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
})}
|
||||
|
||||
<section className="rounded-[10px] border border-[#dfe6f0] bg-white px-3.5 py-4 shadow-[0_8px_24px_rgba(15,23,42,0.04)]">
|
||||
<label className="grid gap-3">
|
||||
<span className="text-[15px] font-semibold text-[#111827]">补充评价</span>
|
||||
<textarea
|
||||
value={comment}
|
||||
onChange={(event) => setComment(event.target.value.slice(0, 300))}
|
||||
rows={4}
|
||||
placeholder="可以从优点、待改进或建议等方面补充说明..."
|
||||
className="min-h-[92px] resize-none rounded-[9px] border border-[#d9e0ea] px-3 py-2.5 text-[14px] leading-6 text-[#111827] outline-none transition placeholder:text-[#c0c7d2] focus:border-[#1677ff] focus:ring-4 focus:ring-[#1677ff]/10"
|
||||
/>
|
||||
<span className="text-right text-[11px] font-medium text-[#9aa3b2]">{comment.length} / 300</span>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
{message && <div className="rounded-[10px] bg-[#eef6ff] px-3 py-2 text-sm font-medium text-[#1677ff]">{message}</div>}
|
||||
|
||||
<div className="fixed inset-x-0 bottom-0 z-20 border-t border-[#e5edf6] bg-white/95 px-5 pb-[calc(14px+env(safe-area-inset-bottom))] pt-3 backdrop-blur sm:sticky sm:-mx-5 sm:mt-1">
|
||||
<div className="mx-auto max-w-[390px]">
|
||||
<button
|
||||
disabled={submitting || !activeTargetId || ratedCount !== questions.length}
|
||||
onClick={submit}
|
||||
className="min-h-[50px] w-full rounded-[9px] bg-[#1677ff] px-4 py-2.5 text-[17px] font-semibold text-white shadow-[0_10px_22px_rgba(22,119,255,0.28)] transition active:scale-[0.99] disabled:bg-[#9ec9ff] disabled:shadow-none"
|
||||
>
|
||||
{submitting ? '提交中...' : '提交评价'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function SurveyUserAvatar({ src, name }: { src?: string; name: string }) {
|
||||
if (src) {
|
||||
return (
|
||||
<Image
|
||||
src={src}
|
||||
alt={name || '用户头像'}
|
||||
width={44}
|
||||
height={44}
|
||||
unoptimized
|
||||
className="h-11 w-11 rounded-full border border-[#eef2f7] object-cover shadow-[0_4px_12px_rgba(15,23,42,0.08)]"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-11 w-11 items-center justify-center rounded-full border border-[#eef2f7] bg-[#f1f6ff] text-[16px] font-semibold text-[#1677ff] shadow-[0_4px_12px_rgba(15,23,42,0.08)]">
|
||||
{getSurveyInitials(name)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function getSurveyInitials(name: string) {
|
||||
const text = String(name || '').trim()
|
||||
if (!text) return '用'
|
||||
const chars = Array.from(text)
|
||||
if (/^[A-Za-z\s]+$/.test(text)) {
|
||||
return text
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.slice(0, 2)
|
||||
.map((item) => item[0]?.toUpperCase())
|
||||
.join('')
|
||||
}
|
||||
return chars.slice(0, 2).join('')
|
||||
}
|
||||
|
||||
function getQuestionAccentColor(index: number) {
|
||||
const colors = ['#1677ff', '#16a34a', '#1677ff', '#7c3aed', '#f59e0b']
|
||||
return colors[index % colors.length]
|
||||
}
|
||||
|
||||
function getSurveyStatusLabel(status: string) {
|
||||
const map: Record<string, string> = {
|
||||
draft: '草稿',
|
||||
open: '进行中',
|
||||
closed: '已关闭',
|
||||
}
|
||||
return map[status] || status
|
||||
}
|
||||
|
||||
function SurveyResults({ surveyId }: { surveyId: string }) {
|
||||
const searchParams = useSearchParams()
|
||||
const query = searchParams.get('targetId') ? `?targetId=${encodeURIComponent(searchParams.get('targetId') || '')}` : ''
|
||||
@ -1534,23 +1623,131 @@ function EmptyState({ title }: { title: string }) {
|
||||
)
|
||||
}
|
||||
|
||||
function LoadingPage() {
|
||||
function MobileSurveyRangeStyles() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gray-50 px-6 text-gray-500 dark:bg-gray-900 dark:text-gray-300">
|
||||
<div className="w-full max-w-xs text-center">
|
||||
<div className="mx-auto mb-5 h-10 w-10 animate-spin rounded-full border-4 border-gray-200 border-t-gray-900 dark:border-gray-700 dark:border-t-white" />
|
||||
<h1 className="text-xl font-semibold text-gray-900 dark:text-white">正在打开问卷</h1>
|
||||
<p className="mt-2 text-sm">请稍候</p>
|
||||
<div className="mt-6 space-y-2 rounded-lg border border-gray-200 bg-white p-3 dark:border-gray-700 dark:bg-gray-800">
|
||||
<div className="h-3 w-2/3 rounded bg-gray-100 dark:bg-gray-700" />
|
||||
<div className="h-3 w-full rounded bg-gray-100 dark:bg-gray-700" />
|
||||
<div className="h-3 w-5/6 rounded bg-gray-100 dark:bg-gray-700" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>{`
|
||||
.survey-score-range {
|
||||
height: 24px;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.survey-score-range:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.survey-score-range::-webkit-slider-runnable-track {
|
||||
height: 4px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
var(--score-color, #1677ff) 0%,
|
||||
var(--score-color, #1677ff) var(--score-percent),
|
||||
#e5eaf2 var(--score-percent),
|
||||
#e5eaf2 100%
|
||||
);
|
||||
}
|
||||
|
||||
.survey-score-range::-webkit-slider-thumb {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-top: -7px;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
border: 3px solid var(--score-color, #1677ff);
|
||||
border-radius: 999px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 6px rgba(22, 119, 255, 0.22);
|
||||
}
|
||||
|
||||
.survey-score-range::-moz-range-track {
|
||||
height: 4px;
|
||||
border-radius: 999px;
|
||||
background: #e5eaf2;
|
||||
}
|
||||
|
||||
.survey-score-range::-moz-range-progress {
|
||||
height: 4px;
|
||||
border-radius: 999px;
|
||||
background: var(--score-color, #1677ff);
|
||||
}
|
||||
|
||||
.survey-score-range::-moz-range-thumb {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 3px solid var(--score-color, #1677ff);
|
||||
border-radius: 999px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 6px rgba(22, 119, 255, 0.22);
|
||||
}
|
||||
`}</style>
|
||||
)
|
||||
}
|
||||
|
||||
function SurveyOpeningScreen() {
|
||||
return (
|
||||
<main className="fixed inset-0 z-[90] min-h-[100dvh] overflow-hidden bg-[#f7fbff] text-[#111827]">
|
||||
<div className="flex h-14 items-center justify-between bg-gradient-to-r from-[#1677ff] to-[#4f9cff] px-5 text-white shadow-[0_10px_24px_rgba(22,119,255,0.18)]">
|
||||
<span className="text-2xl leading-none">‹</span>
|
||||
<span className="text-[15px] font-semibold">素材库</span>
|
||||
<span className="text-xl leading-none">•••</span>
|
||||
</div>
|
||||
<section className="mx-auto max-w-[390px] px-7 pt-12">
|
||||
<div className="text-center">
|
||||
<Image
|
||||
src="/api/favicon"
|
||||
alt="素材库"
|
||||
width={42}
|
||||
height={42}
|
||||
unoptimized
|
||||
className="mx-auto h-[42px] w-[42px] rounded-[10px] object-cover"
|
||||
/>
|
||||
<h1 className="mt-3 text-[23px] font-semibold leading-none">素材库</h1>
|
||||
<p className="mt-2 text-[14px] font-medium text-[#8a94a6]">内部素材协作平台</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-10 flex min-h-[58px] items-center justify-between rounded-full bg-white px-5 shadow-[0_12px_30px_rgba(15,23,42,0.12)]">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="h-7 w-7 animate-spin rounded-full border-[4px] border-[#d7e8ff] border-t-[#1677ff]" />
|
||||
<span className="text-[16px] font-semibold">正在打开问卷</span>
|
||||
</div>
|
||||
<span className="text-[15px] font-semibold text-[#1677ff]">马上进入</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-7 flex items-center justify-center gap-2 text-[13px] font-medium text-[#8a94a6]">
|
||||
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-[#eaf3ff] text-[12px] font-semibold text-[#1677ff]">✓</span>
|
||||
<span>安全访问中,请放心使用</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-11 rounded-[18px] bg-white/90 p-5 shadow-[0_18px_42px_rgba(15,23,42,0.08)]">
|
||||
<div className="text-[15px] font-semibold text-[#7b8494]">绩效考核问卷</div>
|
||||
<div className="mt-6 h-3 rounded-full bg-[#eef3f9]" />
|
||||
<div className="mt-3 h-3 w-3/4 rounded-full bg-[#eef3f9]" />
|
||||
<div className="mt-8 space-y-5">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="h-4 w-4 rounded-full border border-[#e3eaf3]" />
|
||||
<span className="h-3 flex-1 rounded-full bg-[#f1f5f9]" />
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="h-4 w-4 rounded-full border border-[#e3eaf3]" />
|
||||
<span className="h-3 flex-1 rounded-full bg-[#f1f5f9]" />
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="h-4 w-4 rounded-full border border-[#e3eaf3]" />
|
||||
<span className="h-3 w-2/3 rounded-full bg-[#f1f5f9]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function LoadingPage() {
|
||||
return <SurveyOpeningScreen />
|
||||
}
|
||||
|
||||
function formatDate(value: string | Date) {
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return '-'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user