Commit 86cf28cb authored by 谢荣超's avatar 谢荣超

Initial commit

parents
node_modules/
dist/
.env
*.db
.DS_Store
*.log
cms/.tmp/
cms/public/uploads/
cms/dist/
cms/node_modules/
frontend/node_modules/
.vercel/
node_modules
.tmp
.env
uploads
*.db
HOST=0.0.0.0
PORT=1337
APP_KEYS="toBeModified1,toBeModified2"
API_TOKEN_SALT=tobemodified
ADMIN_JWT_SECRET=tobemodified
TRANSFER_TOKEN_SALT=tobemodified
JWT_SECRET=tobemodified
ENCRYPTION_KEY=tobemodified
############################
# OS X
############################
.DS_Store
.AppleDouble
.LSOverride
Icon
.Spotlight-V100
.Trashes
._*
############################
# Linux
############################
*~
############################
# Windows
############################
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
*.cab
*.msi
*.msm
*.msp
############################
# Packages
############################
*.7z
*.csv
*.dat
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
*.com
*.class
*.dll
*.exe
*.o
*.seed
*.so
*.swo
*.swp
*.swn
*.swm
*.out
*.pid
############################
# Logs and databases
############################
.tmp
*.log
*.sql
*.sqlite
*.sqlite3
############################
# Misc.
############################
*#
ssl
.idea
nbproject
public/uploads/*
!public/uploads/.gitkeep
.tsbuildinfo
.eslintcache
############################
# Node.js
############################
lib-cov
lcov.info
pids
logs
results
node_modules
.node_history
############################
# Package managers
############################
.yarn/*
!.yarn/cache
!.yarn/unplugged
!.yarn/patches
!.yarn/releases
!.yarn/sdks
!.yarn/versions
.pnp.*
yarn-error.log
############################
# Tests
############################
coverage
############################
# Strapi
############################
.env
license.txt
exports
.strapi
dist
build
.strapi-updater.json
.strapi-cloud.json
\ No newline at end of file
FROM node:20-alpine
# Install PostgreSQL client for pg provider
RUN apk add --no-cache postgresql-client
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ENV HOST=0.0.0.0
ENV PORT=1337
ENV NODE_ENV=production
RUN npm run build
EXPOSE 1337
CMD ["npm", "start"]
# 🚀 Getting started with Strapi
Strapi comes with a full featured [Command Line Interface](https://docs.strapi.io/dev-docs/cli) (CLI) which lets you scaffold and manage your project in seconds.
### `develop`
Start your Strapi application with autoReload enabled. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-develop)
```
npm run develop
# or
yarn develop
```
### `start`
Start your Strapi application with autoReload disabled. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-start)
```
npm run start
# or
yarn start
```
### `build`
Build your admin panel. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-build)
```
npm run build
# or
yarn build
```
## ⚙️ Deployment
Strapi gives you many possible deployment options for your project including [Strapi Cloud](https://cloud.strapi.io). Browse the [deployment section of the documentation](https://docs.strapi.io/dev-docs/deployment) to find the best solution for your use case.
```
yarn strapi deploy
```
## 📚 Learn more
- [Resource center](https://strapi.io/resource-center) - Strapi resource center.
- [Strapi documentation](https://docs.strapi.io) - Official Strapi documentation.
- [Strapi tutorials](https://strapi.io/tutorials) - List of tutorials made by the core team and the community.
- [Strapi blog](https://strapi.io/blog) - Official Strapi blog containing articles made by the Strapi team and the community.
- [Changelog](https://strapi.io/changelog) - Find out about the Strapi product updates, new features and general improvements.
Feel free to check out the [Strapi GitHub repository](https://github.com/strapi/strapi). Your feedback and contributions are welcome!
## ✨ Community
- [Discord](https://discord.strapi.io) - Come chat with the Strapi community including the core team.
- [Forum](https://forum.strapi.io/) - Place to discuss, ask questions and find answers, show your Strapi project and get feedback or just talk with other Community members.
- [Awesome Strapi](https://github.com/strapi/awesome-strapi) - A curated list of awesome things related to Strapi.
---
<sub>🤫 Psst! [Strapi is hiring](https://strapi.io/careers).</sub>
import type { Core } from '@strapi/strapi';
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Admin => ({
auth: {
secret: env('ADMIN_JWT_SECRET'),
},
apiToken: {
salt: env('API_TOKEN_SALT'),
},
transfer: {
token: {
salt: env('TRANSFER_TOKEN_SALT'),
},
},
secrets: {
encryptionKey: env('ENCRYPTION_KEY'),
},
flags: {
nps: env.bool('FLAG_NPS', true),
promoteEE: env.bool('FLAG_PROMOTE_EE', true),
},
});
export default config;
import type { Core } from '@strapi/strapi';
const config: Core.Config.Api = {
rest: {
defaultLimit: 25,
maxLimit: 100,
withCount: true,
},
};
export default config;
import path from 'path';
import type { Core } from '@strapi/strapi';
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Database => {
const client = env('DATABASE_CLIENT', 'sqlite');
const connections = {
mysql: {
connection: {
host: env('DATABASE_HOST', 'localhost'),
port: env.int('DATABASE_PORT', 3306),
database: env('DATABASE_NAME', 'strapi'),
user: env('DATABASE_USERNAME', 'strapi'),
password: env('DATABASE_PASSWORD', 'strapi'),
ssl: env.bool('DATABASE_SSL', false) && {
key: env('DATABASE_SSL_KEY', undefined),
cert: env('DATABASE_SSL_CERT', undefined),
ca: env('DATABASE_SSL_CA', undefined),
capath: env('DATABASE_SSL_CAPATH', undefined),
cipher: env('DATABASE_SSL_CIPHER', undefined),
rejectUnauthorized: env.bool('DATABASE_SSL_REJECT_UNAUTHORIZED', true),
},
},
pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) },
},
postgres: {
connection: {
connectionString: env('DATABASE_URL'),
host: env('DATABASE_HOST', 'localhost'),
port: env.int('DATABASE_PORT', 5432),
database: env('DATABASE_NAME', 'strapi'),
user: env('DATABASE_USERNAME', 'strapi'),
password: env('DATABASE_PASSWORD', 'strapi'),
ssl: env.bool('DATABASE_SSL', false) && {
key: env('DATABASE_SSL_KEY', undefined),
cert: env('DATABASE_SSL_CERT', undefined),
ca: env('DATABASE_SSL_CA', undefined),
capath: env('DATABASE_SSL_CAPATH', undefined),
cipher: env('DATABASE_SSL_CIPHER', undefined),
rejectUnauthorized: env.bool('DATABASE_SSL_REJECT_UNAUTHORIZED', true),
},
schema: env('DATABASE_SCHEMA', 'public'),
},
pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) },
},
sqlite: {
connection: {
filename: path.join(__dirname, '..', '..', env('DATABASE_FILENAME', '.tmp/data.db')),
},
useNullAsDefault: true,
},
};
return {
connection: {
client,
...connections[client],
acquireConnectionTimeout: env.int('DATABASE_CONNECTION_TIMEOUT', 60000),
},
};
};
export default config;
import type { Core } from '@strapi/strapi';
const config: Core.Config.Middlewares = [
'strapi::logger',
'strapi::errors',
'strapi::security',
'strapi::cors',
'strapi::poweredBy',
'strapi::query',
'strapi::body',
'strapi::session',
'strapi::favicon',
'strapi::public',
];
export default config;
import type { Core } from '@strapi/strapi';
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Plugin => ({});
export default config;
import type { Core } from '@strapi/strapi';
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Server => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
app: {
keys: env.array('APP_KEYS'),
},
});
export default config;
services:
strapi:
build: .
ports:
- "1337:1337"
environment:
DATABASE_CLIENT: postgres
DATABASE_URL: postgres://strapi:strapi_password@postgres:5432/strapi
HOST: 0.0.0.0
PORT: 1337
NODE_ENV: production
APP_KEYS: ${APP_KEYS}
API_TOKEN_SALT: ${API_TOKEN_SALT}
ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET}
TRANSFER_TOKEN_SALT: ${TRANSFER_TOKEN_SALT}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
JWT_SECRET: ${JWT_SECRET}
volumes:
- strapi-uploads:/app/public/uploads
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: strapi
POSTGRES_PASSWORD: strapi_password
POSTGRES_DB: strapi
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U strapi"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
strapi-uploads:
postgres-data:
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "cms",
"version": "0.1.0",
"private": true,
"description": "A Strapi application",
"scripts": {
"build": "strapi build",
"console": "strapi console",
"deploy": "strapi deploy",
"dev": "strapi develop",
"develop": "strapi develop",
"start": "strapi start",
"strapi": "strapi",
"upgrade": "npx @strapi/upgrade latest",
"upgrade:dry": "npx @strapi/upgrade latest --dry"
},
"dependencies": {
"@strapi/plugin-cloud": "5.42.0",
"@strapi/plugin-users-permissions": "5.42.0",
"@strapi/strapi": "5.42.0",
"better-sqlite3": "12.8.0",
"pg": "^8.20.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-router-dom": "^6.0.0",
"styled-components": "^6.0.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"typescript": "^5"
},
"engines": {
"node": ">=20.0.0 <=24.x.x",
"npm": ">=6.0.0"
},
"strapi": {
"uuid": "ee79627d-48e1-4467-a394-be9c22ca2225",
"installId": "395edfd76c2dd7e18b5e014ca3929ebdbf2c82ccf0acdd02a671eebd62166a16"
}
}
# To prevent search engines from seeing the site altogether, uncomment the next two lines:
# User-Agent: *
# Disallow: /
import type { StrapiApp } from '@strapi/strapi/admin';
export default {
config: {
locales: [
// 'ar',
// 'fr',
// 'cs',
// 'de',
// 'dk',
// 'es',
// 'he',
// 'id',
// 'it',
// 'ja',
// 'ko',
// 'ms',
// 'nl',
// 'no',
// 'pl',
// 'pt-BR',
// 'pt',
// 'ru',
// 'sk',
// 'sv',
// 'th',
// 'tr',
// 'uk',
// 'vi',
// 'zh-Hans',
// 'zh',
],
},
bootstrap(app: StrapiApp) {
console.log(app);
},
};
import type { StrapiApp } from '@strapi/strapi/admin';
export default {
config: {
locales: ['zh-Hans', 'zh'],
},
bootstrap(app: StrapiApp) {
// Set default admin locale to Chinese
const user = app.getUser?.();
if (user && !user.preferedLanguage) {
app.updateCurrentUser({ preferedLanguage: 'zh-Hans' });
}
},
};
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["../plugins/**/admin/src/**/*", "./"],
"exclude": ["node_modules/", "build/", "dist/", "**/*.test.ts"]
}
import { mergeConfig, type UserConfig } from 'vite';
export default (config: UserConfig) => {
// Important: always return the modified config
return mergeConfig(config, {
resolve: {
alias: {
'@': '/src',
},
},
});
};
{
"kind": "singleType",
"collectionName": "biographies",
"info": {
"singularName": "biography",
"pluralName": "biographies",
"displayName": "关于",
"description": "工作室介绍"
},
"options": {
"draftAndPublish": true
},
"attributes": {
"intro": {
"type": "richtext"
},
"team": {
"type": "richtext"
}
}
}
/**
* biography controller
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreController('api::biography.biography');
/**
* biography router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::biography.biography');
/**
* biography service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::biography.biography');
{
"kind": "singleType",
"collectionName": "contacts",
"info": {
"singularName": "contact",
"pluralName": "contacts",
"displayName": "联系",
"description": "联系方式"
},
"options": {
"draftAndPublish": true
},
"attributes": {
"email": {
"type": "email"
},
"phone": {
"type": "string"
},
"wechat": {
"type": "string"
},
"instagram": {
"type": "string"
},
"address": {
"type": "text"
}
}
}
/**
* contact controller
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreController('api::contact.contact');
/**
* contact router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::contact.contact');
/**
* contact service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::contact.contact');
{
"kind": "collectionType",
"collectionName": "news",
"info": {
"singularName": "news-item",
"pluralName": "news-items",
"displayName": "动态",
"description": "新闻动态"
},
"options": {
"draftAndPublish": true
},
"attributes": {
"title": {
"type": "string",
"required": true
},
"slug": {
"type": "uid",
"targetField": "title",
"required": true
},
"date": {
"type": "date",
"required": true
},
"content": {
"type": "richtext"
},
"image": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": ["images"]
}
}
}
/**
* news-item controller
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreController('api::news.news-item');
/**
* news-item router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::news.news-item');
/**
* news-item service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::news.news-item');
{
"kind": "singleType",
"collectionName": "site_settings",
"info": {
"singularName": "site-setting",
"pluralName": "site-settings",
"displayName": "站点设置",
"description": "站点全局设置"
},
"options": {
"draftAndPublish": true
},
"attributes": {
"site_title": {
"type": "string"
},
"homepage_images": {
"type": "media",
"multiple": true,
"required": false,
"allowedTypes": ["images"]
}
}
}
/**
* site-setting controller
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreController('api::site-setting.site-setting');
/**
* site-setting router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::site-setting.site-setting');
/**
* site-setting service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::site-setting.site-setting');
{
"kind": "collectionType",
"collectionName": "works",
"info": {
"singularName": "work",
"pluralName": "works",
"displayName": "作品",
"description": "摄影作品项目"
},
"options": {
"draftAndPublish": true
},
"attributes": {
"title": {
"type": "string",
"required": true
},
"slug": {
"type": "uid",
"targetField": "title",
"required": true
},
"subtitle": {
"type": "string"
},
"year": {
"type": "integer"
},
"location": {
"type": "string"
},
"description": {
"type": "richtext"
},
"cover_image": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": ["images", "videos"]
},
"media_items": {
"type": "component",
"repeatable": true,
"component": "media-item.media-item",
"required": false
},
"sort": {
"type": "integer",
"default": 0
}
}
}
/**
* work controller
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreController('api::work.work');
/**
* work router
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreRouter('api::work.work');
/**
* work service
*/
import { factories } from '@strapi/strapi';
export default factories.createCoreService('api::work.work');
{
"collectionName": "components_media_item_media_items",
"info": {
"displayName": "媒体项",
"icon": "picture",
"description": "图片或视频,视频可附带封面图"
},
"options": {},
"attributes": {
"file": {
"type": "media",
"multiple": false,
"required": true,
"allowedTypes": ["images", "videos"]
},
"poster": {
"type": "media",
"multiple": false,
"required": false,
"allowedTypes": ["images"]
}
}
}
// import type { Core } from '@strapi/strapi';
export default {
/**
* An asynchronous register function that runs before
* your application is initialized.
*
* This gives you an opportunity to extend code.
*/
register(/* { strapi }: { strapi: Core.Strapi } */) {},
/**
* An asynchronous bootstrap function that runs before
* your application gets started.
*
* This gives you an opportunity to set up your data model,
* run jobs, or perform some special logic.
*/
bootstrap(/* { strapi }: { strapi: Core.Strapi } */) {},
};
{
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "Node",
"lib": ["ES2020"],
"target": "ES2019",
"strict": false,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"noEmitOnError": true,
"noImplicitThis": true,
"outDir": "dist",
"rootDir": "."
},
"include": [
// Include root files
"./",
// Include all ts files
"./**/*.ts",
// Include all js files
"./**/*.js",
// Force the JSON files in the src folder to be included
"src/**/*.json"
],
"exclude": [
"node_modules/",
"build/",
"dist/",
".cache/",
".tmp/",
".strapi/",
// Do not include admin files in the server compilation
"src/admin/",
// Do not include test files
"**/*.test.*",
// Do not include plugins in the server compilation
"src/plugins/**"
]
}
import type { Schema, Struct } from '@strapi/strapi';
export interface MediaItemMediaItem extends Struct.ComponentSchema {
collectionName: 'components_media_item_media_items';
info: {
description: '\u56FE\u7247\u6216\u89C6\u9891\uFF0C\u89C6\u9891\u53EF\u9644\u5E26\u5C01\u9762\u56FE';
displayName: '\u5A92\u4F53\u9879';
icon: 'picture';
};
attributes: {
file: Schema.Attribute.Media<'images' | 'videos'> &
Schema.Attribute.Required;
poster: Schema.Attribute.Media<'images'>;
};
}
declare module '@strapi/strapi' {
export module Public {
export interface ComponentSchemas {
'media-item.media-item': MediaItemMediaItem;
}
}
}
import type { Schema, Struct } from '@strapi/strapi';
export interface AdminApiToken extends Struct.CollectionTypeSchema {
collectionName: 'strapi_api_tokens';
info: {
description: '';
displayName: 'Api Token';
name: 'Api Token';
pluralName: 'api-tokens';
singularName: 'api-token';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
accessKey: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
description: Schema.Attribute.String &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}> &
Schema.Attribute.DefaultTo<''>;
encryptedKey: Schema.Attribute.Text &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
expiresAt: Schema.Attribute.DateTime;
lastUsedAt: Schema.Attribute.DateTime;
lifespan: Schema.Attribute.BigInteger;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<'oneToMany', 'admin::api-token'> &
Schema.Attribute.Private;
name: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.Unique &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
permissions: Schema.Attribute.Relation<
'oneToMany',
'admin::api-token-permission'
>;
publishedAt: Schema.Attribute.DateTime;
type: Schema.Attribute.Enumeration<['read-only', 'full-access', 'custom']> &
Schema.Attribute.Required &
Schema.Attribute.DefaultTo<'read-only'>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface AdminApiTokenPermission extends Struct.CollectionTypeSchema {
collectionName: 'strapi_api_token_permissions';
info: {
description: '';
displayName: 'API Token Permission';
name: 'API Token Permission';
pluralName: 'api-token-permissions';
singularName: 'api-token-permission';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
action: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'admin::api-token-permission'
> &
Schema.Attribute.Private;
publishedAt: Schema.Attribute.DateTime;
token: Schema.Attribute.Relation<'manyToOne', 'admin::api-token'>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface AdminPermission extends Struct.CollectionTypeSchema {
collectionName: 'admin_permissions';
info: {
description: '';
displayName: 'Permission';
name: 'Permission';
pluralName: 'permissions';
singularName: 'permission';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
action: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
actionParameters: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<{}>;
conditions: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<[]>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<'oneToMany', 'admin::permission'> &
Schema.Attribute.Private;
properties: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<{}>;
publishedAt: Schema.Attribute.DateTime;
role: Schema.Attribute.Relation<'manyToOne', 'admin::role'>;
subject: Schema.Attribute.String &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface AdminRole extends Struct.CollectionTypeSchema {
collectionName: 'admin_roles';
info: {
description: '';
displayName: 'Role';
name: 'Role';
pluralName: 'roles';
singularName: 'role';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
code: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.Unique &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
description: Schema.Attribute.String;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<'oneToMany', 'admin::role'> &
Schema.Attribute.Private;
name: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.Unique &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
permissions: Schema.Attribute.Relation<'oneToMany', 'admin::permission'>;
publishedAt: Schema.Attribute.DateTime;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
users: Schema.Attribute.Relation<'manyToMany', 'admin::user'>;
};
}
export interface AdminSession extends Struct.CollectionTypeSchema {
collectionName: 'strapi_sessions';
info: {
description: 'Session Manager storage';
displayName: 'Session';
name: 'Session';
pluralName: 'sessions';
singularName: 'session';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
i18n: {
localized: false;
};
};
attributes: {
absoluteExpiresAt: Schema.Attribute.DateTime & Schema.Attribute.Private;
childId: Schema.Attribute.String & Schema.Attribute.Private;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
deviceId: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.Private;
expiresAt: Schema.Attribute.DateTime &
Schema.Attribute.Required &
Schema.Attribute.Private;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<'oneToMany', 'admin::session'> &
Schema.Attribute.Private;
origin: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.Private;
publishedAt: Schema.Attribute.DateTime;
sessionId: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.Private &
Schema.Attribute.Unique;
status: Schema.Attribute.String & Schema.Attribute.Private;
type: Schema.Attribute.String & Schema.Attribute.Private;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
userId: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.Private;
};
}
export interface AdminTransferToken extends Struct.CollectionTypeSchema {
collectionName: 'strapi_transfer_tokens';
info: {
description: '';
displayName: 'Transfer Token';
name: 'Transfer Token';
pluralName: 'transfer-tokens';
singularName: 'transfer-token';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
accessKey: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
description: Schema.Attribute.String &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}> &
Schema.Attribute.DefaultTo<''>;
expiresAt: Schema.Attribute.DateTime;
lastUsedAt: Schema.Attribute.DateTime;
lifespan: Schema.Attribute.BigInteger;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'admin::transfer-token'
> &
Schema.Attribute.Private;
name: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.Unique &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
permissions: Schema.Attribute.Relation<
'oneToMany',
'admin::transfer-token-permission'
>;
publishedAt: Schema.Attribute.DateTime;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface AdminTransferTokenPermission
extends Struct.CollectionTypeSchema {
collectionName: 'strapi_transfer_token_permissions';
info: {
description: '';
displayName: 'Transfer Token Permission';
name: 'Transfer Token Permission';
pluralName: 'transfer-token-permissions';
singularName: 'transfer-token-permission';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
action: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'admin::transfer-token-permission'
> &
Schema.Attribute.Private;
publishedAt: Schema.Attribute.DateTime;
token: Schema.Attribute.Relation<'manyToOne', 'admin::transfer-token'>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface AdminUser extends Struct.CollectionTypeSchema {
collectionName: 'admin_users';
info: {
description: '';
displayName: 'User';
name: 'User';
pluralName: 'users';
singularName: 'user';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
blocked: Schema.Attribute.Boolean &
Schema.Attribute.Private &
Schema.Attribute.DefaultTo<false>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
email: Schema.Attribute.Email &
Schema.Attribute.Required &
Schema.Attribute.Private &
Schema.Attribute.Unique &
Schema.Attribute.SetMinMaxLength<{
minLength: 6;
}>;
firstname: Schema.Attribute.String &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
isActive: Schema.Attribute.Boolean &
Schema.Attribute.Private &
Schema.Attribute.DefaultTo<false>;
lastname: Schema.Attribute.String &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<'oneToMany', 'admin::user'> &
Schema.Attribute.Private;
password: Schema.Attribute.Password &
Schema.Attribute.Private &
Schema.Attribute.SetMinMaxLength<{
minLength: 6;
}>;
preferedLanguage: Schema.Attribute.String;
publishedAt: Schema.Attribute.DateTime;
registrationToken: Schema.Attribute.String & Schema.Attribute.Private;
resetPasswordToken: Schema.Attribute.String & Schema.Attribute.Private;
roles: Schema.Attribute.Relation<'manyToMany', 'admin::role'> &
Schema.Attribute.Private;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
username: Schema.Attribute.String;
};
}
export interface ApiBiographyBiography extends Struct.SingleTypeSchema {
collectionName: 'biographies';
info: {
description: '\u5DE5\u4F5C\u5BA4\u4ECB\u7ECD';
displayName: '\u5173\u4E8E';
pluralName: 'biographies';
singularName: 'biography';
};
options: {
draftAndPublish: true;
};
attributes: {
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
intro: Schema.Attribute.RichText;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'api::biography.biography'
> &
Schema.Attribute.Private;
publishedAt: Schema.Attribute.DateTime;
team: Schema.Attribute.RichText;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface ApiContactContact extends Struct.SingleTypeSchema {
collectionName: 'contacts';
info: {
description: '\u8054\u7CFB\u65B9\u5F0F';
displayName: '\u8054\u7CFB';
pluralName: 'contacts';
singularName: 'contact';
};
options: {
draftAndPublish: true;
};
attributes: {
address: Schema.Attribute.Text;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
email: Schema.Attribute.Email;
instagram: Schema.Attribute.String;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'api::contact.contact'
> &
Schema.Attribute.Private;
phone: Schema.Attribute.String;
publishedAt: Schema.Attribute.DateTime;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
wechat: Schema.Attribute.String;
};
}
export interface ApiNewsNewsItem extends Struct.CollectionTypeSchema {
collectionName: 'news';
info: {
description: '\u65B0\u95FB\u52A8\u6001';
displayName: '\u52A8\u6001';
pluralName: 'news-items';
singularName: 'news-item';
};
options: {
draftAndPublish: true;
};
attributes: {
content: Schema.Attribute.RichText;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
date: Schema.Attribute.Date & Schema.Attribute.Required;
image: Schema.Attribute.Media<'images'>;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'api::news.news-item'
> &
Schema.Attribute.Private;
publishedAt: Schema.Attribute.DateTime;
slug: Schema.Attribute.UID<'title'> & Schema.Attribute.Required;
title: Schema.Attribute.String & Schema.Attribute.Required;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface ApiSiteSettingSiteSetting extends Struct.SingleTypeSchema {
collectionName: 'site_settings';
info: {
description: '\u7AD9\u70B9\u5168\u5C40\u8BBE\u7F6E';
displayName: '\u7AD9\u70B9\u8BBE\u7F6E';
pluralName: 'site-settings';
singularName: 'site-setting';
};
options: {
draftAndPublish: true;
};
attributes: {
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
homepage_images: Schema.Attribute.Media<'images', true>;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'api::site-setting.site-setting'
> &
Schema.Attribute.Private;
publishedAt: Schema.Attribute.DateTime;
site_title: Schema.Attribute.String;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface ApiWorkWork extends Struct.CollectionTypeSchema {
collectionName: 'works';
info: {
description: '\u6444\u5F71\u4F5C\u54C1\u9879\u76EE';
displayName: '\u4F5C\u54C1';
pluralName: 'works';
singularName: 'work';
};
options: {
draftAndPublish: true;
};
attributes: {
cover_image: Schema.Attribute.Media<'images' | 'videos'>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
description: Schema.Attribute.RichText;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<'oneToMany', 'api::work.work'> &
Schema.Attribute.Private;
location: Schema.Attribute.String;
media_items: Schema.Attribute.Component<'media-item.media-item', true>;
publishedAt: Schema.Attribute.DateTime;
slug: Schema.Attribute.UID<'title'> & Schema.Attribute.Required;
sort: Schema.Attribute.Integer & Schema.Attribute.DefaultTo<0>;
subtitle: Schema.Attribute.String;
title: Schema.Attribute.String & Schema.Attribute.Required;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
year: Schema.Attribute.Integer;
};
}
export interface PluginContentReleasesRelease
extends Struct.CollectionTypeSchema {
collectionName: 'strapi_releases';
info: {
displayName: 'Release';
pluralName: 'releases';
singularName: 'release';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
actions: Schema.Attribute.Relation<
'oneToMany',
'plugin::content-releases.release-action'
>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'plugin::content-releases.release'
> &
Schema.Attribute.Private;
name: Schema.Attribute.String & Schema.Attribute.Required;
publishedAt: Schema.Attribute.DateTime;
releasedAt: Schema.Attribute.DateTime;
scheduledAt: Schema.Attribute.DateTime;
status: Schema.Attribute.Enumeration<
['ready', 'blocked', 'failed', 'done', 'empty']
> &
Schema.Attribute.Required;
timezone: Schema.Attribute.String;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface PluginContentReleasesReleaseAction
extends Struct.CollectionTypeSchema {
collectionName: 'strapi_release_actions';
info: {
displayName: 'Release Action';
pluralName: 'release-actions';
singularName: 'release-action';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
contentType: Schema.Attribute.String & Schema.Attribute.Required;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
entryDocumentId: Schema.Attribute.String;
isEntryValid: Schema.Attribute.Boolean;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'plugin::content-releases.release-action'
> &
Schema.Attribute.Private;
publishedAt: Schema.Attribute.DateTime;
release: Schema.Attribute.Relation<
'manyToOne',
'plugin::content-releases.release'
>;
type: Schema.Attribute.Enumeration<['publish', 'unpublish']> &
Schema.Attribute.Required;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface PluginI18NLocale extends Struct.CollectionTypeSchema {
collectionName: 'i18n_locale';
info: {
collectionName: 'locales';
description: '';
displayName: 'Locale';
pluralName: 'locales';
singularName: 'locale';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
code: Schema.Attribute.String & Schema.Attribute.Unique;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'plugin::i18n.locale'
> &
Schema.Attribute.Private;
name: Schema.Attribute.String &
Schema.Attribute.SetMinMax<
{
max: 50;
min: 1;
},
number
>;
publishedAt: Schema.Attribute.DateTime;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface PluginReviewWorkflowsWorkflow
extends Struct.CollectionTypeSchema {
collectionName: 'strapi_workflows';
info: {
description: '';
displayName: 'Workflow';
name: 'Workflow';
pluralName: 'workflows';
singularName: 'workflow';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
contentTypes: Schema.Attribute.JSON &
Schema.Attribute.Required &
Schema.Attribute.DefaultTo<'[]'>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'plugin::review-workflows.workflow'
> &
Schema.Attribute.Private;
name: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.Unique;
publishedAt: Schema.Attribute.DateTime;
stageRequiredToPublish: Schema.Attribute.Relation<
'oneToOne',
'plugin::review-workflows.workflow-stage'
>;
stages: Schema.Attribute.Relation<
'oneToMany',
'plugin::review-workflows.workflow-stage'
>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface PluginReviewWorkflowsWorkflowStage
extends Struct.CollectionTypeSchema {
collectionName: 'strapi_workflows_stages';
info: {
description: '';
displayName: 'Stages';
name: 'Workflow Stage';
pluralName: 'workflow-stages';
singularName: 'workflow-stage';
};
options: {
draftAndPublish: false;
version: '1.1.0';
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
color: Schema.Attribute.String & Schema.Attribute.DefaultTo<'#4945FF'>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'plugin::review-workflows.workflow-stage'
> &
Schema.Attribute.Private;
name: Schema.Attribute.String;
permissions: Schema.Attribute.Relation<'manyToMany', 'admin::permission'>;
publishedAt: Schema.Attribute.DateTime;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
workflow: Schema.Attribute.Relation<
'manyToOne',
'plugin::review-workflows.workflow'
>;
};
}
export interface PluginUploadFile extends Struct.CollectionTypeSchema {
collectionName: 'files';
info: {
description: '';
displayName: 'File';
pluralName: 'files';
singularName: 'file';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
alternativeText: Schema.Attribute.Text;
caption: Schema.Attribute.Text;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
ext: Schema.Attribute.String;
focalPoint: Schema.Attribute.JSON;
folder: Schema.Attribute.Relation<'manyToOne', 'plugin::upload.folder'> &
Schema.Attribute.Private;
folderPath: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.Private &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
formats: Schema.Attribute.JSON;
hash: Schema.Attribute.String & Schema.Attribute.Required;
height: Schema.Attribute.Integer;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'plugin::upload.file'
> &
Schema.Attribute.Private;
mime: Schema.Attribute.String & Schema.Attribute.Required;
name: Schema.Attribute.String & Schema.Attribute.Required;
previewUrl: Schema.Attribute.Text;
provider: Schema.Attribute.String & Schema.Attribute.Required;
provider_metadata: Schema.Attribute.JSON;
publishedAt: Schema.Attribute.DateTime;
related: Schema.Attribute.Relation<'morphToMany'>;
size: Schema.Attribute.Decimal & Schema.Attribute.Required;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
url: Schema.Attribute.Text & Schema.Attribute.Required;
width: Schema.Attribute.Integer;
};
}
export interface PluginUploadFolder extends Struct.CollectionTypeSchema {
collectionName: 'upload_folders';
info: {
displayName: 'Folder';
pluralName: 'folders';
singularName: 'folder';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
children: Schema.Attribute.Relation<'oneToMany', 'plugin::upload.folder'>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
files: Schema.Attribute.Relation<'oneToMany', 'plugin::upload.file'>;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'plugin::upload.folder'
> &
Schema.Attribute.Private;
name: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
parent: Schema.Attribute.Relation<'manyToOne', 'plugin::upload.folder'>;
path: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.SetMinMaxLength<{
minLength: 1;
}>;
pathId: Schema.Attribute.Integer &
Schema.Attribute.Required &
Schema.Attribute.Unique;
publishedAt: Schema.Attribute.DateTime;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface PluginUsersPermissionsPermission
extends Struct.CollectionTypeSchema {
collectionName: 'up_permissions';
info: {
description: '';
displayName: 'Permission';
name: 'permission';
pluralName: 'permissions';
singularName: 'permission';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
action: Schema.Attribute.String & Schema.Attribute.Required;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'plugin::users-permissions.permission'
> &
Schema.Attribute.Private;
publishedAt: Schema.Attribute.DateTime;
role: Schema.Attribute.Relation<
'manyToOne',
'plugin::users-permissions.role'
>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
};
}
export interface PluginUsersPermissionsRole
extends Struct.CollectionTypeSchema {
collectionName: 'up_roles';
info: {
description: '';
displayName: 'Role';
name: 'role';
pluralName: 'roles';
singularName: 'role';
};
options: {
draftAndPublish: false;
};
pluginOptions: {
'content-manager': {
visible: false;
};
'content-type-builder': {
visible: false;
};
};
attributes: {
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
description: Schema.Attribute.String;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'plugin::users-permissions.role'
> &
Schema.Attribute.Private;
name: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.SetMinMaxLength<{
minLength: 3;
}>;
permissions: Schema.Attribute.Relation<
'oneToMany',
'plugin::users-permissions.permission'
>;
publishedAt: Schema.Attribute.DateTime;
type: Schema.Attribute.String & Schema.Attribute.Unique;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
users: Schema.Attribute.Relation<
'oneToMany',
'plugin::users-permissions.user'
>;
};
}
export interface PluginUsersPermissionsUser
extends Struct.CollectionTypeSchema {
collectionName: 'up_users';
info: {
description: '';
displayName: 'User';
name: 'user';
pluralName: 'users';
singularName: 'user';
};
options: {
draftAndPublish: false;
timestamps: true;
};
attributes: {
blocked: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo<false>;
confirmationToken: Schema.Attribute.String & Schema.Attribute.Private;
confirmed: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo<false>;
createdAt: Schema.Attribute.DateTime;
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
email: Schema.Attribute.Email &
Schema.Attribute.Required &
Schema.Attribute.SetMinMaxLength<{
minLength: 6;
}>;
locale: Schema.Attribute.String & Schema.Attribute.Private;
localizations: Schema.Attribute.Relation<
'oneToMany',
'plugin::users-permissions.user'
> &
Schema.Attribute.Private;
password: Schema.Attribute.Password &
Schema.Attribute.Private &
Schema.Attribute.SetMinMaxLength<{
minLength: 6;
}>;
provider: Schema.Attribute.String;
publishedAt: Schema.Attribute.DateTime;
resetPasswordToken: Schema.Attribute.String & Schema.Attribute.Private;
role: Schema.Attribute.Relation<
'manyToOne',
'plugin::users-permissions.role'
>;
updatedAt: Schema.Attribute.DateTime;
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
Schema.Attribute.Private;
username: Schema.Attribute.String &
Schema.Attribute.Required &
Schema.Attribute.Unique &
Schema.Attribute.SetMinMaxLength<{
minLength: 3;
}>;
};
}
declare module '@strapi/strapi' {
export module Public {
export interface ContentTypeSchemas {
'admin::api-token': AdminApiToken;
'admin::api-token-permission': AdminApiTokenPermission;
'admin::permission': AdminPermission;
'admin::role': AdminRole;
'admin::session': AdminSession;
'admin::transfer-token': AdminTransferToken;
'admin::transfer-token-permission': AdminTransferTokenPermission;
'admin::user': AdminUser;
'api::biography.biography': ApiBiographyBiography;
'api::contact.contact': ApiContactContact;
'api::news.news-item': ApiNewsNewsItem;
'api::site-setting.site-setting': ApiSiteSettingSiteSetting;
'api::work.work': ApiWorkWork;
'plugin::content-releases.release': PluginContentReleasesRelease;
'plugin::content-releases.release-action': PluginContentReleasesReleaseAction;
'plugin::i18n.locale': PluginI18NLocale;
'plugin::review-workflows.workflow': PluginReviewWorkflowsWorkflow;
'plugin::review-workflows.workflow-stage': PluginReviewWorkflowsWorkflowStage;
'plugin::upload.file': PluginUploadFile;
'plugin::upload.folder': PluginUploadFolder;
'plugin::users-permissions.permission': PluginUsersPermissionsPermission;
'plugin::users-permissions.role': PluginUsersPermissionsRole;
'plugin::users-permissions.user': PluginUsersPermissionsUser;
}
}
}
VITE_STRAPI_URL=https://fearless-vacation-76b7b5ab44.strapiapp.com
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
{
"recommendations": ["Vue.volar"]
}
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>南西工作室</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
{
"name": "frontend",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "frontend",
"version": "0.0.0",
"dependencies": {
"@vueuse/core": "^14.2.1",
"axios": "^1.15.0",
"pinia": "^3.0.4",
"vue": "^3.5.32",
"vue-easy-lightbox": "^1.19.0",
"vue-i18n": "^9.14.4",
"vue-router": "^4.6.4"
},
"devDependencies": {
"@types/node": "^24.12.2",
"@vitejs/plugin-vue": "^6.0.5",
"@vue/tsconfig": "^0.9.1",
"typescript": "~6.0.2",
"vite": "^8.0.4",
"vue-tsc": "^3.2.6"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.29.2",
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.2.tgz",
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.0"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/types": {
"version": "7.29.0",
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@emnapi/core": {
"version": "1.9.2",
"resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.9.2.tgz",
"integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.9.2",
"resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.9.2.tgz",
"integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@intlify/core-base": {
"version": "9.14.4",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.14.4.tgz",
"integrity": "sha512-vtZCt7NqWhKEtHa3SD/322DlgP5uR9MqWxnE0y8Q0tjDs9H5Lxhss+b5wv8rmuXRoHKLESNgw9d+EN9ybBbj9g==",
"license": "MIT",
"dependencies": {
"@intlify/message-compiler": "9.14.4",
"@intlify/shared": "9.14.4"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/message-compiler": {
"version": "9.14.4",
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.14.4.tgz",
"integrity": "sha512-vcyCLiVRN628U38c3PbahrhbbXrckrM9zpy0KZVlDk2Z0OnGwv8uQNNXP3twwGtfLsCf4gu3ci6FMIZnPaqZsw==",
"license": "MIT",
"dependencies": {
"@intlify/shared": "9.14.4",
"source-map-js": "^1.0.2"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/shared": {
"version": "9.14.4",
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.14.4.tgz",
"integrity": "sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz",
"integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
"@emnapi/core": "^1.7.1",
"@emnapi/runtime": "^1.7.1"
}
},
"node_modules/@oxc-project/types": {
"version": "0.124.0",
"resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.124.0.tgz",
"integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz",
"integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz",
"integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz",
"integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz",
"integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz",
"integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz",
"integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz",
"integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz",
"integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz",
"integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz",
"integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz",
"integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz",
"integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz",
"integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "1.9.2",
"@emnapi/runtime": "1.9.2",
"@napi-rs/wasm-runtime": "^1.1.3"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz",
"integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz",
"integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-rc.2",
"resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz",
"integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==",
"dev": true,
"license": "MIT"
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@types/node": {
"version": "24.12.2",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.12.2.tgz",
"integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@types/web-bluetooth": {
"version": "0.0.21",
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
"license": "MIT"
},
"node_modules/@vitejs/plugin-vue": {
"version": "6.0.5",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.5.tgz",
"integrity": "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rolldown/pluginutils": "1.0.0-rc.2"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"vue": "^3.2.25"
}
},
"node_modules/@volar/language-core": {
"version": "2.4.28",
"resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.28.tgz",
"integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/source-map": "2.4.28"
}
},
"node_modules/@volar/source-map": {
"version": "2.4.28",
"resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.28.tgz",
"integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@volar/typescript": {
"version": "2.4.28",
"resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.28.tgz",
"integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/language-core": "2.4.28",
"path-browserify": "^1.0.1",
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.32",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.32.tgz",
"integrity": "sha512-4x74Tbtqnda8s/NSD6e1Dr5p1c8HdMU5RWSjMSUzb8RTcUQqevDCxVAitcLBKT+ie3o0Dl9crc/S/opJM7qBGQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.2",
"@vue/shared": "3.5.32",
"entities": "^7.0.1",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.32",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.32.tgz",
"integrity": "sha512-ybHAu70NtiEI1fvAUz3oXZqkUYEe5J98GjMDpTGl5iHb0T15wQYLR4wE3h9xfuTNA+Cm2f4czfe8B4s+CCH57Q==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.32",
"@vue/shared": "3.5.32"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.32",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.32.tgz",
"integrity": "sha512-8UYUYo71cP/0YHMO814TRZlPuUUw3oifHuMR7Wp9SNoRSrxRQnhMLNlCeaODNn6kNTJsjFoQ/kqIj4qGvya4Xg==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.2",
"@vue/compiler-core": "3.5.32",
"@vue/compiler-dom": "3.5.32",
"@vue/compiler-ssr": "3.5.32",
"@vue/shared": "3.5.32",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.8",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.32",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.32.tgz",
"integrity": "sha512-Gp4gTs22T3DgRotZ8aA/6m2jMR+GMztvBXUBEUOYOcST+giyGWJ4WvFd7QLHBkzTxkfOt8IELKNdpzITLbA2rw==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.32",
"@vue/shared": "3.5.32"
}
},
"node_modules/@vue/devtools-api": {
"version": "7.7.9",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz",
"integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==",
"license": "MIT",
"dependencies": {
"@vue/devtools-kit": "^7.7.9"
}
},
"node_modules/@vue/devtools-kit": {
"version": "7.7.9",
"resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz",
"integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==",
"license": "MIT",
"dependencies": {
"@vue/devtools-shared": "^7.7.9",
"birpc": "^2.3.0",
"hookable": "^5.5.3",
"mitt": "^3.0.1",
"perfect-debounce": "^1.0.0",
"speakingurl": "^14.0.1",
"superjson": "^2.2.2"
}
},
"node_modules/@vue/devtools-shared": {
"version": "7.7.9",
"resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz",
"integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==",
"license": "MIT",
"dependencies": {
"rfdc": "^1.4.1"
}
},
"node_modules/@vue/language-core": {
"version": "3.2.6",
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.2.6.tgz",
"integrity": "sha512-xYYYX3/aVup576tP/23sEUpgiEnujrENaoNRbaozC1/MA9I6EGFQRJb4xrt/MmUCAGlxTKL2RmT8JLTPqagCkg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/language-core": "2.4.28",
"@vue/compiler-dom": "^3.5.0",
"@vue/shared": "^3.5.0",
"alien-signals": "^3.0.0",
"muggle-string": "^0.4.1",
"path-browserify": "^1.0.1",
"picomatch": "^4.0.2"
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.32",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.32.tgz",
"integrity": "sha512-/ORasxSGvZ6MN5gc+uE364SxFdJ0+WqVG0CENXaGW58TOCdrAW76WWaplDtECeS1qphvtBZtR+3/o1g1zL4xPQ==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.32"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.32",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.32.tgz",
"integrity": "sha512-pDrXCejn4UpFDFmMd27AcJEbHaLemaE5o4pbb7sLk79SRIhc6/t34BQA7SGNgYtbMnvbF/HHOftYBgFJtUoJUQ==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.32",
"@vue/shared": "3.5.32"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.32",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.32.tgz",
"integrity": "sha512-1CDVv7tv/IV13V8Nip1k/aaObVbWqRlVCVezTwx3K07p7Vxossp5JU1dcPNhJk3w347gonIUT9jQOGutyJrSVQ==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.32",
"@vue/runtime-core": "3.5.32",
"@vue/shared": "3.5.32",
"csstype": "^3.2.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.32",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.32.tgz",
"integrity": "sha512-IOjm2+JQwRFS7W28HNuJeXQle9KdZbODFY7hFGVtnnghF51ta20EWAZJHX+zLGtsHhaU6uC9BGPV52KVpYryMQ==",
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.32",
"@vue/shared": "3.5.32"
},
"peerDependencies": {
"vue": "3.5.32"
}
},
"node_modules/@vue/shared": {
"version": "3.5.32",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.32.tgz",
"integrity": "sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==",
"license": "MIT"
},
"node_modules/@vue/tsconfig": {
"version": "0.9.1",
"resolved": "https://registry.npmmirror.com/@vue/tsconfig/-/tsconfig-0.9.1.tgz",
"integrity": "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"typescript": ">= 5.8",
"vue": "^3.4.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
},
"vue": {
"optional": true
}
}
},
"node_modules/@vueuse/core": {
"version": "14.2.1",
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-14.2.1.tgz",
"integrity": "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==",
"license": "MIT",
"dependencies": {
"@types/web-bluetooth": "^0.0.21",
"@vueuse/metadata": "14.2.1",
"@vueuse/shared": "14.2.1"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/@vueuse/metadata": {
"version": "14.2.1",
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-14.2.1.tgz",
"integrity": "sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "14.2.1",
"resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-14.2.1.tgz",
"integrity": "sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/alien-signals": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-3.1.2.tgz",
"integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==",
"dev": true,
"license": "MIT"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.15.0",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.15.0.tgz",
"integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
"proxy-from-env": "^2.1.0"
}
},
"node_modules/birpc": {
"version": "2.9.0",
"resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz",
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/copy-anything": {
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz",
"integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==",
"license": "MIT",
"dependencies": {
"is-what": "^5.2.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz",
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"license": "MIT"
},
"node_modules/is-what": {
"version": "5.5.0",
"resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz",
"integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/lightningcss": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.32.0.tgz",
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-android-arm64": "1.32.0",
"lightningcss-darwin-arm64": "1.32.0",
"lightningcss-darwin-x64": "1.32.0",
"lightningcss-freebsd-x64": "1.32.0",
"lightningcss-linux-arm-gnueabihf": "1.32.0",
"lightningcss-linux-arm64-gnu": "1.32.0",
"lightningcss-linux-arm64-musl": "1.32.0",
"lightningcss-linux-x64-gnu": "1.32.0",
"lightningcss-linux-x64-musl": "1.32.0",
"lightningcss-win32-arm64-msvc": "1.32.0",
"lightningcss-win32-x64-msvc": "1.32.0"
}
},
"node_modules/lightningcss-android-arm64": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/muggle-string": {
"version": "0.4.1",
"resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz",
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
"dev": true,
"license": "MIT"
},
"node_modules/perfect-debounce": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pinia": {
"version": "3.0.4",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz",
"integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^7.7.7"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"typescript": ">=4.5.0",
"vue": "^3.5.11"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/postcss": {
"version": "8.5.9",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.9.tgz",
"integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/proxy-from-env": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"license": "MIT"
},
"node_modules/rolldown": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.0.0-rc.15.tgz",
"integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.124.0",
"@rolldown/pluginutils": "1.0.0-rc.15"
},
"bin": {
"rolldown": "bin/cli.mjs"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.0.0-rc.15",
"@rolldown/binding-darwin-arm64": "1.0.0-rc.15",
"@rolldown/binding-darwin-x64": "1.0.0-rc.15",
"@rolldown/binding-freebsd-x64": "1.0.0-rc.15",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15",
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15",
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15",
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15",
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15",
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15",
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.15",
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.15",
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.15",
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15",
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15"
}
},
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz",
"integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==",
"dev": true,
"license": "MIT"
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/speakingurl": {
"version": "14.0.1",
"resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz",
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/superjson": {
"version": "2.2.6",
"resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz",
"integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==",
"license": "MIT",
"dependencies": {
"copy-anything": "^4"
},
"engines": {
"node": ">=16"
}
},
"node_modules/tinyglobby": {
"version": "0.2.16",
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.16.tgz",
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD",
"optional": true
},
"node_modules/typescript": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-6.0.2.tgz",
"integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},
"node_modules/vite": {
"version": "8.0.8",
"resolved": "https://registry.npmmirror.com/vite/-/vite-8.0.8.tgz",
"integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
"postcss": "^8.5.8",
"rolldown": "1.0.0-rc.15",
"tinyglobby": "^0.2.15"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.1.0",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
"sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"@vitejs/devtools": {
"optional": true
},
"esbuild": {
"optional": true
},
"jiti": {
"optional": true
},
"less": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
},
"tsx": {
"optional": true
},
"yaml": {
"optional": true
}
}
},
"node_modules/vscode-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz",
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
"dev": true,
"license": "MIT"
},
"node_modules/vue": {
"version": "3.5.32",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.32.tgz",
"integrity": "sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.32",
"@vue/compiler-sfc": "3.5.32",
"@vue/runtime-dom": "3.5.32",
"@vue/server-renderer": "3.5.32",
"@vue/shared": "3.5.32"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/vue-easy-lightbox": {
"version": "1.19.0",
"resolved": "https://registry.npmmirror.com/vue-easy-lightbox/-/vue-easy-lightbox-1.19.0.tgz",
"integrity": "sha512-YxLXgjEn91UF3DuK1y8u3Pyx2sJ7a/MnBpkyrBSQkvU1glzEJASyAZ7N+5yDpmxBQDVMwCsL2VmxWGIiFrWCgA==",
"license": "MIT",
"engines": {
"node": ">=14.18.3"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-i18n": {
"version": "9.14.4",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.14.4.tgz",
"integrity": "sha512-B934C8yUyWLT0EMud3DySrwSUJI7ZNiWYsEEz2gknTthqKiG4dzWE/WSa8AzCuSQzwBEv4HtG1jZDhgzPfWSKQ==",
"license": "MIT",
"dependencies": {
"@intlify/core-base": "9.14.4",
"@intlify/shared": "9.14.4",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-i18n/node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/vue-router": {
"version": "4.6.4",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz",
"integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/vue-router/node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/vue-tsc": {
"version": "3.2.6",
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.2.6.tgz",
"integrity": "sha512-gYW/kWI0XrwGzd0PKc7tVB/qpdeAkIZLNZb10/InizkQjHjnT8weZ/vBarZoj4kHKbUTZT/bAVgoOr8x4NsQ/Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/typescript": "2.4.28",
"@vue/language-core": "3.2.6"
},
"bin": {
"vue-tsc": "bin/vue-tsc.js"
},
"peerDependencies": {
"typescript": ">=5.0.0"
}
}
}
}
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"@vueuse/core": "^14.2.1",
"axios": "^1.15.0",
"pinia": "^3.0.4",
"vue": "^3.5.32",
"vue-easy-lightbox": "^1.19.0",
"vue-i18n": "^9.14.4",
"vue-router": "^4.6.4"
},
"devDependencies": {
"@types/node": "^24.12.2",
"@vitejs/plugin-vue": "^6.0.5",
"@vue/tsconfig": "^0.9.1",
"typescript": "~6.0.2",
"vite": "^8.0.4",
"vue-tsc": "^3.2.6"
}
}
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="46" fill="none" viewBox="0 0 48 46"><path fill="#863bff" d="M25.946 44.938c-.664.845-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.287c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.497 0-3.578-1.842-3.578H1.237c-.92 0-1.456-1.04-.92-1.788L10.013.474c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.579 1.842 3.579h11.377c.943 0 1.473 1.088.89 1.83L25.947 44.94z" style="fill:#863bff;fill:color(display-p3 .5252 .23 1);fill-opacity:1"/><mask id="a" width="48" height="46" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M25.842 44.938c-.664.844-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.183c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.498 0-3.579-1.842-3.579H1.133c-.92 0-1.456-1.04-.92-1.787L9.91.473c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.578 1.842 3.578h11.377c.943 0 1.473 1.088.89 1.832L25.843 44.94z" style="fill:#000;fill-opacity:1"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#ede6ff" rx="5.508" ry="14.704" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -4.47 31.516)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#ede6ff" rx="10.399" ry="29.851" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -39.328 7.883)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#7e14ff" rx="5.508" ry="30.487" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -25.913 -14.639)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -32.644 -3.334)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -34.34 30.47)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#ede6ff" rx="14.072" ry="22.078" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="rotate(93.35 24.506 48.493)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx=".387" cy="8.972" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(39.51 .387 8.972)"/></g><g filter="url(#k)"><ellipse cx="47.523" cy="-6.092" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 47.523 -6.092)"/></g><g filter="url(#l)"><ellipse cx="41.412" cy="6.333" fill="#47bfff" rx="5.971" ry="9.665" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 41.412 6.333)"/></g><g filter="url(#m)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#n)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#o)"><ellipse cx="35.651" cy="29.907" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 35.651 29.907)"/></g><g filter="url(#p)"><ellipse cx="38.418" cy="32.4" fill="#47bfff" rx="5.971" ry="15.297" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 38.418 32.4)"/></g></g><defs><filter id="b" width="60.045" height="41.654" x="-19.77" y="16.149" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-54.613" y="-7.533" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-49.64" y="2.03" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-45.045" y="20.029" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-43.513" y="21.178" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="15.756" y="-17.901" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-27.636" y="-22.853" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="20.116" y="-38.415" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="24.641" y="-11.323" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="8.244" y="-2.416" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="18.713" y="10.588" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter></defs></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="bluesky-icon" viewBox="0 0 16 17">
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
</symbol>
<symbol id="discord-icon" viewBox="0 0 20 19">
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
</symbol>
<symbol id="documentation-icon" viewBox="0 0 21 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
</symbol>
<symbol id="github-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
</symbol>
<symbol id="social-icon" viewBox="0 0 20 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
</symbol>
<symbol id="x-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
</symbol>
</svg>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useWorksStore } from '@/stores/works'
const route = useRoute()
const { locale, t } = useI18n()
const worksStore = useWorksStore()
const menuOpen = ref(false)
const siteTitle = computed(() =>
locale.value === 'zh' ? '南西工作室' : 'Nancy Studio'
)
const showWorksNav = computed(() => {
return route.path.startsWith('/works')
})
watch(() => route.path, () => {
menuOpen.value = false
})
worksStore.loadWorks()
function setLocale(lang: string) {
locale.value = lang
localStorage.setItem('locale', lang)
}
</script>
<template>
<div class="app">
<!-- Mobile Header (hidden on desktop) -->
<div class="mobile-header">
<div class="mobile-header-inner">
<router-link to="/" class="mobile-logo">{{ siteTitle }}</router-link>
<button class="mobile-menu-toggle" @click="menuOpen = !menuOpen">
{{ menuOpen ? t('common.close') : t('common.menu') }}
</button>
</div>
</div>
<!-- Mobile Overlay Menu -->
<div class="mobile-overlay" :class="{ active: menuOpen }">
<button class="mobile-overlay-close" @click="menuOpen = false">{{ t('common.close') }}</button>
<nav class="mobile-overlay-nav">
<router-link to="/" @click="menuOpen = false">Home</router-link>
<router-link to="/works" @click="menuOpen = false">{{ t('nav.works') }}</router-link>
<router-link to="/news" @click="menuOpen = false">{{ t('nav.news') }}</router-link>
<router-link to="/biography" @click="menuOpen = false">{{ t('nav.about') }}</router-link>
<router-link to="/contact" @click="menuOpen = false">{{ t('nav.contact') }}</router-link>
</nav>
<div class="mobile-overlay-lang">
<a href="#" :class="{ active: locale === 'zh' }" @click.prevent="setLocale('zh')">中文</a>
<a href="#" :class="{ active: locale === 'en' }" @click.prevent="setLocale('en')">EN</a>
</div>
</div>
<!-- Desktop Sidebar -->
<aside class="app-sidebar">
<div class="sidebar-logo"><router-link to="/">{{ siteTitle }}</router-link></div>
<nav class="sidebar-nav">
<div class="sidebar-nav-column">
<router-link to="/news">{{ t('nav.news') }}</router-link>
<router-link to="/biography">{{ t('nav.about') }}</router-link>
<router-link to="/works">{{ t('nav.works') }}</router-link>
</div>
<div class="sidebar-nav-column">
<router-link to="/contact">{{ t('nav.contact') }}</router-link>
</div>
</nav>
<!-- Sub navigation - shown on works pages -->
<div class="sidebar-subnav" v-if="showWorksNav && worksStore.works.length > 0">
<router-link
v-for="work in worksStore.works"
:key="work.documentId"
:to="`/works/${work.slug}`"
>
{{ work.title }}
</router-link>
</div>
<!-- Language switch -->
<div class="sidebar-lang">
<a href="#" :class="{ active: locale === 'zh' }" @click.prevent="setLocale('zh')">中文</a>
<a href="#" :class="{ active: locale === 'en' }" @click.prevent="setLocale('en')">EN</a>
</div>
</aside>
<!-- Main Content -->
<main class="app-content">
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</main>
<!-- Footer -->
<footer class="app-footer">
<p>&copy; {{ new Date().getFullYear() }} {{ siteTitle }}</p>
</footer>
</div>
</template>
import axios from 'axios'
import type {
StrapiMedia,
StrapiListResponse,
StrapiResponse,
Work,
NewsItem,
Biography,
Contact,
SiteSetting,
} from '@/types'
const STRAPI_URL = import.meta.env.VITE_STRAPI_URL || ''
const api = axios.create({
baseURL: `${STRAPI_URL}/api`,
timeout: 15000,
})
export function getMediaUrl(media: StrapiMedia): string {
if (!media?.url) return ''
if (media.url.startsWith('http')) return media.url
return media.url.startsWith('/') ? `${STRAPI_URL}${media.url}` : `${STRAPI_URL}/${media.url}`
}
export async function fetchWorks(): Promise<StrapiListResponse<Work>> {
const { data } = await api.get<StrapiListResponse<Work>>('/works', {
params: { populate: '*', 'sort': 'sort:asc' },
})
return data
}
export async function fetchWork(slug: string): Promise<StrapiListResponse<Work>> {
const { data } = await api.get<StrapiListResponse<Work>>('/works', {
params: { 'filters[slug][$eq]': slug, populate: '*' },
})
return data
}
export async function fetchNews(page = 1): Promise<StrapiListResponse<NewsItem>> {
const { data } = await api.get<StrapiListResponse<NewsItem>>('/news-items', {
params: {
populate: '*',
'sort': 'date:desc',
'pagination[page]': page,
'pagination[pageSize]': 10,
},
})
return data
}
export async function fetchBiography(): Promise<StrapiResponse<Biography>> {
const { data } = await api.get<StrapiResponse<Biography>>('/biography')
return data
}
export async function fetchContact(): Promise<StrapiResponse<Contact>> {
const { data } = await api.get<StrapiResponse<Contact>>('/contact')
return data
}
export async function fetchSiteSetting(): Promise<StrapiResponse<SiteSetting>> {
const { data } = await api.get<StrapiResponse<SiteSetting>>('/site-setting', {
params: { populate: '*' },
})
return data
}
export default api
<svg xmlns="http://www.w3.org/2000/svg" width="77" height="47" fill="none" aria-labelledby="vite-logo-title" viewBox="0 0 77 47"><title id="vite-logo-title">Vite</title><style>.parenthesis{fill:#000}@media (prefers-color-scheme:dark){.parenthesis{fill:#fff}}</style><path fill="#9135ff" d="M40.151 45.71c-.663.844-2.02.374-2.02-.699V34.708a2.26 2.26 0 0 0-2.262-2.262H24.493c-.92 0-1.457-1.04-.92-1.788l7.479-10.471c1.07-1.498 0-3.578-1.842-3.578H15.443c-.92 0-1.456-1.04-.92-1.788l9.696-13.576c.213-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.472c-1.07 1.497 0 3.578 1.842 3.578h11.376c.944 0 1.474 1.087.89 1.83L40.153 45.712z"/><mask id="a" width="48" height="47" x="14" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M40.047 45.71c-.663.843-2.02.374-2.02-.699V34.708a2.26 2.26 0 0 0-2.262-2.262H24.389c-.92 0-1.457-1.04-.92-1.788l7.479-10.472c1.07-1.497 0-3.578-1.842-3.578H15.34c-.92 0-1.456-1.04-.92-1.788l9.696-13.575c.213-.297.556-.474.92-.474H53.93c.92 0 1.456 1.04.92 1.788L47.37 13.03c-1.07 1.498 0 3.578 1.842 3.578h11.376c.944 0 1.474 1.088.89 1.831L40.049 45.712z"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#eee6ff" rx="5.508" ry="14.704" transform="rotate(269.814 20.96 11.29)scale(-1 1)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#eee6ff" rx="10.399" ry="29.851" transform="rotate(89.814 -16.902 -8.275)scale(1 -1)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#8900ff" rx="5.508" ry="30.487" transform="rotate(89.814 -19.197 -7.127)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#8900ff" rx="5.508" ry="30.599" transform="rotate(89.814 -25.928 4.177)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#8900ff" rx="5.508" ry="30.599" transform="rotate(89.814 -25.738 5.52)scale(1 -1)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#eee6ff" rx="14.072" ry="22.078" transform="rotate(93.35 31.245 55.578)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#8900ff" rx="3.47" ry="21.501" transform="rotate(89.009 35.419 55.202)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#8900ff" rx="3.47" ry="21.501" transform="rotate(89.009 35.419 55.202)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx="14.592" cy="9.743" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(39.51 14.592 9.743)"/></g><g filter="url(#k)"><ellipse cx="61.728" cy="-5.321" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 61.728 -5.32)"/></g><g filter="url(#l)"><ellipse cx="55.618" cy="7.104" fill="#00c2ff" rx="5.971" ry="9.665" transform="rotate(37.892 55.618 7.104)"/></g><g filter="url(#m)"><ellipse cx="12.326" cy="39.103" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 12.326 39.103)"/></g><g filter="url(#n)"><ellipse cx="12.326" cy="39.103" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 12.326 39.103)"/></g><g filter="url(#o)"><ellipse cx="49.857" cy="30.678" fill="#8900ff" rx="4.407" ry="29.108" transform="rotate(37.892 49.857 30.678)"/></g><g filter="url(#p)"><ellipse cx="52.623" cy="33.171" fill="#00c2ff" rx="5.971" ry="15.297" transform="rotate(37.892 52.623 33.17)"/></g></g><path d="M6.919 0c-9.198 13.166-9.252 33.575 0 46.789h6.215c-9.25-13.214-9.196-33.623 0-46.789zm62.424 0h-6.215c9.198 13.166 9.252 33.575 0 46.789h6.215c9.25-13.214 9.196-33.623 0-46.789" class="parenthesis"/><defs><filter id="b" width="60.045" height="41.654" x="-5.564" y="16.92" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-40.407" y="-6.762" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-35.435" y="2.801" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-30.84" y="20.8" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-29.307" y="21.949" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="29.961" y="-17.13" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="37.754" y="3.055" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="37.754" y="3.055" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-13.43" y="-22.082" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="34.321" y="-37.644" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="38.847" y="-10.552" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-15.081" y="6.78" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-15.081" y="6.78" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="22.45" y="-1.645" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="32.919" y="11.36" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17286" stdDeviation="4.596"/></filter></defs></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
\ No newline at end of file
import { createI18n } from 'vue-i18n'
import zh from './locales/zh'
import en from './locales/en'
const savedLocale = localStorage.getItem('locale') || 'zh'
const i18n = createI18n({
legacy: false,
locale: savedLocale,
fallbackLocale: 'en',
messages: { zh, en },
})
export default i18n
export default {
nav: {
works: 'Works',
news: 'News',
about: 'About',
contact: 'Contact',
},
home: {
title: 'Nancy Studio',
},
works: {
title: 'Works',
},
work: {
back: 'Back',
showInfo: 'Project Info',
hideInfo: 'Hide Info',
},
news: {
title: 'News',
prev: 'Previous',
next: 'Next',
},
biography: {
title: 'About',
intro: 'Introduction',
team: 'Team',
},
contact: {
title: 'Contact',
email: 'Email',
phone: 'Phone',
wechat: 'WeChat',
instagram: 'Instagram',
address: 'Address',
},
common: {
loading: 'Loading...',
noData: 'No content',
backToTop: 'Back to top',
menu: 'Menu',
close: 'Close',
},
}
export default {
nav: {
works: '作品',
news: '动态',
about: '关于',
contact: '联系',
},
home: {
title: '南西工作室',
},
works: {
title: '作品',
},
work: {
back: '返回',
showInfo: '项目信息',
hideInfo: '收起信息',
},
news: {
title: '动态',
prev: '上一页',
next: '下一页',
},
biography: {
title: '关于',
intro: '简介',
team: '团队',
},
contact: {
title: '联系',
email: '邮箱',
phone: '电话',
wechat: '微信',
instagram: 'Instagram',
address: '地址',
},
common: {
loading: '加载中...',
noData: '暂无内容',
backToTop: '返回顶部',
menu: '菜单',
close: '关闭',
},
}
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import i18n from './i18n'
import 'vue-easy-lightbox/dist/external-css/vue-easy-lightbox.css'
import './style.css'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(i18n)
app.mount('#app')
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'home',
component: () => import('@/views/HomeView.vue'),
},
{
path: '/works',
name: 'works',
component: () => import('@/views/WorksView.vue'),
},
{
path: '/works/:slug',
name: 'work-detail',
component: () => import('@/views/WorkDetailView.vue'),
},
{
path: '/news',
name: 'news',
component: () => import('@/views/NewsView.vue'),
},
{
path: '/biography',
name: 'biography',
component: () => import('@/views/BiographyView.vue'),
},
{
path: '/contact',
name: 'contact',
component: () => import('@/views/ContactView.vue'),
},
],
scrollBehavior() {
return { top: 0 }
},
})
export default router
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { NewsItem } from '@/types'
import { fetchNews } from '@/api'
export const useNewsStore = defineStore('news', () => {
const newsItems = ref<NewsItem[]>([])
const pagination = ref({
page: 1,
pageCount: 1,
pageSize: 10,
total: 0,
})
const loading = ref(false)
async function loadNews(page = 1): Promise<void> {
loading.value = true
try {
const response = await fetchNews(page)
newsItems.value = response.data
pagination.value = response.meta.pagination
} finally {
loading.value = false
}
}
return {
newsItems,
pagination,
loading,
loadNews,
}
})
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { Work } from '@/types'
import { fetchWorks, fetchWork } from '@/api'
export const useWorksStore = defineStore('works', () => {
const works = ref<Work[]>([])
const currentWork = ref<Work | null>(null)
const loading = ref(false)
async function loadWorks(): Promise<void> {
loading.value = true
try {
const response = await fetchWorks()
works.value = response.data
} finally {
loading.value = false
}
}
async function loadWork(slug: string): Promise<Work | null> {
loading.value = true
try {
const response = await fetchWork(slug)
if (response.data.length === 0) {
currentWork.value = null
return null
}
const work = response.data[0]
currentWork.value = work
return work
} finally {
loading.value = false
}
}
function clearCurrent(): void {
currentWork.value = null
}
return {
works,
currentWork,
loading,
loadWorks,
loadWork,
clearCurrent,
}
})
/* ===== Nancy Studio - Design System ===== */
/* Matching rinkokawauchi.com aesthetic */
* {
margin: 0;
padding: 0;
border: 0;
outline: none;
box-sizing: border-box;
}
:root {
--color-bg: #eaeeee;
--color-text: #000000;
--color-text-secondary: #777777;
--color-border: #999999;
--color-selection-bg: #000000;
--color-selection-text: #ffffff;
--font-serif: 'Noto Serif SC', '游明朝体', 'Yu Mincho', 'YuMincho',
'ヒラギノ明朝 Pro', 'Hiragino Mincho Pro', 'MS P明朝', 'MS PMincho',
'Georgia', serif;
--sidebar-width: 360px;
--content-padding: 18px;
--sidebar-padding: 50px;
--font-size-base: 14px;
--font-size-sm: 12px;
--font-size-lg: 16px;
--font-size-h1: 28px;
--font-size-h2: 22px;
--font-size-nav: 14px;
--font-size-lang: 13px;
--font-size-footer: 13px;
--font-size-logo: 15px;
--line-height: 1.6em;
--transition-fast: 0.2s ease;
--transition-normal: 0.3s ease;
--transition-slow: 0.5s ease-in-out;
}
::selection {
background: var(--color-selection-bg);
color: var(--color-selection-text);
}
html {
font-size: 62.5%;
}
body {
font-family: var(--font-serif);
font-size: var(--font-size-base);
line-height: var(--line-height);
color: var(--color-text);
background-color: var(--color-bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
transition: background-color 1s ease-in-out;
}
a {
color: var(--color-text);
text-decoration: none;
transition: color var(--transition-fast);
}
a:hover {
color: var(--color-text-secondary);
}
img {
max-width: 100%;
height: auto;
display: block;
}
button {
font-family: var(--font-serif);
color: var(--color-text);
background: none;
border: none;
cursor: pointer;
}
/* ===== Sidebar (Desktop) ===== */
.app-sidebar {
position: fixed;
top: 0;
left: 0;
width: var(--sidebar-width);
height: 100vh;
background: var(--color-bg);
padding: var(--sidebar-padding);
z-index: 100;
overflow-y: auto;
border-right: 1px solid rgba(0, 0, 0, 0.06);
}
.sidebar-logo {
font-size: var(--font-size-logo);
font-weight: 600;
letter-spacing: 0.05em;
margin-bottom: 40px;
display: block;
}
.sidebar-logo a {
color: var(--color-text);
}
.sidebar-nav {
display: flex;
flex-wrap: wrap;
}
.sidebar-nav-column {
margin-right: 40px;
}
.sidebar-nav a {
display: block;
font-size: var(--font-size-nav);
padding: 4px 0;
position: relative;
}
.sidebar-nav a.router-link-active::before {
content: '';
position: absolute;
left: -1em;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--color-text);
}
.sidebar-subnav {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid rgba(0, 0, 0, 0.08);
}
.sidebar-subnav a {
display: block;
font-size: var(--font-size-nav);
padding: 4px 0;
color: var(--color-text-secondary);
}
.sidebar-subnav a:hover {
color: var(--color-text);
}
.sidebar-subnav a.router-link-active {
color: var(--color-text);
}
.sidebar-subnav a.router-link-active::before {
background: var(--color-text-secondary);
}
.sidebar-lang {
margin-top: 40px;
font-size: var(--font-size-lang);
}
.sidebar-lang a {
display: inline-block;
padding: 0 2px;
}
.sidebar-lang a + a::before {
content: ' / ';
color: var(--color-text);
}
.sidebar-social {
margin-top: 16px;
}
.sidebar-social a {
font-size: var(--font-size-nav);
color: var(--color-text-secondary);
}
.sidebar-social a:hover {
color: var(--color-text);
}
/* ===== Mobile Header ===== */
.mobile-header {
display: none;
padding: var(--content-padding);
background: var(--color-bg);
position: relative;
z-index: 200;
}
.mobile-header-inner {
display: flex;
align-items: center;
justify-content: space-between;
}
.mobile-logo {
font-size: var(--font-size-logo);
font-weight: 600;
letter-spacing: 0.05em;
}
.mobile-menu-toggle {
font-size: var(--font-size-nav);
padding: 4px 8px;
}
/* ===== Mobile Overlay ===== */
.mobile-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--color-bg);
z-index: 10000;
padding: var(--sidebar-padding);
display: none;
flex-direction: column;
justify-content: center;
}
.mobile-overlay.active {
display: flex;
}
.mobile-overlay-close {
position: absolute;
top: var(--content-padding);
right: var(--content-padding);
font-size: var(--font-size-nav);
padding: 4px 8px;
}
.mobile-overlay-nav a {
display: block;
font-size: 18px;
padding: 8px 0;
opacity: 0;
animation: fadeInUp 0.5s ease forwards;
}
.mobile-overlay-nav a:nth-child(1) { animation-delay: 0.1s; }
.mobile-overlay-nav a:nth-child(2) { animation-delay: 0.15s; }
.mobile-overlay-nav a:nth-child(3) { animation-delay: 0.2s; }
.mobile-overlay-nav a:nth-child(4) { animation-delay: 0.25s; }
.mobile-overlay-nav a:nth-child(5) { animation-delay: 0.3s; }
.mobile-overlay-nav a:nth-child(6) { animation-delay: 0.35s; }
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* ===== Main Content Area ===== */
.app-content {
margin-left: var(--sidebar-width);
min-height: 100vh;
padding: var(--content-padding);
}
/* ===== Footer ===== */
.app-footer {
position: fixed;
bottom: var(--content-padding);
left: var(--sidebar-padding);
font-size: var(--font-size-footer);
color: var(--color-text-secondary);
z-index: 50;
}
.app-footer a {
color: var(--color-text-secondary);
}
.app-footer a:hover {
color: var(--color-text);
}
/* ===== Home Slideshow ===== */
.home-slideshow {
position: absolute;
top: var(--content-padding);
left: var(--content-padding);
right: var(--content-padding);
bottom: var(--content-padding);
overflow: hidden;
}
.home-slideshow img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
object-position: center;
opacity: 0;
transition: opacity 1s ease-in-out;
}
.home-slideshow img.active {
opacity: 1;
}
/* ===== Works Grid ===== */
.works-grid {
max-width: 1200px;
}
.works-grid-item {
margin-bottom: 5em;
cursor: pointer;
}
.works-grid-item img {
width: 100%;
margin-bottom: 0.8em;
}
.works-grid-item h2 {
font-size: var(--font-size-base);
font-weight: 500;
margin-bottom: 0.2em;
}
.works-grid-meta {
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
}
.works-grid-subtitle {
margin-right: 0.5em;
}
.works-grid-item .year {
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
}
/* ===== Work Detail ===== */
.work-detail-title {
font-size: var(--font-size-h2);
font-weight: 500;
margin-bottom: 0.3em;
}
.work-detail-meta {
margin-bottom: 2em;
color: var(--color-text-secondary);
font-size: var(--font-size-base);
}
.work-detail-meta span + span::before {
content: ',';
}
.work-detail-year {
font-size: var(--font-size-base);
color: var(--color-text-secondary);
}
.work-info-toggle {
display: inline-block;
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
padding: 4px 0;
margin-bottom: 1.5em;
cursor: pointer;
border-bottom: 1px solid transparent;
transition: border-color var(--transition-fast);
}
.work-info-toggle:hover {
border-bottom-color: var(--color-text-secondary);
}
.work-info-table {
display: table;
width: 100%;
max-width: 800px;
margin-bottom: 4em;
border-top: 1px solid rgba(0, 0, 0, 0.15);
padding-top: 1em;
}
.work-info-row {
display: table-row;
}
.work-info-label {
display: table-cell;
width: 140px;
font-size: var(--font-size-base);
color: var(--color-text-secondary);
vertical-align: top;
padding: 3px 0;
white-space: nowrap;
}
.work-info-value {
display: table-cell;
font-size: var(--font-size-base);
padding: 3px 0 3px 1em;
}
.photo-grid {
display: flex;
flex-direction: column;
gap: var(--content-padding);
}
.photo-grid img {
width: 100%;
display: block;
cursor: pointer;
transition: opacity var(--transition-fast);
}
.photo-grid img:hover {
opacity: 0.85;
}
.photo-grid-video {
width: 100%;
display: block;
background: #000;
}
.photo-grid-row {
display: flex;
gap: 2%;
}
.photo-grid-row img {
width: 49%;
}
/* ===== News ===== */
.news-feed {
max-width: 800px;
}
.news-item {
margin-bottom: 3em;
padding-bottom: 3em;
border-bottom: 1px solid var(--color-text-secondary);
}
.news-item:last-child {
border-bottom: none;
}
.news-item img {
max-width: 600px;
margin-bottom: 1em;
}
.news-item-date {
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
margin-bottom: 0.5em;
}
.news-item h2 {
font-size: var(--font-size-base);
font-weight: 500;
margin-bottom: 0.5em;
}
.news-item-content {
font-size: var(--font-size-base);
line-height: var(--line-height);
}
/* ===== Biography ===== */
.biography-page {
max-width: 800px;
}
.biography-page h1 {
font-size: var(--font-size-h1);
font-weight: 500;
margin-bottom: 1em;
}
.biography-section {
margin-bottom: 4em;
padding-top: var(--content-padding);
}
.biography-section-title {
font-size: var(--font-size-lg);
font-weight: 600;
margin-bottom: 1em;
}
.biography-list {
display: table;
width: 100%;
}
.biography-list-row {
display: table-row;
}
.biography-list-year {
display: table-cell;
width: 80px;
font-size: var(--font-size-base);
vertical-align: top;
padding: 2px 0;
}
.biography-list-detail {
display: table-cell;
font-size: var(--font-size-base);
padding: 2px 0;
}
/* ===== Contact ===== */
.contact-page {
max-width: 800px;
}
.contact-page h1 {
font-size: var(--font-size-h1);
font-weight: 500;
margin-bottom: 1em;
}
.contact-section {
margin-bottom: 2em;
}
.contact-section p {
margin-bottom: 0.5em;
line-height: var(--line-height);
}
.contact-section a {
color: var(--color-text);
}
.contact-section a:hover {
color: var(--color-text-secondary);
}
/* ===== Loading ===== */
.loading {
text-align: center;
padding: 4em;
color: var(--color-text-secondary);
font-size: var(--font-size-base);
}
/* ===== Transitions ===== */
.fade-enter-active,
.fade-leave-active {
transition: opacity var(--transition-normal);
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* ===== Responsive ===== */
@media screen and (max-width: 1365px) {
.app-content {
width: 70%;
}
}
@media screen and (max-width: 1023px) {
.app-sidebar {
display: none;
}
.app-content {
margin-left: 0;
width: 100%;
}
.mobile-header {
display: block;
}
.app-footer {
position: relative;
bottom: auto;
left: auto;
padding: 6em var(--content-padding) var(--content-padding);
}
.home-slideshow img {
max-width: 100%;
max-height: none;
object-fit: cover;
}
}
@media screen and (max-width: 480px) {
:root {
--sidebar-padding: 24px;
--content-padding: 12px;
}
}
export interface StrapiMedia {
id: number
url: string
width: number
height: number
mime: string
formats?: Record<string, { url: string; width: number; height: number }>
alternativeText?: string
}
export interface MediaItem {
id: number
file: StrapiMedia
poster?: StrapiMedia
}
export interface Work {
id: number
documentId: string
title: string
slug: string
subtitle?: string
year?: string
location?: string
description?: string
cover_image: StrapiMedia
media_items: MediaItem[]
sort?: number
}
export interface NewsItem {
id: number
documentId: string
title: string
slug?: string
date: string
content: string
image?: StrapiMedia
}
export interface Biography {
id: number
intro: string
team: string
}
export interface Contact {
id: number
email: string
phone: string
wechat?: string
instagram?: string
address?: string
}
export interface SiteSetting {
id: number
site_title: string
homepage_images: StrapiMedia[]
}
export interface StrapiResponse<T> {
data: T
meta?: {
pagination?: {
page: number
pageCount: number
pageSize: number
total: number
}
}
}
export interface StrapiListResponse<T> {
data: T[]
meta: {
pagination: {
page: number
pageCount: number
pageSize: number
total: number
}
}
}
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { fetchBiography } from '@/api'
import type { Biography } from '@/types'
const { t } = useI18n()
const biography = ref<Biography | null>(null)
const loading = ref(true)
onMounted(async () => {
try {
const response = await fetchBiography()
biography.value = response.data
} finally {
loading.value = false
}
})
</script>
<template>
<div>
<div v-if="loading" class="loading">{{ t('common.loading') }}</div>
<div v-else-if="biography" class="biography-page">
<h1>{{ t('biography.title') }}</h1>
<div class="biography-section">
<h2 class="biography-section-title">{{ t('biography.intro') }}</h2>
<div class="biography-intro" v-html="biography.intro" />
</div>
<div class="biography-section">
<h2 class="biography-section-title">{{ t('biography.team') }}</h2>
<div class="biography-team" v-html="biography.team" />
</div>
</div>
<div v-else class="loading">{{ t('common.noData') }}</div>
</div>
</template>
<style scoped>
.biography-intro,
.biography-team {
font-size: var(--font-size-base);
line-height: var(--line-height);
}
.biography-intro p,
.biography-team p {
margin-bottom: 0.8em;
}
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { fetchContact } from '@/api'
import type { Contact } from '@/types'
const { t } = useI18n()
const contact = ref<Contact | null>(null)
const loading = ref(true)
onMounted(async () => {
try {
const response = await fetchContact()
contact.value = response.data
} finally {
loading.value = false
}
})
</script>
<template>
<div>
<div v-if="loading" class="loading">{{ t('common.loading') }}</div>
<div v-else-if="contact" class="contact-page">
<h1>{{ t('contact.title') }}</h1>
<div class="contact-section">
<p>
<strong>{{ t('contact.email') }}</strong><br />
<a :href="`mailto:${contact.email}`">{{ contact.email }}</a>
</p>
</div>
<div class="contact-section" v-if="contact.phone">
<p>
<strong>{{ t('contact.phone') }}</strong><br />
{{ contact.phone }}
</p>
</div>
<div class="contact-section" v-if="contact.wechat">
<p>
<strong>{{ t('contact.wechat') }}</strong><br />
{{ contact.wechat }}
</p>
</div>
<div class="contact-section" v-if="contact.instagram">
<p>
<strong>{{ t('contact.instagram') }}</strong><br />
<a
:href="`https://instagram.com/${contact.instagram}`"
target="_blank"
rel="noopener noreferrer"
>
@{{ contact.instagram }}
</a>
</p>
</div>
<div class="contact-section" v-if="contact.address">
<p>
<strong>{{ t('contact.address') }}</strong><br />
{{ contact.address }}
</p>
</div>
</div>
<div v-else class="loading">{{ t('common.noData') }}</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useWorksStore } from '@/stores/works'
import { fetchSiteSetting, getMediaUrl } from '@/api'
import type { StrapiMedia } from '@/types'
const worksStore = useWorksStore()
const images = ref<StrapiMedia[]>([])
const currentIndex = ref(0)
let timer: ReturnType<typeof setInterval> | null = null
onMounted(async () => {
try {
const response = await fetchSiteSetting()
const siteSetting = response.data
if (siteSetting.homepage_images && siteSetting.homepage_images.length > 0) {
images.value = siteSetting.homepage_images
} else {
await worksStore.loadWorks()
if (worksStore.works.length > 0) {
const firstWork = worksStore.works[0]
const files = firstWork.media_items?.map((item) => item.file) ?? []
images.value = files.length > 0 ? files : [firstWork.cover_image]
}
}
} catch {
await worksStore.loadWorks()
if (worksStore.works.length > 0) {
const firstWork = worksStore.works[0]
const files = firstWork.media_items?.map((item) => item.file) ?? []
images.value = files.length > 0 ? files : [firstWork.cover_image]
}
}
startSlideshow()
})
onUnmounted(() => {
stopSlideshow()
})
function startSlideshow() {
if (images.value.length <= 1) return
timer = setInterval(() => {
currentIndex.value = (currentIndex.value + 1) % images.value.length
}, 4000)
}
function stopSlideshow() {
if (timer) {
clearInterval(timer)
timer = null
}
}
</script>
<template>
<div class="home-slideshow" v-if="images.length > 0">
<img
v-for="(img, index) in images"
:key="img.id"
:src="getMediaUrl(img)"
:alt="img.alternativeText || ''"
:class="{ active: index === currentIndex }"
/>
</div>
<div v-else class="loading">{{ $t('common.loading') }}</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { useNewsStore } from '@/stores/news'
import { getMediaUrl } from '@/api'
const { t } = useI18n()
const newsStore = useNewsStore()
onMounted(async () => {
await newsStore.loadNews(1)
})
function formatDate(dateStr: string): string {
const date = new Date(dateStr)
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
}
async function goToPage(page: number) {
await newsStore.loadNews(page)
}
</script>
<template>
<div>
<div v-if="newsStore.loading" class="loading">{{ t('common.loading') }}</div>
<div v-else-if="newsStore.newsItems.length === 0" class="loading">{{ t('common.noData') }}</div>
<div v-else class="news-feed">
<div
v-for="item in newsStore.newsItems"
:key="item.documentId"
class="news-item"
>
<div class="news-item-date">{{ formatDate(item.date) }}</div>
<h2>{{ item.title }}</h2>
<img
v-if="item.image"
:src="getMediaUrl(item.image)"
:alt="item.title"
loading="lazy"
/>
<div class="news-item-content" v-html="item.content" />
</div>
<!-- Pagination -->
<div v-if="newsStore.pagination.pageCount > 1" class="news-pagination">
<button
v-if="newsStore.pagination.page > 1"
class="news-pagination-btn"
@click="goToPage(newsStore.pagination.page - 1)"
>
{{ t('news.prev') }}
</button>
<span class="news-pagination-info">
{{ newsStore.pagination.page }} / {{ newsStore.pagination.pageCount }}
</span>
<button
v-if="newsStore.pagination.page < newsStore.pagination.pageCount"
class="news-pagination-btn"
@click="goToPage(newsStore.pagination.page + 1)"
>
{{ t('news.next') }}
</button>
</div>
</div>
</div>
</template>
<style scoped>
.news-pagination {
display: flex;
align-items: center;
gap: 2em;
padding-top: 2em;
}
.news-pagination-btn {
font-family: var(--font-serif);
font-size: var(--font-size-base);
color: var(--color-text);
background: none;
border: 1px solid var(--color-border);
padding: 6px 16px;
cursor: pointer;
transition: all var(--transition-fast);
}
.news-pagination-btn:hover {
color: var(--color-bg);
background: var(--color-text);
}
.news-pagination-info {
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
}
</style>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useWorksStore } from '@/stores/works'
import { getMediaUrl } from '@/api'
import type { StrapiMedia, MediaItem } from '@/types'
import VueEasyLightbox from 'vue-easy-lightbox'
const route = useRoute()
const router = useRouter()
const { t } = useI18n()
const worksStore = useWorksStore()
const showInfo = ref(true)
// Lightbox state
const lightboxVisible = ref(false)
const lightboxIndex = ref(0)
const lightboxImages = computed(() =>
(worksStore.currentWork?.media_items ?? [])
.filter((item) => !isVideo(item.file))
.map((item) => getMediaUrl(item.file))
)
function openLightbox(item: MediaItem) {
if (isVideo(item.file)) return
const imagesOnly = (worksStore.currentWork?.media_items ?? []).filter((m) => !isVideo(m.file))
const idx = imagesOnly.findIndex((m) => m.id === item.id)
lightboxIndex.value = idx >= 0 ? idx : 0
lightboxVisible.value = true
}
function closeLightbox() {
lightboxVisible.value = false
}
async function loadCurrentWork() {
const slug = route.params.slug as string
if (!slug) return
const work = await worksStore.loadWork(slug)
if (!work) {
router.replace('/works')
}
}
onMounted(loadCurrentWork)
watch(() => route.params.slug, () => {
loadCurrentWork()
})
onUnmounted(() => {
worksStore.clearCurrent()
})
function isVideo(media: StrapiMedia): boolean {
return media.mime?.startsWith('video/') ?? false
}
function getVideoPoster(item: MediaItem): string {
if (item.poster) return getMediaUrl(item.poster)
return ''
}
function onVideoLoaded(e: Event) {
const video = e.target as HTMLVideoElement
if (video.poster) return
video.currentTime = 0.5
}
function onVideoSeeked(e: Event) {
const video = e.target as HTMLVideoElement
if (video.poster) return
try {
const canvas = document.createElement('canvas')
canvas.width = video.videoWidth
canvas.height = video.videoHeight
const ctx = canvas.getContext('2d')
if (ctx) {
ctx.drawImage(video, 0, 0)
video.poster = canvas.toDataURL('image/jpeg', 0.7)
}
} catch {
// CORS or other issues, skip auto poster
}
}
interface InfoRow {
label: string
value: string
}
const infoRows = computed<InfoRow[]>(() => {
const work = worksStore.currentWork
if (!work?.description) return []
return work.description
.split('\n')
.filter((line) => line.trim() !== '' && !line.startsWith('#'))
.map((line) => {
const idx = line.indexOf('')
if (idx > 0) {
return { label: line.slice(0, idx + 1), value: line.slice(idx + 1) }
}
const colonIdx = line.indexOf(':')
if (colonIdx > 0) {
return { label: line.slice(0, colonIdx + 1), value: line.slice(colonIdx + 1) }
}
return { label: '', value: line }
})
})
</script>
<template>
<div v-if="worksStore.currentWork">
<!-- Title + Year -->
<h1 class="work-detail-title">{{ worksStore.currentWork.title }}</h1>
<div class="work-detail-meta">
<span v-if="worksStore.currentWork.subtitle">{{ worksStore.currentWork.subtitle }}</span>
<span v-if="worksStore.currentWork.year" class="work-detail-year">{{ worksStore.currentWork.year }}</span>
</div>
<!-- Info Toggle -->
<button class="work-info-toggle" @click="showInfo = !showInfo">
{{ showInfo ? t('work.hideInfo') : t('work.showInfo') }}
</button>
<!-- Structured Project Info -->
<div v-if="showInfo && infoRows.length > 0" class="work-info-table">
<div v-for="(row, i) in infoRows" :key="i" class="work-info-row">
<span v-if="row.label" class="work-info-label">{{ row.label }}</span>
<span class="work-info-value">{{ row.value }}</span>
</div>
</div>
<!-- Photo / Video Grid -->
<div class="photo-grid">
<template v-for="item in worksStore.currentWork.media_items" :key="item.id">
<video
v-if="isVideo(item.file)"
:src="getMediaUrl(item.file)"
:poster="getVideoPoster(item)"
controls
preload="metadata"
class="photo-grid-video"
@loadeddata="onVideoLoaded"
@seeked="onVideoSeeked"
/>
<img
v-else
:src="getMediaUrl(item.file)"
:alt="item.file.alternativeText || worksStore.currentWork!.title"
loading="lazy"
class="photo-grid-img"
@click="openLightbox(item)"
/>
</template>
</div>
<!-- Lightbox -->
<VueEasyLightbox
:visible="lightboxVisible"
:imgs="lightboxImages"
:index="lightboxIndex"
@hide="closeLightbox"
/>
</div>
<div v-else class="loading">{{ t('common.loading') }}</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useWorksStore } from '@/stores/works'
import { getMediaUrl } from '@/api'
const router = useRouter()
const { t } = useI18n()
const worksStore = useWorksStore()
onMounted(async () => {
if (worksStore.works.length === 0) {
await worksStore.loadWorks()
}
})
function goToWork(slug: string) {
router.push(`/works/${slug}`)
}
</script>
<template>
<div>
<div v-if="worksStore.loading" class="loading">{{ t('common.loading') }}</div>
<div v-else-if="worksStore.works.length === 0" class="loading">{{ t('common.noData') }}</div>
<div v-else class="works-grid">
<div
v-for="work in worksStore.works"
:key="work.documentId"
class="works-grid-item"
@click="goToWork(work.slug)"
>
<img
v-if="work.cover_image"
:src="getMediaUrl(work.cover_image)"
:alt="work.title"
loading="lazy"
/>
<h2>{{ work.title }}</h2>
<div class="works-grid-meta">
<span v-if="work.subtitle" class="works-grid-subtitle">{{ work.subtitle }}</span>
<span v-if="work.year" class="year">{{ work.year }}</span>
</div>
</div>
</div>
</div>
</template>
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"types": ["vite/client"],
"baseUrl": ".",
"ignoreDeprecations": "6.0",
"paths": {
"@/*": ["src/*"]
},
/* Linting */
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "es2023",
"lib": ["ES2023"],
"module": "esnext",
"types": ["node"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
const STRAPI_URL = process.env.STRAPI_URL || 'http://localhost:1337'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
},
server: {
proxy: {
'/api': {
target: STRAPI_URL,
changeOrigin: true,
},
'/uploads': {
target: STRAPI_URL,
changeOrigin: true,
},
},
},
})
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "nancy-studio",
"version": "1.0.0",
"private": true,
"scripts": {
"dev:cms": "cd cms && npm run develop",
"dev:front": "cd frontend && npm run dev",
"dev": "concurrently \"npm run dev:cms\" \"npm run dev:front\"",
"install:all": "cd frontend && npm install && cd ../cms && npm install"
},
"devDependencies": {
"concurrently": "^9.2.1"
}
}
#!/bin/bash
# Sync local Strapi data to Strapi Cloud
REMOTE="https://fearless-vacation-76b7b5ab44.strapiapp.com"
UPLOADS_DIR="/Users/xierongchao/workspace/nancy-studio/cms/public/uploads"
# Login to remote
JWT=$(curl -s "$REMOTE/admin/login" \
-H 'Content-Type: application/json' \
-d '{"email":"18796856@qq.com","password":"Xierongchao1986"}' | python3 -c "import json,sys; print(json.load(sys.stdin)['data']['token'])")
echo "Logged in. JWT: ${JWT:0:20}..."
# Get local works data
WORKS_JSON=$(curl -s 'http://localhost:1337/api/works?populate=*')
# Upload a file to remote and return the remote file id
upload_file() {
local file_path="$1"
local file_name=$(basename "$file_path")
result=$(curl -s "$REMOTE/api/upload" \
-H "Authorization: Bearer $JWT" \
-F "files=@$file_path" 2>&1)
echo "$result" | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
if isinstance(d, list) and len(d) > 0:
print(d[0]['id'])
else:
print('ERROR: ' + str(d))
except Exception as e:
print('ERROR: ' + str(e))
"
}
# Upload all local files and build mapping: local_id -> remote_id
echo "=== Uploading files ==="
LOCAL_FILES=$(curl -s 'http://localhost:1337/api/upload/files' \
-H "Authorization: Bearer $(curl -s 'http://localhost:1337/admin/login' -H 'Content-Type: application/json' -d '{"email":"18796856@qq.com","password":"Xierongchao1986"}' | python3 -c "import json,sys; print(json.load(sys.stdin)['data']['token'])")")
# Build file mapping
python3 << 'PYEOF'
import json, subprocess, os, sys
remote = "https://fearless-vacation-76b7b5ab44.strapiapp.com"
uploads_dir = "/Users/xierongchao/workspace/nancy-studio/cms/public/uploads"
jwt = os.environ.get("JWT", "")
# Read JWT from parent shell
import urllib.request
# Get JWT
login_data = json.dumps({"email":"18796856@qq.com","password":"Xierongchao1986"}).encode()
req = urllib.request.Request(f"{remote}/admin/login", data=login_data, headers={"Content-Type":"application/json"})
resp = urllib.request.urlopen(req)
jwt = json.loads(resp.read())["data"]["token"]
# Get local works
req = urllib.request.Request("http://localhost:1337/api/works?populate=*")
resp = urllib.request.urlopen(req)
works = json.loads(resp.read())["data"]
print(f"Found {len(works)} works to sync")
# Get local files mapping (hash -> local info)
req = urllib.request.Request("http://localhost:1337/api/upload/files")
resp = urllib.request.urlopen(req)
local_files = json.loads(resp.read())
# Build url->id mapping for local files
local_url_to_info = {}
for f in local_files:
url = f["url"]
# Get the actual file path
if url.startswith("/uploads/"):
fname = url.replace("/uploads/", "")
local_url_to_info[f["id"]] = {
"path": os.path.join(uploads_dir, fname),
"name": f["name"],
"id": f["id"],
"hash": f.get("hash",""),
}
# Also check formats
for fmt_name, fmt in (f.get("formats") or {}).items():
furl = fmt.get("url","")
if furl.startswith("/uploads/"):
fname = furl.replace("/uploads/", "")
# Don't add formats to upload list, they'll be generated by Strapi
print(f"Found {len(local_url_to_info)} unique local files")
# Upload files in batches and build id mapping
local_to_remote_id = {}
file_ids = sorted(local_url_to_info.keys())
for i, fid in enumerate(file_ids):
info = local_url_to_info[fid]
fpath = info["path"]
if not os.path.exists(fpath):
print(f" SKIP (not found): {fpath}")
continue
print(f" [{i+1}/{len(file_ids)}] Uploading {info['name']}...", end=" ", flush=True)
# Use curl for multipart upload
result = subprocess.run(
["curl", "-s", f"{remote}/api/upload",
"-H", f"Authorization: Bearer {jwt}",
"-F", f"files=@{fpath}"],
capture_output=True, text=True
)
try:
resp_data = json.loads(result.stdout)
if isinstance(resp_data, list) and len(resp_data) > 0:
remote_id = resp_data[0]["id"]
local_to_remote_id[fid] = remote_id
print(f"OK (local:{fid} -> remote:{remote_id})")
else:
print(f"FAILED: {resp_data}")
except Exception as e:
print(f"ERROR: {e} - {result.stdout[:200]}")
print(f"\nUploaded {len(local_to_remote_id)} files")
# Now create works on remote
print("\n=== Creating works ===")
for work in works:
# Map cover_image
cover_local_id = work.get("cover_image", {}).get("id") if work.get("cover_image") else None
cover_remote_id = local_to_remote_id.get(cover_local_id)
# Map photos
photo_remote_ids = []
for p in (work.get("photos") or []):
rid = local_to_remote_id.get(p.get("id"))
if rid:
photo_remote_ids.append(rid)
payload = {
"data": {
"title": work["title"],
"slug": work["slug"],
"subtitle": work.get("subtitle", ""),
"year": work.get("year"),
"location": work.get("location", ""),
"description": work.get("description", ""),
"sort": work.get("sort", 0),
}
}
if cover_remote_id:
payload["data"]["cover_image"] = cover_remote_id
if photo_remote_ids:
payload["data"]["photos"] = photo_remote_ids
print(f" Creating: {work['title']}", end=" ... ", flush=True)
req = urllib.request.Request(
f"{remote}/api/works",
data=json.dumps(payload).encode(),
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {jwt}"
},
method="POST"
)
try:
resp = urllib.request.urlopen(req)
resp_data = json.loads(resp.read())
print(f"OK (remote id: {resp_data['data']['id']})")
except urllib.error.HTTPError as e:
body = e.read().decode()
print(f"FAILED ({e.code}): {body[:200]}")
except Exception as e:
print(f"ERROR: {e}")
print("\n=== Done ===")
PYEOF
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment