公开完整前后端的代码

This commit is contained in:
Linus Torvalds
2026-02-26 19:22:38 +08:00
commit 193de8a34f
161 changed files with 17373 additions and 0 deletions

28
front/.gitignore vendored Normal file
View File

@@ -0,0 +1,28 @@
test-results
node_modules
# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
/.vscode
draft/*

1
front/.npmrc Normal file
View File

@@ -0,0 +1 @@
engine-strict=true

9
front/.prettierignore Normal file
View File

@@ -0,0 +1,9 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock
bun.lock
bun.lockb
# Miscellaneous
/static/

16
front/.prettierrc Normal file
View File

@@ -0,0 +1,16 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
],
"tailwindStylesheet": "./src/routes/layout.css"
}

38
front/README.md Normal file
View File

@@ -0,0 +1,38 @@
# sv
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```sh
# create a new project in the current directory
npx sv create
# create a new project in my-app
npx sv create my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```sh
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```sh
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.

6
front/e2e/demo.test.ts Normal file
View File

@@ -0,0 +1,6 @@
import { expect, test } from '@playwright/test';
test('home page has expected h1', async ({ page }) => {
await page.goto('/');
await expect(page.locator('h1')).toBeVisible();
});

2
front/env.exapmle Normal file
View File

@@ -0,0 +1,2 @@
PUBLIC_BR_URL=https://wwbx.davisye.cn
PUBLIC_AUTH_REDIRECT=https://wwbx.davisye.cn/api/v3p/wx/auth

2944
front/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

47
front/package.json Normal file
View File

@@ -0,0 +1,47 @@
{
"name": "webfr-new",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check .",
"test:e2e": "playwright test",
"test": "npm run test:e2e"
},
"devDependencies": {
"@playwright/test": "^1.57.0",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.49.1",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.1.18",
"carbon-preprocess-svelte": "^0.11.26",
"daisyui": "^5.5.13",
"mdsvex": "^0.12.6",
"prettier": "^3.7.4",
"prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-tailwindcss": "^0.7.2",
"svelte": "^5.45.6",
"svelte-check": "^4.3.4",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"vite": "^7.2.6"
},
"dependencies": {
"axios": "^1.13.2",
"carbon-components-svelte": "^0.101.1",
"carbon-icons-svelte": "^13.8.0",
"carbon-pictograms-svelte": "^13.14.0",
"date-fns": "^4.1.0",
"jwt-decode": "^4.0.0",
"validator": "^13.15.26"
}
}

View File

@@ -0,0 +1,6 @@
import { defineConfig } from '@playwright/test';
export default defineConfig({
webServer: { command: 'npm run build && npm run preview', port: 4173 },
testDir: 'e2e'
});

16
front/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

11
front/src/app.html Normal file
View File

@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

99
front/src/lib/api.ts Normal file
View File

@@ -0,0 +1,99 @@
import { PUBLIC_BR_URL } from '$env/static/public';
import { CheckAndGetJWT } from './jwt';
import axios from 'axios';
import type {
CancelTicketRes,
ChangeProfileRes,
FilterUsersRes,
GetTicketRes,
GetTracesRes,
NewRepairTraceRes,
NewTicketRes,
RegisterRes,
ViewProfileRes,
TicketOverviewRes,
FilterTicketsRes
} from './types/apiResponse';
import type {
ChangeProfileReq,
FilterTicketsReq,
FilterUsersReq,
NewRepairTraceReq,
NewTicketReq,
RegisterReq
} from './types/apiRequest';
const br = PUBLIC_BR_URL;
export const api = axios.create({
baseURL: br,
timeout: 5000
});
api.interceptors.request.use(
(config) => {
const jwt = CheckAndGetJWT('raw');
if (jwt) {
config.headers.Authorization = `Bearer ${jwt}`;
}
return config;
},
(error) => {
return Promise.reject('at sending JWT:' + error);
}
);
export async function Register(r: RegisterReq): Promise<RegisterRes> {
const res = await api.post('/api/v3/register', r);
return res.data;
}
export async function ChangeProfile(r: ChangeProfileReq): Promise<ChangeProfileRes> {
const res = await api.post('/api/v3/change_profile', r);
return res.data;
}
export async function ViewProfile(r: string): Promise<ViewProfileRes> {
const res = await api.get('/api/v3/view_profile?who=' + r);
return res.data;
}
export async function FilterUsers(r: FilterUsersReq): Promise<FilterUsersRes> {
const res = await api.post('/api/v3/filter_users', r);
return res.data;
}
export async function NewTicket(r: NewTicketReq): Promise<NewTicketRes> {
const res = await api.post('/api/v3/new_ticket', r);
return res.data;
}
export async function GetTicket(r: string): Promise<GetTicketRes> {
const res = await api.get('/api/v3/get_ticket?who=' + r);
return res.data;
}
export async function CancelTicket(r: string): Promise<CancelTicketRes> {
const res = await api.post('/api/v3/cancel_ticket?tid=' + r);
return res.data;
}
export async function NewRepairTrace(r: NewRepairTraceReq): Promise<NewRepairTraceRes> {
const res = await api.post('/api/v3/new_repair_trace', r);
return res.data;
}
export async function FilterTickets(r: FilterTicketsReq): Promise<FilterTicketsRes> {
const res = await api.post('/api/v3/filter_tickets', r);
return res.data;
}
export async function GetTraces(r: string): Promise<GetTracesRes> {
const res = await api.get('/api/v3/get_traces?tid=' + r);
return res.data;
}
export async function TicketOverview(): Promise<TicketOverviewRes>{
const res = await api.get('/api/v3/ticket_overview');
return res.data;
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,389 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg1"
width="1496"
height="1496"
viewBox="0 0 1496 1496"
sodipodi:docname="17548.eps"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1">
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1795.6,0,0,-1795.6,2732.48,1018.1)"
spreadMethod="pad"
id="linearGradient4">
<stop
style="stop-opacity:1;stop-color:#d4d3d3"
offset="0"
id="stop3" />
<stop
style="stop-opacity:1;stop-color:#ffffff"
offset="1"
id="stop4" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4">
<path
d="m 3686.64,892.102 c -118.91,0 -237.87,3.398 -356.68,6.597 -111.19,3 -222.37,5.903 -333.59,5.903 -10.66,0 -21.34,0 -32,-0.102 -21.77,-0.102 -69.69,-7.5 -116.36,-7.5 -71.87,0 -140.75,17.699 -106.45,107.1 13.64,35.6 43.08,58.2 72.76,73.7 71.31,37.3 149.64,44.2 227.94,44.2 23.86,0 47.73,-0.7 71.38,-1.3 109.5,-2.9 219,-4.3 328.52,-4.3 256.32,0 512.66,7.9 768.65,23.6 32.2,2 64.72,4 97.09,4 51.99,0 103.6,-5.3 152.85,-24.7 20.68,-8.1 41.55,-19.4 55.12,-40.1 13.55,-20.6 17.36,-52.7 3.5,-73 -10.14,-14.798 -26.69,-20.2 -42.17,-24.501 -135.74,-38 -274.06,-60.898 -412.85,-74 -125.71,-11.801 -251.68,-15.597 -377.71,-15.597"
id="path4" />
</clipPath>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2149.75,0,0,-2149.75,5894.56,1020.2)"
spreadMethod="pad"
id="linearGradient49">
<stop
style="stop-opacity:1;stop-color:#d4d3d3"
offset="0"
id="stop48" />
<stop
style="stop-opacity:1;stop-color:#ffffff"
offset="1"
id="stop49" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath49">
<path
d="m 6472.96,924.801 c -43.45,0 -86.92,0.199 -130.37,0.601 -111.79,1.098 -223.4,5 -335.12,8.297 -57.59,1.801 -151.8,33.199 -95.71,114.701 24.19,35.2 69.56,48.2 111.72,55 21.14,3.4 42.37,5.8 63.68,7.6 -5.75,-2.6 -11.47,-5.3 -17.17,-8 -17.31,-8.2 -34.86,-17.4 -47.21,-32 -12.35,-14.5 -18.39,-35.9 -9.95,-52.9 125.08,8.9 249.71,24 373.27,45.2 12.31,2.1 25.38,4.6 34.36,13.2 9.72,9.3 12.48,23.5 19.76,34.7 0.96,1.5 2.11,3 3.39,4.3 10.33,0 20.66,0 30.99,0 2.23,-15.4 4.46,-30.8 6.69,-46.1 11.82,-1.8 23.78,-2.6 35.74,-2.6 18.89,0 37.77,2.1 56.14,6.5 1.65,14 3.2,28 4.64,42.1 90.04,-0.1 180.02,-0.1 270.06,-0.2 0.43,-11.5 0.85,-23 1.28,-34.4 0.62,-16.5 1.36,-33.5 8.63,-48.3 9.16,-18.6 27.29,-31 44.65,-42.699 19.41,-12.903 39.32,-26.102 61.99,-32 7.18,-1.903 14.72,-2.899 22.23,-2.899 16.2,0 32.21,4.7 43.88,15.5 29.73,27.598 17.58,75.698 5.8,114.298 -3.07,10.1 -5.97,20.2 -8.68,30.4 177.01,-0.2 354.02,-0.3 531.03,-0.4 110.48,-0.1 221.1,-0.2 331.19,-9.4 52.83,-4.4 106.58,-11.1 154.45,-33.9 -69.7,-47.3 -153.87,-67.8 -236.73,-83.099 -257.48,-47.301 -520.65,-52.199 -782.41,-56.899 -184.06,-3.3 -368.13,-6.601 -552.22,-6.601"
id="path49" />
</clipPath>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2149.75,0,0,-2149.75,5894.56,1020.2)"
spreadMethod="pad"
id="linearGradient51">
<stop
style="stop-opacity:1;stop-color:#2a2a2a"
offset="0"
id="stop50" />
<stop
style="stop-opacity:1;stop-color:#333333"
offset="1"
id="stop51" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath51">
<path
d="m 6012.83,1018.1 c -8.44,17 -2.4,38.4 9.95,52.9 12.35,14.6 29.9,23.8 47.21,32 5.7,2.7 11.42,5.4 17.17,8 51.11,4.3 102.6,4.6 153.98,4.6 5.16,0 10.33,0 15.49,0 62.33,0 124.65,-0.1 186.98,-0.1 -1.28,-1.3 -2.43,-2.8 -3.39,-4.3 -7.28,-11.2 -10.04,-25.4 -19.76,-34.7 -8.98,-8.6 -22.05,-11.1 -34.36,-13.2 -123.56,-21.2 -248.19,-36.3 -373.27,-45.2 m 504.2,48.7 c -11.96,0 -23.92,0.8 -35.74,2.6 -2.23,15.3 -4.46,30.7 -6.69,46.1 34.39,-0.1 68.83,-0.1 103.21,-0.1 -1.44,-14.1 -2.99,-28.1 -4.64,-42.1 -18.37,-4.4 -37.25,-6.5 -56.14,-6.5"
id="path51" />
</clipPath>
<linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2149.75,0,0,-2149.75,5894.56,1020.2)"
spreadMethod="pad"
id="linearGradient53">
<stop
style="stop-opacity:1;stop-color:#2a2a2a"
offset="0"
id="stop52" />
<stop
style="stop-opacity:1;stop-color:#333333"
offset="1"
id="stop53" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath53">
<path
d="m 6986.65,954.902 c -7.51,0 -15.05,0.996 -22.23,2.899 -22.67,5.898 -42.58,19.097 -61.99,32 -17.36,11.699 -35.49,24.099 -44.65,42.699 -7.27,14.8 -8.01,31.8 -8.63,48.3 -0.43,11.4 -0.85,22.9 -1.28,34.4 59.92,-0.1 119.86,-0.1 179.78,-0.1 2.71,-10.2 5.61,-20.3 8.68,-30.4 11.78,-38.6 23.93,-86.7 -5.8,-114.298 -11.67,-10.8 -27.68,-15.5 -43.88,-15.5"
id="path53" />
</clipPath>
</defs>
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
<inkscape:page
x="0"
y="0"
inkscape:label="1"
id="page1"
width="1496"
height="1496"
margin="0"
bleed="0" />
</sodipodi:namedview>
<g
id="g1"
inkscape:groupmode="layer"
inkscape:label="1">
<g
id="group-R5">
<path
id="path2"
d="M 0,0 H 11220 V 11220 H 0 Z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path3"
d="m 3686.64,892.102 c -118.91,0 -237.87,3.398 -356.68,6.597 -111.19,3 -222.37,5.903 -333.59,5.903 -10.66,0 -21.34,0 -32,-0.102 -21.77,-0.102 -69.69,-7.5 -116.36,-7.5 -71.87,0 -140.75,17.699 -106.45,107.1 13.64,35.6 43.08,58.2 72.76,73.7 71.31,37.3 149.64,44.2 227.94,44.2 23.86,0 47.73,-0.7 71.38,-1.3 109.5,-2.9 219,-4.3 328.52,-4.3 256.32,0 512.66,7.9 768.65,23.6 32.2,2 64.72,4 97.09,4 51.99,0 103.6,-5.3 152.85,-24.7 20.68,-8.1 41.55,-19.4 55.12,-40.1 13.55,-20.6 17.36,-52.7 3.5,-73 -10.14,-14.798 -26.69,-20.2 -42.17,-24.501 -135.74,-38 -274.06,-60.898 -412.85,-74 -125.71,-11.801 -251.68,-15.597 -377.71,-15.597"
style="fill:url(#linearGradient4);fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)"
clip-path="url(#clipPath4)" />
<path
id="path5"
d="m 5109.59,5741.87 c -10.35,1.75 -21.89,3.26 -30.59,-2.62 -4.09,-2.77 -7.1,-6.94 -11.28,-9.56 -5.95,-3.75 -13.4,-3.87 -20.43,-3.88 -35.22,-0.04 -71.01,-0.09 -104.78,-10.08 -8.33,-2.46 -16.88,-5.77 -22.5,-12.4 -5.62,-6.63 -7.15,-17.44 -1.2,-23.79 -11.34,-8.13 -14.99,-25.25 -7.96,-37.3 6.28,-10.74 18.64,-16.09 30.27,-20.51 86.94,-33.08 178.91,-52.88 271.75,-58.53 3.7,-0.23 7.55,-0.41 10.99,0.99 8.26,3.35 10.52,13.79 11.5,22.65 2.51,22.94 4.05,45.98 4.61,69.06 0.27,11.53 0.31,23.07 0.1,34.61 -0.12,6.24 2,20.69 -1.9,26.26 -7.99,11.43 -45.05,11.01 -58.41,13.26 -23.39,3.95 -46.78,7.9 -70.17,11.84"
style="fill:#f0dcce;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path6"
d="m 6001.33,5745.72 c 127.61,329.72 169.29,540.67 262.7,881.66 18.39,67.12 44.61,142.58 108.93,169.15 57.27,23.65 126.96,-4.4 163.79,-54.24 36.81,-49.83 45.17,-116.01 37.34,-177.48 -7.84,-61.47 -30.27,-119.94 -50.16,-178.63 -59.55,-175.65 -60.91,-222.85 -123.04,-397.6 -46.63,-131.18 -106.89,-258.14 -137.83,-393.87 -6.95,-30.46 -15.82,-65.78 -44.38,-78.46 -11.19,-4.96 -23.78,-5.46 -36.01,-5.88 -285.05,-9.78 -689.6,-8.21 -970.68,40.21 -11.79,2.03 -3.42,166.57 11.92,179.37 30.52,25.51 170.15,0.64 211.81,0.99 149.81,1.27 416.66,-1.25 565.61,14.78"
style="fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path7"
d="m 4208.74,4921.44 c 38.05,-15.64 76.37,-31.36 116.63,-39.79 40.26,-8.42 83.05,-9.21 121.76,4.7 11.78,4.23 24.07,10.81 28.59,22.48 6.97,17.94 -8.03,36.96 -24.24,47.33 -16.2,10.38 -35.65,17.89 -45.55,34.38 -3.35,5.58 -5.38,11.9 -9.04,17.29 -9.39,13.85 -27.1,18.72 -43.05,23.8 -20.92,6.66 -41.19,15.37 -60.43,25.95 -35.72,19.64 -68.86,46.12 -108.69,54.8 -10.59,2.31 -43.39,-42.99 -49.24,-50.94 -14.56,-19.77 -27.21,-41.96 -35.3,-65.21 -8.85,-25.38 0.96,-30.39 23.57,-39.86 28.25,-11.84 56.66,-23.29 84.99,-34.93"
style="fill:#eaddd0;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path8"
d="m 4272.25,6462.65 c 12.57,21.98 26.29,46.39 50.25,54.59 10.31,3.52 21.46,3.57 32.08,6 46.02,10.54 71.04,61.45 113.26,82.55 20.9,10.45 44.75,12.93 68,15.25 19.47,1.94 38.94,3.88 58.41,5.82 11.82,1.18 24.41,2.22 34.98,-3.22 18.08,-9.34 22.5,-33.48 19.98,-53.68 -5.8,-46.57 -35.86,-86.43 -68.58,-120.08 -39.76,-40.87 -84.83,-76.14 -129.74,-111.27 -35.23,-27.56 -70.46,-55.11 -105.68,-82.67 -4.95,-3.85 -10.39,-7.9 -16.66,-7.92 -5.31,-0.01 -10.14,2.9 -14.6,5.78 -20.54,13.2 -40.26,27.67 -59.02,43.29 -13.68,11.39 -47.42,33.13 -53.2,50.1 -5.19,15.26 22.57,40.66 31.83,54.07 13.76,19.9 26.67,40.39 38.69,61.39"
style="fill:#eaddd0;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path9"
d="m 4350.23,6300.37 c -8.18,13.97 -108.93,111.58 -124.45,116.19 -36.41,10.8 -66.39,-28.3 -85.97,-60.85 -96.12,-159.75 -254.86,-271.24 -420.29,-357.22 -20.77,-10.79 -42.59,-21.05 -65.95,-22.46 -21.63,-1.31 -41.4,4.85 -59.55,15.44 -2.74,-49.47 -9.65,-97.45 -20.91,-144.48 3.13,-30.07 4.14,-60.33 2.19,-90.05 -0.39,-5.96 -0.86,-11.88 -1.34,-17.78 20.81,-40.23 33.41,-82.89 38.2,-126.1 12.22,-1.89 24.44,-2.64 36.61,-2.07 83.07,3.92 155.5,55.81 219.11,109.37 127.22,107.12 288.37,213.31 386.58,347.51 62.23,85.04 149.06,141.6 95.77,232.5"
style="fill:#ebebeb;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path10"
d="m 7321.78,2492.11 c 105.54,-19.38 209.85,-45.35 316.01,-61.14 -54.11,558.38 -100.37,1117.49 -138.78,1677.13 -7.07,103.07 -17.8,215.36 -90.26,289.85 -62.14,63.87 -155.87,84.93 -244.42,98.89 -127.02,20.02 -255.38,31.67 -383.96,34.86 -143.92,3.56 -304.7,-10.73 -401.18,-116.28 -62.08,-67.92 -85.23,-161.39 -103.12,-251.18 -80.45,-403.82 -98.56,-816.33 -165.2,-1222.19 -12.69,-77.32 -151.47,-506.77 -93.78,-542.72 34.12,-21.26 241.07,65.08 287.25,75.13 101.97,22.2 205.31,38.25 309.25,48.05 235.97,22.24 475.1,12.41 708.19,-30.4"
style="fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path11"
d="m 3428.06,8092.94 c -73.27,-56.04 -117.58,-141.05 -158.3,-223.81 -50.53,-102.68 -99.98,-209.45 -107.32,-323.65 -5.43,-84.43 11.51,-174.66 -27.15,-249.91 144.15,-55.1 285.97,-116.3 424.95,-183.38 198.07,214 335.65,483.41 392.89,769.34 1.9,9.5 3.67,19.59 0.28,28.67 -3.79,10.13 -13.21,16.9 -22.24,22.86 -123.81,81.76 -266.83,128.58 -407.93,174.48 -14.91,4.86 -30.34,9.77 -45.96,8.35 -18.29,-1.66 -34.65,-11.8 -49.22,-22.95"
style="fill:#eaddd0;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path12"
d="m 3443.01,1071.9 c -65.66,26.6 -167.18,62.3 -184.44,128.2 -20.36,77.73 33.48,228.93 44.16,310.16 27.31,207.49 48.91,416.02 53.2,625.39 2.52,123.25 -0.92,246.65 6.03,369.73 23.99,424.52 95.65,847.08 157.39,1265.73 9.02,61.17 98.88,342.76 89.14,401.11 -14.24,85.2 -156.2,103.99 -228.16,105.66 -231.48,5.36 -432.79,108.23 -663.2,83.76 79.51,-905.59 156.71,-2280.88 236.23,-3186.44 2.06,0 4.13,0 6.2,-0.1 -21.17,-46.3 -26.31,-100 -13.08,-149.3 1.98,-7.3 4.51,-14.9 9.9,-20.2 6.73,-6.698 16.65,-8.698 25.97,-10.401 155.65,-28.398 314,-41.801 472.2,-40.199 20.66,0.199 44.02,2 57.24,17.801 15.11,18.199 8.56,47.199 -7.61,64.399 -16.18,17.3 -39.25,25.9 -61.17,34.7"
style="fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path13"
d="m 4294.79,7978.31 c -59.6,31.41 -151.24,34.38 -192.92,85.51 41.46,34.7 89.58,65.35 114.77,111.42 -187.07,-71.54 -345.6,28.05 -508.8,95.92 -164.71,68.51 -333.53,45.8 -483.34,-56.78 -276.79,-189.54 -105.31,-641.21 -88.76,-919.58 20.73,29.07 76.46,109.99 77.39,144.54 1.22,45.06 -50.85,56.68 -45.54,105.95 8.21,76.1 85.51,130.34 139.86,69.49 62.97,107.44 160.62,297.12 161.81,423.38 211.35,-128.34 425.89,-218.24 678.64,-135.67 l -57.61,-11.53 c 68.16,22.39 165.16,16.97 204.5,87.35"
style="fill:#443b36;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path14"
d="m 3808.35,4731.08 c -36.46,309.41 -56.05,620.63 -52.77,931.89 3.29,312.48 -14.81,623.59 -39.64,935.35 -10.04,126.11 -21.48,252.13 -34.13,378.05 -4.95,49.27 -160.99,100.29 -200.4,108.06 -23.26,4.58 -46.08,10.83 -68.61,17.88 -21.58,6.75 -42.9,14.22 -64.11,21.65 -106.51,37.32 -213.03,74.63 -319.54,111.94 -15.8,5.54 -32.7,11.17 -49.27,8 -12.79,-2.45 -23.67,-9.86 -33.84,-17.35 -198.93,-146.51 -253,-384.73 -307.61,-601.41 -193.02,-765.98 -253.79,-1557.34 -179.63,-2339.01 475.24,-74.15 955.78,-122.34 1437.88,-144.22 -26.82,1.18 -82.14,536.58 -88.33,589.17"
style="fill:#6b6883;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path15"
d="m 6946.05,1382.98 c -39.94,-12.76 -103.47,-0.35 -105.56,15.21 -8.61,253.9 -14.24,524.62 2.8,777.99 11.75,174.75 -8.53,309.22 -5.06,484.41 3.69,186.52 -8.93,372.91 -21.55,558.96 -28.52,420.38 -27,840.76 -55.52,1261.15 -11.66,171.99 -123.1,853.98 245.34,771.74 225,-50.21 130.53,-954.87 126.2,-1231.5 -6.65,-423.57 -24.46,-846.93 -53.4,-1269.34 -15.68,-228.8 -34.62,-457.34 -56.84,-685.48 -7.16,-73.6 -34.82,-669.87 -76.41,-683.14"
style="fill:#f0dcce;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path16"
d="m 6593.31,3950.02 c 4.74,157.1 -40.57,315.09 -46.65,478.75 -5.36,144.38 -19.09,291.43 -36.61,437.26 -7.93,66.03 -17.55,135.78 -37.71,188.53 -33.17,86.87 -81.36,95.9 -122.15,92.47 -33.74,-2.83 -67.13,-8.93 -100.07,-18.28 -21.58,-6.13 -43.84,-14.59 -58.95,-48.12 -26.44,-58.69 -23.27,-173.31 -18.29,-275.78 40.96,-842.13 101.66,-1689.63 167.25,-2537.04 16.24,-209.77 32.78,-419.58 49.41,-629.39 6.18,-78.04 4.22,-225.01 38.46,-251.82 26.72,-20.92 119.86,-4.22 126.81,42.08 21.75,144.84 5.2,387.5 3.95,560.3 -1.45,200.38 -12.21,408.72 -6.17,609.02 13.57,450.67 27.15,901.35 40.72,1352.02"
style="fill:#f0dcce;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path17"
d="m 6172.55,4250.72 c 37.89,283.39 -26.6,574.39 63.22,851.28 10.27,31.65 23.29,64.03 51.85,88.69 45.41,39.2 119.67,50.71 188.59,57.19 151.71,14.27 305.61,15.41 457.62,3.4 369.81,-29.22 438.96,-370.67 476.83,-599.04 23.44,-141.34 44.59,-282.88 65.75,-424.42 19.3,-129.24 38.62,-258.47 57.93,-387.71 2.17,-14.52 3.7,-30.8 -8.71,-42.13 -12.26,-11.18 -33.61,-13.45 -52.88,-14.86 -438.75,-32.13 -881.41,-33.93 -1320.48,-5.39 -13.93,0.91 -29.32,2.37 -38.43,10.27 -8.31,7.19 -8.83,17.57 -8.73,27.07 1.42,146.26 47.97,290.08 67.44,435.65"
style="fill:#8bb7ab;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path18"
d="m 6160.6,4693.02 c 42.91,556.25 118.89,1108.69 194.82,1660.65 13.38,97.26 26.76,194.51 40.14,291.76 8.38,60.88 19.12,126.8 60.62,168.18 47.52,47.37 48.73,41.27 111.76,54.64 63.04,13.37 133.79,62.1 126.66,132.15 2.39,-23.52 300.94,-6.76 329.71,-14.39 87.05,-23.07 47.46,-75.83 43.38,-160.7 -11.28,-234.05 41.35,-504.17 98.05,-728.61 75.56,-299.04 207.68,-576.95 300.62,-870.03 51.48,-162.34 91.25,-333.31 78.07,-504.57 -2.82,-36.61 -8.65,-74.63 -28.61,-104.11 -19.96,-29.47 -57.87,-47.19 -88.09,-31.37 -19.97,10.46 -32.45,32.63 -42.09,54.5 -30.71,69.75 -44.52,149.31 -87.38,210.87 -50.27,72.21 -134.24,108.51 -216.72,116.08 -82.48,7.57 -164.94,-10.12 -246.13,-27.73 -167.07,-36.23 -344.31,-78.14 -465.77,-209.13 -68.66,-74.04 -113.03,-170.19 -163.19,-260.36 -11.24,-20.19 -24.9,-41.84 -45.9,-47.11 -41.85,-10.51 0.93,53.77 -3.26,100.67 -5,56.03 -1.02,112.51 3.31,168.61"
style="fill:#f9f9f9;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path19"
d="m 6452.46,7487.84 c 19.55,-125.69 46.72,-259.66 138.67,-348.67 66.86,-64.72 164.3,-97.22 257.18,-85.78 92.89,11.43 179.27,66.57 228,145.51 23.1,37.43 37.85,79.25 50.49,121.27 41.51,138.13 61.85,282.45 60.08,426.53 -0.61,50.44 -5.5,104.9 -38.98,142.99 -34.44,39.19 -90.13,51.05 -141.74,60.7 -105.76,19.76 -213.77,39.59 -320.4,25.13 -89.57,-12.14 -210.92,-60.4 -254.77,-146.04 -45.77,-89.35 6.29,-244.03 21.47,-341.64"
style="fill:#f0dcce;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path20"
d="m 7064.3,9446.54 c -9.33,-143.63 98.4,-403.09 400.43,-404.63 302.03,-1.54 431.69,226.05 431.69,387.32 0,161.26 -128.52,421.95 -416.34,421.95 -287.82,0 -403.46,-214.73 -415.78,-404.64"
style="fill:#ffd176;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path21"
d="m 7474.77,9945.49 c 1.11,72.41 -9.81,187.41 5.64,187.21 15.44,-0.3 20.66,-4.6 18.96,-66.7 -1.71,-62.1 11.05,-134.86 -4.47,-137.38 -15.52,-2.52 -20.13,16.87 -20.13,16.87"
style="fill:#ffd176;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path22"
d="m 7828.31,9802.4 c 52.01,50.44 125.61,139.47 136.36,128.38 10.75,-11.09 11.35,-17.87 -33.75,-60.56 -45.11,-42.69 -87.55,-103.18 -100.3,-93.99 -12.76,9.2 -2.31,26.17 -2.31,26.17"
style="fill:#ffd176;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path23"
d="m 7977.12,9451.23 c 72.45,-1.12 187.43,9.8 187.2,-5.64 -0.24,-15.44 -4.61,-20.67 -66.69,-18.96 -62.07,1.71 -134.86,-11.05 -137.38,4.47 -2.53,15.51 16.87,20.13 16.87,20.13"
style="fill:#ffd176;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path24"
d="m 7834.02,9097.69 c 50.45,-52.02 139.48,-125.61 128.39,-136.36 -11.08,-10.75 -17.87,-11.35 -60.55,33.75 -42.7,45.1 -103.19,87.54 -94,100.3 9.2,12.76 26.16,2.31 26.16,2.31"
style="fill:#ffd176;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path25"
d="m 7482.86,8948.88 c -1.11,-72.45 9.81,-187.44 -5.64,-187.2 -15.44,0.24 -20.67,4.61 -18.96,66.69 1.71,62.07 -11.05,134.86 4.46,137.37 15.52,2.52 20.14,-16.86 20.14,-16.86"
style="fill:#ffd176;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path26"
d="m 7129.32,9091.97 c -52.02,-50.44 -125.61,-139.47 -136.36,-128.39 -10.75,11.09 -11.35,17.88 33.75,60.56 45.1,42.69 87.55,103.18 100.3,93.99 12.76,-9.19 2.31,-26.16 2.31,-26.16"
style="fill:#ffd176;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path27"
d="m 6980.51,9443.14 c -72.45,1.11 -187.43,-9.81 -187.2,5.63 0.23,15.45 4.61,20.67 66.69,18.96 62.07,-1.7 134.85,11.06 137.37,-4.46 2.53,-15.52 -16.86,-20.13 -16.86,-20.13"
style="fill:#ffd176;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path28"
d="m 7123.6,9796.68 c -50.45,52.01 -139.47,125.6 -128.39,136.35 11.09,10.76 17.88,11.35 60.56,-33.75 42.7,-45.1 103.19,-87.54 93.99,-100.3 -9.19,-12.75 -26.16,-2.3 -26.16,-2.3"
style="fill:#ffd176;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path29"
d="m 7298.95,8664.42 c -44.25,-0.79 -89.23,-0.19 -132.73,0.38 -37.11,0.5 -74.11,0.97 -110.8,0.64 -66.65,-0.62 -134.37,1.82 -199.85,4.19 -31.78,1.13 -63.48,2.27 -95.01,3.07 -26.98,0.68 -53.2,0.67 -79,0.66 -74.12,-0.02 -144.13,-0.04 -218.98,15.63 -13.42,2.83 -55.95,6 -90.11,8.56 -20.13,1.5 -38.58,2.9 -49.82,4.05 -15.88,-4.98 -31.68,-7.63 -47.07,-7.91 h -0.02 c -43.53,-0.78 -83.77,16.83 -119.62,52.35 -64.43,63.79 -102.33,177.52 -101.75,250.61 0.66,78.94 107.21,191.93 183.3,238.19 55.69,33.73 153.18,45.7 195.97,49.45 -13.99,82.45 14.05,173.84 80,259.41 106.51,138.2 289.3,227.67 438.96,213.34 136.75,-13.12 264.65,-98.66 342.11,-228.85 59.04,-99.26 79.72,-207.6 58.85,-299.4 19.25,7.51 44,11.64 73.77,12.17 51.02,0.91 106.19,-8.83 132.19,-18.44 109.96,-40.62 164.02,-97.86 225.35,-189.32 46.52,-69.33 -0.52,-202.07 -52.37,-271.22 -42.11,-56.16 -111.19,-64.2 -177.99,-72.01 -19.49,-2.26 -39.63,-4.63 -58.67,-8.13 -56.68,-10.51 -119.18,-16.04 -196.71,-17.42"
style="fill:#ededed;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path30"
d="m 7125.52,2600.9 c -5.2,-118.1 -11.39,-237.96 -50.67,-350.03 57.05,45.38 226.32,0.19 300.51,0 195.73,-0.51 262.46,45.58 458.19,45.07 -29.8,309.23 -58.99,570.21 -84.18,879.09 -57.4,703.65 -48.5,315.35 -118.41,1415.68 -8.66,136.11 -84.75,708.59 -132.92,951.59 -93.73,472.84 -151.17,833.94 -123.45,1301.89 0.49,8.34 -293.9,43.47 -334.22,-19.72 -52.72,-82.66 16.74,-329.99 24.09,-426.38 23.28,-305.34 42.12,-611 56.53,-916.85 28.81,-611.7 39.89,-1224.16 33.18,-1836.44 -3.81,-348.08 -13.36,-696.11 -28.65,-1043.9"
style="fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path31"
d="m 6237.25,2170.28 c -114.86,70.93 -252.71,104.88 -387.83,95.5 -59.7,-4.14 -127.73,-14 -172.33,25.42 153.83,119.14 231.1,313.42 255.3,505.02 24.19,191.61 2.2,385.51 -5.95,578.43 -15.12,357.42 17.67,716.79 97.25,1065.78 41.2,180.68 94.87,358.61 128.75,540.77 25.48,137.02 39.63,275.82 53.77,414.44 49.91,489.57 99.83,979.14 149.74,1468.71 -2.43,-23.93 166.88,20.79 179.19,24.99 94.68,32.4 79.2,19.48 69.68,-86.49 -15.16,-168.86 -29.48,-337.79 -43.98,-506.7 -28.45,-331.2 -56.31,-662.45 -83.57,-993.75 -54.8,-665.91 -107.21,-1332.01 -157.23,-1998.29 -28.37,-377.89 -55.97,-755.84 -82.79,-1133.83"
style="fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path32"
d="m 6382.78,1618.35 c -5.45,-0.84 -11.14,-1.77 -15.54,-5.08 -8.81,-6.63 -8.78,-19.51 -7.92,-30.43 3.8,-48.42 7.61,-96.85 11.42,-145.28 6.3,-80.16 8.31,-172.07 -50.78,-227.36 -34.55,-32.3 -82.92,-44.5 -128.19,-59 -41.64,-13.3 -82.37,-29.4 -121.78,-48.2 -17.31,-8.2 -34.86,-17.4 -47.21,-32 -12.35,-14.5 -18.39,-35.9 -9.95,-52.9 125.08,8.9 249.71,24 373.27,45.2 12.31,2.1 25.38,4.6 34.36,13.2 9.72,9.3 12.48,23.5 19.76,34.7 7.27,11.3 24.77,19 33.71,8.9 2.45,-16.9 4.91,-33.8 7.36,-50.7 30.5,-4.5 61.87,-3.2 91.88,3.9 15.53,132 21.89,265.17 19.07,398.12 -0.7,32.72 10.19,161.65 -19.06,175.49 -17.83,8.43 -72.46,-10.42 -92.29,-13.47 -32.71,-5.03 -65.4,-10.06 -98.11,-15.09"
style="fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path33"
d="m 6988.83,1677.82 c 10.5,0.02 22.12,-0.37 29.76,-7.48 7.04,-6.55 8.61,-16.91 9.18,-26.45 2.97,-49.29 -9.42,-98.02 -17.73,-146.72 -23.39,-137.06 -14.32,-279.37 26.29,-412.47 11.78,-38.6 23.93,-86.7 -5.8,-114.298 -17.07,-15.902 -43.44,-18.504 -66.11,-12.601 -22.67,5.898 -42.58,19.097 -61.99,32 -17.36,11.699 -35.49,24.099 -44.65,42.699 -7.27,14.8 -8.01,31.8 -8.63,48.3 -4.9,132 -9.81,264.03 -14.71,396.07 -1.45,39.11 -26.5,155.65 -5.87,187.32 14.85,22.8 75.16,19.43 120.25,16.06 16.27,-1.22 30.55,-2.44 40.01,-2.43"
style="fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path34"
d="m 5094,5521.37 c -4.01,-42.48 -11.22,-92.62 -41.12,-109.84 -10.51,-6.06 -22.29,-6.8 -33.77,-7.03 -35.2,-0.72 -70.45,2.42 -105.25,9.38 -15.23,3.05 -31.27,7.4 -42.51,21.2 -9.48,11.62 -14.17,28.32 -17.86,44.61 -12.83,56.52 -17.41,115.57 -21.94,174.22 -6.47,83.98 -12.96,167.97 -19.43,251.95 -0.93,11.98 -1.72,24.87 2.83,35.33 6.13,14.11 19.7,18.94 31.95,21.33 51.73,10.11 100.33,31.77 152.07,21.6 35.59,-7 87.69,-4.5 120.21,-24.67 35.17,-21.81 23.84,-46.67 15.21,-92.12 -18.65,-98.32 -40.65,-244.04 -40.39,-345.96"
style="fill:#d9d9d9;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path35"
d="m 5169.11,6012.84 c -11.46,18.31 -32.96,27.06 -53.53,28.52 -2.75,0.2 -5.47,0.27 -8.21,0.27 -0.75,7.02 -2.06,13.97 -3.92,20.77 -1.53,5.52 -3.58,11.2 -7.78,14.96 -6.38,5.71 -15.73,5.42 -24.12,4.8 -75.27,-5.51 -150.54,-11.01 -225.82,-16.53 -2.17,-8.04 -5.28,-20.92 -5.22,-32.56 -28.87,-3.97 -58.21,-15.6 -67.67,-43.59 -2.84,-8.41 -3.35,-18.25 0.92,-26.04 7.3,-13.35 24.4,-15.22 38.88,-15.54 82.34,-1.8 164.67,-3.61 247,-5.41 45.68,-1 166.27,-20.33 109.47,70.35"
style="fill:#473d36;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path36"
d="m 5012.33,5732.16 -0.61,2.37 c -0.27,2.59 -0.03,7.34 2.39,9.07 1.24,2.64 2.62,1.8 4.13,-2.51 -0.56,-1.47 -1.28,-2.86 -2.13,-4.16 -1.06,-1.73 -2.37,-3.29 -3.78,-4.77 z m -17.46,-3.11 c -0.3,-1.1 -0.71,-2.23 -1.24,-3.32 -0.98,1.24 -1.97,2.47 -2.95,3.7 0.14,2.61 0.84,5.06 2.1,7.36 4.7,1.19 2.47,-6.36 2.09,-7.74 z m -18.69,1.28 c -0.22,1.18 -0.38,2.31 -0.45,3.31 -0.26,3.64 0.01,7.23 0.54,10.76 1.45,0.47 2.93,0.91 4.39,1.35 -0.66,-4.46 -1.82,-8.86 -3.53,-13.12 z m 4.93,29.74 c 0.32,-3.62 0.22,-7.24 -0.1,-10.83 -1.38,-0.43 -2.77,-0.8 -4.13,-1.25 0.93,4.16 2.34,8.21 4.23,12.08 z m -13.45,-43.29 c -6.75,-5.79 -9.36,0.49 -10.67,6.26 l 0.39,0.65 c 2.94,5.02 4.92,10.56 5.95,16.25 2.94,1.09 5.91,2.11 8.88,3.1 -0.27,-2.36 -0.45,-4.73 -0.39,-7.13 0.07,-3.38 0.4,-7.61 1.6,-11.38 -1.61,-2.99 -3.54,-5.83 -5.76,-7.75 z m -12.39,20.09 c 1.28,0.52 2.58,0.98 3.86,1.47 -0.7,-2.74 -1.78,-5.96 -3.21,-9.22 -0.38,2.56 -0.57,5.15 -0.65,7.75 z m 0.77,12.08 c 0.28,1.63 0.62,3.26 1.03,4.88 2.66,-0.6 3.75,-2.05 3.28,-4.35 0.52,-2.3 -0.14,-5.55 -0.53,-7.91 -1.53,-0.63 -3.07,-1.26 -4.59,-1.91 0.04,3.11 0.3,6.22 0.81,9.29 z m -18.82,-36.39 c 2.61,2.6 4.96,5.29 6.92,7.9 2.93,3.91 5.41,8.64 4.57,13.6 0.99,0.43 1.97,0.87 2.96,1.29 0.08,-2.81 0.23,-5 0.34,-5.8 0.23,-1.73 0.48,-3.97 0.9,-6.32 -3.97,-6.67 -9.38,-11.89 -15.69,-10.67 z m -7.04,10.7 c 0.08,3.06 0.66,6.58 1.9,9.42 0.64,2.25 2.01,4.06 4.12,5.42 4.43,-0.09 7.23,-1.2 8.41,-3.31 -0.93,-0.45 -1.89,-0.86 -2.81,-1.32 -1.31,-0.64 -0.17,-2.53 1.14,-1.92 l 2.27,0.99 c 0.07,-0.92 0,-1.91 -0.31,-3.05 -0.75,-3.37 -3.18,-6.37 -5.34,-8.95 -1.89,-2.26 -3.96,-4.42 -6.17,-6.46 -2.75,1.94 -3.33,5.11 -3.21,9.18 z m 97.79,13.74 c -1.17,-2.28 -2.36,-4.84 -4.15,-6.74 -2.82,-2.99 -6.38,-3.05 -8.92,-1.29 4.07,3.86 7.37,8.68 7.63,13.61 0.32,6.02 -5.69,9.4 -10.24,5.05 -4.36,-4.17 -6.09,-12.68 -3.34,-18.54 -1.6,-1.24 -3.26,-2.36 -5.04,-3.27 -1.63,-0.82 -5.02,-2.55 -7.29,-2.64 0.89,1.55 1.58,3.19 2.04,4.83 1.16,4.19 2.18,16.16 -6.04,13.64 -5.82,-1.79 -7.16,-13.44 -4.91,-18.05 0.58,-1.17 1.34,-2.03 2.22,-2.66 l -1.22,-0.8 c -5.55,-2.93 -8.96,0.34 -10.91,4.77 3.67,6.85 5.91,14.34 6.83,22.01 1.69,0.48 3.36,1.02 5.06,1.49 2.07,0.56 1.19,3.75 -0.89,3.19 -1.28,-0.35 -2.53,-0.78 -3.79,-1.15 0.35,5.38 0.05,10.83 -0.92,16.18 -0.3,1.67 -2.69,1.74 -3.48,0.45 -3.92,-6.35 -6.55,-13.32 -7.84,-20.56 -2.98,-1.05 -5.95,-2.1 -8.88,-3.26 0.49,4.19 1.74,13.66 -2.92,16.36 -8.04,4.65 -9.4,-10.99 -9.34,-21.54 -1.2,-0.54 -2.37,-1.11 -3.56,-1.67 -1.55,3.84 -5.08,6.66 -9.48,6.28 -12.4,-1.07 -15.42,-22.14 -9.42,-30.32 l 0.93,-1.16 c -5.2,-4.26 -11.01,-7.83 -17.16,-10.31 -13.46,-5.46 -27.13,3.03 -28.51,17.38 -1.31,13.64 8.11,24.34 19.87,29.96 1.24,0.61 0.15,2.4 -1.08,1.83 -13.24,-6.13 -23.7,-18.86 -21.35,-34.05 1.84,-11.98 12.79,-21.8 25.41,-20.28 8.99,1.08 18.21,6.45 25.83,13.02 7.04,-3.88 15.5,3.21 20.84,9.96 1,-3.44 2.55,-6.57 5.13,-8.14 5.27,-3.19 9.99,1.78 12.99,5.57 1.12,1.42 2.16,2.9 3.14,4.39 1.48,-2.43 3.6,-4.31 6.66,-5.06 4.82,-1.19 8.99,0.78 12.05,4.02 4.01,-0.39 8.78,1.37 11.95,3 1.7,0.87 3.52,2 5.33,3.36 l 1.25,-1.02 c 8.31,-5.69 15.61,3.74 18.94,10.19 1.16,2.24 -2.26,4.22 -3.42,1.97"
style="fill:#919191;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path37"
d="m 5081.2,5703.13 c -5.17,-17.54 -15.74,-31.89 -28.92,-43.08 6.08,8.32 10.59,17.24 13.69,26.49 12.06,21.37 17.8,45.8 14.55,69.28 5.03,-17.05 5.78,-35.44 0.68,-52.69 z m -149.74,-65.74 c -17.59,5.69 -34.03,15.24 -46.96,28.28 l -0.69,0.75 c -21.42,26.3 -24.15,65.75 -13.71,98.01 2.46,7.6 5.96,14.38 10.13,20.49 1.65,2.25 3.34,4.44 5.15,6.55 l 1.08,1.26 c 1.57,1.79 3.27,3.45 4.96,5.11 l 2.35,2.06 c 9.79,6.66 20.32,12.4 31.32,16.98 5.25,1.58 10.62,2.88 16.15,3.55 63.14,7.68 134.21,-49.83 115.53,-122.65 -0.77,-3.03 -1.88,-5.72 -2.87,-8.53 l -0.92,-1.51 c -25.72,-38.78 -77.45,-60.44 -121.52,-50.35 z m 122.15,149.73 c 8.8,-12.93 13.54,-27.67 14.52,-42.64 -7.05,29.77 -25.98,57.15 -53.09,73.43 15.2,-6.56 28.87,-16.52 38.57,-30.79 z m 38.85,-25.13 c -12.94,41.3 -48.15,69.56 -87.6,78.07 -10.91,2.35 -21.85,3.17 -32.66,2.66 -8.61,0.29 -17.21,-0.32 -25.68,-1.74 -67.3,-6.16 -121.63,-79.98 -91.7,-151.42 0.39,-0.91 0.89,-1.7 1.29,-2.58 13.87,-35.94 45.28,-64.17 85.16,-69.74 78.94,-11.02 182.15,46 151.19,144.75"
style="fill:#919191;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path38"
d="m 4548,6368.48 c -5.02,-31.58 -13.38,-68.78 -43.07,-80.67 -10.43,-4.17 -21.98,-4.34 -33.21,-4.14 -34.44,0.6 -68.84,4.1 -102.71,10.43 -14.8,2.77 -30.38,6.54 -41.02,17.21 -8.97,8.98 -13.13,21.6 -16.31,33.89 -11.07,42.61 -14.02,86.85 -16.92,130.78 -4.16,62.91 -8.31,125.82 -12.47,188.73 -0.6,8.98 -1.04,18.62 3.69,26.28 6.36,10.34 19.75,13.5 31.8,14.89 50.86,5.86 102.1,-3.46 152.45,-12.74 34.61,-6.38 85.64,-6.21 116.93,-22.33 33.83,-17.43 22.1,-35.63 12.49,-69.27 -20.8,-72.8 -49.26,-156.96 -51.65,-233.06"
style="fill:#404040;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path39"
d="m 4629.78,6703.13 c -8.23,17.31 -26.89,27.6 -45.67,31.47 -18.78,3.87 -38.18,2.44 -57.36,2.69 -35.03,0.45 -69.56,6.53 -104.11,12.65 0.47,8.67 0.7,17.35 0.45,26.02 -0.07,2.13 -0.2,4.4 -1.43,6.16 -1.59,2.27 -4.57,3.02 -7.28,3.57 -29.13,5.81 -59.44,5.62 -88.47,-0.58 -1.93,-7.93 -4.1,-15.6 -6.45,-23.22 -23.75,-1.42 -46.88,-8.81 -57.62,-28.95 -3.71,-6.94 -5.43,-15.4 -2.49,-22.68 5.02,-12.48 20.52,-16.26 33.81,-18.37 75.62,-11.97 151.21,-23.92 226.8,-35.89 41.94,-6.63 150.53,-38.6 109.82,47.13"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path40"
d="m 3278.84,6793.31 c 57.43,-39.53 68.38,-118.52 72.69,-188.1 16.56,-267.27 11.72,-535.86 -14.43,-802.36 -2.72,-27.78 -5.3,-57.56 8.19,-82 10.5,-19.01 29.2,-31.82 47.23,-43.92 254.68,-170.94 509.35,-341.87 764.01,-512.8 21.74,-14.59 45.75,-33.01 46.99,-59.16 0.83,-17.48 -9.09,-33.45 -18.62,-48.13 -24.72,-38.07 -49.45,-76.14 -74.18,-114.21 -383.73,142.84 -744.52,347.07 -1064.47,602.56 -12.85,10.26 -25.92,20.92 -34.37,35.02 -11.36,18.93 -13.2,41.89 -14.74,63.91 -20.34,289.05 -40.68,578.1 -61.02,867.14 -5.75,81.78 -31.39,192.26 39.15,254.1 69.83,61.19 227.03,80.64 303.57,27.95"
style="fill:#ebebeb;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path41"
d="m 7921.31,5729.3 c -13.76,-7.95 -28.49,-16.08 -44.28,-16.04 -17.25,0.06 -32.27,9.82 -46.01,19.37 -261.81,182.05 -477.66,423.29 -626.16,699.79 -32.45,60.42 -62.12,123.81 -69.77,192.12 -7.66,68.31 9.57,142.98 60.14,193.75 19.41,19.47 45.03,35.49 72.59,35.68 36.24,0.24 65.25,-26.1 88,-52.4 60.42,-69.85 105.14,-151.01 159.03,-225.71 104.81,-145.24 243.71,-265.24 345.87,-412.25 20.1,-28.92 42.28,-61.28 78.02,-68.51 33.86,-6.85 68.11,11.38 98.47,28.9 201.31,116.18 402.62,232.35 603.93,348.53 9.3,5.42 96.66,-116.3 88.27,-143.83 -15.86,-52.02 -116.85,-106.84 -156.69,-140.63 -128.93,-109.36 -264.44,-211.53 -405.51,-305.79 -80.23,-53.6 -162.25,-104.63 -245.9,-152.98"
style="fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path42"
d="m 7551.82,6421.4 c -1.81,117.94 -3.83,237.37 -35.37,351.13 -15.52,55.94 -38.05,109.97 -50.23,166.7 -33.89,157.74 14.39,319.6 43.02,478.34 28.62,158.75 32.18,338.75 -72.36,462.58 -57.17,67.74 -140.68,109.77 -226.95,132.48 -86.27,22.72 -176.15,27.72 -265.3,32.6 -100.52,5.51 -201.51,11.01 -301.7,1.29 -79.4,-7.7 -162.02,-27.06 -220.24,-80.95 -166.8,-154.37 32.88,-279.95 187.89,-277.98 83.29,1.05 165.6,21.36 248.88,19.79 83.27,-1.58 173.63,-31.3 214.88,-102.78 24.97,-43.27 28.28,-95.24 27.87,-145.05 -0.64,-76.5 -8.64,-152.95 -23.86,-227.96 -18.99,-93.57 -49.25,-190.06 -24.89,-282.41 14.95,-56.66 49.29,-106.09 74.39,-159.14 36.59,-77.37 53.46,-162.71 59.05,-247.93 5.1,-77.8 0.58,-158.19 -31.15,-229.58 -31.73,-71.38 -94.54,-132.65 -172.11,-147 -57.1,-10.56 -124.26,10.45 -149.17,62.28 -24.9,51.84 17.43,127.35 75.03,119.9 -83.66,21.95 -179.7,-38.32 -195.12,-122.46 -18.85,-102.7 75.62,-199.61 179.24,-220.02 103.63,-20.41 209.73,16.01 305.67,59.75 79.7,36.35 156.84,78.18 230.69,125.08 39.67,25.2 79.93,53.56 101.4,95.04 21.38,41.33 21.15,89.89 20.44,136.3"
style="fill:#ba8775;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path43"
d="m 8812.48,6401.58 c 34.94,21.28 70.35,42.92 98.36,72.25 28.02,29.32 48.19,67.8 45.77,106.71 -0.2,3.34 -0.65,6.87 -2.81,9.45 -2.16,2.57 -6.59,3.62 -9.23,1.32 -0.15,8.83 -9.48,15.15 -18.66,15.63 -9.17,0.47 -18.1,-3.33 -26.59,-7.05 -27.67,-12.13 -55.34,-24.26 -83,-36.38 -4.3,-1.88 -8.8,-3.81 -13.43,-3.57 -4.12,0.22 -7.85,2.12 -11.85,2.99 -14.16,3.1 -28.04,-6.81 -39.27,-16.24 -32.19,-27.06 -63.01,-55.64 -92.29,-85.61 -5.12,-5.24 -10.49,-11.13 -11.1,-18.19 -0.35,-4.16 1.01,-8.2 2.46,-12.09 6.89,-18.64 15.68,-36.58 26.16,-53.51 6.3,-10.19 18.33,-36.45 32.62,-36.41 14.15,0.05 36.49,20.29 48.19,27.42 18.23,11.1 36.45,22.19 54.67,33.28"
style="fill:#f9e9de;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path44"
d="m 3949.45,8841.67 c 159.34,-10.15 240.44,26.56 316.56,40.56 16.23,3 43.37,7.51 75.53,12.71 27.57,72.66 38.54,146.11 34.95,210.43 -2.08,37.28 -6.93,74 -13.84,109.89 -120.51,-9.72 -122.96,-64.23 -231.53,-113.68 -119.65,-54.49 -245.46,-6.36 -237.14,74.78 8.32,81.14 1.82,111.57 -99.6,331.74 -33.57,72.86 -40.19,128.63 -34.41,171.03 -130.85,7.86 -239.43,4.35 -239.43,4.35 0,0 -204.91,-24.72 -331.31,-175.49 5.87,-1.04 108.38,-20.72 178.24,-138.93 71.92,-121.7 -102.36,-200.26 0,-267.48 102.35,-67.21 112.5,-270.06 112.5,-361.34 0,-54.95 -18.56,-123.16 -61.95,-169.28 55.19,-19.76 118.44,-34.69 190.9,-43.71 28.32,-3.52 55.49,-5.58 82.06,-6.88 2.1,41.52 8.51,93.33 24.61,148.88 38.22,131.85 74.53,182.56 233.86,172.42"
style="fill:#94cbea;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path45"
d="m 3367.47,9101.58 c -102.36,67.22 71.92,145.78 0,267.48 -69.86,118.21 -172.37,137.89 -178.24,138.93 -37.43,-44.65 -68.3,-99.83 -84.53,-169.35 -61.99,-265.65 -66.46,-631.76 313.32,-767.68 43.39,46.12 61.95,114.33 61.95,169.28 0,91.28 -10.15,294.13 -112.5,361.34"
style="fill:#6cb476;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path46"
d="m 3794.38,9508.1 c 101.42,-220.17 107.92,-250.6 99.6,-331.74 -8.32,-81.14 117.49,-129.27 237.14,-74.78 108.57,49.45 111.02,103.96 231.53,113.68 -40.1,208.19 -159.17,384.4 -333.49,427.65 -82.37,20.44 -180.81,30.9 -269.19,36.22 -5.78,-42.4 0.84,-98.17 34.41,-171.03"
style="fill:#6cb476;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path47"
d="m 4341.54,8894.94 c -32.16,-5.2 -59.3,-9.71 -75.53,-12.71 -76.12,-14 -157.22,-50.71 -316.56,-40.56 -159.33,10.14 -195.64,-40.57 -233.86,-172.42 -16.1,-55.55 -22.51,-107.36 -24.61,-148.88 373.09,-18.25 574.84,175 650.56,374.57"
style="fill:#6cb476;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)" />
<path
id="path48"
d="m 6472.96,924.801 c -43.45,0 -86.92,0.199 -130.37,0.601 -111.79,1.098 -223.4,5 -335.12,8.297 -57.59,1.801 -151.8,33.199 -95.71,114.701 24.19,35.2 69.56,48.2 111.72,55 21.14,3.4 42.37,5.8 63.68,7.6 -5.75,-2.6 -11.47,-5.3 -17.17,-8 -17.31,-8.2 -34.86,-17.4 -47.21,-32 -12.35,-14.5 -18.39,-35.9 -9.95,-52.9 125.08,8.9 249.71,24 373.27,45.2 12.31,2.1 25.38,4.6 34.36,13.2 9.72,9.3 12.48,23.5 19.76,34.7 0.96,1.5 2.11,3 3.39,4.3 10.33,0 20.66,0 30.99,0 2.23,-15.4 4.46,-30.8 6.69,-46.1 11.82,-1.8 23.78,-2.6 35.74,-2.6 18.89,0 37.77,2.1 56.14,6.5 1.65,14 3.2,28 4.64,42.1 90.04,-0.1 180.02,-0.1 270.06,-0.2 0.43,-11.5 0.85,-23 1.28,-34.4 0.62,-16.5 1.36,-33.5 8.63,-48.3 9.16,-18.6 27.29,-31 44.65,-42.699 19.41,-12.903 39.32,-26.102 61.99,-32 7.18,-1.903 14.72,-2.899 22.23,-2.899 16.2,0 32.21,4.7 43.88,15.5 29.73,27.598 17.58,75.698 5.8,114.298 -3.07,10.1 -5.97,20.2 -8.68,30.4 177.01,-0.2 354.02,-0.3 531.03,-0.4 110.48,-0.1 221.1,-0.2 331.19,-9.4 52.83,-4.4 106.58,-11.1 154.45,-33.9 -69.7,-47.3 -153.87,-67.8 -236.73,-83.099 -257.48,-47.301 -520.65,-52.199 -782.41,-56.899 -184.06,-3.3 -368.13,-6.601 -552.22,-6.601"
style="fill:url(#linearGradient49);fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)"
clip-path="url(#clipPath49)" />
<path
id="path50"
d="m 6012.83,1018.1 c -8.44,17 -2.4,38.4 9.95,52.9 12.35,14.6 29.9,23.8 47.21,32 5.7,2.7 11.42,5.4 17.17,8 51.11,4.3 102.6,4.6 153.98,4.6 5.16,0 10.33,0 15.49,0 62.33,0 124.65,-0.1 186.98,-0.1 -1.28,-1.3 -2.43,-2.8 -3.39,-4.3 -7.28,-11.2 -10.04,-25.4 -19.76,-34.7 -8.98,-8.6 -22.05,-11.1 -34.36,-13.2 -123.56,-21.2 -248.19,-36.3 -373.27,-45.2 m 504.2,48.7 c -11.96,0 -23.92,0.8 -35.74,2.6 -2.23,15.3 -4.46,30.7 -6.69,46.1 34.39,-0.1 68.83,-0.1 103.21,-0.1 -1.44,-14.1 -2.99,-28.1 -4.64,-42.1 -18.37,-4.4 -37.25,-6.5 -56.14,-6.5"
style="fill:url(#linearGradient51);fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)"
clip-path="url(#clipPath51)" />
<path
id="path52"
d="m 6986.65,954.902 c -7.51,0 -15.05,0.996 -22.23,2.899 -22.67,5.898 -42.58,19.097 -61.99,32 -17.36,11.699 -35.49,24.099 -44.65,42.699 -7.27,14.8 -8.01,31.8 -8.63,48.3 -0.43,11.4 -0.85,22.9 -1.28,34.4 59.92,-0.1 119.86,-0.1 179.78,-0.1 2.71,-10.2 5.61,-20.3 8.68,-30.4 11.78,-38.6 23.93,-86.7 -5.8,-114.298 -11.67,-10.8 -27.68,-15.5 -43.88,-15.5"
style="fill:url(#linearGradient53);fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,1496)"
clip-path="url(#clipPath53)" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="580 440 945 1270" style="enable-background:new 580 440 945 1270;" xml:space="preserve">
<g>
<path style="fill:#FBCBAA;" d="M1508.815,940.315c-1.94-7.443-11.209-42.006-26.525-83.58c10.896-7.463,16.965-13.56,15.938-15.998
c-30.181-93.187-70.637-145.728-106.583-163.164c-25.036-16.424-70.617-28.344-124.554-31.443l-3.182-14.861
c126.75-20.978,144.774-94.13,144.774-94.13s-16.006-27.486-39.385-24.95c-23.38,2.537-41.71-3.75-47.194-19.706
c-5.484-15.955-14.486-28.217-35.558-28.664c-15.991-0.339-25.169-10.317-28.668-15.112c-9.587-15.699-27.361-25.66-46.983-24.323
c-19.622,1.338-35.878,13.621-43.245,30.477c-2.816,5.224-10.554,16.355-26.351,18.863c-20.815,3.303-28.068,16.673-31.336,33.226
c-3.268,16.553-20.573,25.27-44.081,25.931c-23.507,0.661-35.635,30.066-35.635,30.066s28.67,72.242,162.337,73.72l2.288,10.688
c-65.887,6.15-116.411,25.7-126.908,49.921c-0.516,0.946-24.364,45.062-36.632,140.385H877.807c-6.123,0-10.245,6.268-7.821,11.889
l23.924,55.486c-4.262,0.322-8.536,1.349-12.66,3.134l-0.013-0.027l-1.147,0.55l-0.18,0.083l-0.236,0.116
c-37.597,18.102-65.598,37.31-62.804,43.165c1.271,2.664,8.702,2.109,19.973-0.965c-12.764,7.748-22,14.666-25.722,19.401
l-30.824,2.692l0.003,0.217c-50.677,4.996-90.268,47.74-90.268,99.731c0,22.273,7.266,42.847,19.557,59.485l-0.01,0.013
l117.441,164.751l-0.349,1.742c-0.254,1.267,0.568,2.499,1.835,2.753h0l-26.882,134.381c-0.425,1.241,1.487,3.2,5.08,5.567
l-34.84,115.507l-11.448-4.201l-13.227,36.049c-33.693,27.82-136.392,35.732-136.392,35.732l76.757,28.162l37.021-2.257
l-5.122,13.96l87.971,32.276l40.963-111.646l-14.413-5.288l55.289-110.296c3.335,0.07,5.399-0.432,5.791-1.576l0.053,0.018
c0,0,8.485-14.591,22.542-40.119l0.038,0.022c0.807,0.424,1.749,0.328,2.445-0.167l0.382,0.534l0.898-0.475
c0.842,0.925,3.479,0.816,7.374-0.114l37.82,65.48v147.767c-18.287,29.772-38.488,54.896-38.488,54.896l38.488-19.313v3.784
c0,8.576,6.953,15.529,15.529,15.529c8.577,0,15.53-6.953,15.53-15.529v-19.368l3.53-1.771l23.7-28.53l6.669,13.291l68.632-34.439
v70.817c0,8.576,6.953,15.529,15.529,15.529c8.576,0,15.529-6.953,15.529-15.529V1300.85h133.59v379.716
c0,8.576,6.952,15.529,15.529,15.529c8.577,0,15.529-6.953,15.529-15.529V1300.85h102.532v379.716
c0,8.576,6.953,15.529,15.529,15.529c8.577,0,15.529-6.953,15.529-15.529v-379.721c8.484-0.107,15.329-7.015,15.329-15.524
c0-8.029-6.094-14.634-13.909-15.445c-1.929-130.918-61.449-125.273-139.05-122.768c3.234-1.622,6.405-3.35,9.509-5.182
c22.979-13.107,41.026-29.701,52.781-50.536c16.239-24.961,25.677-54.754,25.677-86.753c0-5.702-0.302-11.333-0.886-16.88
c27.036,6.964,53.804,11.587,77.855,11.916c7.922,1.106,16.232,0.212,24.181-2.999
C1503.468,987.589,1515.147,963.146,1508.815,940.315z M1100.193,1572.027l-38.215-76.159l-13.721,6.885l-42.543-115.81
c2.334-2.383,3.382-4.23,2.816-5.299c-0.195-0.369-0.577-0.629-1.126-0.79c-1.135-3.753-4.453-14.619-9.741-31.144v-48.861h102.531
V1572.027z"/>
<g>
<path style="fill:#EC624B;" d="M1007.402,1380.855c-1.136-3.753-4.453-14.619-9.742-31.144v43.958
c3.417-2.509,6.158-4.793,8.051-6.726c2.334-2.383,3.381-4.23,2.816-5.299C1008.332,1381.276,1007.95,1381.016,1007.402,1380.855z
"/>
<path style="fill:#EC624B;" d="M1350.403,979.621c-48.228-14.993-94.544-35.288-124.858-49.646
c-3.731,2.784-8.02,4.951-12.764,6.303c-1.675,0.477-3.355,0.838-5.033,1.088c-16.601,4.45-31.831,7.601-43.802,9.218
l10.27,23.819h100.056c5.36,0,9.707,4.345,9.707,9.707c0,2.761-1.156,5.252-3.007,7.02c-1.743,1.663-4.101,2.687-6.7,2.687
h-336.08c-3.827,0-7.354-1.857-9.532-4.878c-0.494-0.685-0.918-1.427-1.261-2.223l-0.849-1.97l-8.448-19.593l-106.983,9.343
l-30.824,2.692l0.003,0.217c-50.678,4.996-90.269,47.74-90.269,99.731c0,22.273,7.266,42.847,19.557,59.485l-0.01,0.013
l117.44,164.751l-0.349,1.742c-0.254,1.267,0.568,2.5,1.834,2.753h0.001l-26.881,134.382c-0.425,1.241,1.487,3.2,5.08,5.567
c7.853,5.174,23.737,12.297,40.819,18.147c17.69,6.057,33.552,9.681,41.739,9.853c3.335,0.07,5.399-0.431,5.791-1.575l0.054,0.018
c0,0,8.484-14.591,22.542-40.12c0.013,0.007,0.024,0.016,0.038,0.022c0.808,0.425,1.749,0.328,2.445-0.167l0.382,0.535
l0.897-0.475c0.842,0.924,3.479,0.815,7.374-0.114c8.507-2.03,22.998-7.967,37.82-15.582v-111.695
c-7.469-1.125-13.196-7.571-13.196-15.355c0-6.414,3.889-11.92,9.438-14.288c1.775-147.035,75.454-123.397,166.084-123.397
c24.211,0,47.212-1.687,67.956-1.356c20.744-0.331,43.745,1.356,67.956,1.356c9.201,0,18.226-0.243,27.019-0.527
c3.233-1.622,6.405-3.35,9.508-5.181c22.979-13.107,41.026-29.702,52.781-50.536c16.239-24.961,25.676-54.754,25.676-86.752
c0-5.702-0.302-11.333-0.886-16.88C1369.424,985.307,1359.877,982.566,1350.403,979.621z"/>
<path style="fill:#EC624B;" d="M1498.226,840.737c-30.181-93.187-70.637-145.728-106.583-163.164
c-25.036-16.424-70.617-28.344-124.555-31.443l2.89,13.5c0.953,4.457-13.787,11.39-32.926,15.488
c-19.138,4.096-35.427,3.805-36.38-0.653l-5.803-27.108c-65.887,6.15-116.411,25.7-126.908,49.921
c-0.516,0.946-24.364,45.062-36.632,140.385h77.922c4.693,0,8.936,2.792,10.794,7.101l15.618,36.222
c12.505-6.37,32.374-13.857,55.269-20.466c0.08-0.023,0.158-0.048,0.238-0.071c0.057-0.016,0.115-0.031,0.173-0.047
c0.413-0.119,0.826-0.237,1.241-0.356l0.008,0.029c5.469-1.344,10.97-1.473,16.21-0.548l140.539,21.68l5.43,0.838l55.336,8.537
c0.357-1.078,1.454-1.764,2.607-1.569l3.276,0.554c5.959-2.257,12.191-4.745,18.579-7.435
c19.491-8.206,36.271-17.557,47.72-25.399C1493.185,849.271,1499.253,843.175,1498.226,840.737z"/>
</g>
<g>
<path style="fill:#1F2D50;" d="M1212.781,936.278c-1.675,0.477-3.355,0.838-5.033,1.088c-16.601,4.45-31.831,7.601-43.802,9.218
l10.27,23.819h100.056c5.36,0,9.707,4.345,9.707,9.707c0,2.761-1.156,5.252-3.007,7.02c26.221-1.62,49.829-4.205,69.43-7.508
c-48.228-14.993-94.544-35.288-124.858-49.646C1221.814,932.759,1217.525,934.926,1212.781,936.278z"/>
<path style="fill:#1F2D50;" d="M1498.226,840.737c-30.181-93.187-70.637-145.728-106.583-163.164
c-25.036-16.424-70.617-28.344-124.555-31.443l2.89,13.5c0.953,4.457-13.787,11.39-32.926,15.488
c-19.138,4.096-35.427,3.805-36.38-0.653l-5.803-27.108c-65.887,6.15-116.411,25.7-126.908,49.921
c-0.516,0.946-24.364,45.062-36.632,140.385h77.922c4.693,0,8.936,2.792,10.794,7.101l15.618,36.222
c12.505-6.37,32.374-13.857,55.269-20.466c0.08-0.023,0.158-0.048,0.238-0.071c0.057-0.016,0.115-0.031,0.173-0.047
c0.413-0.119,0.826-0.237,1.241-0.356l0.008,0.029c5.469-1.344,10.97-1.473,16.21-0.548l140.539,21.68l5.43,0.838l55.336,8.537
c0.357-1.078,1.454-1.764,2.607-1.569l3.276,0.554c5.959-2.257,12.191-4.745,18.579-7.435
c19.491-8.206,36.271-17.557,47.72-25.399C1493.185,849.271,1499.253,843.175,1498.226,840.737z"/>
</g>
<g>
<path style="fill:#D8A740;" d="M1274.273,970.403h-100.056l-10.27-23.819l-28.283-65.597l-15.618-36.222
c-1.858-4.309-6.101-7.101-10.794-7.101H877.806c-6.123,0-10.246,6.268-7.822,11.889l23.924,55.487l24.194,56.114l8.448,19.593
l0.849,1.97c0.343,0.796,0.768,1.539,1.261,2.223c2.179,3.021,5.706,4.878,9.532,4.878h336.08c2.599,0,4.957-1.024,6.7-2.687
c1.851-1.767,3.007-4.258,3.007-7.02C1283.98,974.748,1279.633,970.403,1274.273,970.403z"/>
<path style="fill:#7681BF;" d="M1430.908,1269.876c-1.929-130.918-61.449-125.273-139.05-122.768
c-8.794,0.284-17.819,0.527-27.019,0.527c-24.211,0-47.212-1.687-67.956-1.356c-20.744-0.331-43.745,1.356-67.956,1.356
c-90.63,0-164.309-23.638-166.084,123.397c-5.549,2.368-9.438,7.874-9.438,14.288c0,7.784,5.727,14.23,13.196,15.355v379.891
c0,8.576,6.953,15.529,15.529,15.529s15.529-6.953,15.529-15.529v-379.716h102.532v271.178l15.12,30.134l-15.12,7.587v70.817
c0,8.576,6.953,15.529,15.529,15.529s15.529-6.953,15.529-15.529v-379.716h133.59v379.716c0,8.576,6.952,15.529,15.529,15.529
c8.577,0,15.529-6.953,15.529-15.529v-379.716h102.531v379.716c0,8.576,6.953,15.529,15.529,15.529
c8.577,0,15.529-6.953,15.529-15.529v-379.721c8.484-0.107,15.33-7.015,15.33-15.525
C1444.818,1277.291,1438.723,1270.686,1430.908,1269.876z"/>
</g>
<path style="fill:#FED584;" d="M1430.908,1269.876c-0.532-0.055-1.073-0.085-1.619-0.085H968.934c-2.162,0-4.22,0.443-6.09,1.241
c-5.549,2.368-9.438,7.874-9.438,14.288c0,7.784,5.727,14.23,13.196,15.355v379.891c0,8.576,6.953,15.529,15.529,15.529
s15.529-6.953,15.529-15.529v-379.716h102.532v271.178l15.12,30.134l-15.12,7.587v70.817c0,8.576,6.953,15.529,15.529,15.529
s15.529-6.953,15.529-15.529v-379.716h133.59v379.716c0,8.576,6.952,15.529,15.529,15.529c8.577,0,15.529-6.953,15.529-15.529
v-379.716h102.531v379.716c0,8.576,6.953,15.529,15.529,15.529c8.577,0,15.529-6.953,15.529-15.529v-379.721
c8.484-0.107,15.33-7.015,15.33-15.525C1444.818,1277.291,1438.723,1270.686,1430.908,1269.876z"/>
<path style="fill:#D89324;" d="M1100.192,1300.85v271.177l15.121,30.134l-15.121,7.587v70.817c0,8.576,6.953,15.529,15.529,15.529
s15.529-6.953,15.529-15.529V1300.85H1100.192z"/>
<path style="fill:#D89324;" d="M1398.43,1300.85v379.716c0,8.576,6.953,15.529,15.529,15.529c8.577,0,15.529-6.953,15.529-15.529
v-379.721c-0.067,0.001-0.133,0.005-0.199,0.005H1398.43z"/>
<path style="fill:#8E2A53;" d="M1187.453,614.853L1187.453,614.853c-0.484-0.109-0.897-0.363-1.208-0.703
c-21.643-6.575-36.546-27.53-34.623-50.992l3.056-37.304c0.513-6.259,2.18-12.147,4.773-17.474
c21.985-2.421,34.158-17.049,34.158-17.049c9.548,27.958,53.442,26.069,53.442,26.069c-7.985,12.906-6.991,24.561-5.452,30.878
c1.084-1.025,2.276-1.989,3.572-2.87c9.864-6.709,21.946-6.143,26.987,1.267c5.04,7.41,1.13,18.855-8.735,25.565
c-3.588,2.44-7.468,3.913-11.196,4.45l11.683,54.579c126.749-20.978,144.774-94.131,144.774-94.131s-16.006-27.486-39.386-24.95
c-23.38,2.537-41.71-3.75-47.194-19.706c-5.484-15.955-14.486-28.217-35.558-28.664c-15.991-0.339-25.168-10.317-28.668-15.111
c-9.587-15.699-27.361-25.661-46.983-24.323c-19.621,1.338-35.877,13.62-43.245,30.477c-2.816,5.224-10.554,16.355-26.352,18.863
c-20.815,3.303-28.068,16.673-31.336,33.226c-3.268,16.553-20.573,25.27-44.081,25.931c-23.507,0.661-35.635,30.066-35.635,30.066
s28.671,72.243,162.337,73.721l-4.648-21.713C1187.774,614.921,1187.612,614.889,1187.453,614.853z"/>
<polygon style="fill:#1F2D50;" points="1100.192,1572.027 1061.976,1495.869 1048.255,1502.754 997.661,1528.141 997.661,1661.197
1001.19,1659.426 1024.891,1630.896 1031.56,1644.187 1100.192,1609.748 1115.312,1602.161 "/>
<path style="fill:#1F2D50;" d="M928.115,1696.094l38.487-19.313v-35.583C948.314,1670.971,928.115,1696.094,928.115,1696.094z"/>
<path style="fill:#1F2D50;" d="M724.571,1650.82l-5.122,13.96l87.971,32.276l40.962-111.646l-14.412-5.288l-62.11-22.788
l-11.449-4.2l-13.227,36.049c-33.693,27.82-136.392,35.731-136.392,35.731l76.757,28.162L724.571,1650.82z"/>
<path style="fill:#F29484;" d="M1192.574,567.317c0.982-0.84,1.097-2.317,0.258-3.298l-9.053-10.585
c-0.531-0.624-0.783-1.366-0.785-2.124c0.003-0.918,0.374-1.81,1.115-2.462l10.116-8.868c0.971-0.852,1.069-2.33,0.217-3.301
c-0.852-0.971-2.33-1.069-3.301-0.217l0,0l-10.116,8.867c-1.788,1.565-2.712,3.777-2.709,5.98
c-0.002,1.823,0.632,3.674,1.909,5.165l9.053,10.585C1190.116,568.041,1191.592,568.156,1192.574,567.317z"/>
<path style="fill:#010202;" d="M1174.772,530.485c-1.288-0.122-2.488,1.409-2.679,3.421c-0.191,2.012,0.698,3.742,1.988,3.865
c1.288,0.122,2.487-1.41,2.679-3.422C1176.95,532.338,1176.06,530.607,1174.772,530.485z"/>
<path style="fill:#010202;" d="M1208.309,537.241c1.288,0.122,2.488-1.41,2.679-3.422c0.191-2.012-0.699-3.742-1.988-3.864
c-1.288-0.122-2.488,1.409-2.679,3.421C1206.131,535.388,1207.021,537.119,1208.309,537.241z"/>
<path style="fill:#F29484;" d="M1196.22,580.697L1196.22,580.697c1.293,0.313,2.59,0.463,3.868,0.463
c7.387,0.002,14.074-5.026,15.894-12.513c0.304-1.256-0.466-2.521-1.722-2.825c-1.256-0.304-2.52,0.466-2.825,1.722
c-1.292,5.347-6.086,8.936-11.348,8.937c-0.911,0-1.837-0.107-2.765-0.332c-1.256-0.305-2.521,0.466-2.825,1.722
C1194.193,579.127,1194.964,580.393,1196.22,580.697z"/>
<path style="fill:#F29484;" d="M1187.453,614.853L1187.453,614.853c0.16,0.036,0.321,0.067,0.481,0.102
c3.527,0.764,7.188,1.17,10.939,1.17c13.213,0.002,25.285-4.99,34.391-13.183c0.961-0.865,1.039-2.343,0.175-3.304
c-0.864-0.961-2.343-1.039-3.304-0.175c-8.29,7.453-19.236,11.982-31.263,11.984c-3.573,0-7.047-0.401-10.387-1.157
c-1.26-0.285-2.513,0.504-2.798,1.765c-0.176,0.776,0.057,1.549,0.556,2.095C1186.555,614.49,1186.969,614.744,1187.453,614.853z"
/>
<path style="fill:#F29484;" d="M1241.652,562.261c-1.119,0.646-1.502,2.076-0.856,3.195c0.646,1.119,2.077,1.502,3.195,0.856
l21.811-12.594c1.119-0.646,1.502-2.076,0.856-3.195c-0.646-1.118-2.077-1.502-3.195-0.856L1241.652,562.261z"/>
<path style="fill:#375492;" d="M1354.771,882.045c-21.852-51.303-33.188-100.479-33.441-138.095
c-0.009-1.292-1.064-2.332-2.356-2.323s-2.332,1.064-2.322,2.356c0.274,37.834,11.38,86.43,32.689,137.224L1354.771,882.045z"/>
<path style="fill:#C13E2A;" d="M831.255,1300.044l49.419-246.929c5.859-29.275,23.778-53.595,47.987-68.178
c-0.494-0.684-0.918-1.427-1.261-2.223l-0.85-1.971c-25.46,15.223-44.314,40.728-50.464,71.453l-49.07,245.186l-0.349,1.742
c-0.254,1.267,0.568,2.5,1.834,2.753h0.001C829.769,1302.132,831.001,1301.311,831.255,1300.044z"/>
<path style="fill:#C13E2A;" d="M920.842,1427.19l45.759-87.068v-10.057l-49.901,94.948c-0.594,1.13-0.17,2.524,0.944,3.137
c0.013,0.007,0.024,0.016,0.038,0.022c0.808,0.425,1.749,0.328,2.445-0.167C920.417,1427.8,920.666,1427.526,920.842,1427.19z"/>
<path style="fill:#F29484;" d="M1411.934,893.627l51.751,8.758c1.274,0.216,2.481-0.642,2.696-1.916
c0.216-1.274-0.642-2.482-1.916-2.697l-48.475-8.204l-3.276-0.554c-1.153-0.195-2.25,0.491-2.607,1.568
c-0.037,0.113-0.069,0.228-0.089,0.348C1409.802,892.204,1410.66,893.411,1411.934,893.627z"/>
<path style="fill:#AE8932;" d="M918.102,961.151l8.448,19.593l0.85,1.971c0.343,0.795,0.767,1.539,1.261,2.223
c2.179,3.022,5.706,4.878,9.533,4.878h211.874c5.643,0,9.443-5.776,7.209-10.957l-57.817-134.095
c-1.858-4.309-6.101-7.101-10.794-7.101H877.806c-6.123,0-10.246,6.268-7.822,11.889l23.924,55.486L918.102,961.151z
M1001.844,898.429c5.15-2.972,13.281,1.471,18.164,9.927c4.882,8.455,4.665,17.72-0.485,20.693
c-5.15,2.974-13.282-1.471-18.164-9.927C996.478,910.667,996.695,901.403,1001.844,898.429z"/>
<path style="fill:#F7BA4D;" d="M997.661,1300.85h-28.727c-0.793,0-1.571-0.06-2.332-0.175v379.891
c0,8.576,6.953,15.529,15.529,15.529s15.529-6.953,15.529-15.529V1300.85z"/>
<path style="fill:#F7BA4D;" d="M1264.84,1680.566c0,8.576,6.952,15.529,15.529,15.529c8.577,0,15.529-6.953,15.529-15.529V1300.85
h-31.058V1680.566z"/>
<path style="fill:#4458A4;" d="M1264.839,1147.635c-24.211,0-47.212-1.686-67.956-1.356c56.689,0.904,96.519,16.874,98.111,123.513
h134.295c0.547,0,1.087,0.029,1.619,0.084c-1.929-130.918-61.449-125.273-139.05-122.768
C1283.065,1147.392,1274.04,1147.635,1264.839,1147.635z"/>
<path style="fill:#F7BA4D;" d="M1297.831,1269.791c8.577,0,15.53,6.953,15.53,15.529c0,8.577-6.953,15.53-15.53,15.53h131.458
c0.066,0,0.132-0.004,0.199-0.005c8.484-0.107,15.33-7.015,15.33-15.525c0-8.029-6.095-14.634-13.91-15.444
c-0.532-0.055-1.072-0.084-1.619-0.084H1297.831z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg1"
width="259.18665"
height="260.13879"
viewBox="0 0 259.18665 260.1388"
sodipodi:docname="9932340(1).svg"
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showguides="true"
showgrid="true"
inkscape:zoom="1.1741508"
inkscape:cx="-65.579308"
inkscape:cy="28.531258"
inkscape:window-width="2560"
inkscape:window-height="1346"
inkscape:window-x="-11"
inkscape:window-y="-11"
inkscape:window-maximized="1"
inkscape:current-layer="group-R5">
<inkscape:page
x="0"
y="0"
inkscape:label="1"
id="page1"
width="259.18665"
height="260.13879"
margin="0"
bleed="0" />
</sodipodi:namedview>
<g
id="g1"
inkscape:groupmode="layer"
inkscape:label="1"
transform="translate(-197.12207,-179.21263)">
<g
id="group-R5">
<path
id="path3-2"
d="m 456.30871,400.98849 c 0,21.18693 -56.9426,38.36293 -127.18398,38.36293 -70.24133,0 -127.18266,-17.176 -127.18266,-38.36293 0,-21.188 56.94133,-38.36413 127.18266,-38.36413 70.24138,0 127.18398,17.17613 127.18398,38.36413"
style="fill:#4a5ab2;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path4-4"
d="m 260.8794,401.96449 c -10.484,0 -17.18133,-2.1224 -19.952,-6.33586 -2.768,-4.20947 -0.46533,-8.80427 -0.36533,-8.99854 l 1.34266,0.688 c -0.0213,0.0396 -2,4.0292 0.292,7.49587 2.04534,3.09587 8.01867,6.55213 24.876,5.42133 l 0.1,1.5052 c -2.22933,0.14947 -4.32933,0.224 -6.29333,0.224"
style="fill:#7a86cc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path5-5"
d="m 259.8194,408.44729 c -16.99867,0 -27.296,-3.2744 -30.68933,-9.7864 -4.24934,-8.15213 4.01333,-18.04013 4.36666,-18.45786 l 1.15067,0.976 c -0.08,0.0948 -7.96267,9.5376 -4.17733,16.7896 2.824,5.4104 13.168,11.2104 46.384,8.08386 l 0.14266,1.5016 c -6.32933,0.59587 -12.05733,0.8932 -17.17733,0.8932"
style="fill:#7a86cc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path6-5"
d="m 376.46471,414.00876 v -1.50827 c 21.3826,-0.0412 34.3533,-3.9052 37.512,-11.17613 3.3173,-7.63587 -5.0507,-16.75987 -5.136,-16.852 l 1.104,-1.02667 c 0.3746,0.40267 9.1213,9.948 5.4146,18.48027 -3.464,7.97453 -16.5493,12.04013 -38.892,12.0828"
style="fill:#7a86cc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path7-1"
d="m 374.41671,420.61396 c -9.3386,0 -19.89331,-0.40414 -31.65331,-1.21454 l 0.10266,-1.50573 c 62.80935,4.32547 79.64665,-3.8104 82.70935,-11.3984 3.04,-7.53186 -7.016,-16.09226 -7.1173,-16.1772 l 0.968,-1.15466 c 0.4506,0.37706 10.9933,9.35986 7.5466,17.89586 -3.6386,9.0188 -21.2773,13.55467 -52.556,13.55467"
style="fill:#7a86cc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path8-7"
d="m 406.22871,334.83129 -34.6053,-31.412 -76.91468,-9.64533 -62.06133,9.66667 c -4.99067,-0.0653 -9.8,0.0213 -14.372,0.27733 l 33.20133,87.0216 50.87334,1.33853 -2.14267,14.72654 101.21201,-14.19227 15.796,-54.62107 c 0,0 -4.0053,-1.2 -10.9867,-3.16"
style="fill:#afe2f8;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path9-1"
d="m 248.8994,322.34463 18.22267,54.38813 -2.01067,-47.88413 9.42533,30.1628 v -45.24414 l -25.63733,8.57734"
style="fill:#7a86cc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path10-1"
d="m 300.2074,406.80463 -3.236,-88.136 5.844,35.6308 9.048,-7.42414 -11.656,59.92934"
style="fill:#7a86cc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path11-5"
d="m 333.92073,398.60623 14.516,-30.16827 c 0,0 0.75333,21.49067 0.75333,21.30253 0,-0.18906 27.27335,-47.31853 27.27335,-47.31853 l -23.88001,54.10507 -8.86,-6.78654 -9.80267,8.86574"
style="fill:#7a86cc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path12-2"
d="m 398.95941,339.78329 c 0,0 -3.308,23.43747 -2.9733,22.84107 0.3333,-0.59573 8.0626,-15.74907 8.0626,-15.74907 0,0 -7.1626,39.0944 -6.9746,38.52934 0.188,-0.56507 16.024,-43.92534 16.024,-43.92534 l -14.1387,-1.696"
style="fill:#7a86cc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path13-7"
d="m 234.0714,271.85663 v -40.47867 l 9.908,3.528 -6.96133,36.95067 h -2.94667"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path14-6"
d="m 197.12207,242.4033 v -63.19067 l 93.71333,8.30133 -8.568,57.56667 -85.14533,-2.67733"
style="fill:#f25043;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path15-1"
d="m 204.26873,237.43796 73.40134,1.97334 7.30666,-44.692 -81.68266,-9.116 z m 74.676,3.516 -76.156,-2.048 -1.036,-54.992 84.968,9.48267 -7.776,47.55733"
style="fill:#f9aa37;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path16-4"
d="m 226.0194,223.6353 -4.51333,0.11466 c -0.064,-1.97066 -0.072,-3.88133 -0.136,-5.85066 l -4.26267,0.204 c -0.10133,1.972 0.13067,3.93866 0.29467,5.89466 -1.79334,0.108 -3.60267,-0.0133 -5.396,-0.0227 0.16,-5.50667 0.30666,-10.97734 0.31733,-16.484 h 3.78667 c 0.25733,2.268 0.256,4.508 0.45333,6.78 1.468,-0.0347 2.85467,-0.012 4.33067,-0.0453 l 0.22666,-7.34666 c 1.60934,-0.052 3.19734,-0.0493 4.78534,0.22666 0.168,5.53867 0.0347,10.99067 0.11333,16.52934"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path17-2"
d="m 242.2994,209.8953 c -2.97067,0.15466 -5.85467,0.29866 -8.82133,0.544 l -0.044,1.79066 5.84933,-0.34 c 0.228,1.45067 0.25067,2.89067 0.204,4.35334 l -5.44133,0.976 0.34,2.81066 c 2.31466,-0.13866 4.57466,-0.50533 6.892,-0.70266 0.224,1.49733 0.364,3.056 0.772,4.512 -4.30134,-0.0373 -8.488,0.0533 -12.78934,0.0453 0.188,-5.736 0.37334,-11.44267 0.11334,-17.18667 4.22666,0.0453 8.44933,-0.092 12.676,0.18134 l 0.24933,3.016"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path18-3"
d="m 255.2914,223.13663 c -3.60267,0.27333 -7.12,0.51333 -10.72533,0.72667 -0.16534,-5.8 -0.176,-11.42934 -0.43067,-17.23334 2.02133,-0.0293 3.952,-0.12 5.96267,-0.272 -0.67334,4.11734 -0.94934,8.28 -1.04267,12.448 1.988,0.0693 3.91467,0.0227 5.896,0.0907 l 0.34,4.24"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path19-2"
d="m 267.6034,212.16196 c 0.43467,-3.396 -3.62133,-2.18133 -5.71333,-1.99466 0.15866,1.61466 0.0347,3.18666 0.15866,4.80666 2.01334,-0.16533 6.04534,0.324 5.55467,-2.812 z m 6.34933,0.15867 c -0.22,1.68933 0.152,4.84933 -2.02933,5.29467 -2.748,0.508 -6.264,0.856 -9.01333,1.05466 l -0.18134,4.716 c -1.53333,0.076 -3.04933,-0.124 -4.58,0.068 0.0733,-5.38266 -0.18933,-10.71733 -0.31733,-16.09866 5.01867,-0.17334 10.80933,-1.73867 15.48667,0.68 0.612,1.104 0.556,3.05733 0.63466,4.28533"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path20-2"
d="m 380.37271,247.56596 c 33.6987,14.96934 12.4787,-20.15733 12.4787,-20.15733 l -18.8787,13.45067 -37.86531,-8.90534 -4.84,-17.66533 c 0,0 -35.65467,19.06933 -1.69067,20.764 l -69.532,62.24 -6.96133,-28.51067 -22.224,-5.76133 c 0,0 -0.0213,1.28533 0.0533,3.16933 l -16.92266,-2.63466 c 0,0 -14.72667,49.53466 21.68933,29.18533 l -3.032,10.7 c 24.12,0.332 52.49067,4.23066 79.64133,9.28533 -2.036,9.51067 -3.89866,25.78 5.452,31.156 13.52667,7.764 29.90267,-0.40667 29.58133,-14.94133 l 58.90668,5.89066 c 0,0 -5.6173,-55.35599 -25.856,-87.26533"
style="fill:#7a86cc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path21-1"
d="m 346.55006,328.10596 0.0187,0.852 c 0.12,5.30933 -2.09333,10.00133 -6.13067,13.19467 -4.188,-7.56534 -0.192,-19.43734 -0.14933,-19.56134 l -1.42533,-0.49333 c -0.184,0.532 -4.308,12.772 0.32933,20.91867 -0.236,0.156 -0.45067,0.33066 -0.69733,0.47733 -1.88134,1.116 -3.93334,1.87467 -6.06534,2.27733 -9.40266,-7.80533 -0.92,-28.10933 -0.832,-28.31466 l -1.388,-0.59067 c -0.364,0.85333 -8.43599,20.17867 0.28,29.16133 -2.75466,0.24 -5.58933,-0.0907 -8.32399,-0.99466 -9.34934,-10.83334 -1.96267,-27.392 -1.888,-27.55867 l -1.37067,-0.62667 c -0.076,0.16534 -6.98667,15.61334 0.53067,27.07734 -0.444,-0.21734 -0.88267,-0.448 -1.31734,-0.69734 -11.74266,-6.74266 -4.82,-32.20266 -3.48133,-36.71733 l 70.12928,18.80933 v -17.852 h -1.508 v 15.88534 l -69.62528,-18.67334 -0.22133,0.68134 c -0.412,1.26933 -9.93734,31.19733 3.956,39.17466 3.57866,2.056 7.51999,3.08534 11.38666,3.08534 3.69867,0 7.328,-0.94134 10.508,-2.83067 5.504,-3.268 8.68133,-8.708 8.81333,-15.016 l 58.07062,5.80667 0.1507,-1.5 -59.74932,-5.97467"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path22-6"
d="m 253.70873,268.1593 -23.58933,-6.10934 -0.0147,0.95867 c -0.005,0.428 -0.08,7.81067 1.316,13.344 l -18.004,-5.22 -0.42,1.44667 18.92534,5.48933 c 0.48,1.39067 1.07866,2.576 1.84266,3.35067 0.50267,0.51066 1.06267,0.83466 1.66267,0.988 l -0.19733,4.06533 -23.36534,-1.29467 -0.084,1.50533 23.376,1.29467 -0.23066,4.72667 1.50666,0.0733 0.50267,-10.332 c 0.932,-0.15066 1.74133,-0.58533 2.40133,-1.32666 2.78534,-3.128 1.948,-10.63867 1.91067,-10.95734 l -1.49733,0.17467 c 0.228,1.98133 0.39866,7.604 -1.54134,9.78 -0.53466,0.6 -1.18666,0.892 -1.99466,0.892 -0.496,0 -0.94534,-0.212 -1.376,-0.648 -2.59867,-2.636 -3.196,-11.94133 -3.228,-16.36667 l 20.84666,5.4 6.85467,28.07733 1.46533,-0.35733 -7.068,-28.95466"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path23-8"
d="m 357.7274,240.98196 c -3.11734,0 -5.92267,1.14 -7.91467,2.95867 -1.62667,-2.716 -5.07733,-4.59467 -9.08533,-4.59467 -5.56934,0 -10.08534,3.62134 -10.08534,8.088 0,4.46667 4.516,8.08934 10.08534,8.08934 2.54666,0 4.86666,-0.764 6.64133,-2.012 1.456,3.76133 5.54,6.47466 10.35867,6.47466 6.04001,0 10.93601,-4.25333 10.93601,-9.50133 0,-5.248 -4.896,-9.50267 -10.93601,-9.50267"
style="fill:#afe2f8;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path24-5"
d="m 343.02873,242.34863 c -3.59867,0 -6.44133,-2.41867 -6.57867,-2.53867 l 2.652,-3.08 c 0.024,0.02 2.452,2.04667 4.82667,1.444 2.16,-0.54666 3.59067,-3.16533 4.41067,-5.26666 l 3.78666,1.47866 c -1.70666,4.36934 -4.14266,6.972 -7.24,7.73867 -0.63333,0.156 -1.256,0.224 -1.85733,0.224"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path25-7"
d="m 364.72741,244.65396 c -2.488,0 -4.5067,-0.70266 -6.03068,-2.10133 -3.568,-3.27867 -2.66933,-8.996 -2.628,-9.23733 l 4.00938,0.664 -2.00538,-0.332 2.00668,0.324 c -0.1667,1.056 -0.2427,4.12 1.376,5.596 1.588,1.44533 4.524,1.088 6.7067,0.532 l 1.0013,3.94 c -1.612,0.40933 -3.092,0.61466 -4.436,0.61466"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path26-6"
d="m 321.2994,273.53263 c -1.28533,0 -2.296,-0.35867 -3.02267,-1.076 -2.35733,-2.328 -0.53866,-7.43067 -0.46,-7.64667 l 1.41734,0.51467 c -0.44267,1.22133 -1.28534,4.692 0.104,6.06 0.94533,0.93333 2.964,0.85333 5.83199,-0.23467 l 0.53334,1.412 c -1.71067,0.64667 -3.18133,0.97067 -4.404,0.97067"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path27-1"
d="m 369.88341,279.2193 c -2.8493,0 -5.532,-2.81867 -5.6587,-2.95334 l 1.1,-1.03333 c 0.7987,0.84933 3.2974,2.92533 5.236,2.38933 1.736,-0.48933 2.6054,-2.98533 3.028,-4.99333 l 1.476,0.31067 c -0.7413,3.516 -2.1213,5.58 -4.1,6.13466 -0.36,0.1 -0.7226,0.14534 -1.0813,0.14534"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path28-8"
d="m 347.5874,246.46596 c 0,0.98934 -0.80134,1.792 -1.79067,1.792 -0.98933,0 -1.79067,-0.80266 -1.79067,-1.792 0,-0.988 0.80134,-1.79066 1.79067,-1.79066 0.98933,0 1.79067,0.80266 1.79067,1.79066"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path29-9"
d="m 353.99806,247.87396 c 0,0.98934 -0.80266,1.792 -1.79066,1.792 -0.98934,0 -1.792,-0.80266 -1.792,-1.792 0,-0.988 0.80266,-1.79066 1.792,-1.79066 0.988,0 1.79066,0.80266 1.79066,1.79066"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path30-2"
d="m 362.56071,265.65396 c -3.26665,-0.52533 -5.36665,1.312 -7.59065,3.25467 -2.436,2.12933 -4.95466,4.33067 -9.488,3.95333 -0.21866,-0.0187 -0.39733,-0.064 -0.60533,-0.0907 l 2.16933,-13.232 c 4.16267,0.67333 6.48,-6.09734 6.48,-6.09734 l -10.68133,-2.35733 c 0,0 -0.5,5.96 2.75867,7.92667 l -2.21067,13.488 c -3.54533,-0.91334 -5.13333,-3.22 -6.65467,-5.47734 -1.26266,-1.872 -2.456,-3.64133 -4.636,-4.424 -5.37733,-1.92666 -10.09866,4.644 -10.29733,4.924 l 1.232,0.87067 c 0.0413,-0.06 4.24533,-5.924 8.55467,-4.37467 1.716,0.616 2.72533,2.11334 3.896,3.84667 1.80666,2.68 4.05733,6.016 9.87066,6.5 5.16,0.43867 8.056,-2.092 10.604,-4.32 2.12,-1.852 3.79335,-3.312 6.36005,-2.90267 5.6226,0.9 6.3253,7.16 6.3533,7.42667 l 1.5,-0.14933 c -0.01,-0.0773 -0.836,-7.68 -7.6147,-8.76534"
style="fill:#131c2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path31-7"
d="m 332.60073,233.6473 -2.54533,-13.47867 c 0,0 -19.41733,12.06533 2.54533,13.47867"
style="fill:#4a5ab2;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
<path
id="path32-9"
d="m 378.41011,242.78996 13.668,-6.78666 c 0,0 2.9226,21.96266 -13.668,6.78666"
style="fill:#4a5ab2;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.133333" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,230 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg1"
width="1513.9161"
height="2256.8333"
viewBox="0 0 1513.9162 2256.8333"
sodipodi:docname="74223809_DJV MAR 981-17.svg"
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.5553221"
inkscape:cx="757.21819"
inkscape:cy="636.56749"
inkscape:window-width="2560"
inkscape:window-height="1346"
inkscape:window-x="-11"
inkscape:window-y="-11"
inkscape:window-maximized="1"
inkscape:current-layer="g1">
<inkscape:page
x="0"
y="0"
inkscape:label="1"
id="page1"
width="1513.9163"
height="2256.8333"
margin="0"
bleed="0" />
</sodipodi:namedview>
<g
id="g1"
inkscape:groupmode="layer"
inkscape:label="1"
transform="translate(-657.25468,-285.79342)">
<g
id="group-R5">
<path
id="path2"
d="M 11113.1,11545.2 9207.74,11851 c -1.89,0.3 -3.72,-0.2 -5.15,-1.5 -1.4,-1.2 -2.15,-3.1 -2.07,-4.9 l 11.71,-246.1 -2186.47,472.4 c -3.31,0 -6.03,-2.6 -6.23,-5.9 l -26.82,-495.1 -536.61,-3874.1 -649.47,-912.7 c -0.6,-0.9 -1,-1.9 -1.09,-3 L 5381.9,2990.9 c -0.36,-3.3 1.88,-6.2 5.12,-6.8 l 424.02,-73.9 c 0.37,-0.1 0.74,-0.1 1.07,-0.1 2.9,0 5.48,2 6.09,4.9 l 975.05,4576.6 1022.45,3441.8 1222.18,162.9 -350.42,-2923.9 c -0.39,-3.1 1.63,-6.1 4.68,-6.8 3.26,-1.1 6.21,0.9 7.33,3.8 l 1099.23,2855.3 1239.2,66.8 c 2.9,0.1 5.4,2.3 5.8,5.2 l 74.6,441.4 c 0.3,1.6 -0.1,3.3 -1.1,4.6 -1,1.3 -2.4,2.3 -4.1,2.5"
style="fill:#37225a;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path3"
d="m 9772.23,2870 c -3.17,0.3 -6.36,-2.1 -6.78,-5.5 l -5.51,-47 -283.77,22.4 c -3.68,-0.3 -6.42,-2.3 -6.7,-5.7 -8.59,-100.4 -17.9,-200.7 -17.9,-200.7 -0.14,-1.7 0.36,-3.3 1.44,-4.6 1.07,-1.3 2.62,-2.1 4.28,-2.2 l 294.72,-23.3 h 0.48 c 1.48,0 2.92,0.6 4.07,1.5 1.24,1.1 2.02,2.6 2.14,4.3 l 19.15,254.1 c 0.27,3.4 -2.24,6.4 -5.62,6.7"
style="fill:#b5bce5;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path4"
d="m 8430.61,2801.5 h -0.1 c -3.34,0 -6.12,-2.6 -6.24,-6 l -1.75,-47 h -284.68 c -3.44,0 -6.21,-2.8 -6.23,-6.2 -0.66,-100.8 -2.05,-201.5 -2.05,-201.5 -0.02,-1.6 0.64,-3.2 1.8,-4.5 1.17,-1.1 2.75,-1.8 4.44,-1.8 h 295.63 c 1.66,0 3.25,0.7 4.41,1.8 1.17,1.2 1.83,2.8 1.83,4.4 l -0.92,254.6 c -0.03,3.4 -2.74,6.2 -6.14,6.2"
style="fill:#b5bce5;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path5"
d="m 11023.9,15917.8 -545.5,356.1 c -1.7,1.1 -3.9,1.1 -5.8,0.3 -1.9,-0.7 -3.3,-2.6 -3.8,-4.6 l -385.8,-1770.7 -916.91,1268.7 20.69,143.4 c 0.38,2.6 -0.91,5.1 -3.23,6.4 l -491.92,264.8 -34.47,18.4 -166.92,146.4 138.9,440.9 c 0.57,1.8 0.29,3.8 -0.75,5.3 -1.03,1.6 -2.74,2.6 -4.61,2.7 l -324.75,31 c -1.66,0.5 -3.36,-0.4 -4.65,-1.5 -1.29,-1.1 -2.07,-2.7 -2.16,-4.4 l -16.71,-296 c -0.08,-1.6 0.46,-3.2 1.5,-4.4 l 369.75,-429.7 c -136.84,84.5 -286.24,176.7 -428.63,264.2 l 50.99,375.1 c 0.36,2.7 -1.17,5.4 -3.75,6.5 l -617.29,261.4 c -1.63,0.7 -3.48,0.7 -5.02,-0.1 -1.6,-0.7 -2.81,-2.1 -3.36,-3.7 l -92.33,-284.4 c -0.39,-1.3 -0.39,-2.7 0.02,-3.9 224.95,-685.4 1225.31,-3817.8 1256.58,-3915.9 l -372.05,-948.2 -378.17,56.6 c -3.24,0.6 -6.06,-1.5 -6.94,-4.5 -0.83,-3 0.7,-6.2 3.6,-7.4 l 246.82,-103.1 920.31,-198.9 c 0.44,-0.1 0.88,-0.2 1.32,-0.2 1.45,0 2.89,0.6 4.01,1.5 1.5,1.3 2.32,3.1 2.22,5.1 l -85.63,1796.9 13.19,1191.1 785.15,-767.8 496,-504.7 c 1.6,-1.6 3.9,-2.2 6.2,-1.6 2.2,0.6 3.8,2.4 4.3,4.6 l 582.2,2601.8 c 0.6,2.5 -0.5,5.1 -2.6,6.5"
style="fill:#b5bce5;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path6"
d="m 7304.18,15548.9 -162.33,-142.8 c -1.93,-1.7 -2.61,-4.4 -1.75,-6.8 l 3.72,-10.4 -186.44,-145.2 c -1.75,-1.4 -2.62,-3.5 -2.35,-5.8 l 18.88,-146.7 c 0.31,-2.5 2.16,-4.6 4.65,-5.3 0.51,-0.1 1.02,-0.1 1.53,-0.1 1.98,0 3.86,0.9 5.05,2.5 l 328.2,452.3 c 1.87,2.6 1.51,6.1 -0.85,8.3 -2.31,2.1 -5.95,2.1 -8.31,0"
style="fill:#5b42a3;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path7"
d="m 7187.71,3247.4 c -3.29,1.1 -6.63,-1 -7.62,-4.3 l -13.52,-45.5 -275.73,70.8 c -3.44,0.9 -6.7,-1.2 -7.61,-4.5 -25.67,-97.5 -52.08,-194.7 -52.08,-194.7 -0.44,-1.6 -0.02,-3.3 0.81,-4.8 0.82,-1.4 2.36,-2.5 3.97,-3 0.39,-0.1 0.78,-0.1 1.17,-0.1 l 284.87,-73.2 c 0.51,-0.2 1.05,-0.2 1.56,-0.2 1.12,0 2.21,0.3 3.19,0.9 1.41,0.8 2.43,2.2 2.85,3.8 l 62.53,247.3 c 0.83,3.2 -1.11,6.6 -4.39,7.5"
style="fill:#a494d1;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path8"
d="m 11304.2,12274.2 -2121.04,76.5 c -1.53,-0.7 -3.42,-0.6 -4.64,-1.8 -1.23,-1.2 -1.88,-3 -1.81,-4.7 l 23.81,-499.6 c 0.13,-3 2.33,-5.5 5.23,-5.9 l 2175.75,-349.1 c 0.3,-0.1 0.7,-0.1 1,-0.1 1.6,0 3.1,0.6 4.3,1.7 1.4,1.3 2.1,3.2 1.9,5.2 l -78.6,772.2 c -0.3,3.1 -2.8,5.5 -5.9,5.6"
style="fill:#a494d1;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path9"
d="m 5824.44,2974.8 c -3.29,0.3 -6.58,-1.5 -7.26,-4.8 l -9.91,-46.5 -280.44,48.8 c -3.68,0.4 -6.62,-1.6 -7.21,-5 -17.96,-99.2 -36.61,-198.1 -36.64,-198.1 -0.31,-1.7 0.29,-3.5 1.29,-4.9 1.02,-1.4 2.75,-2.3 4.46,-2.5 0.18,0 0.34,-0.1 0.51,-0.1 l 290.06,-50.6 c 0.37,-0.1 0.71,-0.1 1.07,-0.1 1.27,0 2.53,0.4 3.6,1.1 1.35,1 2.27,2.5 2.54,4.1 l 42.9,251.4 c 0.58,3.4 -1.63,6.6 -4.97,7.2"
style="fill:#a494d1;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path10"
d="m 8169.69,13185.6 273.53,19.5 -57.13,-67.3 -224.09,-17.7 -322.55,-30 331.18,-17 252.71,-166.4 -80.87,-9.1 -71.43,27.8 -134.29,65.8 -293.99,-12.5 269.62,-25.8 199.81,-137.5 -81.73,-1 -160.59,70.6 -244.27,-11.4 167.01,-130.4 -4.09,-127.4 -51.89,19.3 9.38,78.2 -166,87.8 -401.02,-75.5 -100.11,37.8 -203.62,88.9 82.6,181.4 37.94,-27.3 615.37,164.9 z m -2490.56,1054.2 1282.09,999 190,148 -5.25,14.6 162.33,142.9 -328.21,-452.3 244.68,-1882.3 -80.39,-155.9 z m 1813.72,2259.4 -669.34,-201.7 0.02,-0.1 -1676.51,-2089.8 -217.61,-235.1 1998.68,-1338.1 -254.55,-493.5 352.22,-76.2 h 0.11 l 1272.67,-275.1 -245.72,102.8 383.1,-57.4 374.68,954.9 c 0,0 -1027.28,3217.2 -1257.28,3918 l -60.47,-208.7"
style="fill:#a494d1;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path11"
d="m 8911.7,17906.6 248.23,-429.1 -37.71,-128.4 c -0.69,-2.4 0.07,-5 1.97,-6.6 l 335.19,-282.4 c 1.18,-1 2.59,-1.5 4.03,-1.5 1.27,0 2.56,0.4 3.65,1.2 l 9.36,6.8 c 39.1,28.2 196.83,142.4 391.2,283.6 h 0.03 c 78.82,57.3 163.65,119.1 249.05,181.4 112.2,81.8 225.3,164.5 327,239.2 1.9,1.4 2.8,3.7 2.5,6.1 l -97.9,585.4 c -0.3,1.7 -1.2,3.2 -2.7,4.1 l -392.77,263.1 -80.73,35.3 c -1.19,0.6 -2.53,0.8 -3.77,0.4 l -983.96,-202.7 c -3.03,-0.6 -5.15,-3.4 -4.98,-6.5 l 31.48,-546.6 c 0.08,-1 0.34,-2 0.83,-2.8"
style="fill:#f25d23;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path12"
d="m 7946.07,2363.2 c -0.6,0.8 -1.38,1.3 -2.28,1.7 l -437.49,199.6 -378.07,433.7 c -0.83,1 -1.92,1.7 -3.14,2 l -285.95,73.4 c -2.99,0.9 -5.82,-0.6 -7.13,-3.2 l -0.05,-0.1 c -0.06,-0.1 -0.1,-0.2 -0.14,-0.4 l -139.59,-326.3 c -1.32,-3 0,-6.5 2.99,-8 l 364.95,-176.9 401.19,-277.7 c 0.9,-0.6 1.97,-1 3.06,-1.1 l 570.65,-45.1 h 0.49 c 2.28,0 4.4,1.3 5.49,3.4 1.18,2.2 0.93,4.8 -0.63,6.8 l -94.35,118.2"
style="fill:#f25d23;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path13"
d="m 11122.2,16276 -9.4,51.8 c -0.4,2.7 -2.6,4.8 -5.3,5.1 l -227.9,29.8 c -1,0.2 -2,0 -2.9,-0.3 l -85.3,-29.1 -113.2,26.7 c -1.7,0.3 -3.5,0 -5,-0.9 -1.5,-1.1 -2.4,-2.7 -2.6,-4.4 l -23.7,-201.6 c -0.3,-2.3 0.8,-4.7 2.8,-5.9 l 363.7,-237.4 -578.7,-2586.4 -488.08,496.7 -795.68,778 c -1.8,1.8 -4.46,2.4 -6.75,1.3 -2.31,-0.9 -3.82,-3.1 -3.85,-5.7 l -13.35,-1206 49.72,-1043.5 c 0.17,-3.3 2.78,-5.9 6.01,-6 l 2664.48,-96.1 h 0.2 c 1.8,0 3.6,0.8 4.8,2.1 1.2,1.5 1.7,3.4 1.4,5.2 l -358.7,1974.8 1099.1,-616.1 c 2,-1.2 4.6,-1 6.6,0.3 l 118.4,82.5 c 2.8,1.9 3.5,5.6 1.8,8.4 L 11122.2,16276"
style="fill:#f25d23;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path14"
d="m 9153.4,15766.9 c -0.25,-1.6 0.16,-3.3 1.12,-4.6 l 926.48,-1282 c 1.2,-1.7 3.1,-2.6 5.1,-2.6 0.4,0 0.8,0.1 1.2,0.2 2.5,0.5 4.4,2.3 4.9,4.7 l 388.9,1784.7 23.2,137.9 c 0.3,1.6 -0.1,3.3 -1.1,4.7 -1,1.3 -2.5,2.2 -4.2,2.5 l -1029.06,134.4 c -1.12,0.1 -2.34,-0.1 -3.42,-0.5 l -780.89,-358.8 c -2.39,-1.1 -3.82,-3.6 -3.6,-6.2 l 0.44,-5.1 c 0.17,-2.1 1.41,-4 3.26,-5 l 488.08,-262.8 -20.41,-141.5"
style="fill:#f25d23;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path15"
d="m 6545.36,2271.1 c -0.68,0.8 -1.51,1.3 -2.41,1.7 l -451.93,164.5 -306.41,284.5 c -0.88,0.8 -1.97,1.4 -3.16,1.6 l -290.84,50.7 c -2.64,0.3 -5.43,-0.8 -6.65,-3.3 l -0.02,-0.1 c -0.13,-0.2 -0.25,-0.5 -0.36,-0.8 l -113.51,-336.2 c -1.05,-3.2 0.54,-6.6 3.66,-7.8 l 378.56,-148.1 317.14,-127.1 c 0.73,-0.3 1.52,-0.4 2.32,-0.4 h 572.39 c 2.48,0 4.73,1.4 5.72,3.7 0.98,2.3 0.54,4.9 -1.17,6.7 l -103.33,110.4"
style="fill:#f25d23;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path16"
d="m 10475.3,18459.7 -67.4,-28.7 -455.09,198.5 c -2.93,1.4 -6.4,0.1 -7.97,-2.7 -1.54,-2.9 -0.68,-6.4 2.03,-8.2 l 389.63,-261 97.4,-582.8 c 0.3,-2.1 1.8,-3.9 3.8,-4.7 0.7,-0.3 1.5,-0.5 2.3,-0.5 1.3,0 2.6,0.4 3.7,1.2 151,110.9 257.8,190.2 317.4,235.8 58.5,44.8 58.9,47.3 59.4,50.6 3.2,19.1 -210.5,259.4 -338.1,400.9 -1.8,2 -4.7,2.7 -7.1,1.6"
style="fill:#f5761d;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path17"
d="m 10687.9,2311.4 c -0.8,0.9 -1.8,1.6 -2.9,2 l -932.02,302.5 -294.71,23.2 c -3.63,0.3 -6.45,-2.3 -6.7,-5.7 l -28.03,-355.1 -36.14,38.6 c -0.82,1 -1.92,1.6 -3.09,1.8 L 8431.43,2547 H 8135.8 c -3.44,0 -6.24,-2.8 -6.24,-6.3 v -369 c 0,-3.5 2.8,-6.3 6.24,-6.3 h 1379.08 c 2.48,0 4.73,1.5 5.73,3.8 0.97,2.3 0.53,4.9 -1.18,6.7 l -76.43,81.7 1360.1,-107.3 h 0.5 c 2.3,0 4.4,1.2 5.5,3.3 1.1,2.2 0.9,4.9 -0.7,6.8 l -120.5,151"
style="fill:#f5761d;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path18"
d="m 16278.3,16461.8 -161.8,20.1 c -2.5,0.6 -4.4,-0.6 -5.7,-2.3 l -435.5,-569.8 c -1.4,-1.8 -1.7,-4.2 -0.7,-6.3 0.9,-2.1 2.9,-3.5 5.1,-3.7 l 36.9,-3 0.1,-0.2 c 0.4,-2.7 2.7,-4.8 5.4,-5.1 l 90,-9.8 c 0.3,0 0.5,0 0.7,0 1.9,0 3.6,0.8 4.8,2.3 l 464.8,567.7 c 1.4,1.7 1.8,4.1 0.9,6.3 -0.8,2.1 -2.7,3.5 -5,3.8"
style="fill:#f5761d;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path19"
d="m 16116.5,16481.9 -111.3,13.8 c -2.3,0.5 -4.3,-0.6 -5.6,-2.3 l -462.6,-572.1 c -1.4,-1.8 -1.8,-4.3 -0.8,-6.4 0.9,-2.1 2.9,-3.6 5.2,-3.8 l 138.3,-11.3 h 0.6 c 1.9,0 3.7,0.9 4.9,2.4 l 435.5,569.7 c 1.3,1.9 1.7,4.2 0.8,6.3 -0.9,2 -2.8,3.4 -5,3.7"
style="fill:#37225a;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path20"
d="m 16010.2,16491.9 c -0.8,2.1 -2.8,3.5 -5,3.8 l -161.9,20.1 c -2.2,0.5 -4.3,-0.5 -5.6,-2.2 l -440.3,-540.7 c -1.9,-2.3 -1.9,-5.6 0,-7.9 1.9,-2.4 5.2,-2.9 7.8,-1.6 l 184,97.4 13.1,-56.1 -65.3,-83.5 c -2.1,-2.7 -1.7,-6.6 1,-8.7 1.2,-0.9 2.5,-1.4 3.9,-1.4 1.8,0 3.6,0.8 4.8,2.4 l 462.6,572.1 c 1.4,1.8 1.7,4.2 0.9,6.3"
style="fill:#f5761d;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path21"
d="m 14962.2,15612.8 -2379,-320.5 -1470.7,1027.5 c -2.1,1.5 -4.9,1.5 -7,0.2 -2.1,-1.4 -3.2,-3.9 -2.8,-6.4 l 7.5,-41 c 0.1,-0.7 0.4,-1.5 0.8,-2.1 l 1599.2,-2577.7 c 0.9,-1.5 2.4,-2.5 4,-2.9 0.5,0 0.9,-0.1 1.3,-0.1 1.2,0 2.5,0.4 3.6,1.1 l 2183.8,1524 c 1.4,1 2.4,2.4 2.7,4.1 l 63.4,386 v 0.1 c 0.6,2 0,4.2 -1.4,5.7 -1.3,1.6 -3.4,2.4 -5.4,2"
style="fill:#f5761d;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path22"
d="m 11105.6,16411.9 -47.8,11.2 c -1.1,0.3 -2.3,0.3 -3.4,-0.2 l -177.7,-60.5 c -2.7,-1 -4.4,-3.7 -4.1,-6.5 0.3,-2.9 2.5,-5.2 5.4,-5.6 l 226.3,-29.6 c 0.3,0 0.6,0 0.8,0 1.5,0 3,0.5 4.2,1.5 1.3,1.2 2.1,3 2.1,4.8 l -1,78.9 c -0.1,2.9 -2.1,5.3 -4.8,6"
style="fill:#ef3651;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path23"
d="m 10834.2,16819.6 -33.1,24.3 c -1.8,1.4 -4.3,1.6 -6.3,0.7 -2.1,-1 -3.5,-3 -3.6,-5.2 -2.9,-41.5 -9.4,-53.9 -13.1,-57.7 -6.1,-6.3 -22.6,-14.8 -66.3,-19.5 -2,-0.2 -3.9,-1.5 -4.8,-3.3 -1.3,1.6 -3.4,2.5 -5.4,2.4 -30.2,-2.8 -69,-3.4 -115.3,-2 h -0.1 c -1.9,0 -3.7,-0.9 -4.9,-2.3 -1.2,-1.6 -1.7,-3.5 -1.2,-5.4 l 29.1,-126.1 c 0.5,-2.1 2.1,-3.8 4.2,-4.5 0.6,-0.2 1.3,-0.3 1.9,-0.3 1.5,0 3,0.5 4.1,1.6 l 215.2,187.6 c 1.4,1.2 2.2,3 2.1,4.9 -0.1,1.9 -1,3.6 -2.5,4.8"
style="fill:#ef3651;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path24"
d="m 10183.6,16794.7 c -98.5,12.2 -176.5,23.8 -197.84,27.1 l 28.44,315.8 c 0.2,1.8 -0.4,3.6 -1.7,4.8 l -8.3,8.9 c -1.6,1.6 -3.8,2.1 -5.96,1.8 -2.17,-0.5 -3.89,-2.1 -4.56,-4.3 -22.26,-71.2 -27.94,-151 -31.71,-217.6 l -99.15,99.8 c -1.97,1.9 -4.92,2.4 -7.38,1.1 -2.43,-1.3 -3.73,-4.1 -3.16,-6.8 12.56,-60 22.77,-121.1 30.32,-181.9 l -174.61,123.7 c -2.12,1.6 -4.97,1.6 -7.14,0.1 -2.15,-1.5 -3.16,-4.1 -2.51,-6.7 l 31.65,-124.8 -116.6,120.2 c -1.41,1.4 -3.46,2.2 -5.33,1.8 -1.97,-0.3 -3.68,-1.4 -4.63,-3.2 l -74.9,-137.9 c -0.33,-0.6 -0.54,-1.3 -0.66,-1.9 l -50.02,-274.9 c -0.29,-1.7 0.1,-3.4 1.1,-4.8 1.02,-1.4 2.53,-2.3 4.24,-2.6 l 698.01,-91.1 c 0.2,0 0.5,0 0.8,0 1.5,0 2.9,0.5 4.1,1.5 1.3,1.2 2.1,2.9 2.1,4.7 l 0.9,341 c 0,3.2 -2.3,5.8 -5.5,6.2"
style="fill:#ef3651;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path25"
d="m 8288.1,16526.8 35.01,619.8 c 0.09,1.8 -0.59,3.5 -1.86,4.8 -1.29,1.3 -2.75,2 -4.84,1.8 -61.73,-4.5 -107.48,-7.1 -122.32,-7.9 l -70.5,290.1 c -0.73,3.1 -3.27,5.2 -6.74,4.8 l -362.86,-39.7 -1.9,1.3 c -1.7,1.1 -3.77,1.3 -5.72,0.7 -1.9,-0.8 -3.31,-2.4 -3.82,-4.3 l -102.95,-404.3 c -0.75,-3 0.78,-6.1 3.6,-7.3 l 612.91,-259.5 -50.89,-374.4 c -0.32,-2.4 0.83,-4.8 2.92,-6.1 155.42,-95.5 319.37,-196.7 466.36,-287.5 1.02,-0.7 2.17,-0.9 3.28,-0.9 1.79,0 3.52,0.7 4.76,2.2 2,2.3 1.97,5.8 -0.02,8 l -394.42,458.4"
style="fill:#ef3651;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path26"
d="m 8442.78,13211.4 -273.35,-19.5 -359.87,-14.5 -612.51,-164.3 -35.53,25.7 c -1.5,1.1 -3.4,1.5 -5.25,1 -1.81,-0.5 -3.29,-1.8 -4.07,-3.5 l -82.61,-181.4 c -0.67,-1.5 -0.75,-3.3 -0.15,-4.8 0.59,-1.6 1.81,-2.9 3.34,-3.5 l 203.9,-89.1 100.13,-37.8 c 1.09,-0.4 2.28,-0.5 3.33,-0.3 l 398.87,75.2 160.3,-84.8 -8.87,-74 c -0.34,-2.8 1.31,-5.5 4.01,-6.5 l 51.9,-19.3 c 0.7,-0.3 1.43,-0.4 2.17,-0.4 1.21,0 2.43,0.3 3.47,1 1.66,1.2 2.69,3 2.76,5 l 4.09,127.4 c 0.05,2 -0.83,3.9 -2.38,5.1 l -153.76,120.1 226.01,10.6 159.24,-70.1 c 0.8,-0.3 1.58,-0.4 2.59,-0.5 l 81.73,0.9 c 2.72,0.1 5.08,1.8 5.88,4.5 0.79,2.5 -0.19,5.4 -2.43,6.9 l -199.8,137.6 c -0.88,0.6 -1.88,0.9 -2.95,1 l -179.46,17.2 201.92,8.6 133.35,-65.3 71.43,-27.8 c 0.94,-0.4 1.94,-0.6 2.96,-0.4 l 80.88,9.1 c 2.61,0.3 4.75,2.2 5.36,4.8 0.61,2.5 -0.44,5.2 -2.63,6.6 l -252.72,166.4 c -0.92,0.6 -1.99,0.9 -3.11,1 l -244.88,12.6 236.42,21.9 224.09,17.8 c 1.66,0.2 3.2,0.9 4.27,2.2 l 57.13,67.3 c 1.63,1.9 1.94,4.6 0.8,6.9 -1.12,2.2 -3.61,3.5 -6,3.4"
style="fill:#ef3651;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path27"
d="m 15728.1,15903.7 c -0.6,2.8 -2.8,4.9 -5.7,5.1 l -168.5,13.8 60.1,76.8 c 1.1,1.5 1.5,3.4 1.1,5.2 l -15.6,67 c -0.5,1.9 -1.7,3.4 -3.5,4.2 -1.8,0.8 -3.8,0.8 -5.5,-0.1 l -332.3,-176 c -0.7,-0.3 -1.3,-0.8 -1.8,-1.4 l -235.2,-270.7 -59.8,-15 c -2.3,-0.6 -4.2,-2.6 -4.6,-5 l -41.5,-252.5 c -0.3,-2.2 0.5,-4.4 2.2,-5.8 1.1,-0.9 2.5,-1.4 4,-1.4 0.7,0 1.5,0.1 2.2,0.4 l 128.9,49.2 443,81.2 c 2,0.4 3.7,1.7 4.6,3.6 l 84.3,189.2 156.7,143.4 c 1.5,1.4 2.3,3.6 1.9,5.7 l -15,83.1"
style="fill:#f14664;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path28"
d="m 11868.4,16970.1 -363.4,-72.9 -388,-54 -197.4,53.7 c -2,0.7 -4.1,0.1 -5.7,-1.3 l -116.7,-101.8 c 3,11 5.1,25.7 6.4,44.7 8.8,126.2 -20,433.5 -85.6,913.4 -0.4,2.7 -2.3,4.8 -5,5.3 -2.4,0.5 -5.2,-0.6 -6.6,-2.9 -23.1,-38.8 -47,-77.8 -71.1,-116.4 -10.2,45.8 -23.3,91.3 -39.4,135.2 -0.9,2.5 -3.1,4.1 -5.7,4.1 h -0.1 c -2.6,0 -4.9,-1.5 -5.8,-3.9 l -93.4,-231.7 c -27.1,49 -59.7,95.1 -97.2,137 -1.4,1.5 -3.4,2.5 -5.4,2 -2,-0.2 -3.7,-1.4 -4.7,-3.2 -49.1,-89.7 -103,-175.9 -144.4,-241.1 -4.3,-6.6 -8.7,-13 -13.6,-19.2 -9.3,16.7 -47.8,80.9 -108.9,124.6 -2.3,1.6 -5.2,1.6 -7.3,-0.1 -85.4,-62.2 -170.28,-123.9 -249.11,-181.2 -2.19,-1.6 -3.09,-4.5 -2.24,-7 22.18,-66.8 68.3,-137.7 137.05,-210.7 l 6.5,-6.8 -28.8,-318.9 c -0.28,-3.2 2,-6.1 5.27,-6.6 0.88,-0.2 88.33,-13.7 204.03,-28.1 114.3,-14.2 277.1,-32 403.9,-35.5 15.5,-0.5 30.3,-0.7 44.1,-0.7 27.8,0 52.1,0.9 72.5,2.7 3.7,0.3 7.2,0.6 10.6,1 16.6,1.8 30.6,4.2 42,7.3 l -245.5,-214 c -1.2,-1 -2,-2.5 -2.1,-4.2 l -5,-69.4 -33.8,-200.2 c -0.5,-3 1.2,-5.7 4.1,-6.7 l 176.8,-115.4 c 1,-0.7 2.2,-1 3.4,-1 0.9,0 1.8,0.2 2.7,0.5 1.9,1 3.3,2.8 3.5,5 l 22.9,194.6 108.1,-25.6 c 1.1,-0.2 2.3,-0.2 3.4,0.2 l 263,89.7 170.2,-40 c 2.6,-0.5 5.1,0.3 6.6,2.5 l 192.9,276.7 c 1.1,1.5 1.4,3.5 0.9,5.3 -0.5,1.8 -1.8,3.3 -3.5,4 l -158.1,67.1 296.2,76 290.1,75.4 c 2.2,0.6 4,2.3 4.5,4.6 l 19.2,80.3 c 0.5,2.1 -0.1,4.2 -1.6,5.8 -1.5,1.5 -3.8,2.1 -5.7,1.8"
style="fill:#f14664;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path29"
d="m 8511.91,18395 -478.78,8.4 h -0.08 c -1.76,0 -3.42,-0.7 -4.61,-2.1 -1.21,-1.3 -1.78,-3 -1.6,-4.8 l 55.36,-511.7 1.83,-14.6 c -63.21,18.6 -119.83,11 -168.28,-22.6 -44.41,-30.8 -66.55,-75.7 -62.39,-126.5 8.05,-97.5 105.77,-198.9 269.18,-280.3 l -5.69,-0.6 c -1.79,-0.2 -3.39,-1.1 -4.44,-2.6 -1.02,-1.5 -1.37,-3.3 -0.95,-5.1 l 71.71,-295.2 c 0.69,-2.8 3.2,-4.7 6.07,-4.7 h 0.32 c 0.51,0 52.21,2.7 127.76,8.1 284.91,20.8 631.21,59.6 663.17,126.5 25.57,53.6 -125.87,421.3 -463.04,1124.2 -1.02,2.2 -3.16,3.5 -5.54,3.6"
style="fill:#f14664;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path30"
d="m 11037.3,11104 -857.5,-46.2 c -3.1,-0.2 -5.6,-2.7 -5.9,-5.9 L 9961.8,7634.6 9397.78,6660.1 c -0.51,-1 -0.8,-2 -0.83,-3.1 l -66.81,-3812.2 c -0.06,-3.3 2.45,-6.1 5.74,-6.4 l 429.08,-33.8 h 0.49 c 3.14,0 5.83,2.3 6.19,5.5 l 542.06,4647.8 730,3638.6 c 0.4,1.9 -0.1,3.9 -1.4,5.3 -1.3,1.5 -3.3,2.1 -5,2.2"
style="fill:#4d274f;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path31"
d="m 9044.19,11109.8 -851.15,-113.5 c -3.14,-0.5 -5.45,-3.1 -5.4,-6.3 L 8244.9,7566.6 7759.29,6550.7 c -0.47,-1 -0.67,-2 -0.62,-3.1 l 233.21,-3805.7 c 0.21,-3.3 2.94,-5.9 6.24,-5.9 h 430.42 c 3.35,0 6.11,2.7 6.23,6 l 174.83,4676.1 441.61,3684.7 c 0.25,1.9 -0.44,3.8 -1.81,5.2 -1.33,1.4 -3.34,2 -5.21,1.8"
style="fill:#4d274f;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path32"
d="m 8266.3,18386.8 245.42,-4.3 h 0.11 c 3.03,0 5.64,2.2 6.16,5.2 l 111.37,681.6 c 0.35,2.1 -0.41,4.2 -2.03,5.7 -1.58,1.4 -3.86,1.9 -5.84,1.3 l -290.88,-83.1 c -2.51,-0.7 -4.29,-2.9 -4.52,-5.5 -12.24,-144.4 -29.86,-291.4 -46.91,-433.5 -6.41,-53.5 -12.83,-107 -18.97,-160.4 -0.23,-1.8 0.34,-3.5 1.48,-4.8 1.17,-1.3 2.82,-2.1 4.61,-2.2"
style="fill:#4d274f;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path33"
d="m 9969.62,16911.1 c 2.28,0.8 3.87,3 4.01,5.5 3.8,68.5 8.58,153.7 31.97,228.5 0.7,2.2 0.1,4.5 -1.4,6.2 -66.56,70.6 -113,141.9 -134.32,206 -0.62,1.9 -2.12,3.3 -4.01,3.9 -1.81,0.6 -3.93,0.3 -5.56,-0.8 -194.37,-141.3 -352.11,-255.4 -391.2,-283.7 -0.15,-0.1 -0.27,-0.2 -0.4,-0.3 l -2.04,-1.5 c -1.97,-1.4 -2.95,-3.8 -2.51,-6.2 l 1.03,-5.5 c -1.42,-22.4 6.01,-49.8 14.44,-78.2 0.67,-2.4 2.6,-4 4.83,-4.4 2.66,-14.2 5.23,-28.4 7.62,-42.6 9.11,-54.1 13.89,-109.3 14.15,-164.1 0.03,-2.9 1.95,-5.3 4.71,-6 0.51,-0.2 1.01,-0.2 1.53,-0.2 2.24,0 4.36,1.2 5.48,3.2 l 92.4,170.2 126.94,-130.8 c 1.95,-2.1 5.09,-2.5 7.52,-1.2 2.47,1.5 3.71,4.3 3,7.1 l -33.22,131 172.29,-122.2 c 1.98,-1.4 4.64,-1.5 6.8,-0.3 2.12,1.3 3.29,3.7 3,6.1 -6.94,58.9 -16.35,118.2 -27.99,176.6 l 94.28,-94.9 c 1.78,-1.8 4.33,-2.3 6.65,-1.4"
style="fill:#37225a;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path34"
d="m 10219.5,17396.8 c 0.8,-0.3 1.6,-0.5 2.4,-0.5 1.7,0 3.5,0.8 4.7,2.2 8.6,10 16.1,20.2 23.1,31.1 40.3,63.4 92.4,146.8 140.5,234.1 37.3,-42.8 69.6,-89.7 96,-139.7 1.1,-2.2 3.2,-2.9 5.8,-3.3 2.5,0.1 4.6,1.6 5.5,3.9 l 92.2,228.8 c 15,-43.4 27.3,-88.1 36.6,-133.2 0.5,-2.4 2.5,-4.4 5,-4.8 2.6,-0.4 5,0.6 6.4,2.8 26.9,42.9 53.6,86.5 79.5,129.7 32.6,54.7 64.5,109.7 94.6,163.4 3.8,6.8 9,16.1 7.8,26.5 -1.1,9.4 -7.3,17 -11.9,22.5 l -16.9,20.4 c -9.5,11.7 -19.1,23.3 -28.7,34.8 -2.2,2.6 -5.9,2.9 -8.6,1 -59.5,-45.6 -166.2,-124.8 -317.2,-235.6 v 0 c -101.6,-74.8 -214.7,-157.5 -326.9,-239.3 -1.6,-1.2 -2.6,-3.1 -2.6,-5.1 0,-2 1,-3.8 2.6,-5 61.7,-44 100.6,-111.9 106.6,-122.7 l -0.4,-5.9 c -0.1,-2.6 1.5,-5.1 3.9,-6.1"
style="fill:#37225a;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path35"
d="m 8242.57,8123.4 c -3.29,0.8 -6.05,-1.7 -6.73,-4.9 l -47.16,-214.4 -719.22,-858.9 c -0.68,-0.7 -1.14,-1.7 -1.33,-2.8 L 6747.82,3298.2 c -0.63,-3.2 1.36,-6.4 4.55,-7.2 l 416.91,-107 c 0.53,-0.1 1.04,-0.2 1.56,-0.2 2.7,0 5.19,1.8 5.97,4.5 l 663,2232.9 c 0.23,0.7 0.3,1.4 0.28,2.1 l -68.85,1123.5 485.56,1015.8 c 0.41,0.9 0.62,1.8 0.6,2.8 l -9.23,551.9 c -0.05,3.2 -2.46,5.8 -5.6,6.1"
style="fill:#37225a;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
<path
id="path36"
d="m 8272.58,18392.3 c 6.16,53.5 12.57,107 19,160.5 17.05,142.1 34.69,289.2 46.94,433.9 0.17,2 -0.65,4 -2.24,5.3 -1.6,1.3 -3.7,1.7 -5.67,1.2 l -1260.82,-360.3 c -1.62,-0.4 -2.97,-1.6 -3.77,-3 -0.81,-1.5 -0.98,-3.2 -0.49,-4.8 l 153.63,-505.5 153.75,-474.8 c 0.43,-1.3 1.28,-2.4 2.45,-3.2 l 369.72,-250.1 3.8,-2.6 c 1.05,-0.7 2.26,-1.1 3.51,-1.1 0.22,0 0.46,0 0.69,0 l 392.36,43 c 2.78,0.3 5,2.4 5.46,5.1 0.47,2.8 -0.96,5.5 -3.49,6.7 -168.47,79.7 -273.76,184.2 -281.6,279.5 -3.8,46.2 16.45,87.1 57.05,115.3 47.43,32.9 103.46,38.9 166.6,18 2.07,-0.6 4.24,-0.3 5.95,1.1 1.63,1.4 2.48,3.4 2.21,5.6 l -57.61,528.7 226.29,-4 c 3.77,-0.5 5.89,2.4 6.28,5.5"
style="fill:#37225a;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.13333333,0,0,-0.13333333,0,2829.3333)" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,132 @@
<script lang="ts">
import Report from 'carbon-icons-svelte/lib/Report.svelte';
import Help from 'carbon-icons-svelte/lib/Help.svelte';
import Settings from 'carbon-icons-svelte/lib/Settings.svelte';
import Home from 'carbon-icons-svelte/lib/Home.svelte';
import Return from 'carbon-icons-svelte/lib/Return.svelte';
import SearchAdvanced from "carbon-icons-svelte/lib/SearchAdvanced.svelte";
import CarbonForIbmDotcom from "carbon-icons-svelte/lib/CarbonForIbmDotcom.svelte";
import EventSchedule from "carbon-icons-svelte/lib/EventSchedule.svelte";
import { page } from '$app/state';
let isAdminView = $derived(page.url.pathname.startsWith('/admin'));
let isOperatorView = $derived(page.url.pathname.startsWith('/op'));
let isUserView = $derived(!isAdminView && !isOperatorView);
</script>
{#if isUserView}
<!-- 底部 Dock 导航栏 -->
<nav class="bottom-nav">
<a href="/" class="nav-item" class:active={page.url.pathname === '/'}>
<Home />
<span>主页</span>
</a>
<a href="/repair" class="nav-item" class:active={page.url.pathname === '/repair'}>
<Report />
<span>我的报修</span>
</a>
<a href="/help" class="nav-item" class:active={page.url.pathname === '/help'}>
<Help />
<span>网络攻略</span>
</a>
<a href="/me" class="nav-item" class:active={page.url.pathname === '/me'}>
<Settings />
<span></span>
</a>
</nav>
{/if}
{#if isOperatorView}
<!-- 底部 Dock 导航栏 -->
<nav class="bottom-nav">
<a href="/op" class="nav-item" class:active={page.url.pathname === '/op'}>
<CarbonForIbmDotcom />
<span>后台中心</span>
</a>
<a href="/op/ticket_search" class="nav-item" class:active={page.url.pathname === '/op/ticket_search'}>
<SearchAdvanced />
<span>检索工单</span>
</a>
<a href="/op/scheduler" class="nav-item" class:active={page.url.pathname === '/op/scheduler'}>
<EventSchedule />
<span>我的排班</span>
</a>
<a href="/" class="nav-item" class:active={page.url.pathname === '/'}>
<Return />
<span>返回首页</span>
</a>
</nav>
{/if}
{#if isAdminView}
<!-- 底部 Dock 导航栏 -->
<nav class="bottom-nav">
<a href="/admin" class="nav-item" class:active={page.url.pathname === '/admin'}>
<CarbonForIbmDotcom />
<span>管理中心</span>
</a>
<a href="/admin/add_ticket" class="nav-item" class:active={page.url.pathname === '/admin/add_ticket'}>
<SearchAdvanced />
<span>增添工单</span>
</a>
<a href="/admin/scheduler" class="nav-item" class:active={page.url.pathname === '/admin/scheduler'}>
<EventSchedule />
<span>成员排班</span>
</a>
<a href="/" class="nav-item" class:active={page.url.pathname === '/'}>
<Return />
<span>返回首页</span>
</a>
</nav>
{/if}
<style>
.bottom-nav {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
width: 90%;
max-width: 450px;
display: flex;
justify-content: space-around;
align-items: center;
padding: 12px 0;
background-color: #262626; /* Carbon Gray 90 */
border-radius: 30px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
z-index: 100;
}
.nav-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
color: #f4f4f4; /* Carbon Gray 10 */
text-decoration: none;
font-size: 12px;
padding: 4px 12px;
border-radius: 15px;
transition: background-color 0.2s ease-in-out;
}
.nav-item:hover,
.nav-item:focus {
background-color: #393939; /* Carbon Gray 80 */
outline: none;
}
.nav-item.active {
color: #0f62fe; /* IBM Blue 60 */
}
</style>

