运行lint,将theLastPage的内容存储在sessionStorage里

This commit is contained in:
Linus Torvalds
2026-02-27 19:38:16 +08:00
parent bd1d9f2cec
commit 1f8a5f1d98
40 changed files with 663 additions and 514 deletions

3
front/src/app.d.ts vendored
View File

@@ -10,7 +10,4 @@ declare global {
} }
} }
export {}; export {};

View File

@@ -4,9 +4,9 @@
import Settings from 'carbon-icons-svelte/lib/Settings.svelte'; import Settings from 'carbon-icons-svelte/lib/Settings.svelte';
import Home from 'carbon-icons-svelte/lib/Home.svelte'; import Home from 'carbon-icons-svelte/lib/Home.svelte';
import Return from 'carbon-icons-svelte/lib/Return.svelte'; import Return from 'carbon-icons-svelte/lib/Return.svelte';
import SearchAdvanced from "carbon-icons-svelte/lib/SearchAdvanced.svelte"; import SearchAdvanced from 'carbon-icons-svelte/lib/SearchAdvanced.svelte';
import CarbonForIbmDotcom from "carbon-icons-svelte/lib/CarbonForIbmDotcom.svelte"; import CarbonForIbmDotcom from 'carbon-icons-svelte/lib/CarbonForIbmDotcom.svelte';
import EventSchedule from "carbon-icons-svelte/lib/EventSchedule.svelte"; import EventSchedule from 'carbon-icons-svelte/lib/EventSchedule.svelte';
import { page } from '$app/state'; import { page } from '$app/state';
let isAdminView = $derived(page.url.pathname.startsWith('/admin')); let isAdminView = $derived(page.url.pathname.startsWith('/admin'));
@@ -45,12 +45,15 @@
<span>后台中心</span> <span>后台中心</span>
</a> </a>
<a href="/op/ticket_search" class="nav-item" class:active={page.url.pathname === '/op/ticket_search'}> <a
href="/op/ticket_search"
class="nav-item"
class:active={page.url.pathname === '/op/ticket_search'}
>
<SearchAdvanced /> <SearchAdvanced />
<span>检索工单</span> <span>检索工单</span>
</a> </a>
<a href="/op/scheduler" class="nav-item" class:active={page.url.pathname === '/op/scheduler'}> <a href="/op/scheduler" class="nav-item" class:active={page.url.pathname === '/op/scheduler'}>
<EventSchedule /> <EventSchedule />
<span>我的排班</span> <span>我的排班</span>
</a> </a>
@@ -69,12 +72,19 @@
<span>管理中心</span> <span>管理中心</span>
</a> </a>
<a href="/admin/add_ticket" class="nav-item" class:active={page.url.pathname === '/admin/add_ticket'}> <a
href="/admin/add_ticket"
class="nav-item"
class:active={page.url.pathname === '/admin/add_ticket'}
>
<SearchAdvanced /> <SearchAdvanced />
<span>增添工单</span> <span>增添工单</span>
</a> </a>
<a href="/admin/scheduler" class="nav-item" class:active={page.url.pathname === '/admin/scheduler'}> <a
href="/admin/scheduler"
class="nav-item"
class:active={page.url.pathname === '/admin/scheduler'}
>
<EventSchedule /> <EventSchedule />
<span>成员排班</span> <span>成员排班</span>
</a> </a>

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { BlockMap } from '$lib/types/enum'; import { BlockMap } from '$lib/types/enum';
import type { WtsBlock } from '$lib/types/enum'; import type { WtsBlock } from '$lib/types/enum';
let { b,r }: { b: WtsBlock, r: string } = $props(); let { b, r }: { b: WtsBlock; r: string } = $props();
</script> </script>
<span> <span>

View File

@@ -4,6 +4,6 @@
let { a }: { a: WtsAccess } = $props(); let { a }: { a: WtsAccess } = $props();
</script> </script>
<span class="inline-block bg-gray-300 px-3 py-1 text-[15px] rounded-[19px]"> <span class="inline-block rounded-[19px] bg-gray-300 px-3 py-1 text-[15px]">
{AccessMap[a]} {AccessMap[a]}
</span> </span>

View File

@@ -14,6 +14,7 @@
canceled: 'text-gray-500' canceled: 'text-gray-500'
}; };
</script> </script>
{#if s === 'scheduled' && isSameDay(ap, new Date())} {#if s === 'scheduled' && isSameDay(ap, new Date())}
<span> <span>
<strong class="text-blue-600">已预约</strong> <strong class="text-blue-600">已预约</strong>

View File

@@ -25,7 +25,12 @@
open = $bindable(TicketModal.Opened), open = $bindable(TicketModal.Opened),
src = TicketModal.SRC, src = TicketModal.SRC,
onTicketChanged onTicketChanged
}: { t?: Ticket | null; open?: boolean; src?: 'user' | 'operator'; onTicketChanged?: () => void | Promise<void>;} = $props(); }: {
t?: Ticket | null;
open?: boolean;
src?: 'user' | 'operator';
onTicketChanged?: () => void | Promise<void>;
} = $props();
let loading = $state(false); let loading = $state(false);
let traces: Trace[] = $state([]); let traces: Trace[] = $state([]);
@@ -129,7 +134,15 @@
<NotificationQueue bind:this={q1} /> <NotificationQueue bind:this={q1} />
<ModalHeader title=" 🖋请更新No.{getTid(t)}的状态" /> <ModalHeader title=" 🖋请更新No.{getTid(t)}的状态" />
<ModalBody> <ModalBody>
<TraceUpdateView {t} bind:view bind:open bind:isUpReady {q} {q1} onUpdated={onTicketChanged} /> <TraceUpdateView
{t}
bind:view
bind:open
bind:isUpReady
{q}
{q1}
onUpdated={onTicketChanged}
/>
</ModalBody> </ModalBody>
<UpViewButton bind:view bind:isUpReady /> <UpViewButton bind:view bind:isUpReady />
</ComposedModal> </ComposedModal>

View File

@@ -4,10 +4,10 @@
let { let {
view = $bindable<'trace' | 'cancel' | 'update'>(), view = $bindable<'trace' | 'cancel' | 'update'>(),
isUpReady = $bindable<boolean>(), isUpReady = $bindable<boolean>()
}: { }: {
view: 'trace' | 'cancel' | 'update', view: 'trace' | 'cancel' | 'update';
isUpReady: boolean, isUpReady: boolean;
} = $props(); } = $props();
</script> </script>

View File

@@ -91,7 +91,7 @@ export function Guard(a: (subject: WtsAccess) => boolean) {
return; return;
} }
if (!a(jwt.access)) { if (!a(jwt.access)) {
if(jwt.access === "unregistered"){ if (jwt.access === 'unregistered') {
goto('/register'); goto('/register');
return; return;
} }

View File

@@ -1,14 +1,28 @@
import { browser } from '$app/environment';
//全局状态保存用户刚刚访问的页面以便JWT获取后跳转回来 //全局状态保存用户刚刚访问的页面以便JWT获取后跳转回来
class theLastPage { class theLastPage {
p = $state('/'); p = $state('/');
constructor() {
if (browser) {
this.p = sessionStorage.getItem('_the_last_page') || '/';
}
}
Write(p: string) { Write(p: string) {
this.p = p; this.p = p;
if (browser) {
sessionStorage.setItem('_the_last_page', p);
}
} }
Read(): string { Read(): string {
const p1 = this.p; const p1 = this.p;
this.p = '/'; this.p = '/';
if (browser) {
sessionStorage.removeItem('_the_last_page');
}
return p1; return p1;
} }
} }

View File

@@ -19,7 +19,7 @@ export let criteria: Criteria = {
category: [], category: [],
isp: [], isp: [],
newer_than: undefined, newer_than: undefined,
older_than: undefined, older_than: undefined
}, },
_order: 'priority', _order: 'priority',
_floor: null, _floor: null,
@@ -38,12 +38,11 @@ export function resetCriteria() {
category: [], category: [],
isp: [], isp: [],
newer_than: undefined, newer_than: undefined,
older_than: undefined, older_than: undefined
}, },
_order: 'priority', _order: 'priority',
_floor: null, _floor: null,
_blocks_in_zone: [], _blocks_in_zone: [],
_view_today_scheduled: false _view_today_scheduled: false
} as Criteria; } as Criteria;
} }

View File