View File

@@ -0,0 +1,60 @@
<script>
let { children,...rest } = $props();
</script>
<div class="card" {...rest}>
<div>
{@render children?.()}
</div>
</div>
<style>
.card {
position: relative;
padding: 20px;
margin-bottom: 30px; /* Increased margin to accommodate shadow */
margin-left: 15px;
margin-right: 20px; /* Increased to balance the shadow offset */
z-index: 0;
}
.card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #efefef;
border: 2px solid #000;
z-index: -1;
box-sizing: border-box;
}
.card::after {
content: '';
position: absolute;
top: 10px;
left: 10px;
width: 100%;
height: 100%;
background-color: lightblue;
background-image: repeating-linear-gradient(
45deg,
#000000,
#000000 1px,
transparent 1px,
transparent 5px
);
z-index: -2;
}
.card :global(p) {
line-height: 1.6;
color: #4a4949;
}
.card :global(h2) {
font-size: 25px;
}
</style>

View File

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

View File

@@ -0,0 +1,74 @@
<script lang="ts">
import RetroCard from '../RetroCard.svelte';
import type { Ticket } from '$lib/types/apiResponse';
import { FormatDate, FormatTime } from '$lib/types/RFC3339';
import WtsStatus from './WtsStatus.svelte';
import WtsPriority from './WtsPriority.svelte';
import BlockRoom from './BlockRoom.svelte';
import WtsISP from './WtsISP.svelte';
import WtsCategory from './WtsCategory.svelte';
import { TicketModal } from '$lib/states/ticketDetails.svelte';
let { t }: { t: Ticket } = $props();
</script>
<RetroCard style="padding: 10px;">
<div
role="button"
tabindex="0"
onclick={() => TicketModal.open(t, 'operator')}
onkeydown={(e) => e.key === 'Enter' && TicketModal.open(t, 'operator')}
style="cursor: pointer; outline: none;"
>
<div class="flex items-center justify-between">
<p class="font-bold" style="font-size: 19px;">📃No.{t.tid}</p>
<WtsPriority p={t.priority} />
</div>
{#if t.appointed_at}
<p style="color: #0f62fe; font-size: 12.5px;">
<strong>该报修已预约在{FormatDate(t.appointed_at)}</strong>
</p>
{/if}
<div class="flex items-baseline" style="margin-top: 12.5px; font-size: 15.5px;">
<strong style="flex-shrink: 0;width: 7em;">状态</strong>
<div style="font-size: 15px;"><WtsStatus s={t.status} ap={t.appointed_at}/></div>
</div>
<div class="flex items-baseline" style="margin-top: 12.5px; font-size: 15.5px;">
<strong style="flex-shrink: 0;width: 7em;">报修时间</strong>
<p style="font-size: 15px;">{FormatTime(t.submitted_at)}</p>
</div>
<div class="flex items-baseline" style="margin-top: 12.5px; font-size: 15.5px;">
<strong style="flex-shrink: 0;width: 7em;">联系方式</strong>
<p style="font-size: 15px;">{t.issuer.name} {t.issuer.phone}</p>
</div>
<div class="flex items-baseline" style="margin-top: 12.5px; font-size: 15.5px;">
<strong style="flex-shrink: 0;width: 7em;">信息</strong>
<p style="font-size: 15px;">
<strong
><BlockRoom b={t.issuer.block} r={t.issuer.room} /><WtsISP i={t.issuer.isp} /></strong
><br />账号:{t.issuer.account}
</p>
</div>
{#if t.category != 'others'}
<div class="flex items-baseline" style="margin-top: 12.5px; font-size: 15.5px;">
<strong style="flex-shrink: 0;width: 7em;">故障类型</strong>
<p style="font-size: 15px;"><strong><WtsCategory c={t.category} /></strong></p>
</div>
{/if}
<div class="flex items-baseline" style="margin-top: 12.5px; font-size: 15.5px;">
<strong style="flex-shrink: 0;width: 7em;">描述</strong>
<p style="font-size: 15px;">
{t.description}
{#if t.occur_at}
<br />发生时间:{FormatDate(t.occur_at)}
{/if}
</p>
</div>
{#if t.notes}
<div class="flex items-baseline" style="margin-top: 12.5px; font-size: 15.5px;">
<strong style="flex-shrink: 0;width: 7em;">备注</strong>
<p style="font-size: 15px;">{t.notes}</p>
</div>
{/if}
</div>
</RetroCard>

View File

@@ -0,0 +1,61 @@
<script lang="ts">
import RetroCard from '../RetroCard.svelte';
import type { Ticket } from '$lib/types/apiResponse';
import { FormatDate, FormatTime } from '$lib/types/RFC3339';
import WtsStatus from './WtsStatus.svelte';
import WtsPriority from './WtsPriority.svelte';
import BlockRoom from './BlockRoom.svelte';
import WtsISP from './WtsISP.svelte';
import WtsCategory from './WtsCategory.svelte';
import { TicketModal } from '$lib/states/ticketDetails.svelte';
let { t }: { t: Ticket } = $props();
</script>
<RetroCard style="padding: 10px;">
<div
role="button"
tabindex="0"
onclick={() => TicketModal.open(t, 'user')}
onkeydown={(e) => e.key === 'Enter' && TicketModal.open(t, 'user')}
style="cursor: pointer; outline: none;"
>
<div class="flex items-center justify-between">
<p class="font-bold" style="font-size: 19px;">📃No.{t.tid}</p>
</div>
{#if t.appointed_at}
<p style="color: #0f62fe; font-size: 12.5px;">
<strong>该报修已预约在{FormatDate(t.appointed_at)}</strong>
</p>
{/if}
<div class="flex items-baseline" style="margin-top: 12.5px; font-size: 15.5px;">
<strong style="flex-shrink: 0;width: 7em;">状态</strong>
<div style="font-size: 15px;"><WtsStatus s={t.status} ap={t.appointed_at} /></div>
</div>
<div class="flex items-baseline" style="margin-top: 12.5px; font-size: 15.5px;">
<strong style="flex-shrink: 0;width: 7em;">报修时间</strong>
<p style="font-size: 15px;">{FormatTime(t.submitted_at)}</p>
</div>
{#if t.category != 'others'}
<div class="flex items-baseline" style="margin-top: 12.5px; font-size: 15.5px;">
<strong style="flex-shrink: 0;width: 7em;">故障类型</strong>
<p style="font-size: 15px;"><strong><WtsCategory c={t.category} /></strong></p>
</div>
{/if}
<div class="flex items-baseline" style="margin-top: 12.5px; font-size: 15.5px;">
<strong style="flex-shrink: 0;width: 7em;">描述</strong>
<p style="font-size: 15px;">
{t.description}
{#if t.occur_at}
<br />发生时间:{FormatDate(t.occur_at)}
{/if}
</p>
</div>
{#if t.notes}
<div class="flex items-baseline" style="margin-top: 12.5px; font-size: 15.5px;">
<strong style="flex-shrink: 0;width: 7em;">备注</strong>
<p style="font-size: 15px;">{t.notes}</p>
</div>
{/if}
</div>
</RetroCard>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
import { AccessMap } from '$lib/types/enum';
import type { WtsAccess } from '$lib/types/enum';
let { a }: { a: WtsAccess } = $props();
</script>
<span class="inline-block bg-gray-300 px-3 py-1 text-[15px] rounded-[19px]">
{AccessMap[a]}
</span>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
import { CategoryMap } from '$lib/types/enum';
import type { WtsCategory } from '$lib/types/enum';
let { c }: { c: WtsCategory } = $props();
</script>
<span>
{CategoryMap[c]}
</span>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
import { ISPMap } from '$lib/types/enum';
import type { WtsISP } from '$lib/types/enum';
let { i }: { i: WtsISP } = $props();
</script>
<span>
{ISPMap[i]}
</span>

View File

@@ -0,0 +1,17 @@
<script lang="ts">
import { PriorityMap } from '$lib/types/enum';
import type { WtsPriority } from '$lib/types/enum';
let { p }: { p: WtsPriority } = $props();
</script>
{#if p === 'highest' || p==='assigned'}
<span class="text-red-600" style="font-size: 15px;">
<strong>{PriorityMap[p]}</strong>
</span>
{/if}
{#if p!== 'highest' && p!=='assigned'}
<span class="text-gray-600" style="font-size: 15px;">
<strong>{PriorityMap[p]}</strong>
</span>
{/if}

View File

@@ -0,0 +1,29 @@
<script lang="ts">
import { StatusMap } from '$lib/types/enum';
import type { WtsStatus } from '$lib/types/enum';
import { DateRFC3339, type RFC3339 } from '$lib/types/RFC3339';
import { isSameDay} from 'date-fns';
let { s, ap }: { s: WtsStatus; ap: RFC3339 } = $props();
const colorMap: Record<WtsStatus, string> = {
fresh: 'text-red-600',
scheduled: 'text-blue-600',
delay: 'text-blue-500',
escalated: 'text-green-600',
solved: 'text-green-600',
canceled: 'text-gray-500'
};
</script>
{#if s === 'scheduled' && isSameDay(ap, new Date())}
<span>
<strong class="text-blue-600">已预约</strong>
<strong class="text-red-600">(今天)</strong>
</span>
{:else}
<span class={colorMap[s]}>
<strong>{StatusMap[s]}</strong>
</span>
{/if}
<style>
</style>

View File

@@ -0,0 +1,38 @@
<script lang="ts">
import type { Ticket } from '$lib/types/apiResponse';
import { ModalFooter, Button } from 'carbon-components-svelte';
let {
src,
t,
loading = false,
view = $bindable<'trace' | 'cancel' | 'update'>(),
open = $bindable<boolean>()
}: {
src: 'user' | 'operator' | null;
t: Ticket | null;
loading: boolean;
view: 'trace' | 'cancel' | 'update';
open: boolean;
} = $props();
function isTicketActive(t: Ticket | null): boolean {
if (!t) return false;
return t.status !== 'canceled' && t.status !== 'solved';
}
</script>
{#if loading}
<div></div>
{:else if src === 'user' && isTicketActive(t)}
<!-- 用户工单卡片的下方按钮 -->
<ModalFooter>
<Button kind="danger" on:click={() => (view = 'cancel')}>取消工单</Button>
<Button kind="secondary" on:click={() => ((open = false), (view = 'trace'))}>返回</Button>
</ModalFooter>
{:else if src === 'operator'}
<!-- 后台工单记录视图下的下方按钮 -->
<ModalFooter>
<Button kind="secondary" on:click={() => ((open = false), (view = 'trace'))}>返回 </Button>
<Button kind="primary" on:click={() => (view = 'update')}>更新状态</Button>
</ModalFooter>
{/if}

View File

@@ -0,0 +1,176 @@
<script lang="ts">
import {
ComposedModal,
ModalHeader,
ModalBody,
Portal,
Button,
NotificationQueue,
Modal
} from 'carbon-components-svelte';
import type { Ticket, Trace } from '$lib/types/apiResponse';
import { GetTraces, CancelTicket } from '$lib/api';
import TraceTimeline from './TraceTimeline.svelte';
import { sampleTrace } from '$lib/testData/ticket';
import ModalButton from './ModalButton.svelte';
import UpViewButton from './UpViewButton.svelte';
import TraceUpdateView from './TraceUpdateView.svelte';
import { TicketModal } from '$lib/states/ticketDetails.svelte';
let view: 'trace' | 'cancel' | 'update' = $state('trace');
// TODO现在不能不传递参数为了简化使用考虑默认从全局状态获取参数
let {
t = TicketModal.NowTicket,
open = $bindable(TicketModal.Opened),
src = TicketModal.SRC,
onTicketChanged
}: { t?: Ticket | null; open?: boolean; src?: 'user' | 'operator'; onTicketChanged?: () => void | Promise<void>;} = $props();
let loading = $state(false);
let traces: Trace[] = $state([]);
let q: NotificationQueue = $state<NotificationQueue>(null);
let q1: NotificationQueue = $state<NotificationQueue>(null);
// 当模态框打开且有工单ID时获取记录
$effect(() => {
if (open && t != null) {
fetchTraces(t.tid);
}
});
async function fetchTraces(tid: number) {
loading = true;
try {
const res = await GetTraces(tid.toString());
traces = res.traces.reverse();
// traces = sampleTrace; // 使用测试数据
} catch (e: any) {
open = false;
console.error('Failed to load traces', e);
const errMsg = e.response?.data?.msg || e.message || '未知错误';
q.add({
kind: 'error',
title: '加载记录失败',
subtitle: errMsg,
timeout: 5000
});
} finally {
loading = false;
}
}
async function cancel(tid: number) {
try {
const res = await CancelTicket(tid.toString());
if (!res.success) {
throw new Error(res.msg || '取消工单失败');
}
q.add({
kind: 'success',
title: '工单已取消',
timeout: 3000
});
} catch (e: any) {
console.error('Failed to cancel ticket', e);
const errMsg = e.response?.data?.msg || e.message || '未知错误';
q.add({
kind: 'error',
title: '取消工单失败',
subtitle: errMsg,
timeout: 5000
});
} finally {
open = false;
view = 'trace';
await onTicketChanged?.();
}
}
function getTid(t: Ticket | null): string {
return t ? t.tid.toString() : '...';
}
let isUpReady = $state(false);
</script>
<Portal>
{#if view === 'trace'}
<ComposedModal size="sm" bind:open class="mobile-floating-modal">
<ModalHeader title="🗃No.{getTid(t)}-记录" />
<ModalBody>
<TraceTimeline t={traces} {loading} {src} />
</ModalBody>
<ModalButton {src} {t} {loading} bind:view bind:open />
</ComposedModal>
{/if}
{#if view === 'cancel'}
<Modal
size="sm"
class="mobile-floating-modal"
danger
bind:open
modalHeading="确定取消工单吗?"
primaryButtonText="确定取消 "
secondaryButtonText="算了"
on:close={() => ((open = false), (view = 'trace'))}
on:click:button--secondary={() => ((open = false), (view = 'trace'))}
on:submit={() => cancel(t!.tid)}
>
<p>该操作不可逆,若要重新开启工单,您可以在稍后提交一个新的工单。</p>
<br />
<br />
</Modal>
{/if}
{#if view === 'update'}
<!-- 使用moobile-floating-model在这里貌似会显示不好所以不用了 -->
<ComposedModal on:close={() => (view = 'trace')} bind:open>
<NotificationQueue bind:this={q1} />
<ModalHeader title=" 🖋请更新No.{getTid(t)}的状态" />
<ModalBody>
<TraceUpdateView {t} bind:view bind:open bind:isUpReady {q} {q1} onUpdated={onTicketChanged} />
</ModalBody>
<UpViewButton bind:view bind:isUpReady />
</ComposedModal>
{/if}
</Portal>
<NotificationQueue bind:this={q} />
<style>
:global(.mobile-floating-modal.bx--modal) {
@media (max-width: 672px) {
display: flex !important;
align-items: center !important;
justify-content: center !important;
/* 确保背景色存在 (Carbon默认有但为了保险起见) */
background-color: rgba(22, 22, 22, 0.5) !important;
}
}
:global(.mobile-floating-modal .bx--modal-container) {
@media (max-width: 672px) {
width: 90% !important;
max-width: 400px !important;
height: auto !important;
max-height: 85vh !important;
position: relative !important;
margin: 0 !important;
top: auto !important;
left: auto !important;
transform: none !important;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4) !important;
}
}
:global(.mobile-floating-modal .bx--modal-content) {
@media (max-width: 672px) {
max-height: 60vh !important;
overflow-y: auto !important;
margin-bottom: 0 !important;
}
}
</style>

View File

@@ -0,0 +1,191 @@
<script lang="ts">
import { SkeletonText, Tag } from 'carbon-components-svelte';
import type { Trace } from '$lib/types/apiResponse';
import { FormatTime } from '$lib/types/RFC3339';
import { StatusMap, PriorityMap } from '$lib/types/enum';
let {
t = [],
loading = false,
src = 'user'
}: { t: Trace[]; loading: boolean; src: 'operator' | 'user' | null } = $props();
function getStatusClass(status?: string) {
if (status === 'solved' || status === 'escalated') return 'status-green';
if (status === 'canceled') return 'status-gray';
if (status === 'fresh') return 'status-red';
return ''; // 默认为蓝色
}
function userOPName(op: string):string {
if (op === '用户' && src === 'user'){
return '您';
}
return op;
}
</script>
<div class="timeline-container">
{#if loading}
<div class="timeline-skeleton">
<SkeletonText paragraph lines={3} />
<br />
<SkeletonText paragraph lines={3} />
</div>
{:else if t.length === 0}
<div class="empty-state">暂无操作记录</div>
{:else}
<ul class="timeline">
{#each t as trace}
<li class="timeline-item">
<!-- 左侧时间点圆圈 -->
<div class="timeline-marker {getStatusClass(trace.new_status)}"></div>
<!-- 右侧内容 -->
<div class="timeline-content">
<div class="header">
<span class="operator">{userOPName(trace.op_name)}</span>
<span class="time">{FormatTime(trace.updated_at)}</span>
</div>
<div class="body">
{#if trace.remark}
<p class="remark">{trace.remark}</p>
{/if}
<!-- 如果有状态/优先级变更,显示标签 -->
<div class="tags">
{#if trace.new_status}
<Tag type="blue" size="sm">{StatusMap[trace.new_status]}</Tag>
{/if}
{#if trace.new_priority && src === 'operator'}
<Tag type="red" size="sm">{PriorityMap[trace.new_priority]}</Tag>
{/if}
{#if trace.new_appointment}
<Tag type="purple" size="sm">预约: {FormatTime(trace.new_appointment)}</Tag>
{/if}
</div>
</div>
</div>
</li>
{/each}
</ul>
{/if}
</div>
<style>
.timeline-container {
padding: 1rem 0;
font-family: 'IBM Plex Sans', 'Helvetica Neue', Arial, sans-serif;
width: 100%;
}
.empty-state {
text-align: center;
color: #8d8d8d;
padding: 2rem 0;
}
.timeline {
display: flex;
flex-direction: column;
list-style: none;
padding: 0;
margin: 0;
position: relative;
width: 100%;
}
/* 垂直连接线 */
.timeline::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 7px; /* 圆点中心对齐 */
width: 2px;
background: #e0e0e0;
z-index: 0;
}
.timeline-item {
display: flex;
position: relative;
margin-bottom: 2rem;
padding-left: 1.5rem; /* 为圆点留出空间 */
box-sizing: border-box;
width: 100%;
}
.timeline-item:last-child {
margin-bottom: 0;
}
/* 时间轴圆点 */
.timeline-marker {
position: absolute;
left: 0;
top: 4px;
width: 16px;
height: 16px;
border-radius: 50%;
background: #ffffff;
border: 4px solid #0f62fe; /* Carbon Blue 60 */
z-index: 1;
transition: border-color 0.2s;
}
.timeline-marker.status-green {
border-color: #198038; /* Carbon Green 60 */
}
.timeline-marker.status-gray {
border-color: #8d8d8d; /* Carbon Gray 60 */
}
.timeline-marker.status-red {
border-color: #da1e28; /* Carbon Red 60 */
}
.timeline-content {
position: relative;
width: 100%;
min-width: 0;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.25rem;
width: 100%;
}
.operator {
font-weight: 600;
font-size: 0.875rem;
color: #161616;
}
.time {
font-size: 0.75rem;
color: #6f6f6f;
}
.remark {
font-size: 0.875rem;
line-height: 1.3;
color: #393939;
margin-bottom: 0.5rem;
white-space: pre-wrap; /* 保留换行符 */
min-width: 100%;
word-break: break-word;
overflow-wrap: break-word;
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
</style>

View File

@@ -0,0 +1,174 @@
<script lang="ts">
import {
Button,
NotificationQueue,
Select,
SelectItem,
DatePicker,
DatePickerInput,
TextArea
} from 'carbon-components-svelte';
import type { NewRepairTraceReq } from '$lib/types/apiRequest';
import type { Ticket } from '$lib/types/apiResponse';
import { type WtsStatus, type WtsPriority, IsAdmin } from '$lib/types/enum';
import { FormatDate, RFC3339 } from '$lib/types/RFC3339';
import { CheckAndGetJWT } from '$lib/jwt';
import { Category } from 'carbon-icons-svelte';
import { NewRepairTrace } from '$lib/api';
import { invalidateAll } from '$app/navigation';
let r = $state({} as NewRepairTraceReq);
let access = CheckAndGetJWT('parsed').access || 'user';
let {
t,
view = $bindable<'trace' | 'cancel' | 'update'>(),
open = $bindable<boolean>(),
isUpReady = $bindable<boolean>(),
q,
q1,
onUpdated
}: {
t: Ticket | null;
view: 'trace' | 'cancel' | 'update';
open: boolean;
isUpReady: boolean;
q: NotificationQueue; //页面的
q1: NotificationQueue; //模态框的
onUpdated?: () => void | Promise<void>;
} = $props();
$effect(() => {
if (isUpReady) {
checkAndSubmitAndExit();
isUpReady = false;
}
});
$effect(() => {
if (t) {
r.new_category = t.category;
r.new_priority = t.priority;
}
});
//检查合法性,提交更新,退出模态框
async function checkAndSubmitAndExit() {
//检查部分
try {
$inspect(r).with(console.log);
assert(t != null, '工单不能为空');
assert(r.new_status !== '', '新状态不能为空。');
r.tid = t.tid;
assert(
r.new_status !== 'scheduled' || r.new_appointment != null,
'新状态是“已预约”时,预约时间不能为空。'
);
assert(r.remark != null && r.remark.trim().length > 0, '备注不能为空。');
assert(r.remark.length <= 500, '备注不能超过500字。');
if (r.new_category === t.category) {
r.new_category = undefined;
}
if (r.new_priority === t.priority) {
r.new_priority = undefined;
}
} catch (e: any) {
console.error('检查更新状态失败', e);
q1.add({
kind: 'error',
title: '数据校验失败',
subtitle: e.message || '未知错误',
timeout: 5000
});
return;
}
//提交部分
try {
//退出模态框
open = false;
view = 'trace';
let res = await NewRepairTrace(r);
if (!res.success) {
throw new Error(res.msg || '未知错误');
}
q.add({
kind: 'success',
title: '更新状态成功',
timeout: 3000
});
} catch (e: any) {
console.error('提交更新状态失败', e);
q.add({
kind: 'error',
title: '提交更新状态失败',
subtitle: e.response?.data?.msg || e.message || '未知错误',
timeout: 5000
});
} finally {
await onUpdated?.();
}
}
function assert(condition: boolean, expression: string) {
if (!condition) {
throw new Error(`无效的输入: ${expression}`);
}
}
</script>
<Select labelText="工单新状态" bind:selected={r.new_status} required={true}>
<SelectItem value="" text="请选择工单的新状态..." disabled={true} hidden={true} />
<SelectItem value="scheduled" text="已预约" style="color: #2563eb;" />
<SelectItem value="delay" text="改日修" style="color: #3b82f6;" />
<SelectItem value="escalated" text="已上报" style="color: #16a34a;" />
<SelectItem value="solved" text="已解决" style="color: #16a34a;" />
<SelectItem value="canceled" text="已取消" style="color: var(--color-red-600);" />
{#if IsAdmin(access)}
<SelectItem value="fresh" text="待解决(重启报修)" style="color: var(--color-red-600);" />
{/if}
</Select>
{#if r.new_status === 'scheduled'}
<br />
<DatePicker
datePickerType="single"
on:change={(e) => (r.new_appointment = RFC3339((e.detail as { dateStr: string }).dateStr))}
>
<DatePickerInput labelText="预约的时间" placeholder="x年y月z日" required={true} />
</DatePicker>
{/if}
<br />
<Select
labelText="工单新故障类型"
bind:selected={r.new_category}
helperText="如果情况变化或信息有误,你可以修改该报修的故障类型"
>
<SelectItem value="first-install" text="新装" />
<SelectItem value="low-speed" text="网速慢" />
<SelectItem value="ip-or-device" text="IP或设备问题" />
<SelectItem value="client-or-account" text="客户端或账号问题" />
<SelectItem value="others" text="其它问题" />
</Select>
{#if IsAdmin(access)}
<br />
<Select
labelText="工单新优先级"
bind:selected={r.new_priority}
helperText="你可以修改该报修的优先级,更高优先级会在系统中优先显示"
>
<SelectItem value="highest" text="最高优先级!" style="color: var(--color-red-600);" />
<SelectItem value="assigned" text="运营商工单" style="color: #2563eb;" />
<SelectItem value="mainline" text="主线任务" />
<SelectItem value="normal" text="一般报修" />
<SelectItem value="in-passing" text="顺路看看" />
<SelectItem value="least" text="不紧急" />
</Select>
{/if}
<br />
<TextArea labelText="备注" placeholder="本次工单更新的备注" bind:value={r.remark} />

View File

@@ -0,0 +1,17 @@
<script lang="ts">
import type { Ticket } from '$lib/types/apiResponse';
import { ModalFooter, Button } from 'carbon-components-svelte';
let{
view = $bindable<'trace' | 'cancel' | 'update'>(),
isUpReady = $bindable<boolean>(),
}:{
view: 'trace' | 'cancel' | 'update',
isUpReady: boolean,
} = $props();
</script>
<ModalFooter>
<Button kind="secondary" on:click={() => (view = 'trace')}>返回上一级</Button>
<Button kind="primary" on:click={() =>(isUpReady=true)}>确认</Button>
</ModalFooter>

1
front/src/lib/index.ts Normal file
View File

@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

112
front/src/lib/jwt.ts Normal file
View File

@@ -0,0 +1,112 @@
import { jwtDecode } from 'jwt-decode';
import type { WtsAccess } from './types/enum';
import { docCookies } from '$lib/vendor/docCookie';
import { TheLastPage } from './states/theLastPage.svelte';
import { goto } from '$app/navigation';
import { page } from '$app/state';
import { get } from 'svelte/store';
import { browser } from '$app/environment';
export interface WtsJWT {
openid: string;
sid: string;
username: string;
avatar: string;
access: WtsAccess;
name: string;
// 下面是 JWT 的标准字段
iss?: string;
sub?: string;
aud?: string;
exp?: number;
iat?: number;
nbf?: number;
jti?: string;
}
export function CheckAndGetJWT(tx: 'parsed'): WtsJWT | null;
export function CheckAndGetJWT(tx: 'raw'): string | null;
export function CheckAndGetJWT(tx: 'raw' | 'parsed'): WtsJWT | string | null {
if (!browser) {
if (tx === 'parsed') {
return {
openid: '',
access: 'unregistered',
sid: '',
username: '',
avatar: '',
name: '',
} as WtsJWT;
}
if (tx === 'raw') {
return "aaaaa";
}
}
let token: string;
token = localStorage.getItem('jwt');
if (!token) {
console.log('CheckAndGetJWT():没有找到 JWT');
return null;
}
try {
const raw = jwtDecode<WtsJWT>(token);
if (raw.exp && Date.now() / 1000 > raw.exp) {
console.log('CheckAndGetJWT():JWT 已过期');
localStorage.removeItem('jwt');
return null;
}
//console.log(raw);
if (tx === 'parsed') {
return raw;
} else {
return token;
}
} catch (e) {
console.error('Error at CheckAndGetJWT() :', e);
localStorage.removeItem('jwt');
return null;
}
}
//在回调页面下执行,从 cookie 中获取 JWT存入 localStorage 并删除 cookie
export function GetJWTFromCookie(): boolean {
try {
const c = docCookies.getItem('jwt');
if (!c) {
console.log('GetJWTFromCookie():没有在 cookie 中找到 JWT');
return false;
}
localStorage.setItem('jwt', c);
docCookies.removeItem('jwt');
let jwt = CheckAndGetJWT('parsed');
if (!jwt) {
console.log('GetJWTFromCookie():存入 localStorage 后 JWT 无效,可能已损坏;');
return false;
}
return true;
} catch (e) {
console.error('Error at GetJWTFromCookie() :', e);
return false;
}
}
export function Guard(a: (subject: WtsAccess) => boolean) {
let jwt = CheckAndGetJWT('parsed');
if (!jwt) {
TheLastPage.Write(page.url.pathname);
goto('/login');
return;
}
if (!a(jwt.access)) {
console.log('Guard():权限不足,跳转到首页');
goto('/forbidden');
return;
}
}

View File

@@ -0,0 +1,16 @@
//全局状态保存用户刚刚访问的页面以便JWT获取后跳转回来
class theLastPage {
p = $state('/');
Write(p: string) {
this.p = p;
}
Read(): string {
const p1 = this.p;
this.p = '/';
return p1;
}
}
export const TheLastPage = new theLastPage();

View File

@@ -0,0 +1,49 @@
import type { FilterTicketsReq } from '$lib/types/apiRequest';
import type { WtsZone } from '$lib/types/enum';
export type Criteria = {
r: FilterTicketsReq;
_order: 'priority' | 'newest' | 'oldest';
_floor: number | null;
_blocks_in_zone: WtsZone[]; //需要和req.block保持一致注意
_view_today_scheduled: boolean;
};
export let criteria: Criteria = {
r: {
scope: 'active',
issuer: undefined,
block: [],
status: [],
priority: [],
category: [],
isp: [],
newer_than: undefined,
older_than: undefined,
},
_order: 'priority',
_floor: null,
_blocks_in_zone: [],
_view_today_scheduled: false
} as Criteria;
export function resetCriteria() {
criteria = {
r: {
scope: 'active',
issuer: undefined,
block: [],
status: [],
priority: [],
category: [],
isp: [],
newer_than: undefined,
older_than: undefined,
},
_order: 'priority',
_floor: null,
_blocks_in_zone: [],
_view_today_scheduled: false
} as Criteria;
}

View File

@@ -0,0 +1,25 @@
import type { Ticket } from '$lib/types/apiResponse';
class TicketDetailState {
//用于工单详情页面的展示这是被页面中所有Ticket组件使用的全局变量
//用法:在需要展示工单详情的地方添加<TicketDetail />即可,也可以显式指定数据的参数 (更新:目前还不行,必须传参...
NowTicket = $state<Ticket | null>(null);
Opened = $state(false);
SRC = $state<'operator' | 'user' | null>(null);
// 打开详情页
open(t: Ticket, s: 'operator' | 'user') {
this.NowTicket = t;
this.Opened = true;
this.SRC = s;
}
// 关闭详情页
close() {
this.NowTicket = null;
this.Opened = false;
this.SRC = null;
}
}
export const TicketModal = new TicketDetailState();

View File

@@ -0,0 +1,99 @@
import type { Ticket, UserProfile, Trace } from '$lib/types/apiResponse';
import { NowRFC3339, RFC3339 } from '$lib/types/RFC3339';
export const sample1issuer: UserProfile = {
sid: '2020123456',
name: '张三',
block: 'XHA',
access: 'user',
room: '123',
phone: '13800138000',
isp: 'mobile',
account: '12345678901@139.gd',
wx: 'zhangsan_wx',
};
export const sample1: Ticket = {
tid: 3,
issuer: sample1issuer,
description: '网络坏了啊啊啊,快来修!好像是网线被蟑螂咬坏了。',
occur_at: RFC3339('2023-12-31T23:59:59Z'),
submitted_at: RFC3339('2024-01-01T00:10:00Z'),
category: 'ip-or-device',
status: 'scheduled',
priority: 'assigned',
appointed_at: NowRFC3339(),
};
export const sample2issuer: UserProfile = {
sid: '2020123456',
name: 'hajimi',
block: '17',
access: 'user',
room: '701',
phone: '13800138000',
isp: 'telecom',
account: '18923456789',
wx: 'zhangsan_wx',
};
export const sample2: Ticket = {
tid: 2,
issuer: sample2issuer,
description: '才办的宽带,麻烦来装下,谢谢!',
occur_at: RFC3339('2023-12-31T23:59:59Z'),
submitted_at: RFC3339('2024-01-01T00:10:00Z'),
category: 'first-install',
status: 'fresh',
priority: 'mainline',
};
export const sampleTrace: Trace[] = [
{
opid: 1,
tid: 10,
updated_at: RFC3339('2024-01-01T10:00:00Z'),
op: '2395',
op_name: '(用户操作)',
remark: '工单已提交',
new_status: 'fresh',
new_priority: 'mainline',
},
{
opid: 2,
tid: 10,
updated_at: RFC3339('2024-01-01T10:00:00Z'),
op: '2395',
op_name: '哈哈哈',
remark: '用户预约了时间',
new_status: 'scheduled',
new_appointment: RFC3339('2024-01-13T14:00:00Z'),
},
{
opid: 3,
tid: 10,
updated_at: RFC3339('2024-01-01T10:00:00Z'),
op: '2395',
op_name: '啊啊啊',
remark: '材料不足,改日修',
new_status: 'delay',
},
{
opid: 4,
tid: 10,
updated_at: RFC3339('2024-01-01T10:00:00Z'),
op: '2395',
op_name: '嘿嘿嘿',
remark: '与用户重新约定时间预约在2024-01-21下午',
new_status: 'scheduled',
new_appointment: RFC3339('2024-01-21T15:00:00Z'),
},
{
opid: 5,
tid: 10,
updated_at: RFC3339('2024-01-01T10:00:00Z'),
op: '2395',
op_name: '喵喵喵',
remark: '问题解决:用户路由器线路接触不良,更换后恢复正常',
new_status: 'solved',
},
];

View File

@@ -0,0 +1,47 @@
import { formatISO, parseISO } from 'date-fns';
export type RFC3339 = string & { readonly brand: unique symbol };
export function RFC3339(input: Date | number | string): RFC3339 {
return formatISO(input) as RFC3339;
}
export function NowRFC3339(): RFC3339 {
return RFC3339(new Date());
}
export function DateRFC3339(input: RFC3339): Date {
return parseISO(input);
}
export function FormatDate(dateStr: string) {
if (!dateStr) return '未知时间';
return new Date(dateStr).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
}
export function FormatTime(dateStr: string) {
if (!dateStr) return '未知时间';
return new Date(dateStr).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
export function IsRFC3339(t: string): boolean {
const rfc3339Regex =
/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):([0-5]\d):([0-5]\d)(\.\d+)?(Z|[+-]([01]\d|2[0-3]):[0-5]\d)$/;
if (!rfc3339Regex.test(t)) {
return false;
}
const date = new Date(t);
return !isNaN(date.getTime());
}

View File

@@ -0,0 +1,62 @@
import type { WtsBlock, WtsCategory, WtsISP, WtsPriority, WtsStatus, WtsZone } from './enum';
import type { RFC3339 } from './RFC3339';
export type RegisterReq = {
sid: string;
name: string;
block: WtsBlock | '0'; //方便和Carbon的组件配合
room: string;
phone: string;
isp: WtsISP;
account: string;
};
export type ChangeProfileReq = {
who: string;
block: WtsBlock | '0'; //方便和Carbon的组件配合
room: string;
phone: string;
isp: WtsISP;
account: string;
};
export type FilterUsersReq = {
name?: string;
block?: WtsBlock;
room?: string;
phone?: string;
isp?: WtsISP;
account?: string;
};
export type NewTicketReq = {
issuer_sid: string;
occur_at?: RFC3339;
description: string;
appointed_at?: RFC3339;
notes?: string;
priority?: WtsPriority;
category: WtsCategory;
status?: WtsStatus;
};
export type NewRepairTraceReq = {
tid: number;
new_status: WtsStatus | ''; //同样方便检查
new_priority?: WtsPriority;
new_appointment?: RFC3339;
new_category?: WtsCategory;
remark: string;
};
export type FilterTicketsReq = {
block?: WtsBlock[];
scope?: 'active' | 'all';
status?: WtsStatus[];
priority?: WtsPriority[];
isp?: WtsISP[];
issuer?: string;
category?: WtsCategory[];
newer_than?: RFC3339;
older_than?: RFC3339;
};

View File

@@ -0,0 +1,95 @@
import type { RFC3339 } from './RFC3339';
import type { WtsBlock, WtsAccess, WtsISP, WtsStatus, WtsPriority, WtsCategory } from './enum';
export type UserProfile = {
sid: string;
name: string;
block: WtsBlock;
access: WtsAccess;
room: string;
phone: string;
isp: WtsISP;
account: string;
wx: string;
};
export type Ticket = {
tid: number;
issuer: UserProfile;
submitted_at: RFC3339;
occur_at?: RFC3339;
description: string;
category: WtsCategory;
priority: WtsPriority;
notes?: string;
status: WtsStatus;
appointed_at?: RFC3339;
last_updated_at?: RFC3339;
};
export type Trace = {
opid: number;
tid: number;
updated_at: RFC3339;
op: string;
op_name: string;
new_status?: WtsStatus;
new_priority?: WtsPriority;
new_appointment?: RFC3339;
new_category?: WtsCategory;
remark: string;
};
export interface CommonResponse {
success: boolean;
msg?: string;
debug?: string;
error_type?: number;
}
// used by: /api/v3/view_profile
export type ViewProfileRes = CommonResponse & {
profile: UserProfile;
};
// used by: /api/v3/filter_users
export type FilterUsersRes = CommonResponse & {
profiles: UserProfile[];
};
// used by: /api/v3/new_ticket
export type NewTicketRes = CommonResponse & {
tid: number;
};
// used by: /api/v3/get_ticket
export type GetTicketRes = CommonResponse & {
tickets: Ticket[];
};
// used by: /api/v3/register
export type RegisterRes = CommonResponse;
// used by: /api/v3/change_profile
export type ChangeProfileRes = CommonResponse;
// used by: /api/v3/cancel_ticket
export type CancelTicketRes = CommonResponse;
// used by: /api/v3/new_repair_trace
export type NewRepairTraceRes = CommonResponse;
// used by: /api/v3/filter_tickets
export type FilterTicketsRes = CommonResponse & {
tickets: Ticket[];
};
// used by: /api/v3/get_traces
export type GetTracesRes = CommonResponse & {
traces: Trace[];
};
// used by: /api/v3/ticket_overview
export type TicketOverviewRes = CommonResponse & {
count_by_block: Record<WtsBlock, number>;
};

164
front/src/lib/types/enum.ts Normal file
View File

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

View File

@@ -0,0 +1,16 @@
// 用于表单提交时的校验方便
export class invalidState {
notOK = $state(false);
txt = $state('');
assert(x: boolean, e: string) {
if (!x && !this.notOK) {
this.notOK = true;
this.txt = e;
}
}
reset() {
this.notOK = false;
this.txt = '';
}
}

99
front/src/lib/vendor/docCookie.ts vendored Normal file
View File

@@ -0,0 +1,99 @@
/*\
|*|
|*| :: cookies.js ::
|*|
|*| A complete cookies reader/writer framework with full unicode support.
|*|
|*| https://developer.mozilla.org/zh-CN/docs/DOM/document.cookie
|*|
|*| This framework is released under the GNU Public License, version 3 or later.
|*| http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
|*| Syntaxes:
|*|
|*| * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]])
|*| * docCookies.getItem(name)
|*| * docCookies.removeItem(name[, path], domain)
|*| * docCookies.hasItem(name)
|*| * docCookies.keys()
|*|
\*/
// modify: add TypeScript support , exported from module
export var docCookies = {
getItem: function (sKey: string): string | null {
return (
decodeURIComponent(
document.cookie.replace(
new RegExp(
'(?:(?:^|.*;)\\s*' +
encodeURIComponent(sKey).replace(/[-.+*]/g, '\\$&') +
'\\s*\\=\\s*([^;]*).*$)|^.*$'
),
'$1'
)
) || null
);
},
setItem: function (
sKey: string,
sValue: string,
vEnd?: number | string | Date,
sPath?: string,
sDomain?: string,
bSecure?: boolean
): boolean {
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) {
return false;
}
var sExpires = '';
if (vEnd) {
switch (vEnd.constructor) {
case Number:
sExpires =
vEnd === Infinity ? '; expires=Fri, 31 Dec 9999 23:59:59 GMT' : '; max-age=' + vEnd;
break;
case String:
sExpires = '; expires=' + vEnd;
break;
case Date:
sExpires = '; expires=' + (vEnd as Date).toUTCString();
break;
}
}
document.cookie =
encodeURIComponent(sKey) +
'=' +
encodeURIComponent(sValue) +
sExpires +
(sDomain ? '; domain=' + sDomain : '') +
(sPath ? '; path=' + sPath : '') +
(bSecure ? '; secure' : '');
return true;
},
removeItem: function (sKey: string, sPath?: string, sDomain?: string): boolean {
if (!sKey || !this.hasItem(sKey)) {
return false;
}
document.cookie =
encodeURIComponent(sKey) +
'=; expires=Thu, 01 Jan 1970 00:00:00 GMT' +
(sDomain ? '; domain=' + sDomain : '') +
(sPath ? '; path=' + sPath : '');
return true;
},
hasItem: function (sKey: string): boolean {
return new RegExp(
'(?:^|;\\s*)' + encodeURIComponent(sKey).replace(/[-.+*]/g, '\\$&') + '\\s*\\='
).test(document.cookie);
},
keys: /* optional method: you can safely remove it! */ function (): string[] {
var aKeys = document.cookie
.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, '')
.split(/\s*(?:\=[^;]*)?;\s*/);
for (var nIdx = 0; nIdx < aKeys.length; nIdx++) {
aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]);
}
return aKeys;
}
};

View File

@@ -0,0 +1,69 @@
<script lang="ts">
import './layout.css';
import favicon from '$lib/assets/favicon.svg';
import logo from '$lib/assets/logo-256.svg';
import { Theme } from 'carbon-components-svelte';
let { children } = $props();
// import 'carbon-components-svelte/css/g10.css'; //主样式
import Dock from '$lib/components/Dock.svelte';
import 'carbon-components-svelte/css/all.css';
import type { CarbonTheme } from 'carbon-components-svelte/src/Theme/Theme.svelte';
let theme: CarbonTheme = $state('g10');
</script>
<svelte:head><link rel="icon" href={logo} /></svelte:head>
<div class="app-container">
<Theme bind:theme />
<!--页面组件将在这里渲染 -->
<main class="main-content">
{@render children()}
</main>
<Dock />
</div>
<style>
:global(body) {
margin: 0;
padding: 0;
box-sizing: border-box;
/* 使用 Carbon 推荐的字体 */
font-family: 'IBM Plex Sans', sans-serif;
}
.app-container {
display: flex;
flex-direction: column;
min-height: 100vh;
margin: 0 auto;
max-width: 500px;
box-shadow: 0 0 25px rgba(0, 0, 0, 0);
position: relative;
}
.main-content {
flex-grow: 1;
padding: 1rem;
padding-bottom: 110px;
}
/* 全局修复 Carbon Select 组件的双箭头问题 */
/* 不知道为什么会有这个奇怪的问题大概是Tailwind CSS导致的 */
:global(.bx--select-input) {
-webkit-appearance: none !important;
-moz-appearance: none !important;
appearance: none !important;
background-image: none !important;
}
:global(.bx--select-input::-ms-expand) {
display: none;
}
</style>

View File

@@ -0,0 +1,7 @@
import type { LayoutLoad } from './$types';
export const load = (async () => {
return {};
}) satisfies LayoutLoad;
export const prerender = true;

View File

@@ -0,0 +1,71 @@
<script>
import RetroCard from '$lib/components/RetroCard.svelte';
import { Button } from 'carbon-components-svelte';
import group from '$lib/assets/picture/girl-using-computer.svg';
import help from '$lib/assets/picture/help.svg';
import outlook from '$lib/assets/picture/two-people-credit-card.svg';
</script>
<h1 style="font-size: 25px">欢迎使用ZSC网维报修系统</h1>
<br />
<hr />
<br />
<br />
<RetroCard>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<div>
<h2>😵网络遇到了问题?</h2>
<p>点击下方按钮,向我们提交报修。</p>
<Button href="/repair/new">我要报修!</Button>
</div>
<img
src={help}
alt="Need help with network?"
style="width: 120px; height: 120px; object-fit: contain; margin-left: auto; align-self: flex-end;"
/>
</div>
</RetroCard>
<br />
<br />
<br />
<RetroCard>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<img
src={outlook}
alt="how to use campus network"
style="width: 120px; height: 120px; object-fit: contain; margin-right: 0.5rem;"
/>
<div style="display: flex; flex-direction: column; gap: 0.25rem;">
<h2>🥺不会使用校园网?</h2>
<p>点击下方按钮,我们准备了校园网方方面面的攻略!</p>
<Button href="/help" kind="secondary" style="align-self: flex-end;transform: translate(-25px,0px);">网络攻略</Button>
</div>
</div>
</RetroCard>
<br />
<br />
<br />
<RetroCard>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<div>
<h2>🧑‍🤝‍🧑网络支持群</h2>
<p style="display: inline-block; margin: 0 0 0.5rem 0;">
如果有其它问题您可以加入我们的网络支持QQ群我们的工作人员会详细解答任何问题。
</p>
<p style="display: inline-block; margin: 0;">
群号:<strong>123123123</strong>
</p>
</div>
<img
src={group}
alt="Join our support group"
style="width: 120px; height: 120px; object-fit: contain;"
/>
</div>
</RetroCard>

View File

@@ -0,0 +1,23 @@
<h1>关于网维</h1>
<br />
<hr />
<br />
<p>中山学院网络维护科是一个独立的<strong>学生组织</strong>成立于2005年接受校信息中心的指导和管理。</p>
<p>
网维的主要任务是保证校园宿舍网络的正常运行,处理解决在校学生所报修的有线校园网问题,这项业务主要通过依托于微信的网络报修系统来运行。
</p>
<p>
在网维工作没有工资,促使我们成立这个组织的原因是对网络技术的热爱和对校园网正常运行的责任感,我们的宗旨是全心全意为同学服务。我们对外认真负责,在内和谐友善。
目前我们与三大运营商建立了良好的合作关系承接运营商在校园内的装维业务在信息中心的关怀下我们也承担学校部分IT后勤工作。
</p>
<p>
对于学生校园网报修,我们的成员会在<strong>每天16:30~18:00</strong>统一上门解决问题,用户在这个时间段必须本人在宿舍。
</p>
<p>
网维一般会在每年的9~10月招新对象为当年新生要求有基础的计算机知识。具体招新事宜请以实际通告为准。
</p>
<br />
<hr />
<br />
<a href="/about/TOS">报修系统的服务条款</a> |
<a href="/about/privacy">报修系统会如何处理您的个人信息?</a>

View File

@@ -0,0 +1 @@
<h1>服务条款</h1>

View File

@@ -0,0 +1,64 @@
<script>
import RetroCard from "$lib/components/RetroCard.svelte";
</script>
<h1>隐私权条款</h1>
<br />
<hr />
<br />
<p>
网维在向您提供服务时,需要和您的个人信息打交道。我们非常注重用户的数字个人隐私,对于有关问题,我们做出以下承诺:
</p>
<br/>
<h2>我们需要您的什么信息?</h2>
<p>为了派遣成员上门为您维修网络问题,在您注册报修系统时,我们需要获取您的:</p>
<br/>
<ul>
<li>宿舍住址</li>
<li>联系电话</li>
<li>校园卡账号</li>
</ul>
<br/>
<p>此外,为了确认您身份的真实性,我们还需要您的学号和真实姓名。</p>
<p>
在初次注册时,您的微信账户与您提交的学号和真实姓名绑定,原则上无法改动,而其余个人信息皆可以随时修改。
</p>
<br/>
<h2>我们如何保管您的信息?</h2>
<p>
我们使用妥善的措施,将您的个人信息保护在多项安全机制之内。除非根据下文所描述的信息使用条款合规将您的信息提供给有关人员之外,您提交给我们的个人信息绝不会被任何个人与集体知晓。
</p>
<br/>
<p>
当您修改您的个人信息,提交成功后,您的旧信息将在现有数据库中被永久删除。但是我们会定期备份我们的数据库,其中可能包含了旧的个人信息。不过,这些备份会被加密妥善保存,除非报修系统数据库遇到了损坏的情况,需要恢复备份的,否则任何人都无权访问这些数据。
</p>
<br/>
<h2>我们如何使用您的信息?</h2>
<p>
为了为您进行上门维修服务,当您在报修系统中有未解决的工单时,您的姓名,联系电话,宿舍住址和校园网账号将会在报修系统后台中被展示给有关的网维成员,以便它们进行上门维修或联系您进行其它相关事宜。当工单完成或被取消时,您的个人信息也将对普通网维成员不可见,但是您的信息会随着工单处理记录在管理层后台中保留一段时间,以供网维的管理层来进行审计工作。
</p>
<br/>
<p>
当遇到网维无法解决的问题,我们将启动上报程序,将您的网络问题告知运营商方面的有关工程师。此时,您的姓名,住址,学号等个人信息将会通过网维和运营商的对接机制,妥善地随着工单一并发送给您校园卡所在的运营商。
</p>
<br/>
<p>
除此之外,<strong
>我们不会将您的个人信息以任何方式泄露给任何个人或集体,尤其不会将您的信息用于商业营销或广告方面</strong
>
</p>
<br/>
<p>
如果您发现有网维成员或者运营商方面有关人员使用您提供给网维的个人信息,进行除了网络维修及其有关事宜之外的活动的,您可以通过多种方式进行投诉:
</p>
<br/>
<ul>
<li>在我们的报修系统后台聊天栏进行留言</li>
<li>加入我们的网络支持QQ群私聊群管理反馈问题。</li>
</ul>
<br/>
<br />
<hr />
<br />
<p>本条款最后更新于2025年12月27日最终解释权归网维所有。</p>

View File

@@ -0,0 +1,17 @@
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
import { onMount } from 'svelte';
import { Guard } from '$lib/jwt';
import { IsAdmin } from '$lib/types/enum';
onMount(() => Guard(IsAdmin));
</script>
<h1>管理后台</h1>
<br />
<hr />
<br />
<p>正在开发中</p>

View File

@@ -0,0 +1,226 @@
<script lang="ts">
import { CheckAndGetJWT, Guard } from '$lib/jwt';
import type { NewTicketReq } from '$lib/types/apiRequest';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
import { RFC3339 } from '$lib/types/RFC3339';
import { onMount } from 'svelte';
import { IsAdmin, IsUser } from '$lib/types/enum';
import {
DatePicker,
DatePickerInput,
RadioButtonGroup,
RadioButton,
TextArea,
Button,
NotificationQueue,
Loading,
TextInput,
Select,
SelectItem
} from 'carbon-components-svelte';
import { IsRFC3339 } from '$lib/types/RFC3339';
import { invalidState } from '$lib/types/invalidState.svelte';
import { NewTicket } from '$lib/api';
import { goto } from '$app/navigation';
let notLoading: boolean = $state(true);
let q: NotificationQueue;
let r = $state({
priority: 'normal',
} as NewTicketReq);
function onOccurDateChange(event: CustomEvent) {
const { dateStr } = event.detail;
if (dateStr) {
r.occur_at = RFC3339(dateStr);
}
}
function onAppointDateChange(event: CustomEvent) {
const { dateStr } = event.detail;
if (dateStr) {
const date = new Date(dateStr);
date.setHours(16, 30, 0, 0); // Set time to 16:30:00
r.appointed_at = RFC3339(date);
}
}
function handleSubmit() {
console.log('提交的表单数据:', r);
check() ? submit() : jumpInvalid();
}
let occurAt = new invalidState();
let appointedAt = new invalidState();
let description = new invalidState();
let notes = new invalidState();
function check(): boolean {
notLoading = false;
let ok = false;
occurAt.reset();
appointedAt.reset();
description.reset();
notes.reset();
occurAt.assert(!r.occur_at || IsRFC3339(r.occur_at), '请输入正确的故障发生时间');
appointedAt.assert(!r.appointed_at || IsRFC3339(r.appointed_at), '请输入正确的预约时间');
description.assert(r.description && r.description.length > 0, '请填写故障描述');
description.assert(r.description.length <= 100, '字数太多了请控制在100字以内');
notes.assert(!r.notes || r.notes.length <= 100, '字数太多了...请控制在100字以内');
if (r.category == undefined) {
r.category = 'others';
}
if (!r.occur_at) {
r.occur_at = undefined;
}
if (!r.appointed_at) {
r.appointed_at = undefined; //防止序列化问题
}
notLoading = true;
if (occurAt.notOK || appointedAt.notOK || description.notOK || notes.notOK) {
ok = false;
} else {
ok = true;
}
return ok;
}
async function submit() {
try {
notLoading = false;
let res = await NewTicket(r);
notLoading = true;
if (!res.success) {
throw new Error(res.msg || '提交失败.........');
}
q.add({
kind: 'success',
title: '提交成功',
timeout: 3000
});
setTimeout(() => goto('/repair'), 3900);
} catch (e: any) {
notLoading = true;
const errMsg = e.response?.data?.msg || e.message || '未知错误';
q.add({
kind: 'error',
title: '提交失败',
subtitle: errMsg + ',请重试',
timeout: 5000
});
}
}
function jumpInvalid() {
if (occurAt.notOK) {
document.getElementById('occur_at')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (appointedAt.notOK) {
document
.getElementById('appointed_at')
?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (description.notOK) {
document
.getElementById('description')
?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (notes.notOK) {
document.getElementById('notes')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
onMount(() => Guard(IsAdmin));
</script>
<h1>增添工单</h1>
<br />
<hr />
<br />
<p>为他人增添报修,注意,首先需要知道他人的学号。单独的工单增添正在开发中...</p>
<br />
<TextInput labelText="用户的学号" placeholder="请输入用户学号" bind:value={r.issuer_sid}/>
<br/>
<br/>
<DatePicker datePickerType="single" on:change={onOccurDateChange}>
<DatePickerInput
labelText="故障发生的日期"
placeholder="记不清楚可不填"
invalid={occurAt.notOK}
invalidText={occurAt.txt}
/>
</DatePicker>
<br />
<br />
<RadioButtonGroup
legendText="故障问题的类型"
orientation="vertical"
bind:selected={r.category}
required={true}
>
<RadioButton labelText="需要新安装宽带" value="first-install" />
<RadioButton labelText="IP地址或者网络设备问题" value="ip-or-device" />
<RadioButton labelText="电脑软件或者账号的问题" value="client-or-account" />
<RadioButton labelText="网速问题" value="low-speed" />
<RadioButton labelText="其它问题/不清楚" value="others" />
</RadioButtonGroup>
<br />
<br />
<TextArea
labelText="故障描述"
placeholder="描述一下故障的情况"
bind:value={r.description}
invalid={description.notOK}
invalidText={description.txt}
/>
<br />
<br />
<DatePicker datePickerType="single" on:change={onAppointDateChange}>
<DatePickerInput
labelText="预约上门维修的日期"
placeholder="当天4:30~6:00用户需要在宿舍"
invalid={appointedAt.notOK}
invalidText={appointedAt.txt}
/>
</DatePicker>
<br />
<br />
<hr />
<br />
<br />
<TextArea
labelText="备注"
placeholder="其它对维修成员有用的信息,注意事项等。"
bind:value={r.notes}
invalid={notes.notOK}
invalidText={notes.txt}
/>
<br />
<br />
<Select
labelText="工单优先级"
bind:selected={r.priority}
helperText="选择工单的优先级类型,更高优先级会在系统中优先显示"
>
<SelectItem value="highest" text="十万火急" style="color: var(--color-red-600);" />
<SelectItem value="assigned" text="运营商工单" style="color: #2563eb;" />
<SelectItem value="mainline" text="主线任务" />
<SelectItem value="normal" text="一般报修" />
<SelectItem value="in-passing" text="顺路看看" />
<SelectItem value="least" text="不紧急" />
</Select>
<br/>
<br/>
<Button on:click={handleSubmit}>提交</Button>
<NotificationQueue bind:this={q} />
<Loading active={!notLoading} />

View File

@@ -0,0 +1,11 @@
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<h1>管理排班</h1>
<br />
<hr />
<br />
<p>正在开发中</p>

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<h1>Forbidden</h1>
<p>对不起,您没有权限访问这个页面。</p>

View File

@@ -0,0 +1,128 @@
<script lang="ts">
import { Accordion, AccordionItem } from 'carbon-components-svelte';
import RetroCard from '$lib/components/RetroCard.svelte';
</script>
<h1>校园网使用攻略</h1>
<hr />
<br />
<br />
<br />
<RetroCard>
<h3 style="font-size: 25px; ">⚙️ 常见问题</h3>
<br/>
<Accordion size="xl">
<AccordionItem title="电脑获取不到IP地址">
<p>
Natural Language Classifier uses advanced natural language processing and machine learning
techniques to create custom classification models. Users train their data and the service
predicts the appropriate category for the inputted text.
</p>
</AccordionItem>
<AccordionItem title="忘记了账号密码">
<p>
Analyze text to extract meta-data from content such as concepts, entities, emotion,
relations, sentiment and more.
</p>
</AccordionItem>
<AccordionItem title="Wi-Fi没法用了">
<p>
Translate text, documents, and websites from one language to another. Create industry or
region-specific translations via the service's customization capability.
</p>
</AccordionItem>
<AccordionItem title="电信客户端故障">
<p>
Translate text, documents, and websites from one language to another. Create industry or
region-specific translations via the service's customization capability.
</p>
</AccordionItem>
<AccordionItem title="出现169.254开头的IP">
<p>
Translate text, documents, and websites from one language to another. Create industry or
region-specific translations via the service's customization capability.
</p>
</AccordionItem>
</Accordion>
</RetroCard>
<br />
<br />
<RetroCard>
<h3 style="font-size: 25px; ">🤔 新生相关</h3>
<br/>
<Accordion size="xl">
<AccordionItem title="去哪里办校园网?">
<p>
Natural Language Classifier uses advanced natural language processing and machine learning
techniques to create custom classification models. Users train their data and the service
predicts the appropriate category for the inputted text.
</p>
</AccordionItem>
<AccordionItem title="我该办什么套餐?">
<p>
Analyze text to extract meta-data from content such as concepts, entities, emotion,
relations, sentiment and more.
</p>
</AccordionItem>
<AccordionItem title="如何把我的电脑手机连接到校园网?">
<p>
Translate text, documents, and websites from one language to another. Create industry or
region-specific translations via the service's customization capability.
</p>
</AccordionItem>
<AccordionItem title="我该如何登录校园网?">
<p>
Translate text, documents, and websites from one language to another. Create industry or
region-specific translations via the service's customization capability.
</p>
</AccordionItem>
</Accordion>
</RetroCard>
<br />
<br />
<RetroCard>
<h3 style="font-size: 25px; ">🧐 关于报修</h3>
<br/>
<Accordion size="xl">
<AccordionItem title="如何报修我的网络故障?">
<p>
Natural Language Classifier uses advanced natural language processing and machine learning
techniques to create custom classification models. Users train their data and the service
predicts the appropriate category for the inputted text.
</p>
</AccordionItem>
<AccordionItem title="我报修了,大概多久能上门?">
<p>
Analyze text to extract meta-data from content such as concepts, entities, emotion,
relations, sentiment and more.
</p>
</AccordionItem>
<AccordionItem title="怎么一直没人来?">
<p>
Translate text, documents, and websites from one language to another. Create industry or
region-specific translations via the service's customization capability.
</p>
</AccordionItem>
<AccordionItem title="你们的人没把问题解决就跑了!">
<p>
Translate text, documents, and websites from one language to another. Create industry or
region-specific translations via the service's customization capability.
</p>
</AccordionItem>
<AccordionItem title="我应该如何投诉网维成员?">
<p>
Translate text, documents, and websites from one language to another. Create industry or
region-specific translations via the service's customization capability.
</p>
</AccordionItem>
</Accordion>
</RetroCard>
<br />
<br />

View File

@@ -0,0 +1,59 @@
@import 'tailwindcss';
@plugin '@tailwindcss/forms';
@plugin '@tailwindcss/typography';
@plugin 'daisyui';
@plugin "daisyui/theme" {
name: "wireframe";
default: true;
prefersdark: false;
color-scheme: "light";
--color-base-100: #f4f4f4;
--color-base-200: #e6e6e6;
--color-base-300: #d5d5d5;
--color-base-content: #000000;
--color-primary: #0f62fe;
--color-primary-content: oklch(100% 0 360);
--color-secondary: #393939;
--color-secondary-content: oklch(100% 0 360);
--color-accent: #0f62fe;
--color-accent-content: oklch(100% 0 0);
--color-neutral: #0f62fe;
--color-neutral-content: oklch(100% 0 0);
--color-info: #0f62fe;
--color-info-content: oklch(100% 0 360);
--color-success: #006d44;
--color-success-content: oklch(100% 0 360);
--color-warning: oklch(68% 0.162 75.834);
--color-warning-content: #ffffff;
--color-error: #da1e28;
--color-error-content: oklch(100% 0 360);
--radius-selector: 2rem;
--radius-field: 0rem;
--radius-box: 0rem;
--size-selector: 0.25rem;
--size-field: 0.21875rem;
--border: 0.5px;
--depth: 1;
--noise: 1;
}
@layer base {
html {
/* 移动端优化:防止点击高亮 */
-webkit-tap-highlight-color: transparent;
/* 移动端优化:平滑滚动 */
scroll-behavior: smooth;
}
body {
/* 移动端优化:字体抗锯齿 */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* 移动端优化:防止水平溢出 */
@apply min-h-screen overflow-x-hidden bg-base-100 text-base-content;
}
}

View File

@@ -0,0 +1,27 @@
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
import { dev } from '$app/environment';
import { env } from '$env/dynamic/public';
import { docCookies } from '$lib/vendor/docCookie';
import { PUBLIC_AUTH_REDIRECT } from '$env/static/public';
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
function gotoAuthAPI() {
if (!env.PUBLIC_JWT) {
console.log("未找到PUBLIC_JWT")
}
if (dev && env.PUBLIC_JWT) {
docCookies.setItem('jwt', env.PUBLIC_JWT, Infinity, '/');
goto('/login/success');
} else {
window.location.href = PUBLIC_AUTH_REDIRECT;
}
}
onMount(() => {
gotoAuthAPI();
});
</script>

View File

@@ -0,0 +1,37 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { CheckAndGetJWT, GetJWTFromCookie } from '$lib/jwt';
import { SelectProduct } from 'carbon-pictograms-svelte';
import type { PageProps } from './$types';
import { NotificationQueue } from 'carbon-components-svelte';
import { onMount } from 'svelte';
import { TheLastPage } from '$lib/states/theLastPage.svelte';
let { data }: PageProps = $props();
let q: NotificationQueue;
onMount(() => {
let ok = GetJWTFromCookie();
if (!ok) {
q.add({
kind: 'error',
title: '登录失败',
subtitle: '请查看控制台',
timeout: 5000
});
setTimeout(() => goto(TheLastPage.Read()), 5500);
return;
}
let isRegistered = CheckAndGetJWT('parsed').access !== 'unregistered';
if (!isRegistered) {
goto('/register');
return;
}
goto(TheLastPage.Read());
});
</script>
<h1>登录成功!请稍等...</h1>
<NotificationQueue bind:this={q} />

View File

@@ -0,0 +1,171 @@
<script lang="ts">
import RetroCard from '$lib/components/RetroCard.svelte';
import type { UserProfile } from '$lib/types/apiResponse';
import {
Accordion,
AccordionItem,
Toggle,
Button,
StructuredList,
StructuredListBody,
StructuredListCell,
StructuredListRow,
Modal,
NotificationQueue,
ButtonSet
} from 'carbon-components-svelte';
import BlockRoom from '$lib/components/Ticket/BlockRoom.svelte';
import { ViewProfile } from '$lib/api';
import { CheckAndGetJWT, Guard } from '$lib/jwt';
import { onMount } from 'svelte';
import { IsAdmin, IsOperator, IsUser } from '$lib/types/enum';
import WtsISP from '$lib/components/Ticket/WtsISP.svelte';
import Renew from 'carbon-icons-svelte/lib/Renew.svelte';
import { TheLastPage } from '$lib/states/theLastPage.svelte';
import { goto } from '$app/navigation';
let pending = $state(true);
let info = $state({} as UserProfile);
let q: NotificationQueue;
onMount(() => (Guard(IsUser), fetchUser()));
async function fetchUser() {
try {
const wx = CheckAndGetJWT('parsed').openid;
if (!wx) {
throw new Error('未找到用户信息,请重新登录');
}
const res = await ViewProfile(wx);
console.log(res);
pending = false;
if (!res.success) {
throw new Error(res.msg || '获取用户信息失败');
}
info = res.profile;
} catch (e: any) {
pending = false;
const errMsg = e.response?.data?.msg || e.message || '未知错误';
q.add({
kind: 'error',
title: '获取用户信息失败',
subtitle: errMsg + ',请重试',
timeout: 5000
});
}
}
</script>
<h1></h1>
<br />
<hr />
<br />
<div class="profile">
{#await pending}
<p>加载中...</p>
{:then}
<RetroCard>
<span style="display: flex; align-items: center;">
<h2 style="margin-right: 0.5rem;">个人信息</h2>
<Renew onclick={() => {TheLastPage.Write('/me'),goto('/login')}} style="cursor: pointer;" />
</span>
<StructuredList style="margin-bottom: 1rem;">
<StructuredListBody>
<StructuredListRow>
<StructuredListCell noWrap>姓名</StructuredListCell>
<StructuredListCell>{info.name}</StructuredListCell>
</StructuredListRow>
<StructuredListRow>
<StructuredListCell noWrap>学号</StructuredListCell>
<StructuredListCell>{info.sid}</StructuredListCell>
</StructuredListRow>
<StructuredListRow>
<StructuredListCell noWrap>联系电话</StructuredListCell>
<StructuredListCell>{info.phone}</StructuredListCell>
</StructuredListRow>
<StructuredListRow>
<StructuredListCell noWrap>宿舍地址</StructuredListCell>
<StructuredListCell><BlockRoom b={info.block} r={info.room} /></StructuredListCell>
</StructuredListRow>
<StructuredListRow>
<StructuredListCell noWrap>账号</StructuredListCell>
<StructuredListCell>{info.account}</StructuredListCell>
</StructuredListRow>
<StructuredListRow>
<StructuredListCell noWrap>运营商</StructuredListCell>
<StructuredListCell><WtsISP i={info.isp} /></StructuredListCell>
</StructuredListRow>
</StructuredListBody>
</StructuredList>
<p style="font-size:small;margin-bottom:1rem;">
该信息用于我们提供上门服务,如有变化请修改:
</p>
<Button on:click={() => (window.location.href = '/me/update')}>修改信息</Button>
</RetroCard>
{/await}
{#if IsOperator(info.access)}
<br />
<br />
<br />
<RetroCard style="margin-bottom:1rem;">
<h2>网维操作</h2>
<p>在这里进入网维后台系统</p>
<br />
<ButtonSet stacked>
<Button on:click={() => (window.location.href = '/op')}>进入后台</Button>
{#if IsAdmin(info.access)}
<Button kind="ghost" on:click={() => (window.location.href = '/admin')}
>进入管理后台</Button
>
{/if}
</ButtonSet>
</RetroCard>
{/if}
</div>
<br />
<br />
<br />
<RetroCard>
<Accordion size="xl">
<AccordionItem title="设置">
<div>
<!-- <Theme bind:theme persist persistKey="__carbon-theme" /> -->
<Toggle labelText="深色模式(暂时用不了)" disabled />
</div>
</AccordionItem>
<AccordionItem title="联系我们">
<p>如果您对网维的服务或本系统有任何意见或建议,请尽管联系我们!我们非常重视您的建议。</p>
<br />
<p>ZSC学生网络支撑QQ群:123123123</p>
<br />
<p>科长QQ:</p>
<br />
<p>科长微信/电话:</p>
<br />
</AccordionItem>
<AccordionItem title="关于">
<a href="/about">关于网络维护科</a>
<br />
<br />
<hr />
<br />
<br />
<p>作者paakaauuxx</p>
<br />
<p>前端框架SvelteKit</p>
<br />
<p>UI框架Carbon Components Svelte</p>
<br />
<p>后端框架Go(Echo)+PostgreSQL(sqlc)</p>
<br />
<p>ZSCNetworkSupport 版权所有</p>
<br />
<!-- //TODO: 源码的地址-->
<p>Under AGPLv3,<a href="/">源代码</a></p>
</AccordionItem>
</Accordion>
</RetroCard>
<NotificationQueue bind:this={q} />

View File

@@ -0,0 +1,321 @@
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
import {
TextInput,
RadioButtonGroup,
RadioButton,
Select,
SelectItem,
SelectItemGroup,
Button,
Checkbox,
ComposedModal,
ModalBody,
ModalFooter,
ModalHeader,
NotificationQueue,
Loading
} from 'carbon-components-svelte';
import type { ChangeProfileReq } from '$lib/types/apiRequest';
import { invalidState } from '$lib/types/invalidState.svelte';
import { ChangeProfile } from '$lib/api';
import { onMount } from 'svelte';
import { Guard, CheckAndGetJWT } from '$lib/jwt';
import { IsUser } from '$lib/types/enum';
onMount(() => Guard(IsUser));
let notLoading: boolean = $state(true);
let account = new invalidState();
let phone = new invalidState();
let block = new invalidState();
let room = new invalidState();
//模态框状态变量
let checked = $state(false);
let open = $state(false);
let req = $state({
block: '0'
} as unknown as ChangeProfileReq);
let q: NotificationQueue;
//检查输入合法性
function check(): boolean {
let ok = false;
// 重置所有无效状态
account.reset();
phone.reset();
block.reset();
room.reset();
// 然后校园网账号和手机号是中国大陆的11位手机号码
const phoneRegex = /^1[3-9]\d{9}$/;
account.assert(
req.isp === 'others' || phoneRegex.test(req.account),
'校园网账号应为有效的11位手机号'
);
account.assert(req.isp !== 'others' || req.account.length > 0, '请输入您的校园网账号');
account.assert(req.isp !== 'others' || req.account.length <= 15, '校园网账号不能超过15个字符');
phone.assert(phoneRegex.test(req.phone), '联系电话应为有效的11位手机号');
// 接着宿舍楼不能为空且房间号不能超过5个字符且不能为空
block.assert(req.block !== '0', '请选择宿舍楼');
room.assert(req.room.length > 0, '房间号不能为空');
room.assert(
req.block === 'other' || /^[0-9]{1,4}$/.test(req.room),
'请填写一个5位以内的纯数字...'
);
//最后,总结断言结果
if (account.notOK || phone.notOK || block.notOK || room.notOK || req.isp === undefined) {
ok = false;
} else {
ok = true;
}
return ok;
}
// 在不合法时跳转到对应的地方以便用户修改
function jump() {
if (account.notOK) {
document.getElementById('account')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (phone.notOK) {
document.getElementById('phone')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (block.notOK) {
document.getElementById('block')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (room.notOK) {
document.getElementById('room')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
if (req.isp === undefined) {
document.getElementById('isp')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
q.add({
kind: 'error',
title: '请选择校园卡运营商',
timeout: 3000
});
}
}
async function submit() {
req.who = CheckAndGetJWT('parsed').openid;
open = false;
checked = false;
notLoading = false;
try {
const res = await ChangeProfile(req);
notLoading = true;
if (!res.success) {
throw new Error(res.msg || '修改信息失败.........');
}
q.add({
kind: 'success',
title: '修改成功',
timeout: 3000
});
setTimeout(() => {
window.location.href = '/me';
}, 2500);
} catch (e: any) {
notLoading = true;
console.error('register fail:', e);
const errMsg = e.response?.data?.msg || e.message || '未知错误';
q.add({
kind: 'error',
title: '修改失败',
subtitle: errMsg + ',请重试',
timeout: 5000
});
}
}
</script>
<h1>修改个人信息</h1>
<br />
<hr />
<br />
<p>
您的个人信息被我们用于提供上门维修服务,如果信息有更新的,可以在这里修改。但是如果需要修改学号或者姓名则不能在这里修改,请联系我们手动修改。
</p>
<br />
<RadioButtonGroup
id="isp"
legendText="请选择您校园卡的运营商"
bind:selected={req.isp}
required={true}
>
<RadioButton labelText="电信" value="telecom" />
<RadioButton labelText="联通" value="unicom" />
<RadioButton labelText="移动" value="mobile" />
<RadioButton labelText="其它" value="others" />
</RadioButtonGroup>
<br />
<br />
<TextInput
id="account"
labelText="校园网账号"
placeholder="请输入您校园卡的手机号..."
bind:value={req.account}
invalid={account.notOK}
invalidText={account.txt}
/>
<br />
<br />
<hr />
<br />
<br />
<TextInput
id="phone"
labelText="电话"
placeholder="请输入您的联系电话..."
bind:value={req.phone}
invalid={phone.notOK}
invalidText={phone.txt}
/>
<br />
<br />
<Select
id="block"
labelText="宿舍楼"
bind:selected={req.block}
invalid={block.notOK}
invalidText={block.txt}
>
<SelectItem value="0" text="请选择您的所住的宿舍楼..." disabled hidden />
<SelectItemGroup label="凤翔宿舍区">
<SelectItem value="1" text="1栋" />
<SelectItem value="2" text="2栋" />
<SelectItem value="3" text="3栋" />
<SelectItem value="4" text="4栋" />
<SelectItem value="5" text="5栋" />
<SelectItem value="6" text="6栋" />
</SelectItemGroup>
<SelectItemGroup label="北门宿舍区">
<SelectItem value="7" text="7栋" />
<SelectItem value="8" text="8栋" />
<SelectItem value="9" text="9栋" />
<SelectItem value="10" text="10栋" />
<SelectItem value="11" text="11栋" />
</SelectItemGroup>
<SelectItemGroup label="东门宿舍区">
<SelectItem value="12" text="12栋" />
<SelectItem value="13" text="13栋" />
<SelectItem value="14" text="14栋" />
<SelectItem value="15" text="15栋" />
<SelectItem value="20" text="20栋" />
<SelectItem value="21" text="21栋" />
<SelectItem value="22" text="22栋" />
</SelectItemGroup>
<SelectItemGroup label="歧头山宿舍区">
<SelectItem value="16" text="16栋" />
<SelectItem value="17" text="17栋" />
<SelectItem value="18" text="18栋" />
<SelectItem value="19" text="19栋" />
</SelectItemGroup>
<SelectItemGroup label="香晖苑">
<SelectItem value="XHA" text="香晖苑-A栋" />
<SelectItem value="XHB" text="香晖苑-B栋" />
<SelectItem value="XHC" text="香晖苑-C栋" />
<SelectItem value="XHD" text="香晖苑-D栋" />
</SelectItemGroup>
<SelectItemGroup label="朝晖苑">
<SelectItem value="ZH" text="朝晖苑" />
</SelectItemGroup>
<SelectItemGroup label="其它">
<SelectItem value="other" text="其它" />
</SelectItemGroup>
</Select>
<br />
<br />
<TextInput
id="room"
labelText="房间号"
placeholder="请输入您所住的房间..."
bind:value={req.room}
invalid={room.notOK}
invalidText={room.txt}
/>
<br />
<br />
<Button
on:click={() => {
check() ? (open = true) : jump();
}}>提交注册</Button
>
<ComposedModal
bind:open
on:close={() => {
((open = false), (checked = false));
}}
class="mobile-floating-modal"
>
<ModalHeader title="确认您的信息" />
<ModalBody hasForm>
<Checkbox labelText="我确认所填信息准确无误,真实有效,且未盗用他人信息" bind:checked />
<br />
<br />
</ModalBody>
<ModalFooter>
<Button kind="secondary" on:click={() => ((open = false), (checked = false))}>取消</Button>
<Button
kind="primary"
disabled={!checked}
on:click={() => {
submit();
}}>确认并提交</Button
>
</ModalFooter>
</ComposedModal>
<NotificationQueue bind:this={q} />
<Loading active={!notLoading} />
<NotificationQueue bind:this={q} />
<Loading active={!notLoading} />
<style>
:global(.mobile-floating-modal.bx--modal) {
@media (max-width: 672px) {
display: flex !important;
align-items: center !important;
justify-content: center !important;
/* 确保背景色存在 (Carbon默认有但为了保险起见) */
background-color: rgba(22, 22, 22, 0.5) !important;
}
}
:global(.mobile-floating-modal .bx--modal-container) {
@media (max-width: 672px) {
width: 90% !important;
max-width: 400px !important;
height: auto !important;
max-height: 85vh !important;
position: relative !important;
margin: 0 !important;
top: auto !important;
left: auto !important;
transform: none !important;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4) !important;
}
}
:global(.mobile-floating-modal .bx--modal-content) {
@media (max-width: 672px) {
max-height: 60vh !important;
overflow-y: auto !important;
margin-bottom: 0 !important;
}
}
</style>

View File

@@ -0,0 +1,176 @@
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
import { onMount } from 'svelte';
import { CheckAndGetJWT, Guard } from '$lib/jwt';
import { IsOperator, ZoneMap, ZoneToBlock, type WtsBlock, type WtsZone } from '$lib/types/enum';
import { TicketOverview } from '$lib/api';
import { NotificationQueue, Tile } from 'carbon-components-svelte';
import { Radio } from 'carbon-icons-svelte';
import { criteria, type Criteria } from '$lib/states/ticketCriteriaSearch.svelte';
import { PriorityMap, type WtsPriority } from '$lib/types/enum';
import { CategoryMap, type WtsCategory } from '$lib/types/enum';
import { ISPMap, type WtsISP } from '$lib/types/enum';
import { goto } from '$app/navigation';
let name: string = $state('网维成员');
onMount(() => Guard(IsOperator));
onMount(() => {
name = CheckAndGetJWT('parsed').name;
});
onMount(() => getTicketOverview());
let countByBlock: Record<WtsBlock, number> = $state(undefined);
let countByZone: Record<WtsZone, number> = $state(undefined);
const zoneDisplayOrder: WtsZone[] = ['FX', 'BM', 'DM', 'QT', 'XHAB', 'XHCD', 'ZH'];
function zoneTone(count: number | undefined) {
const value = count ?? 0;
if (value === 0) return 'none';
if (value <= 5) return 'green';
if (value <= 15) return 'yellow';
return 'red';
}
async function getTicketOverview() {
try {
let res = await TicketOverview();
if (!res.success) {
throw new Error(res.msg || '获取片区总览失败');
}
countByBlock = res.count_by_block;
parseTicketCount();
} catch (e: any) {
const errMsg = e.response?.data?.msg || e.message || '未知错误';
q.add({
kind: 'error',
title: '获取片区总览失败',
subtitle: errMsg + ',请重试',
timeout: 5000
});
}
}
let q: NotificationQueue;
function parseTicketCount() {
const zoneCounts: Record<WtsZone, number> = {} as Record<WtsZone, number>;
(Object.keys(ZoneToBlock) as WtsZone[]).forEach((zone) => {
const blocks = ZoneToBlock[zone];
zoneCounts[zone] = blocks.reduce((acc, block) => {
return acc + (countByBlock?.[block] ?? 0);
}, 0);
});
countByZone = zoneCounts;
}
function search(zone: WtsZone): Criteria {
return {
r: {
scope: 'active',
issuer: undefined,
block: ZoneToBlock[zone],
status: ['fresh','scheduled','escalated','delay'],
priority: Object.keys(PriorityMap) as WtsPriority[],
category: Object.keys(CategoryMap) as WtsCategory[],
isp: Object.keys(ISPMap) as WtsISP[],
newer_than: undefined,
older_than: undefined
},
_order: 'priority',
_floor: null,
_blocks_in_zone: [zone],
_view_today_scheduled: true
} as Criteria;
}
function jumpSearch(zone: WtsZone){
Object.assign(criteria, search(zone));
goto('/op/tickets');
}
</script>
<h1>报修操作后台</h1>
<br />
<hr />
<br />
<p>
你好,{name}!今天修了多少单?
</p>
<br />
<br />
<br />
<br />
<br />
<h2>片区总览</h2>
<p>每个片区的报修单数量</p>
<br />
<div class="zone-tiles">
{#each zoneDisplayOrder as zone}
{#if typeof countByZone?.[zone] !== 'undefined'}
<Tile class={`zone-tile zone-${zoneTone(countByZone?.[zone])}`} on:click={() => jumpSearch(zone)}>
<span class="zone-name">{ZoneMap[zone]}</span>
<span class="zone-count">{countByZone?.[zone] ?? 0}</span>
</Tile>
{:else}
<Tile class="zone-tile zone-none" on:click={() => jumpSearch(zone)}>
<span class="zone-name">{ZoneMap[zone]}</span>
<span class="zone-count">0</span>
</Tile>
{/if}
{/each}
</div>
<NotificationQueue bind:this={q} />
<style>
.zone-tiles {
display: flex;
flex-direction: column;
gap: 0;
}
:global(.zone-tile) {
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid #c6c6c6;
padding: 0.5rem 0.75rem;
}
.zone-name {
flex: 1;
}
.zone-count {
text-align: right;
margin-left: auto;
min-width: 2ch;
font-variant-numeric: tabular-nums;
color: var(--cds-text-secondary, #525252);
font-weight: 600;
}
:global(.zone-none) {
background-color: #ffffff;
}
:global(.zone-green) {
background-color: #d9fbdb; /* Green 10 */
}
:global(.zone-yellow) {
background-color: #fcf4d6; /* Yellow 10 */
}
:global(.zone-red) {
background-color: #fff1f1; /* Red 10 */
}
</style>

View File

@@ -0,0 +1,5 @@
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>

View File

@@ -0,0 +1,416 @@
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
import { CheckAndGetJWT, Guard } from '$lib/jwt';
import { IsAdmin, IsOperator, PriorityMap, CategoryMap, ISPMap } from '$lib/types/enum';
import { onMount } from 'svelte';
import {
RadioButtonGroup,
RadioButton,
Checkbox,
CheckboxGroup,
DatePicker,
DatePickerInput,
TimePicker,
Grid,
Row,
Column,
NumberInput,
Button,
NotificationQueue,
Toggle
} from 'carbon-components-svelte';
import type { FilterTicketsReq } from '$lib/types/apiRequest';
import { ZoneMap, ZoneToBlock, StatusMap, type WtsZone } from '$lib/types/enum';
import { RFC3339 } from '$lib/types/RFC3339';
import { startOfDay, endOfDay } from 'date-fns';
import { criteria } from '$lib/states/ticketCriteriaSearch.svelte';
import { goto } from '$app/navigation';
import type { WtsStatus, WtsPriority, WtsCategory, WtsISP } from '$lib/types/enum';
onMount(() => Guard(IsOperator));
let req = $state(criteria.r as FilterTicketsReq);
let zoneSelected: WtsZone[] = $state(criteria._blocks_in_zone ?? []);
let order: 'priority' | 'newest' | 'oldest' = $state(criteria._order ?? 'priority');
let floor: number | null = $state(criteria._floor ?? null);
let viewTodayScheduled = $state(criteria._view_today_scheduled ?? false);
let isScheduledSelected = $state(req.status?.includes('scheduled') ?? false);
// $effect(() => {
// $inspect(req);
// $inspect(zoneSelected);
// $inspect(order);
// $inspect(floor);
// });
let onDateChange = (which: 'newer' | 'older') => (event: CustomEvent) => {
const { dateStr } = event.detail;
if (dateStr) {
const date = new Date(dateStr);
const adjustedDate = which === 'newer' ? startOfDay(date) : endOfDay(date);
const rfcDate = RFC3339(adjustedDate);
if (which === 'newer') {
req.newer_than = rfcDate;
} else {
req.older_than = rfcDate;
}
}
};
let q: NotificationQueue;
function search() {
req.block = zoneSelected.flatMap((zone) => ZoneToBlock[zone as WtsZone]);
criteria.r = $state.snapshot(req);
criteria._blocks_in_zone = $state.snapshot(zoneSelected) as WtsZone[];
criteria._order = $state.snapshot(order);
criteria._floor = $state.snapshot(floor);
criteria._view_today_scheduled = $state.snapshot(viewTodayScheduled);
console.log(criteria);
setTimeout(() => goto('/op/tickets'), 500);
}
const allZones = Object.keys(ZoneMap) as WtsZone[];
const allStatuses = IsAdmin(CheckAndGetJWT('parsed').access)
? (Object.keys(StatusMap) as WtsStatus[])
: (Object.keys(StatusMap).filter(
(status) => status !== 'solved' && status !== 'canceled'
) as WtsStatus[]);
const allPriorities = Object.keys(PriorityMap) as WtsPriority[];
const allCategories = Object.keys(CategoryMap) as WtsCategory[];
const allISPs = Object.keys(ISPMap) as WtsISP[];
const zoneOptions = [
'FX',
'BM',
'DM',
'QT',
'XHAB',
'XHCD',
'ZH',
'other'
] as const satisfies readonly WtsZone[];
const statusOptionsAdmin = [
'fresh',
'scheduled',
'delay',
'escalated',
'solved',
'canceled'
] as const satisfies readonly WtsStatus[];
const statusOptionsUser = [
'fresh',
'scheduled',
'delay',
'escalated'
] as const satisfies readonly WtsStatus[];
const statusOptions: readonly WtsStatus[] = IsAdmin(CheckAndGetJWT('parsed').access)
? statusOptionsAdmin
: statusOptionsUser;
const priorityOptions = [
'highest',
'assigned',
'mainline',
'normal',
'in-passing',
'least'
] as const satisfies readonly WtsPriority[];
const categoryOptions = [
'first-install',
'low-speed',
'ip-or-device',
'client-or-account',
'others'
] as const satisfies readonly WtsCategory[];
const ispOptions = ['telecom', 'unicom', 'mobile', 'others'] as const satisfies readonly WtsISP[];
function allSelected(selected: readonly string[] | null | undefined, options: readonly string[]) {
if (!selected) return false;
return options.length > 0 && options.every((o) => selected.includes(o));
}
function uniq<T>(arr: T[]) {
return Array.from(new Set(arr));
}
function sameArray<T>(a: readonly T[], b: readonly T[]) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
return true;
}
$effect(() => {
req.status ??= [];
req.priority ??= [];
req.category ??= [];
req.isp ??= [];
const nextZone = uniq(zoneSelected).filter((z) =>
(zoneOptions as readonly WtsZone[]).includes(z)
);
if (!sameArray(zoneSelected, nextZone)) zoneSelected = nextZone;
const nextStatus = uniq(req.status).filter((s) =>
(statusOptions as readonly WtsStatus[]).includes(s)
);
if (!sameArray(req.status, nextStatus)) req.status = nextStatus;
const nextPriority = uniq(req.priority).filter((p) =>
(priorityOptions as readonly WtsPriority[]).includes(p)
);
if (!sameArray(req.priority, nextPriority)) req.priority = nextPriority;
const nextCategory = uniq(req.category).filter((c) =>
(categoryOptions as readonly WtsCategory[]).includes(c)
);
if (!sameArray(req.category, nextCategory)) req.category = nextCategory;
const nextIsp = uniq(req.isp).filter((i) => (ispOptions as readonly WtsISP[]).includes(i));
if (!sameArray(req.isp, nextIsp)) req.isp = nextIsp;
});
$effect(() =>{
isScheduledSelected = req.status?.includes('scheduled') ?? false;
})
</script>
<h1>报修单检索</h1>
<br />
<hr />
<br />
<p>选择您需要检索报修工单的条件</p>
{#if IsAdmin(CheckAndGetJWT('parsed').access)}
<br />
<RadioButtonGroup id="scope" legendText="范围" bind:selected={req.scope} required={true}>
<RadioButton labelText="只看活跃的" value="active" />
<RadioButton labelText="所有报修单" value="all" />
</RadioButtonGroup>
{/if}
<br />
<DatePicker datePickerType="single" on:change={onDateChange('newer')}>
<DatePickerInput labelText="只看这之后的报修单:" placeholder="从该日开始时起" />
</DatePicker>
<br />
<DatePicker datePickerType="single" on:change={onDateChange('older')}>
<DatePickerInput labelText="只看这之前的报修单:" placeholder="从该日结束时起" />
</DatePicker>
<!-- TODO:可以选择时间 -->
<br />
<CheckboxGroup legendText="片区" id="block" bind:selected={zoneSelected} required={true}>
<Grid narrow>
<Row>
<Column sm={2} md={2} lg={4}><Checkbox value="FX" labelText={ZoneMap['FX']} /></Column>
<Column sm={2} md={2} lg={4}><Checkbox value="BM" labelText={ZoneMap['BM']} /></Column>
<Column sm={2} md={2} lg={4}><Checkbox value="DM" labelText={ZoneMap['DM']} /></Column>
<Column sm={2} md={2} lg={4}><Checkbox value="QT" labelText={ZoneMap['QT']} /></Column>
<Column sm={2} md={2} lg={4}><Checkbox value="XHAB" labelText={ZoneMap['XHAB']} /></Column>
<Column sm={2} md={2} lg={4}><Checkbox value="XHCD" labelText={ZoneMap['XHCD']} /></Column>
<Column sm={2} md={2} lg={4}><Checkbox value="ZH" labelText={ZoneMap['ZH']} /></Column>
<Column sm={2} md={2} lg={4}><Checkbox value="other" labelText={ZoneMap['other']} /></Column>
</Row>
</Grid>
</CheckboxGroup>
<div class="toggle">
<Toggle
size="sm"
toggled={allSelected(zoneSelected, zoneOptions)}
on:toggle={(e) => {
const { toggled } = e.detail as { toggled: boolean };
zoneSelected = toggled ? [...zoneOptions] : [];
}}
>
<span slot="labelA">全不选</span>
<span slot="labelB">全选</span>
</Toggle>
</div>
<br />
<br />
<CheckboxGroup legendText="状态" id="status" bind:selected={req.status} required={true}>
<Grid narrow>
<Row>
<Column sm={2} md={2} lg={4}><Checkbox value="fresh" labelText={StatusMap['fresh']} /></Column
>
<Column sm={2} md={2} lg={4}
><Checkbox value="scheduled" labelText={StatusMap['scheduled']} /></Column
>
<Column sm={2} md={2} lg={4}><Checkbox value="delay" labelText={StatusMap['delay']} /></Column
>
<Column sm={2} md={2} lg={4}
><Checkbox value="escalated" labelText={StatusMap['escalated']} /></Column
>
{#if IsAdmin(CheckAndGetJWT('parsed').access)}
<Column sm={2} md={2} lg={4}
><Checkbox value="solved" labelText={StatusMap['solved']} /></Column
>
<Column sm={2} md={2} lg={4}
><Checkbox value="canceled" labelText={StatusMap['canceled']} /></Column
>
{/if}
</Row>
</Grid>
</CheckboxGroup>
<div class="toggle">
<Toggle
size="sm"
toggled={allSelected(req.status, statusOptions)}
on:toggle={(e) => {
const { toggled } = e.detail as { toggled: boolean };
req.status = toggled ? [...statusOptions] : [];
}}
>
<span slot="labelA">全不选</span>
<span slot="labelB">全选</span>
</Toggle>
</div>
<br />
<br />
<CheckboxGroup legendText="优先级" id="priority" bind:selected={req.priority} required={true}>
<Grid narrow>
<Row>
<Column sm={2} md={2} lg={4}><Checkbox value="highest" labelText="最高" /></Column>
<Column sm={2} md={2} lg={4}
><Checkbox value="assigned" labelText={PriorityMap['assigned']} /></Column
>
<Column sm={2} md={2} lg={4}
><Checkbox value="mainline" labelText={PriorityMap['mainline']} /></Column
>
<Column sm={2} md={2} lg={4}
><Checkbox value="normal" labelText={PriorityMap['normal']} /></Column
>
<Column sm={2} md={2} lg={4}
><Checkbox value="in-passing" labelText={PriorityMap['in-passing']} /></Column
>
<Column sm={2} md={2} lg={4}
><Checkbox value="least" labelText={PriorityMap['least']} /></Column
>
</Row>
</Grid>
</CheckboxGroup>
<div class="toggle">
<Toggle
size="sm"
toggled={allSelected(req.priority, priorityOptions)}
on:toggle={(e) => {
const { toggled } = e.detail as { toggled: boolean };
req.priority = toggled ? [...priorityOptions] : [];
}}
>
<span slot="labelA">全不选</span>
<span slot="labelB">全选</span>
</Toggle>
</div>
<br />
<br />
<CheckboxGroup legendText="类型" id="category" bind:selected={req.category} required={true}>
<Grid narrow>
<Row>
<Column sm={2} md={2} lg={4}
><Checkbox value="first-install" labelText={CategoryMap['first-install']} /></Column
>
<Column sm={2} md={2} lg={4}
><Checkbox value="client-or-account" labelText={CategoryMap['client-or-account']} /></Column
>
<Column sm={2} md={2} lg={4}
><Checkbox value="ip-or-device" labelText={CategoryMap['ip-or-device']} /></Column
>
<Column sm={2} md={2} lg={4}
><Checkbox value="low-speed" labelText={CategoryMap['low-speed']} /></Column
>
<Column sm={2} md={2} lg={4}
><Checkbox value="others" labelText={CategoryMap['others']} /></Column
>
</Row>
</Grid>
</CheckboxGroup>
<div class="toggle">
<Toggle
size="sm"
toggled={allSelected(req.category, categoryOptions)}
on:toggle={(e) => {
const { toggled } = e.detail as { toggled: boolean };
req.category = toggled ? [...categoryOptions] : [];
}}
>
<span slot="labelA">全不选</span>
<span slot="labelB">全选</span>
</Toggle>
</div>
<br />
<br />
<CheckboxGroup legendText="运营商" id="isp" bind:selected={req.isp} required={true}>
<Grid narrow>
<Row>
<Column sm={2} md={2} lg={4}
><Checkbox value="telecom" labelText={ISPMap['telecom']} /></Column
>
<Column sm={2} md={2} lg={4}><Checkbox value="unicom" labelText={ISPMap['unicom']} /></Column>
<Column sm={2} md={2} lg={4}><Checkbox value="mobile" labelText={ISPMap['mobile']} /></Column>
<Column sm={2} md={2} lg={4}><Checkbox value="others" labelText={ISPMap['others']} /></Column>
<Column sm={2} md={2} lg={4}
><Checkbox value="broadnet" labelText={ISPMap['broadnet']} hidden /></Column
>
<!--暂时藏起来-->
</Row>
</Grid>
</CheckboxGroup>
<div class="toggle">
<Toggle
size="sm"
toggled={allSelected(req.isp, ispOptions)}
on:toggle={(e) => {
const { toggled } = e.detail as { toggled: boolean };
req.isp = toggled ? [...ispOptions] : [];
}}
>
<span slot="labelA">全不选</span>
<span slot="labelB">全选</span>
</Toggle>
</div>
<br />
<br />
<hr />
<br />
<RadioButtonGroup id="order" legendText="排序" bind:selected={order} required={true}>
<RadioButton labelText="优先级从高到低" value="priority" />
<RadioButton labelText="时间从新到旧" value="newest" />
<RadioButton labelText="时间从旧到新" value="oldest" />
</RadioButtonGroup>
<br />
<br />
<NumberInput
min={1}
max={20}
step={1}
bind:value={floor}
allowEmpty={true}
allowDecimal={false}
labelText="只看如下楼层(不填代表查看全部楼层)"
/>
<br />
<br />
<Toggle labelText="只显示预约在今天的预约单" bind:toggled={viewTodayScheduled} disabled={!isScheduledSelected}/>
<br />
<br />
<Button on:click={search}>搜索</Button>
<NotificationQueue bind:this={q} />

View File

@@ -0,0 +1,128 @@
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
import { CheckAndGetJWT, Guard } from '$lib/jwt';
import { IsAdmin, IsOperator, PriorityMap, CategoryMap } from '$lib/types/enum';
import { onMount } from 'svelte';
import { Button, NotificationQueue } from 'carbon-components-svelte';
import Return from 'carbon-icons-svelte/lib/Return.svelte';
import type { FilterTicketsReq } from '$lib/types/apiRequest';
import type { Ticket } from '$lib/types/apiResponse';
import { FilterTickets } from '$lib/api';
import { criteria } from '$lib/states/ticketCriteriaSearch.svelte';
import OperatorTicket from '$lib/components/Ticket/OperatorTicket.svelte';
import TicketDetail from '$lib/components/TraceDetail/TicketDetail.svelte';
import { TicketModal } from '$lib/states/ticketDetails.svelte';
import type { RFC3339 } from '$lib/types/RFC3339';
let q: NotificationQueue;
let tickets = $state([] as Ticket[]);
let ticketEmpty = $state(false);
let ok = $state(false);
onMount(() => (Guard(IsOperator), fetchTickets1()));
function toMs(rfc3339: RFC3339 | string | undefined) {
const t = rfc3339 ? Date.parse(rfc3339) : NaN;
return Number.isFinite(t) ? t : 0;
}
/** 从房间号字符串推导楼层109 -> 1, 1033 -> 10 */
function getFloorFromRoom(room: string | undefined | null): number | null {
//console.log('getFloorFromRoom', { room });
if (!room) return null;
const digits = String(room).match(/\d+/g)?.join('') ?? '';
if (digits.length < 3 || digits.length > 4) return null;
const roomNum = Number.parseInt(digits, 10);
if (!Number.isFinite(roomNum)) return null;
const floor = Math.floor(roomNum / 100);
//console.log('getFloorFromRoom', { room, digits, roomNum, floor });
return Number.isFinite(floor) ? floor : null;
}
function postProcess() {
if (ticketEmpty) {
return;
}
if (criteria._order === 'newest') {
tickets = [...tickets].sort((a, b) => toMs(b.submitted_at) - toMs(a.submitted_at));
}
if (criteria._order === 'oldest') {
tickets = [...tickets].sort((a, b) => toMs(a.submitted_at) - toMs(b.submitted_at));
}
if (criteria._floor !== null && criteria._floor !== undefined && criteria._floor !== 0) {
tickets = tickets.filter((t) => getFloorFromRoom(t?.issuer?.room) === criteria._floor);
}
if (criteria._view_today_scheduled) {
const todayStart = new Date().setHours(0, 0, 0, 0);
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));
}
ok = true;
ticketEmpty = tickets.length === 0;
return;
}
async function fetchTickets1() {
ok = false;
try {
let req: FilterTicketsReq = {} as FilterTicketsReq;
Object.assign(req, criteria.r);
let res = await FilterTickets(req);
if (!res.success) {
throw new Error(res.msg || '获取工单列表失败');
}
tickets = res.tickets;
if (!tickets) {
ticketEmpty = true;
}
postProcess();
} catch (e: any) {
const errMsg = e.response?.data?.msg || e.message || '未知错误';
q.add({
kind: 'error',
title: '获取工单列表失败',
subtitle: errMsg,
timeout: 3000
});
return;
}
}
async function refreshTickets(){
await fetchTickets1();
}
</script>
<h1>报修单检索结果</h1>
<br />
<hr />
<br />
<p>按照您提供的条件,获得的检索结果。</p>
<br />
<div
style="display: flex; justify-content: flex-end; transform: translate(-17px,0px); margin-bottom: 15px;"
>
<Button href="/op/ticket_search">修改条件<Return /></Button>
</div>
{#if !ok}
<p>处理中,请稍等...</p>
{/if}
{#if ticketEmpty === false}
{#each tickets as t}
<OperatorTicket {t} />
{/each}
{:else}
<span>没有找到符合条件的报修单。</span>
{/if}
<TicketDetail t={TicketModal.NowTicket} bind:open={TicketModal.Opened} src={TicketModal.SRC} onTicketChanged={refreshTickets}/>
<NotificationQueue bind:this={q} />

View File

@@ -0,0 +1,366 @@
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
import {
TextInput,
RadioButtonGroup,
RadioButton,
Select,
SelectItem,
SelectItemGroup,
Button,
Checkbox,
ComposedModal,
ModalBody,
ModalFooter,
ModalHeader,
NotificationQueue,
Loading
} from 'carbon-components-svelte';
import type { RegisterReq } from '$lib/types/apiRequest';
import { invalidState } from '$lib/types/invalidState.svelte';
import { Register } from '$lib/api';
import { TheLastPage } from '$lib/states/theLastPage.svelte';
import { onMount } from 'svelte';
import { IsUnregistered } from '$lib/types/enum';
import { Guard } from '$lib/jwt';
let q: NotificationQueue;
let notLoading: boolean = $state(true);
onMount(() => Guard(IsUnregistered));
//注册请求体状态变量
let req = $state({
block: '0'
} as unknown as RegisterReq);
//模态框状态变量
let checked = $state(false);
let open = $state(false);
//无效状态变量
let name = new invalidState();
let sid = new invalidState();
let account = new invalidState();
let phone = new invalidState();
let block = new invalidState();
let room = new invalidState();
//检查输入合法性
function check(): boolean {
let ok = false;
// 重置所有无效状态
name.reset();
sid.reset();
account.reset();
phone.reset();
block.reset();
room.reset();
// 首先姓名学号不能超过15个字符且不能为空
name.assert(req.name.length > 0, '姓名不能为空');
name.assert(req.name.length <= 15, '姓名不能超过15个字符');
sid.assert(req.sid.length > 0, '学号不能为空');
sid.assert(req.sid.length <= 15, '学号不能超过15个字符');
// 然后校园网账号和手机号是中国大陆的11位手机号码
const phoneRegex = /^1[3-9]\d{9}$/;
account.assert(
req.isp === 'others' || phoneRegex.test(req.account),
'校园网账号应为有效的11位手机号'
);
account.assert(req.isp !== 'others' || req.account.length > 0, '请输入您的校园网账号');
account.assert(req.isp !== 'others' || req.account.length <= 15, '校园网账号不能超过15个字符');
phone.assert(phoneRegex.test(req.phone), '联系电话应为有效的11位手机号');
// 接着宿舍楼不能为空且房间号不能超过5个字符且不能为空
block.assert(req.block !== '0', '请选择宿舍楼');
room.assert(req.room.length > 0, '房间号不能为空');
room.assert(
req.block === 'other' || /^[0-9]{1,4}$/.test(req.room),
'请填写一个5位以内的纯数字...'
);
//最后,总结断言结果
if (
name.notOK ||
sid.notOK ||
account.notOK ||
phone.notOK ||
block.notOK ||
room.notOK ||
req.isp === undefined
) {
ok = false;
} else {
ok = true;
}
return ok;
}
// 在不合法时跳转到对应的地方以便用户修改
function jump() {
if (name.notOK) {
document.getElementById('name')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (sid.notOK) {
document.getElementById('sid')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (account.notOK) {
document.getElementById('account')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (phone.notOK) {
document.getElementById('phone')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (block.notOK) {
document.getElementById('block')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (room.notOK) {
document.getElementById('room')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
if (req.isp === undefined) {
document.getElementById('isp')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
q.add({
kind: 'error',
title: '请选择运营商',
subtitle: '请选择您校园卡的运营商',
timeout: 5000
});
}
}
async function submit() {
open = false;
checked = false;
notLoading = false;
try {
const res = await Register(req);
notLoading = true;
if (!res.success) {
throw new Error(res.msg || '注册失败.........');
}
q.add({
kind: 'success',
title: '注册成功',
timeout: 3000
});
setTimeout(() => {
TheLastPage.Write('/');
window.location.href = '/login';
}, 3900);
} catch (e: any) {
notLoading = true;
console.error('register fail:', e);
const errMsg = e.response?.data?.msg || e.message || '未知错误';
q.add({
kind: 'error',
title: '注册失败',
subtitle: errMsg + ',请重试',
timeout: 5000
});
}
}
</script>
<h1>注册</h1>
<br />
<hr />
<br />
<p style="font-size: 15px;">
<i
>您似乎还没有注册。在使用该系统之前,请先注册您的个人信息。这些信息将被用于我们上门为您维修网络问题。</i
>
</p>
<br />
<TextInput
id="name"
labelText="姓名"
placeholder="请输入您的真实姓名..."
bind:value={req.name}
invalid={name.notOK}
invalidText={name.txt}
/>
<br />
<br />
<TextInput
id="sid"
labelText="学号"
placeholder="请输入您的学号..."
bind:value={req.sid}
invalid={sid.notOK}
invalidText={sid.txt}
/>
<br />
<br />
<RadioButtonGroup
id="isp"
legendText="请选择您校园卡的运营商"
bind:selected={req.isp}
required={true}
>
<RadioButton labelText="电信" value="telecom" />
<RadioButton labelText="联通" value="unicom" />
<RadioButton labelText="移动" value="mobile" />
<RadioButton labelText="其它" value="others" />
</RadioButtonGroup>
<br />
<br />
<TextInput
id="account"
labelText="校园网账号"
placeholder="请输入您校园卡的手机号..."
bind:value={req.account}
invalid={account.notOK}
invalidText={account.txt}
/>
<br />
<br />
<hr />
<br />
<br />
<TextInput
id="phone"
labelText="电话"
placeholder="请输入您的联系电话..."
bind:value={req.phone}
invalid={phone.notOK}
invalidText={phone.txt}
/>
<br />
<br />
<Select
id="block"
labelText="宿舍楼"
bind:selected={req.block}
invalid={block.notOK}
invalidText={block.txt}
>
<SelectItem value="0" text="请选择您的所住的宿舍楼..." disabled hidden />
<SelectItemGroup label="凤翔宿舍区">
<SelectItem value="1" text="1栋" />
<SelectItem value="2" text="2栋" />
<SelectItem value="3" text="3栋" />
<SelectItem value="4" text="4栋" />
<SelectItem value="5" text="5栋" />
<SelectItem value="6" text="6栋" />
</SelectItemGroup>
<SelectItemGroup label="北门宿舍区">
<SelectItem value="7" text="7栋" />
<SelectItem value="8" text="8栋" />
<SelectItem value="9" text="9栋" />
<SelectItem value="10" text="10栋" />
<SelectItem value="11" text="11栋" />
</SelectItemGroup>
<SelectItemGroup label="东门宿舍区">
<SelectItem value="12" text="12栋" />
<SelectItem value="13" text="13栋" />
<SelectItem value="14" text="14栋" />
<SelectItem value="15" text="15栋" />
<SelectItem value="20" text="20栋" />
<SelectItem value="21" text="21栋" />
<SelectItem value="22" text="22栋" />
</SelectItemGroup>
<SelectItemGroup label="歧头山宿舍区">
<SelectItem value="16" text="16栋" />
<SelectItem value="17" text="17栋" />
<SelectItem value="18" text="18栋" />
<SelectItem value="19" text="19栋" />
</SelectItemGroup>
<SelectItemGroup label="香晖苑">
<SelectItem value="XHA" text="香晖苑-A栋" />
<SelectItem value="XHB" text="香晖苑-B栋" />
<SelectItem value="XHC" text="香晖苑-C栋" />
<SelectItem value="XHD" text="香晖苑-D栋" />
</SelectItemGroup>
<SelectItemGroup label="朝晖苑">
<SelectItem value="ZH" text="朝晖苑" />
</SelectItemGroup>
<SelectItemGroup label="其它">
<SelectItem value="other" text="其它" />
</SelectItemGroup>
</Select>
<br />
<br />
<TextInput
id="room"
labelText="房间号"
placeholder="请输入您所住的房间..."
bind:value={req.room}
invalid={room.notOK}
invalidText={room.txt}
/>
<br />
<br />
<br />
<Button
on:click={() => {
check() ? (open = true) : jump();
}}>提交注册</Button
>
<ComposedModal
bind:open
on:close={() => {
((open = false), (checked = false));
}}
class="mobile-floating-modal"
>
<ModalHeader title="确认您的信息" />
<ModalBody hasForm>
<Checkbox labelText="我确认所填信息准确无误,真实有效,且未盗用他人信息" bind:checked />
<br />
<br />
</ModalBody>
<ModalFooter>
<Button kind="secondary" on:click={() => ((open = false), (checked = false))}>取消</Button>
<Button
kind="primary"
disabled={!checked}
on:click={() => {
submit();
}}>确认并提交</Button
>
</ModalFooter>
</ComposedModal>
<NotificationQueue bind:this={q} />
<Loading active={!notLoading} />
<style>
:global(.mobile-floating-modal.bx--modal) {
@media (max-width: 672px) {
display: flex !important;
align-items: center !important;
justify-content: center !important;
/* 确保背景色存在 (Carbon默认有但为了保险起见) */
background-color: rgba(22, 22, 22, 0.5) !important;
}
}
:global(.mobile-floating-modal .bx--modal-container) {
@media (max-width: 672px) {
width: 90% !important;
max-width: 400px !important;
height: auto !important;
max-height: 85vh !important;
position: relative !important;
margin: 0 !important;
top: auto !important;
left: auto !important;
transform: none !important;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4) !important;
}
}
:global(.mobile-floating-modal .bx--modal-content) {
@media (max-width: 672px) {
max-height: 60vh !important;
overflow-y: auto !important;
margin-bottom: 0 !important;
}
}
</style>

View File

@@ -0,0 +1,74 @@
<script lang="ts">
import OperatorTicket from '$lib/components/Ticket/OperatorTicket.svelte';
import TicketDetail from '$lib/components/TraceDetail/TicketDetail.svelte';
import UserTicket from '$lib/components/Ticket/UserTicket.svelte';
import { sample1, sample2 } from '$lib/testData/ticket';
import { Button } from 'carbon-components-svelte';
import { TicketModal } from '$lib/states/ticketDetails.svelte';
import Contract from 'carbon-pictograms-svelte/lib/Contract.svelte';
import { onMount } from 'svelte';
import { IsUser } from '$lib/types/enum';
import { CheckAndGetJWT, Guard } from '$lib/jwt';
import type { Ticket } from '$lib/types/apiResponse';
import { GetTicket } from '$lib/api';
import { NotificationQueue } from 'carbon-components-svelte';
let q: NotificationQueue;
let tickets = $state([] as Ticket[]);
onMount(() => (Guard(IsUser), fetchTickets()));
async function fetchTickets() {
try {
let res = await GetTicket(CheckAndGetJWT('parsed').openid);
if (!res.success) {
throw new Error(res.msg || '获取报修记录失败');
}
tickets = res.tickets;
} catch (e: any) {
const errMsg = e.response?.data?.msg || e.message || '未知错误';
q.add({
kind: 'error',
title: '获取报修记录失败',
subtitle: errMsg,
timeout: 3000
});
return;
}
}
async function refreshTickets1(){
await fetchTickets();
}
</script>
<h1 style="display: flex; align-items: center;">
<span><Contract /></span>
<span style="margin-left: 8px;">报修记录</span>
</h1>
<br />
<hr />
<br />
<p>
这里将显示您提交的所有报修记录,由于各种原因,我们可能只会显示您最近几个报修单。点击单子可展开详情。
</p>
<br />
<div
style="display: flex; justify-content: flex-end; transform: translate(-17px,0px); margin-bottom: 15px;"
>
<Button href="/repair/new">提交新报修</Button>
</div>
<OperatorTicket t={sample1} />
<OperatorTicket t={sample2} />
<hr />
{#each tickets as t}
<UserTicket {t} />
{/each}
<TicketDetail t={TicketModal.NowTicket} bind:open={TicketModal.Opened} src={TicketModal.SRC} onTicketChanged={refreshTickets1}/>
<NotificationQueue bind:this={q} />

View File

@@ -0,0 +1,208 @@
<script lang="ts">
import { CheckAndGetJWT, Guard } from '$lib/jwt';
import type { NewTicketReq } from '$lib/types/apiRequest';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
import { RFC3339 } from '$lib/types/RFC3339';
import { onMount } from 'svelte';
import { IsUser } from '$lib/types/enum';
import {
DatePicker,
DatePickerInput,
RadioButtonGroup,
RadioButton,
TextArea,
Button,
NotificationQueue,
Loading
} from 'carbon-components-svelte';
import { IsRFC3339 } from '$lib/types/RFC3339';
import { invalidState } from '$lib/types/invalidState.svelte';
import { NewTicket } from '$lib/api';
import { goto } from '$app/navigation';
let notLoading: boolean = $state(true);
let q: NotificationQueue;
let r = $state({} as NewTicketReq);
function onOccurDateChange(event: CustomEvent) {
const { dateStr } = event.detail;
if (dateStr) {
r.occur_at = RFC3339(dateStr);
}
}
function onAppointDateChange(event: CustomEvent) {
const { dateStr } = event.detail;
if (dateStr) {
const date = new Date(dateStr);
date.setHours(16, 30, 0, 0); // Set time to 16:30:00
r.appointed_at = RFC3339(date);
}
}
function handleSubmit() {
console.log('提交的表单数据:', r);
check() ? submit() : jumpInvalid();
}
let occurAt = new invalidState();
let appointedAt = new invalidState();
let description = new invalidState();
let notes = new invalidState();
function check(): boolean {
notLoading = false;
let ok = false;
occurAt.reset();
appointedAt.reset();
description.reset();
notes.reset();
occurAt.assert(!r.occur_at || IsRFC3339(r.occur_at), '请输入正确的故障发生时间');
appointedAt.assert(!r.appointed_at || IsRFC3339(r.appointed_at), '请输入正确的预约时间');
description.assert(r.description && r.description.length > 0, '请填写故障描述');
description.assert(r.description.length <= 100, '字数太多了请控制在100字以内');
notes.assert(!r.notes || r.notes.length <= 100, '字数太多了...请控制在100字以内');
if (r.category == undefined) {
r.category = 'others';
}
if (!r.occur_at) {
r.occur_at = undefined;
}
if (!r.appointed_at) {
r.appointed_at = undefined; //防止序列化问题
}
notLoading = true;
if (occurAt.notOK || appointedAt.notOK || description.notOK || notes.notOK) {
ok = false;
} else {
ok = true;
}
return ok;
}
async function submit() {
let issuerSID = CheckAndGetJWT('parsed').sid;
r.issuer_sid = issuerSID;
try {
notLoading = false;
let res = await NewTicket(r);
notLoading = true;
if (!res.success) {
throw new Error(res.msg || '提交失败.........');
}
q.add({
kind: 'success',
title: '提交成功',
timeout: 3000
});
setTimeout(() => goto('/repair'), 3900);
} catch (e: any) {
notLoading = true;
const errMsg = e.response?.data?.msg || e.message || '未知错误';
q.add({
kind: 'error',
title: '提交失败',
subtitle: errMsg + ',请重试',
timeout: 5000
});
}
}
function jumpInvalid() {
if (occurAt.notOK) {
document.getElementById('occur_at')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (appointedAt.notOK) {
document
.getElementById('appointed_at')
?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (description.notOK) {
document
.getElementById('description')
?.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else if (notes.notOK) {
document.getElementById('notes')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
onMount(() => Guard(IsUser));
</script>
<h1>提交新报修</h1>
<br />
<hr />
<br />
<p>
<i
>请仔细填写这张报修表,在成功提交后,会有网维的工作人员在您预约的时间通过电话联系您或上门维修您的问题。</i
>
</p>
<br />
<DatePicker datePickerType="single" on:change={onOccurDateChange}>
<DatePickerInput
labelText="故障是在什么时候发生的?"
placeholder="记不清楚可不填"
invalid={occurAt.notOK}
invalidText={occurAt.txt}
/>
</DatePicker>
<br />
<br />
<RadioButtonGroup
legendText="故障大概是什么问题?(准确填写有助于我们维修)"
orientation="vertical"
bind:selected={r.category}
required={true}
>
<RadioButton labelText="需要新安装宽带" value="first-install" />
<RadioButton labelText="IP地址或者网络设备问题" value="ip-or-device" />
<RadioButton labelText="电脑软件或者账号的问题" value="client-or-account" />
<RadioButton labelText="网速问题" value="low-speed" />
<RadioButton labelText="其它问题/不清楚" value="others" />
</RadioButtonGroup>
<br />
<br />
<TextArea
labelText="故障描述"
placeholder="请告诉我们你遇到了什么网络问题,越详细越好~"
bind:value={r.description}
invalid={description.notOK}
invalidText={description.txt}
/>
<br />
<br />
<DatePicker datePickerType="single" on:change={onAppointDateChange}>
<DatePickerInput
labelText="预约我们上门维修的日期"
placeholder="当天4:30~6:00您本人需要在宿舍"
invalid={appointedAt.notOK}
invalidText={appointedAt.txt}
/>
</DatePicker>
<br />
<br />
<hr />
<br />
<br />
<TextArea
labelText="备注"
placeholder="其它您需要告诉我们的事情,没有可不填"
bind:value={r.notes}
invalid={notes.notOK}
invalidText={notes.txt}
/>
<br />
<br />
<Button on:click={handleSubmit}>提交</Button>
<NotificationQueue bind:this={q} />
<Loading active={!notLoading} />

2
front/static/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow: *

16
front/svelte.config.js Normal file
View File

@@ -0,0 +1,16 @@
import { mdsvex } from 'mdsvex';
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { optimizeImports, optimizeCss } from 'carbon-preprocess-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: [vitePreprocess(), mdsvex(), optimizeImports(), optimizeCss()],
kit: { adapter: adapter() },
extensions: ['.svelte', '.svx']
};
export default config;

21
front/tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"rewriteRelativeImportExtensions": true,
"allowJs": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler",
"noImplicitAny": false,//暂时
"strictNullChecks": false
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
}

10
front/vite.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()],
server: {
port: 25007
}
});