@@ -9,7 +9,7 @@ export const sample1issuer: UserProfile = {
phone: '13800138000', phone: '13800138000',
isp: 'mobile', isp: 'mobile',
account: '12345678901@139.gd', account: '12345678901@139.gd',
wx: 'zhangsan_wx', wx: 'zhangsan_wx'
}; };
export const sample1: Ticket = { export const sample1: Ticket = {
@@ -21,7 +21,7 @@ export const sample1: Ticket = {
category: 'ip-or-device', category: 'ip-or-device',
status: 'scheduled', status: 'scheduled',
priority: 'assigned', priority: 'assigned',
appointed_at: NowRFC3339(), appointed_at: NowRFC3339()
}; };
export const sample2issuer: UserProfile = { export const sample2issuer: UserProfile = {
@@ -33,7 +33,7 @@ export const sample2issuer: UserProfile = {
phone: '13800138000', phone: '13800138000',
isp: 'telecom', isp: 'telecom',
account: '18923456789', account: '18923456789',
wx: 'zhangsan_wx', wx: 'zhangsan_wx'
}; };
export const sample2: Ticket = { export const sample2: Ticket = {
@@ -44,7 +44,7 @@ export const sample2: Ticket = {
submitted_at: RFC3339('2024-01-01T00:10:00Z'), submitted_at: RFC3339('2024-01-01T00:10:00Z'),
category: 'first-install', category: 'first-install',
status: 'fresh', status: 'fresh',
priority: 'mainline', priority: 'mainline'
}; };
export const sampleTrace: Trace[] = [ export const sampleTrace: Trace[] = [
@@ -56,7 +56,7 @@ export const sampleTrace: Trace[] = [
op_name: '(用户操作)', op_name: '(用户操作)',
remark: '工单已提交', remark: '工单已提交',
new_status: 'fresh', new_status: 'fresh',
new_priority: 'mainline', new_priority: 'mainline'
}, },
{ {
opid: 2, opid: 2,
@@ -66,7 +66,7 @@ export const sampleTrace: Trace[] = [
op_name: '哈哈哈', op_name: '哈哈哈',
remark: '用户预约了时间', remark: '用户预约了时间',
new_status: 'scheduled', new_status: 'scheduled',
new_appointment: RFC3339('2024-01-13T14:00:00Z'), new_appointment: RFC3339('2024-01-13T14:00:00Z')
}, },
{ {
opid: 3, opid: 3,
@@ -75,7 +75,7 @@ export const sampleTrace: Trace[] = [
op: '2395', op: '2395',
op_name: '啊啊啊', op_name: '啊啊啊',
remark: '材料不足,改日修', remark: '材料不足,改日修',
new_status: 'delay', new_status: 'delay'
}, },
{ {
opid: 4, opid: 4,
@@ -85,7 +85,7 @@ export const sampleTrace: Trace[] = [
op_name: '嘿嘿嘿', op_name: '嘿嘿嘿',
remark: '与用户重新约定时间预约在2024-01-21下午', remark: '与用户重新约定时间预约在2024-01-21下午',
new_status: 'scheduled', new_status: 'scheduled',
new_appointment: RFC3339('2024-01-21T15:00:00Z'), new_appointment: RFC3339('2024-01-21T15:00:00Z')
}, },
{ {
opid: 5, opid: 5,
@@ -94,6 +94,6 @@ export const sampleTrace: Trace[] = [
op: '2395', op: '2395',
op_name: '喵喵喵', op_name: '喵喵喵',
remark: '问题解决:用户路由器线路接触不良,更换后恢复正常', remark: '问题解决:用户路由器线路接触不良,更换后恢复正常',
new_status: 'solved', new_status: 'solved'
}, }
]; ];

View File

@@ -1,32 +1,80 @@
export type WtsBlock = export type WtsBlock =
| '1'| '2'| '3'| '4'| '5'| '6' //凤翔 | '1'
| '7'| '8'| '9'| '10'| '11' //北门 | '2'
| '12'| '13'| '14'| '15'| '20'| '21'| '22' //东门 | '3'
| '16'| '17'| '18'| '19' //岐头 | '4'
| 'XHA'| 'XHB'| 'XHC'| 'XHD' //香晖 | '5'
| '6' //凤翔
| '7'
| '8'
| '9'
| '10'
| '11' //北门
| '12'
| '13'
| '14'
| '15'
| '20'
| '21'
| '22' //东门
| '16'
| '17'
| '18'
| '19' //岐头
| 'XHA'
| 'XHB'
| 'XHC'
| 'XHD' //香晖
| 'ZH' //朝晖 | 'ZH' //朝晖
| 'other'; | 'other';
export const BlockMap: Record<WtsBlock, string> = { export const BlockMap: Record<WtsBlock, string> = {
'1':'1栋','2':'2栋','3':'3栋','4':'4栋','5':'5栋','6':'6栋', '1': '1栋',
'7':'7栋','8':'8栋','9':'9栋','10':'10栋','11':'11栋', '2': '2栋',
'12':'12栋','13':'13栋','14':'14栋','15':'15栋','20':'20栋','21':'21栋','22':'22栋', '3': '3栋',
'16':'16栋','17':'17栋','18':'18栋','19':'19栋', '4': '4栋',
'XHA':'香晖A','XHB':'香晖B','XHC':'香晖C','XHD':'香晖D', '5': '5栋',
'ZH':'朝晖', '6': '6栋',
'other':'其它', '7': '7栋',
} '8': '8栋',
'9': '9栋',
'10': '10栋',
'11': '11栋',
'12': '12栋',
'13': '13栋',
'14': '14栋',
'15': '15栋',
'20': '20栋',
'21': '21栋',
'22': '22栋',
'16': '16栋',
'17': '17栋',
'18': '18栋',
'19': '19栋',
XHA: '香晖A',
XHB: '香晖B',
XHC: '香晖C',
XHD: '香晖D',
ZH: '朝晖',
other: '其它'
};
export type WtsAccess = export type WtsAccess =
| 'dev' | 'dev'
| 'chief'| 'api'| 'group-leader' | 'chief'
| 'formal-member'| 'informal-member' | 'api'
| 'pre-member'| 'user' | 'group-leader'
| 'formal-member'
| 'informal-member'
| 'pre-member'
| 'user'
| 'unregistered'; | 'unregistered';
export const IsAccessIn = (...targets: WtsAccess[]) => (subject: WtsAccess): boolean => { export const IsAccessIn =
(...targets: WtsAccess[]) =>
(subject: WtsAccess): boolean => {
return targets.includes(subject); return targets.includes(subject);
} };
export const IsOperator = IsAccessIn( export const IsOperator = IsAccessIn(
'api', 'api',
@@ -37,12 +85,7 @@ export const IsOperator = IsAccessIn(
'informal-member' 'informal-member'
); );
export const IsAdmin = IsAccessIn( export const IsAdmin = IsAccessIn('group-leader', 'api', 'chief', 'dev');
'group-leader',
'api',
'chief',
'dev'
);
export const IsUser = IsAccessIn( export const IsUser = IsAccessIn(
'api', 'api',
@@ -57,108 +100,129 @@ export const IsUser = IsAccessIn(
export const IsPreMember = IsAccessIn('pre-member'); export const IsPreMember = IsAccessIn('pre-member');
export const IsFormalMember = IsAccessIn( export const IsFormalMember = IsAccessIn('group-leader', 'api', 'chief', 'dev', 'formal-member');
'group-leader',
'api',
'chief',
'dev',
'formal-member'
);
export const IsUnregistered = IsAccessIn('unregistered'); export const IsUnregistered = IsAccessIn('unregistered');
export const AccessMap: Record<WtsAccess, string> = { export const AccessMap: Record<WtsAccess, string> = {
'dev': '开发组','chief':'科长','api':'API','group-leader':'组长', dev: '开发组',
'formal-member':'正式成员','informal-member':'实习成员', chief: '科长',
'pre-member':'前成员','user':'用户', api: 'API',
'unregistered':'未注册用户', 'group-leader': '组长',
} 'formal-member': '正式成员',
'informal-member': '实习成员',
'pre-member': '前成员',
user: '用户',
unregistered: '未注册用户'
};
export type WtsISP = 'telecom' | 'unicom' | 'mobile' | 'broadnet' | 'others'; export type WtsISP = 'telecom' | 'unicom' | 'mobile' | 'broadnet' | 'others';
export const ISPMap: Record<WtsISP, string> = { export const ISPMap: Record<WtsISP, string> = {
'telecom': '电信', telecom: '电信',
'unicom': '联通', unicom: '联通',
'mobile': '移动', mobile: '移动',
'broadnet': '广电', broadnet: '广电',
'others': '其它', others: '其它'
} };
export type WtsStatus = 'fresh' | 'scheduled' | 'delay' | 'escalated' | 'solved' | 'canceled'; export type WtsStatus = 'fresh' | 'scheduled' | 'delay' | 'escalated' | 'solved' | 'canceled';
export const StatusMap: Record<WtsStatus, string> = { export const StatusMap: Record<WtsStatus, string> = {
'fresh': '待解决', fresh: '待解决',
'scheduled': '已预约', scheduled: '已预约',
'delay': '改日修', delay: '改日修',
'escalated': '已上报', escalated: '已上报',
'solved': '已解决', solved: '已解决',
'canceled': '已取消', canceled: '已取消'
} };
export type WtsPriority = 'highest' | 'assigned' | 'mainline' | 'normal' | 'in-passing' | 'least'; export type WtsPriority = 'highest' | 'assigned' | 'mainline' | 'normal' | 'in-passing' | 'least';
export const PriorityMap: Record<WtsPriority, string> = { export const PriorityMap: Record<WtsPriority, string> = {
'highest': '>>紧急派单!<<', highest: '>>紧急派单!<<',
'assigned': '运营商工单', assigned: '运营商工单',
'mainline': '主线任务', mainline: '主线任务',
'normal': '普通报修', normal: '普通报修',
'in-passing': '顺路看看', 'in-passing': '顺路看看',
'least': '最低', least: '最低'
} };
export type WtsCategory = export type WtsCategory =
| 'first-install'| 'low-speed'| 'ip-or-device'| 'client-or-account'| 'others'; | 'first-install'
| 'low-speed'
| 'ip-or-device'
| 'client-or-account'
| 'others';
export const CategoryMap: Record<WtsCategory, string> = { export const CategoryMap: Record<WtsCategory, string> = {
'first-install': '新装', 'first-install': '新装',
'low-speed': '网速慢', 'low-speed': '网速慢',
'ip-or-device': 'IP或设备问题', 'ip-or-device': 'IP或设备问题',
'client-or-account': '客户端或账号问题', 'client-or-account': '客户端或账号问题',
'others': '其它问题', others: '其它问题'
} };
export type WtsAPIErrorType = export type WtsAPIErrorType = 1 | 2 | 3 | 4 | 5;
| 1 | 2 | 3 | 4 | 5;
export const APIErrorTypeMap: Record<WtsAPIErrorType, string> = { export const APIErrorTypeMap: Record<WtsAPIErrorType, string> = {
1: '服务器内部错误,请联系我们的技术人员。', 1: '服务器内部错误,请联系我们的技术人员。',
2: '你的请求无效,可能是由于格式错误或不支持的操作。', 2: '你的请求无效,可能是由于格式错误或不支持的操作。',
3: '您没有进行该操作的权限。', 3: '您没有进行该操作的权限。',
4: '数据库出现错误。', 4: '数据库出现错误。',
5: '您的请求在逻辑上不被允许。', 5: '您的请求在逻辑上不被允许。'
} };
export type WtsZone = 'FX' | 'BM' | 'DM' | 'QT' | 'XHAB' | 'XHCD' | 'ZH' | 'other' | 'all'; export type WtsZone = 'FX' | 'BM' | 'DM' | 'QT' | 'XHAB' | 'XHCD' | 'ZH' | 'other' | 'all';
export const ZoneMap: Record<WtsZone, string> = { export const ZoneMap: Record<WtsZone, string> = {
'FX':'凤翔', FX: '凤翔',
'BM':'北门', BM: '北门',
'DM':'东门', DM: '东门',
'QT':'岐头', QT: '岐头',
'XHAB':'香晖AB', XHAB: '香晖AB',
'XHCD':'香晖CD', XHCD: '香晖CD',
'ZH':'朝晖', ZH: '朝晖',
'other':'其它', other: '其它',
'all':'全部', all: '全部'
} };
export const ZoneToBlock: Record<WtsZone, WtsBlock[]> = { export const ZoneToBlock: Record<WtsZone, WtsBlock[]> = {
'FX':['1','2','3','4','5','6'], FX: ['1', '2', '3', '4', '5', '6'],
'BM':['7','8','9','10','11'], BM: ['7', '8', '9', '10', '11'],
'DM':['12','13','14','15','20','21','22'], DM: ['12', '13', '14', '15', '20', '21', '22'],
'QT':['16','17','18','19'], QT: ['16', '17', '18', '19'],
'XHAB':['XHA','XHB'], XHAB: ['XHA', 'XHB'],
'XHCD':['XHC','XHD'], XHCD: ['XHC', 'XHD'],
'ZH':['ZH'], ZH: ['ZH'],
'other':['other'], other: ['other'],
'all':[ all: [
'1','2','3','4','5','6', '1',
'7','8','9','10','11', '2',
'12','13','14','15','20','21','22', '3',
'16','17','18','19', '4',
'XHA','XHB', '5',
'XHC','XHD', '6',
'7',
'8',
'9',
'10',
'11',
'12',
'13',
'14',
'15',
'20',
'21',
'22',
'16',
'17',
'18',
'19',
'XHA',
'XHB',
'XHC',
'XHD',
'ZH', 'ZH',
'other' 'other'
], ]
} };

View File

@@ -41,12 +41,15 @@
<div style="display: flex; flex-direction: column; gap: 0.25rem;"> <div style="display: flex; flex-direction: column; gap: 0.25rem;">
<h2>🥺不会使用校园网?</h2> <h2>🥺不会使用校园网?</h2>
<p>点击下方按钮,我们准备了校园网方方面面的攻略!</p> <p>点击下方按钮,我们准备了校园网方方面面的攻略!</p>
<Button href="/help" kind="secondary" style="align-self: flex-end;transform: translate(-25px,0px);">网络攻略</Button> <Button
href="/help"
kind="secondary"
style="align-self: flex-end;transform: translate(-25px,0px);">网络攻略</Button
>
</div> </div>
</div> </div>
</RetroCard> </RetroCard>
<br /> <br />
<br /> <br />
<br /> <br />

View File

@@ -2,7 +2,10 @@
<br /> <br />
<hr /> <hr />
<br /> <br />
<p>中山学院网络维护科是一个独立的<strong>学生组织</strong>成立于2005年接受校信息中心的指导和管理。</p> <p>
中山学院网络维护科是一个独立的<strong>学生组织</strong
>成立于2005年接受校信息中心的指导和管理。
</p>
<p> <p>
网维的主要任务是保证校园宿舍网络的正常运行,处理解决在校学生所报修的有线校园网问题,这项业务主要通过依托于微信的网络报修系统来运行。 网维的主要任务是保证校园宿舍网络的正常运行,处理解决在校学生所报修的有线校园网问题,这项业务主要通过依托于微信的网络报修系统来运行。
</p> </p>
@@ -11,7 +14,8 @@
目前我们与三大运营商建立了良好的合作关系承接运营商在校园内的装维业务在信息中心的关怀下我们也承担学校部分IT后勤工作。 目前我们与三大运营商建立了良好的合作关系承接运营商在校园内的装维业务在信息中心的关怀下我们也承担学校部分IT后勤工作。
</p> </p>
<p> <p>
对于学生校园网报修,我们的成员会在<strong>每天16:30~18:00</strong>统一上门解决问题,用户在这个时间段必须本人在宿舍。 对于学生校园网报修,我们的成员会在<strong>每天16:30~18:00</strong
>统一上门解决问题,用户在这个时间段必须本人在宿舍。
</p> </p>
<p> <p>
网维一般会在每年的9~10月招新对象为当年新生要求有基础的计算机知识。具体招新事宜请以实际通告为准。 网维一般会在每年的9~10月招新对象为当年新生要求有基础的计算机知识。具体招新事宜请以实际通告为准。

View File

@@ -1,7 +1,7 @@
<script> <script>
import RetroCard from "$lib/components/RetroCard.svelte"; import RetroCard from '$lib/components/RetroCard.svelte';
</script> </script>
<h1>隐私权条款</h1> <h1>隐私权条款</h1>
<br /> <br />
<hr /> <hr />
@@ -61,4 +61,3 @@
<hr /> <hr />
<br /> <br />
<p>本条款最后更新于2025年12月27日最终解释权归网维所有。</p> <p>本条款最后更新于2025年12月27日最终解释权归网维所有。</p>

View File

@@ -30,7 +30,7 @@
let q: NotificationQueue; let q: NotificationQueue;
let r = $state({ let r = $state({
priority: 'normal', priority: 'normal'
} as NewTicketReq); } as NewTicketReq);
function onOccurDateChange(event: CustomEvent) { function onOccurDateChange(event: CustomEvent) {
@@ -223,4 +223,3 @@ helperText="选择工单的优先级类型,更高优先级会在系统中优
<NotificationQueue bind:this={q} /> <NotificationQueue bind:this={q} />
<Loading active={!notLoading} /> <Loading active={!notLoading} />

View File

@@ -3,5 +3,6 @@
let { data }: PageProps = $props(); let { data }: PageProps = $props();
</script> </script>
<h1>Forbidden</h1> <h1>Forbidden</h1>
<p>对不起,您没有权限访问这个页面。</p> <p>对不起,您没有权限访问这个页面。</p>

View File

@@ -3,11 +3,11 @@
@plugin '@tailwindcss/typography'; @plugin '@tailwindcss/typography';
@plugin 'daisyui'; @plugin 'daisyui';
@plugin "daisyui/theme" { @plugin 'daisyui/theme' {
name: "wireframe"; name: 'wireframe';
default: true; default: true;
prefersdark: false; prefersdark: false;
color-scheme: "light"; color-scheme: 'light';
--color-base-100: #f4f4f4; --color-base-100: #f4f4f4;
--color-base-200: #e6e6e6; --color-base-200: #e6e6e6;
--color-base-300: #d5d5d5; --color-base-300: #d5d5d5;
@@ -38,9 +38,7 @@
--noise: 1; --noise: 1;
} }
@layer base { @layer base {
html { html {
/* 移动端优化:防止点击高亮 */ /* 移动端优化:防止点击高亮 */
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
@@ -55,5 +53,4 @@
/* 移动端优化:防止水平溢出 */ /* 移动端优化:防止水平溢出 */
@apply min-h-screen overflow-x-hidden bg-base-100 text-base-content; @apply min-h-screen overflow-x-hidden bg-base-100 text-base-content;
} }
} }

View File

@@ -11,7 +11,7 @@
function gotoAuthAPI() { function gotoAuthAPI() {
if (!env.PUBLIC_JWT) { if (!env.PUBLIC_JWT) {
console.log("未找到PUBLIC_JWT") console.log('未找到PUBLIC_JWT');
} }
if (dev && env.PUBLIC_JWT) { if (dev && env.PUBLIC_JWT) {
docCookies.setItem('jwt', env.PUBLIC_JWT, Infinity, '/'); docCookies.setItem('jwt', env.PUBLIC_JWT, Infinity, '/');

View File

@@ -67,7 +67,12 @@
<RetroCard> <RetroCard>
<span style="display: flex; align-items: center;"> <span style="display: flex; align-items: center;">
<h2 style="margin-right: 0.5rem;">个人信息</h2> <h2 style="margin-right: 0.5rem;">个人信息</h2>
<Renew onclick={() => {TheLastPage.Write('/me'),goto('/login')}} style="cursor: pointer;" /> <Renew
onclick={() => {
(TheLastPage.Write('/me'), goto('/login'));
}}
style="cursor: pointer;"
/>
</span> </span>
<StructuredList style="margin-bottom: 1rem;"> <StructuredList style="margin-bottom: 1rem;">
<StructuredListBody> <StructuredListBody>

View File

@@ -115,7 +115,10 @@
<div class="zone-tiles"> <div class="zone-tiles">
{#each zoneDisplayOrder as zone} {#each zoneDisplayOrder as zone}
{#if typeof countByZone?.[zone] !== 'undefined'} {#if typeof countByZone?.[zone] !== 'undefined'}
<Tile class={`zone-tile zone-${zoneTone(countByZone?.[zone])}`} on:click={() => jumpSearch(zone)}> <Tile
class={`zone-tile zone-${zoneTone(countByZone?.[zone])}`}
on:click={() => jumpSearch(zone)}
>
<span class="zone-name">{ZoneMap[zone]}</span> <span class="zone-name">{ZoneMap[zone]}</span>
<span class="zone-count">{countByZone?.[zone] ?? 0}</span> <span class="zone-count">{countByZone?.[zone] ?? 0}</span>
</Tile> </Tile>

View File

@@ -29,13 +29,15 @@
import { criteria } from '$lib/states/ticketCriteriaSearch.svelte'; import { criteria } from '$lib/states/ticketCriteriaSearch.svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import type { WtsStatus, WtsPriority, WtsCategory, WtsISP } from '$lib/types/enum'; import type { WtsStatus, WtsPriority, WtsCategory, WtsISP } from '$lib/types/enum';
import {WtsJWT} from '$lib/jwt' import { WtsJWT } from '$lib/jwt';
onMount(() => Guard(IsOperator)); onMount(() => Guard(IsOperator));
let token: WtsJWT = $state({} as WtsJWT); let token: WtsJWT = $state({} as WtsJWT);
onMount(()=>{token =CheckAndGetJWT('parsed');}) onMount(() => {
token = CheckAndGetJWT('parsed');
});
let req = $state(criteria.r as FilterTicketsReq); let req = $state(criteria.r as FilterTicketsReq);
@@ -116,7 +118,7 @@
'delay', 'delay',
'escalated' 'escalated'
] as const satisfies readonly WtsStatus[]; ] as const satisfies readonly WtsStatus[];
const statusOptions: readonly WtsStatus[] = statusOptionsAdmin //之前的区分没有意义,在后端会拦截的,在这里高花样,好像反而会破坏正常功能,感觉这个页面还是重写的样子。。 const statusOptions: readonly WtsStatus[] = statusOptionsAdmin; //之前的区分没有意义,在后端会拦截的,在这里高花样,好像反而会破坏正常功能,感觉这个页面还是重写的样子。。
const priorityOptions = [ const priorityOptions = [
'highest', 'highest',
@@ -184,7 +186,7 @@
$effect(() => { $effect(() => {
isScheduledSelected = req.status?.includes('scheduled') ?? false; isScheduledSelected = req.status?.includes('scheduled') ?? false;
}) });
</script> </script>
<h1>报修单检索</h1> <h1>报修单检索</h1>
@@ -244,7 +246,10 @@
</Grid> </Grid>
</CheckboxGroup> </CheckboxGroup>
<div class="toggle"> <div class="toggle">
<Toggle size="sm" toggled={allSelected(zoneSelected, zoneOptions)} on:toggle={(e)=> { <Toggle
size="sm"
toggled={allSelected(zoneSelected, zoneOptions)}
on:toggle={(e) => {
const { toggled } = e.detail as { toggled: boolean }; const { toggled } = e.detail as { toggled: boolean };
zoneSelected = toggled ? [...zoneOptions] : []; zoneSelected = toggled ? [...zoneOptions] : [];
}} }}
@@ -283,7 +288,10 @@
</Grid> </Grid>
</CheckboxGroup> </CheckboxGroup>
<div class="toggle"> <div class="toggle">
<Toggle size="sm" toggled={allSelected(req.status, statusOptions)} on:toggle={(e)=> { <Toggle
size="sm"
toggled={allSelected(req.status, statusOptions)}
on:toggle={(e) => {
const { toggled } = e.detail as { toggled: boolean }; const { toggled } = e.detail as { toggled: boolean };
req.status = toggled ? [...statusOptions] : []; req.status = toggled ? [...statusOptions] : [];
}} }}
@@ -320,7 +328,10 @@
</Grid> </Grid>
</CheckboxGroup> </CheckboxGroup>
<div class="toggle"> <div class="toggle">
<Toggle size="sm" toggled={allSelected(req.priority, priorityOptions)} on:toggle={(e)=> { <Toggle
size="sm"
toggled={allSelected(req.priority, priorityOptions)}
on:toggle={(e) => {
const { toggled } = e.detail as { toggled: boolean }; const { toggled } = e.detail as { toggled: boolean };
req.priority = toggled ? [...priorityOptions] : []; req.priority = toggled ? [...priorityOptions] : [];
}} }}
@@ -353,7 +364,10 @@
</Grid> </Grid>
</CheckboxGroup> </CheckboxGroup>
<div class="toggle"> <div class="toggle">
<Toggle size="sm" toggled={allSelected(req.category, categoryOptions)} on:toggle={(e)=> { <Toggle
size="sm"
toggled={allSelected(req.category, categoryOptions)}
on:toggle={(e) => {
const { toggled } = e.detail as { toggled: boolean }; const { toggled } = e.detail as { toggled: boolean };
req.category = toggled ? [...categoryOptions] : []; req.category = toggled ? [...categoryOptions] : [];
}} }}
@@ -388,7 +402,10 @@
</Grid> </Grid>
</CheckboxGroup> </CheckboxGroup>
<div class="toggle"> <div class="toggle">
<Toggle size="sm" toggled={allSelected(req.isp, ispOptions)} on:toggle={(e)=> { <Toggle
size="sm"
toggled={allSelected(req.isp, ispOptions)}
on:toggle={(e) => {
const { toggled } = e.detail as { toggled: boolean }; const { toggled } = e.detail as { toggled: boolean };
req.isp = toggled ? [...ispOptions] : []; req.isp = toggled ? [...ispOptions] : [];
}} }}
@@ -410,12 +427,23 @@
<br /> <br />
<br /> <br />
<NumberInput min={1} max={20} step={1} bind:value={floor} allowEmpty={true} allowDecimal={false} <NumberInput
labelText="只看如下楼层(不填代表查看全部楼层)" /> min={1}
max={20}
step={1}
bind:value={floor}
allowEmpty={true}
allowDecimal={false}
labelText="只看如下楼层(不填代表查看全部楼层)"
/>
<br /> <br />
<br /> <br />
<Toggle labelText="只显示预约在今天的预约单" bind:toggled={viewTodayScheduled} disabled={!isScheduledSelected} /> <Toggle
labelText="只显示预约在今天的预约单"
bind:toggled={viewTodayScheduled}
disabled={!isScheduledSelected}
/>
<br /> <br />
<br /> <br />
<Button on:click={search}>搜索</Button> <Button on:click={search}>搜索</Button>

View File

@@ -40,8 +40,6 @@
const roomNum = Number.parseInt(digits, 10); const roomNum = Number.parseInt(digits, 10);
if (!Number.isFinite(roomNum)) return null; if (!Number.isFinite(roomNum)) return null;
const floor = Math.floor(roomNum / 100); const floor = Math.floor(roomNum / 100);
//console.log('getFloorFromRoom', { room, digits, roomNum, floor }); //console.log('getFloorFromRoom', { room, digits, roomNum, floor });
return Number.isFinite(floor) ? floor : null; return Number.isFinite(floor) ? floor : null;
@@ -63,7 +61,11 @@
if (criteria._view_today_scheduled) { if (criteria._view_today_scheduled) {
const todayStart = new Date().setHours(0, 0, 0, 0); const todayStart = new Date().setHours(0, 0, 0, 0);
const todayEnd = new Date().setHours(23, 59, 59, 999); const todayEnd = new Date().setHours(23, 59, 59, 999);
tickets = tickets.filter((t) => t.status !== 'scheduled' || (toMs(t.appointed_at) >= todayStart && toMs(t.appointed_at) <= todayEnd)); tickets = tickets.filter(
(t) =>
t.status !== 'scheduled' ||
(toMs(t.appointed_at) >= todayStart && toMs(t.appointed_at) <= todayEnd)
);
} }
ok = true; ok = true;
@@ -123,6 +125,11 @@
{:else} {:else}
<span>没有找到符合条件的报修单。</span> <span>没有找到符合条件的报修单。</span>
{/if} {/if}
<TicketDetail t={TicketModal.NowTicket} bind:open={TicketModal.Opened} src={TicketModal.SRC} onTicketChanged={refreshTickets}/> <TicketDetail
t={TicketModal.NowTicket}
bind:open={TicketModal.Opened}
src={TicketModal.SRC}
onTicketChanged={refreshTickets}
/>
<NotificationQueue bind:this={q} /> <NotificationQueue bind:this={q} />

View File

@@ -69,6 +69,11 @@
<UserTicket {t} /> <UserTicket {t} />
{/each} {/each}
<TicketDetail t={TicketModal.NowTicket} bind:open={TicketModal.Opened} src={TicketModal.SRC} onTicketChanged={refreshTickets1}/> <TicketDetail
t={TicketModal.NowTicket}
bind:open={TicketModal.Opened}
src={TicketModal.SRC}
onTicketChanged={refreshTickets1}
/>
<NotificationQueue bind:this={q} /> <NotificationQueue bind:this={q} />