hookehuyr

feat: 初始化项目结构并添加核心功能

- 初始化项目结构,包括Vue 3、Pinia、Vue Router、TailwindCSS等依赖
- 添加首页、活动详情页、创建活动页等核心页面
- 实现活动分类、搜索、报名等基本功能
- 添加公共组件如按钮、输入框、模态框等
- 配置Vite、TailwindCSS、PostCSS等构建工具
Showing 53 changed files with 13111 additions and 0 deletions
# 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?
.history
node_modules
.vscode
mlaj-read
# React + Vite Template
A modern React template for web applications and games, featuring React 18, Vite, TailwindCSS, and Material UI.
## Project Structure
```
├── src/
│ ├── App.jsx # Main application component
│ ├── main.jsx # Application entry point
│ └── index.css # Global styles (Tailwind)
├── public/ # Static assets
├── index.html # HTML template
├── vite.config.js # Vite configuration
├── tailwind.config.js # Tailwind configuration
├── postcss.config.js # PostCSS configuration
└── eslint.config.js # ESLint configuration
```
## Development Guidelines
- Modify `index.html` and `src/App.jsx` as needed
- Create new folders or files in `src/` directory as needed
- Style components using TailwindCSS utility classes
- Avoid modifying `src/main.jsx` and `src/index.css`
- Only modify `vite.config.js` if absolutely necessary
## Available Scripts
- `pnpm install` - Install dependencies
- `pnpm run dev` - Start development server
- `pnpm run lint` - Lint source files
## Tech Stack
- React
- Vite
- TailwindCSS
- ESLint
- Javascript
......
<!--
* @Date: 2025-04-17 08:14:25
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-04-17 13:26:33
* @FilePath: /reading-club-app/index.html
* @Description: 文件描述
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>读书会 - 连接爱读书的人</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
{
"name": "vue-reading-club",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint ./src --quiet",
"preview": "vite preview"
},
"dependencies": {
"@vitejs/plugin-vue-jsx": "4.1.2",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"@vue/compiler-sfc": "^3.4.21",
"autoprefixer": "^10.4.20",
"eslint": "^9.9.0",
"eslint-plugin-vue": "^9.24.0",
"postcss": "^8.4.45",
"tailwindcss": "^3.4.10",
"vite": "^5.4.1"
}
}
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
'@vitejs/plugin-vue-jsx':
specifier: 4.1.2
version: 4.1.2(vite@5.4.18)(vue@3.5.13)
pinia:
specifier: ^2.1.7
version: 2.3.1(vue@3.5.13)
vue:
specifier: ^3.4.21
version: 3.5.13
vue-router:
specifier: ^4.3.0
version: 4.5.0(vue@3.5.13)
devDependencies:
'@vitejs/plugin-vue':
specifier: ^5.0.4
version: 5.2.3(vite@5.4.18)(vue@3.5.13)
'@vue/compiler-sfc':
specifier: ^3.4.21
version: 3.5.13
autoprefixer:
specifier: ^10.4.20
version: 10.4.21(postcss@8.5.3)
eslint:
specifier: ^9.9.0
version: 9.24.0(jiti@1.21.7)
eslint-plugin-vue:
specifier: ^9.24.0
version: 9.33.0(eslint@9.24.0(jiti@1.21.7))
postcss:
specifier: ^8.4.45
version: 8.5.3
tailwindcss:
specifier: ^3.4.10
version: 3.4.17
vite:
specifier: ^5.4.1
version: 5.4.18
packages:
'@alloc/quick-lru@5.2.0':
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
'@ampproject/remapping@2.3.0':
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
'@babel/code-frame@7.26.2':
resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
engines: {node: '>=6.9.0'}
'@babel/compat-data@7.26.8':
resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==}
engines: {node: '>=6.9.0'}
'@babel/core@7.26.10':
resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==}
engines: {node: '>=6.9.0'}
'@babel/generator@7.27.0':
resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==}
engines: {node: '>=6.9.0'}
'@babel/helper-annotate-as-pure@7.25.9':
resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==}
engines: {node: '>=6.9.0'}
'@babel/helper-compilation-targets@7.27.0':
resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==}
engines: {node: '>=6.9.0'}
'@babel/helper-create-class-features-plugin@7.27.0':
resolution: {integrity: sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
'@babel/helper-member-expression-to-functions@7.25.9':
resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==}
engines: {node: '>=6.9.0'}
'@babel/helper-module-imports@7.25.9':
resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
engines: {node: '>=6.9.0'}
'@babel/helper-module-transforms@7.26.0':
resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
'@babel/helper-optimise-call-expression@7.25.9':
resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==}
engines: {node: '>=6.9.0'}
'@babel/helper-plugin-utils@7.26.5':
resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==}
engines: {node: '>=6.9.0'}
'@babel/helper-replace-supers@7.26.5':
resolution: {integrity: sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
'@babel/helper-skip-transparent-expression-wrappers@7.25.9':
resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==}
engines: {node: '>=6.9.0'}
'@babel/helper-string-parser@7.25.9':
resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-identifier@7.25.9':
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-option@7.25.9':
resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
engines: {node: '>=6.9.0'}
'@babel/helpers@7.27.0':
resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==}
engines: {node: '>=6.9.0'}
'@babel/parser@7.27.0':
resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==}
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/plugin-syntax-jsx@7.25.9':
resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
'@babel/plugin-syntax-typescript@7.25.9':
resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
'@babel/plugin-transform-typescript@7.27.0':
resolution: {integrity: sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
'@babel/template@7.27.0':
resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==}
engines: {node: '>=6.9.0'}
'@babel/traverse@7.27.0':
resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==}
engines: {node: '>=6.9.0'}
'@babel/types@7.27.0':
resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==}
engines: {node: '>=6.9.0'}
'@esbuild/aix-ppc64@0.21.5':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.21.5':
resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.21.5':
resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.21.5':
resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.21.5':
resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.21.5':
resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.21.5':
resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.21.5':
resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.21.5':
resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.21.5':
resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.21.5':
resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.21.5':
resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.21.5':
resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.21.5':
resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.21.5':
resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.21.5':
resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.21.5':
resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-x64@0.21.5':
resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-x64@0.21.5':
resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
'@esbuild/sunos-x64@0.21.5':
resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.21.5':
resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.21.5':
resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.21.5':
resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
'@eslint-community/eslint-utils@4.6.1':
resolution: {integrity: sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
'@eslint-community/regexpp@4.12.1':
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
'@eslint/config-array@0.20.0':
resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/config-helpers@0.2.1':
resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/core@0.12.0':
resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/core@0.13.0':
resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/eslintrc@3.3.1':
resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/js@9.24.0':
resolution: {integrity: sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/object-schema@2.1.6':
resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/plugin-kit@0.2.8':
resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
'@humanfs/node@0.16.6':
resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
engines: {node: '>=18.18.0'}
'@humanwhocodes/module-importer@1.0.1':
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
engines: {node: '>=12.22'}
'@humanwhocodes/retry@0.3.1':
resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
engines: {node: '>=18.18'}
'@humanwhocodes/retry@0.4.2':
resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==}
engines: {node: '>=18.18'}
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
'@jridgewell/gen-mapping@0.3.8':
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
engines: {node: '>=6.0.0'}
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
'@jridgewell/set-array@1.2.1':
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
engines: {node: '>=6.0.0'}
'@jridgewell/sourcemap-codec@1.5.0':
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
'@nodelib/fs.stat@2.0.5':
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
engines: {node: '>= 8'}
'@nodelib/fs.walk@1.2.8':
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@rollup/rollup-android-arm-eabi@4.40.0':
resolution: {integrity: sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.40.0':
resolution: {integrity: sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.40.0':
resolution: {integrity: sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.40.0':
resolution: {integrity: sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-freebsd-arm64@4.40.0':
resolution: {integrity: sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==}
cpu: [arm64]
os: [freebsd]
'@rollup/rollup-freebsd-x64@4.40.0':
resolution: {integrity: sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==}
cpu: [x64]
os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.40.0':
resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.40.0':
resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.40.0':
resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.40.0':
resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-loongarch64-gnu@4.40.0':
resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==}
cpu: [loong64]
os: [linux]
'@rollup/rollup-linux-powerpc64le-gnu@4.40.0':
resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==}
cpu: [ppc64]
os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.40.0':
resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-riscv64-musl@4.40.0':
resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.40.0':
resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==}
cpu: [s390x]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.40.0':
resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.40.0':
resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==}
cpu: [x64]
os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.40.0':
resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.40.0':
resolution: {integrity: sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.40.0':
resolution: {integrity: sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==}
cpu: [x64]
os: [win32]
'@types/estree@1.0.7':
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@vitejs/plugin-vue-jsx@4.1.2':
resolution: {integrity: sha512-4Rk0GdE0QCdsIkuMmWeg11gmM4x8UmTnZR/LWPm7QJ7+BsK4tq08udrN0isrrWqz5heFy9HLV/7bOLgFS8hUjA==}
engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies:
vite: ^5.0.0 || ^6.0.0
vue: ^3.0.0
'@vitejs/plugin-vue@5.2.3':
resolution: {integrity: sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==}
engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies:
vite: ^5.0.0 || ^6.0.0
vue: ^3.2.25
'@vue/babel-helper-vue-transform-on@1.4.0':
resolution: {integrity: sha512-mCokbouEQ/ocRce/FpKCRItGo+013tHg7tixg3DUNS+6bmIchPt66012kBMm476vyEIJPafrvOf4E5OYj3shSw==}
'@vue/babel-plugin-jsx@1.4.0':
resolution: {integrity: sha512-9zAHmwgMWlaN6qRKdrg1uKsBKHvnUU+Py+MOCTuYZBoZsopa90Di10QRjB+YPnVss0BZbG/H5XFwJY1fTxJWhA==}
peerDependencies:
'@babel/core': ^7.0.0-0
peerDependenciesMeta:
'@babel/core':
optional: true
'@vue/babel-plugin-resolve-type@1.4.0':
resolution: {integrity: sha512-4xqDRRbQQEWHQyjlYSgZsWj44KfiF6D+ktCuXyZ8EnVDYV3pztmXJDf1HveAjUAXxAnR8daCQT51RneWWxtTyQ==}
peerDependencies:
'@babel/core': ^7.0.0-0
'@vue/compiler-core@3.5.13':
resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==}
'@vue/compiler-dom@3.5.13':
resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==}
'@vue/compiler-sfc@3.5.13':
resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==}
'@vue/compiler-ssr@3.5.13':
resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==}
'@vue/devtools-api@6.6.4':
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
'@vue/reactivity@3.5.13':
resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==}
'@vue/runtime-core@3.5.13':
resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==}
'@vue/runtime-dom@3.5.13':
resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==}
'@vue/server-renderer@3.5.13':
resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==}
peerDependencies:
vue: 3.5.13
'@vue/shared@3.5.13':
resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
acorn@8.14.1:
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
engines: {node: '>=0.4.0'}
hasBin: true
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-regex@6.1.0:
resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
engines: {node: '>=12'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
autoprefixer@10.4.21:
resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
engines: {node: ^10 || ^12 || >=14}
hasBin: true
peerDependencies:
postcss: ^8.1.0
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
brace-expansion@2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
browserslist@4.24.4:
resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
camelcase-css@2.0.1:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'}
caniuse-lite@1.0.30001714:
resolution: {integrity: sha512-mtgapdwDLSSBnCI3JokHM7oEQBLxiJKVRtg10AxM1AyeiKcM96f0Mkbqeq+1AbiCtvMcHRulAAEMu693JrSWqg==}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
hasBin: true
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
debug@4.4.0:
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
dlv@1.1.3:
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
electron-to-chromium@1.5.137:
resolution: {integrity: sha512-/QSJaU2JyIuTbbABAo/crOs+SuAZLS+fVVS10PVrIT9hrRkmZl8Hb0xPSkKRUUWHQtYzXHpQUW3Dy5hwMzGZkA==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
esbuild@0.21.5:
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
engines: {node: '>=12'}
hasBin: true
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
eslint-plugin-vue@9.33.0:
resolution: {integrity: sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
eslint-scope@7.2.2:
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
eslint-scope@8.3.0:
resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint-visitor-keys@3.4.3:
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
eslint-visitor-keys@4.2.0:
resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
eslint@9.24.0:
resolution: {integrity: sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
hasBin: true
peerDependencies:
jiti: '*'
peerDependenciesMeta:
jiti:
optional: true
espree@10.3.0:
resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
espree@9.6.1:
resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
esquery@1.6.0:
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
engines: {node: '>=0.10'}
esrecurse@4.3.0:
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
engines: {node: '>=4.0'}
estraverse@5.3.0:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-glob@3.3.3:
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'}
fast-json-stable-stringify@2.1.0:
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
flat-cache@4.0.1:
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
engines: {node: '>=16'}
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
glob-parent@6.0.2:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
hasBin: true
globals@11.12.0:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'}
globals@13.24.0:
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
engines: {node: '>=8'}
globals@14.0.0:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
is-core-module@2.16.1:
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
engines: {node: '>= 0.4'}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
jiti@1.21.7:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
hasBin: true
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-yaml@4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
hasBin: true
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
hasBin: true
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
lilconfig@3.1.3:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
minipass@7.1.2:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
node-releases@2.0.19:
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
normalize-range@0.1.2:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
engines: {node: '>=0.10.0'}
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
object-hash@3.0.0:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
path-scurry@1.11.1:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
pinia@2.3.1:
resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==}
peerDependencies:
typescript: '>=4.4.4'
vue: ^2.7.0 || ^3.5.11
peerDependenciesMeta:
typescript:
optional: true
pirates@4.0.7:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'}
postcss-import@15.1.0:
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
engines: {node: '>=14.0.0'}
peerDependencies:
postcss: ^8.0.0
postcss-js@4.0.1:
resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
engines: {node: ^12 || ^14 || >= 16}
peerDependencies:
postcss: ^8.4.21
postcss-load-config@4.0.2:
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
engines: {node: '>= 14'}
peerDependencies:
postcss: '>=8.0.9'
ts-node: '>=9.0.0'
peerDependenciesMeta:
postcss:
optional: true
ts-node:
optional: true
postcss-nested@6.2.0:
resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.2.14
postcss-selector-parser@6.1.2:
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
engines: {node: '>=4'}
postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
postcss@8.5.3:
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
engines: {node: ^10 || ^12 || >=14}
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
resolve@1.22.10:
resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
engines: {node: '>= 0.4'}
hasBin: true
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rollup@4.40.0:
resolution: {integrity: sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
semver@7.7.1:
resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
engines: {node: '>=10'}
hasBin: true
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
string-width@5.1.2:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-ansi@7.1.0:
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
engines: {node: '>=12'}
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
sucrase@3.35.0:
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
tailwindcss@3.4.17:
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
engines: {node: '>=14.0.0'}
hasBin: true
thenify-all@1.6.0:
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
engines: {node: '>=0.8'}
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
type-fest@0.20.2:
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
engines: {node: '>=10'}
update-browserslist-db@1.1.3:
resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
vite@5.4.18:
resolution: {integrity: sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || >=20.0.0
less: '*'
lightningcss: ^1.21.0
sass: '*'
sass-embedded: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
vue-demi@0.14.10:
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
engines: {node: '>=12'}
hasBin: true
peerDependencies:
'@vue/composition-api': ^1.0.0-rc.1
vue: ^3.0.0-0 || ^2.6.0
peerDependenciesMeta:
'@vue/composition-api':
optional: true
vue-eslint-parser@9.4.3:
resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: '>=6.0.0'
vue-router@4.5.0:
resolution: {integrity: sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==}
peerDependencies:
vue: ^3.2.0
vue@3.5.13:
resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
wrap-ansi@8.1.0:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
xml-name-validator@4.0.0:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'}
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
yaml@2.7.1:
resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==}
engines: {node: '>= 14'}
hasBin: true
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
snapshots:
'@alloc/quick-lru@5.2.0': {}
'@ampproject/remapping@2.3.0':
dependencies:
'@jridgewell/gen-mapping': 0.3.8
'@jridgewell/trace-mapping': 0.3.25
'@babel/code-frame@7.26.2':
dependencies:
'@babel/helper-validator-identifier': 7.25.9
js-tokens: 4.0.0
picocolors: 1.1.1
'@babel/compat-data@7.26.8': {}
'@babel/core@7.26.10':
dependencies:
'@ampproject/remapping': 2.3.0
'@babel/code-frame': 7.26.2
'@babel/generator': 7.27.0
'@babel/helper-compilation-targets': 7.27.0
'@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10)
'@babel/helpers': 7.27.0
'@babel/parser': 7.27.0
'@babel/template': 7.27.0
'@babel/traverse': 7.27.0
'@babel/types': 7.27.0
convert-source-map: 2.0.0
debug: 4.4.0
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
transitivePeerDependencies:
- supports-color
'@babel/generator@7.27.0':
dependencies:
'@babel/parser': 7.27.0
'@babel/types': 7.27.0
'@jridgewell/gen-mapping': 0.3.8
'@jridgewell/trace-mapping': 0.3.25
jsesc: 3.1.0
'@babel/helper-annotate-as-pure@7.25.9':
dependencies:
'@babel/types': 7.27.0
'@babel/helper-compilation-targets@7.27.0':
dependencies:
'@babel/compat-data': 7.26.8
'@babel/helper-validator-option': 7.25.9
browserslist: 4.24.4
lru-cache: 5.1.1
semver: 6.3.1
'@babel/helper-create-class-features-plugin@7.27.0(@babel/core@7.26.10)':
dependencies:
'@babel/core': 7.26.10
'@babel/helper-annotate-as-pure': 7.25.9
'@babel/helper-member-expression-to-functions': 7.25.9
'@babel/helper-optimise-call-expression': 7.25.9
'@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10)
'@babel/helper-skip-transparent-expression-wrappers': 7.25.9
'@babel/traverse': 7.27.0
semver: 6.3.1
transitivePeerDependencies:
- supports-color
'@babel/helper-member-expression-to-functions@7.25.9':
dependencies:
'@babel/traverse': 7.27.0
'@babel/types': 7.27.0
transitivePeerDependencies:
- supports-color
'@babel/helper-module-imports@7.25.9':
dependencies:
'@babel/traverse': 7.27.0
'@babel/types': 7.27.0
transitivePeerDependencies:
- supports-color
'@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)':
dependencies:
'@babel/core': 7.26.10
'@babel/helper-module-imports': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
'@babel/traverse': 7.27.0
transitivePeerDependencies:
- supports-color
'@babel/helper-optimise-call-expression@7.25.9':
dependencies:
'@babel/types': 7.27.0
'@babel/helper-plugin-utils@7.26.5': {}
'@babel/helper-replace-supers@7.26.5(@babel/core@7.26.10)':
dependencies:
'@babel/core': 7.26.10
'@babel/helper-member-expression-to-functions': 7.25.9
'@babel/helper-optimise-call-expression': 7.25.9
'@babel/traverse': 7.27.0
transitivePeerDependencies:
- supports-color
'@babel/helper-skip-transparent-expression-wrappers@7.25.9':
dependencies:
'@babel/traverse': 7.27.0
'@babel/types': 7.27.0
transitivePeerDependencies:
- supports-color
'@babel/helper-string-parser@7.25.9': {}
'@babel/helper-validator-identifier@7.25.9': {}
'@babel/helper-validator-option@7.25.9': {}
'@babel/helpers@7.27.0':
dependencies:
'@babel/template': 7.27.0
'@babel/types': 7.27.0
'@babel/parser@7.27.0':
dependencies:
'@babel/types': 7.27.0
'@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.10)':
dependencies:
'@babel/core': 7.26.10
'@babel/helper-plugin-utils': 7.26.5
'@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.10)':
dependencies:
'@babel/core': 7.26.10
'@babel/helper-plugin-utils': 7.26.5
'@babel/plugin-transform-typescript@7.27.0(@babel/core@7.26.10)':
dependencies:
'@babel/core': 7.26.10
'@babel/helper-annotate-as-pure': 7.25.9
'@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10)
'@babel/helper-plugin-utils': 7.26.5
'@babel/helper-skip-transparent-expression-wrappers': 7.25.9
'@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.10)
transitivePeerDependencies:
- supports-color
'@babel/template@7.27.0':
dependencies:
'@babel/code-frame': 7.26.2
'@babel/parser': 7.27.0
'@babel/types': 7.27.0
'@babel/traverse@7.27.0':
dependencies:
'@babel/code-frame': 7.26.2
'@babel/generator': 7.27.0
'@babel/parser': 7.27.0
'@babel/template': 7.27.0
'@babel/types': 7.27.0
debug: 4.4.0
globals: 11.12.0
transitivePeerDependencies:
- supports-color
'@babel/types@7.27.0':
dependencies:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
'@esbuild/aix-ppc64@0.21.5':
optional: true
'@esbuild/android-arm64@0.21.5':
optional: true
'@esbuild/android-arm@0.21.5':
optional: true
'@esbuild/android-x64@0.21.5':
optional: true
'@esbuild/darwin-arm64@0.21.5':
optional: true
'@esbuild/darwin-x64@0.21.5':
optional: true
'@esbuild/freebsd-arm64@0.21.5':
optional: true
'@esbuild/freebsd-x64@0.21.5':
optional: true
'@esbuild/linux-arm64@0.21.5':
optional: true
'@esbuild/linux-arm@0.21.5':
optional: true
'@esbuild/linux-ia32@0.21.5':
optional: true
'@esbuild/linux-loong64@0.21.5':
optional: true
'@esbuild/linux-mips64el@0.21.5':
optional: true
'@esbuild/linux-ppc64@0.21.5':
optional: true
'@esbuild/linux-riscv64@0.21.5':
optional: true
'@esbuild/linux-s390x@0.21.5':
optional: true
'@esbuild/linux-x64@0.21.5':
optional: true
'@esbuild/netbsd-x64@0.21.5':
optional: true
'@esbuild/openbsd-x64@0.21.5':
optional: true
'@esbuild/sunos-x64@0.21.5':
optional: true
'@esbuild/win32-arm64@0.21.5':
optional: true
'@esbuild/win32-ia32@0.21.5':
optional: true
'@esbuild/win32-x64@0.21.5':
optional: true
'@eslint-community/eslint-utils@4.6.1(eslint@9.24.0(jiti@1.21.7))':
dependencies:
eslint: 9.24.0(jiti@1.21.7)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.1': {}
'@eslint/config-array@0.20.0':
dependencies:
'@eslint/object-schema': 2.1.6
debug: 4.4.0
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
'@eslint/config-helpers@0.2.1': {}
'@eslint/core@0.12.0':
dependencies:
'@types/json-schema': 7.0.15
'@eslint/core@0.13.0':
dependencies:
'@types/json-schema': 7.0.15
'@eslint/eslintrc@3.3.1':
dependencies:
ajv: 6.12.6
debug: 4.4.0
espree: 10.3.0
globals: 14.0.0
ignore: 5.3.2
import-fresh: 3.3.1
js-yaml: 4.1.0
minimatch: 3.1.2
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
'@eslint/js@9.24.0': {}
'@eslint/object-schema@2.1.6': {}
'@eslint/plugin-kit@0.2.8':
dependencies:
'@eslint/core': 0.13.0
levn: 0.4.1
'@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.6':
dependencies:
'@humanfs/core': 0.19.1
'@humanwhocodes/retry': 0.3.1
'@humanwhocodes/module-importer@1.0.1': {}
'@humanwhocodes/retry@0.3.1': {}
'@humanwhocodes/retry@0.4.2': {}
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
string-width-cjs: string-width@4.2.3
strip-ansi: 7.1.0
strip-ansi-cjs: strip-ansi@6.0.1
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
'@jridgewell/gen-mapping@0.3.8':
dependencies:
'@jridgewell/set-array': 1.2.1
'@jridgewell/sourcemap-codec': 1.5.0
'@jridgewell/trace-mapping': 0.3.25
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/set-array@1.2.1': {}
'@jridgewell/sourcemap-codec@1.5.0': {}
'@jridgewell/trace-mapping@0.3.25':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
run-parallel: 1.2.0
'@nodelib/fs.stat@2.0.5': {}
'@nodelib/fs.walk@1.2.8':
dependencies:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.19.1
'@pkgjs/parseargs@0.11.0':
optional: true
'@rollup/rollup-android-arm-eabi@4.40.0':
optional: true
'@rollup/rollup-android-arm64@4.40.0':
optional: true
'@rollup/rollup-darwin-arm64@4.40.0':
optional: true
'@rollup/rollup-darwin-x64@4.40.0':
optional: true
'@rollup/rollup-freebsd-arm64@4.40.0':
optional: true
'@rollup/rollup-freebsd-x64@4.40.0':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.40.0':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.40.0':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.40.0':
optional: true
'@rollup/rollup-linux-arm64-musl@4.40.0':
optional: true
'@rollup/rollup-linux-loongarch64-gnu@4.40.0':
optional: true
'@rollup/rollup-linux-powerpc64le-gnu@4.40.0':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.40.0':
optional: true
'@rollup/rollup-linux-riscv64-musl@4.40.0':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.40.0':
optional: true
'@rollup/rollup-linux-x64-gnu@4.40.0':
optional: true
'@rollup/rollup-linux-x64-musl@4.40.0':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.40.0':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.40.0':
optional: true
'@rollup/rollup-win32-x64-msvc@4.40.0':
optional: true
'@types/estree@1.0.7': {}
'@types/json-schema@7.0.15': {}
'@vitejs/plugin-vue-jsx@4.1.2(vite@5.4.18)(vue@3.5.13)':
dependencies:
'@babel/core': 7.26.10
'@babel/plugin-transform-typescript': 7.27.0(@babel/core@7.26.10)
'@vue/babel-plugin-jsx': 1.4.0(@babel/core@7.26.10)
vite: 5.4.18
vue: 3.5.13
transitivePeerDependencies:
- supports-color
'@vitejs/plugin-vue@5.2.3(vite@5.4.18)(vue@3.5.13)':
dependencies:
vite: 5.4.18
vue: 3.5.13
'@vue/babel-helper-vue-transform-on@1.4.0': {}
'@vue/babel-plugin-jsx@1.4.0(@babel/core@7.26.10)':
dependencies:
'@babel/helper-module-imports': 7.25.9
'@babel/helper-plugin-utils': 7.26.5
'@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10)
'@babel/template': 7.27.0
'@babel/traverse': 7.27.0
'@babel/types': 7.27.0
'@vue/babel-helper-vue-transform-on': 1.4.0
'@vue/babel-plugin-resolve-type': 1.4.0(@babel/core@7.26.10)
'@vue/shared': 3.5.13
optionalDependencies:
'@babel/core': 7.26.10
transitivePeerDependencies:
- supports-color
'@vue/babel-plugin-resolve-type@1.4.0(@babel/core@7.26.10)':
dependencies:
'@babel/code-frame': 7.26.2
'@babel/core': 7.26.10
'@babel/helper-module-imports': 7.25.9
'@babel/helper-plugin-utils': 7.26.5
'@babel/parser': 7.27.0
'@vue/compiler-sfc': 3.5.13
transitivePeerDependencies:
- supports-color
'@vue/compiler-core@3.5.13':
dependencies:
'@babel/parser': 7.27.0
'@vue/shared': 3.5.13
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.2.1
'@vue/compiler-dom@3.5.13':
dependencies:
'@vue/compiler-core': 3.5.13
'@vue/shared': 3.5.13
'@vue/compiler-sfc@3.5.13':
dependencies:
'@babel/parser': 7.27.0
'@vue/compiler-core': 3.5.13
'@vue/compiler-dom': 3.5.13
'@vue/compiler-ssr': 3.5.13
'@vue/shared': 3.5.13
estree-walker: 2.0.2
magic-string: 0.30.17
postcss: 8.5.3
source-map-js: 1.2.1
'@vue/compiler-ssr@3.5.13':
dependencies:
'@vue/compiler-dom': 3.5.13
'@vue/shared': 3.5.13
'@vue/devtools-api@6.6.4': {}
'@vue/reactivity@3.5.13':
dependencies:
'@vue/shared': 3.5.13
'@vue/runtime-core@3.5.13':
dependencies:
'@vue/reactivity': 3.5.13
'@vue/shared': 3.5.13
'@vue/runtime-dom@3.5.13':
dependencies:
'@vue/reactivity': 3.5.13
'@vue/runtime-core': 3.5.13
'@vue/shared': 3.5.13
csstype: 3.1.3
'@vue/server-renderer@3.5.13(vue@3.5.13)':
dependencies:
'@vue/compiler-ssr': 3.5.13
'@vue/shared': 3.5.13
vue: 3.5.13
'@vue/shared@3.5.13': {}
acorn-jsx@5.3.2(acorn@8.14.1):
dependencies:
acorn: 8.14.1
acorn@8.14.1: {}
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
fast-json-stable-stringify: 2.1.0
json-schema-traverse: 0.4.1
uri-js: 4.4.1
ansi-regex@5.0.1: {}
ansi-regex@6.1.0: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
ansi-styles@6.2.1: {}
any-promise@1.3.0: {}
anymatch@3.1.3:
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
arg@5.0.2: {}
argparse@2.0.1: {}
autoprefixer@10.4.21(postcss@8.5.3):
dependencies:
browserslist: 4.24.4
caniuse-lite: 1.0.30001714
fraction.js: 4.3.7
normalize-range: 0.1.2
picocolors: 1.1.1
postcss: 8.5.3
postcss-value-parser: 4.2.0
balanced-match@1.0.2: {}
binary-extensions@2.3.0: {}
boolbase@1.0.0: {}
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
brace-expansion@2.0.1:
dependencies:
balanced-match: 1.0.2
braces@3.0.3:
dependencies:
fill-range: 7.1.1
browserslist@4.24.4:
dependencies:
caniuse-lite: 1.0.30001714
electron-to-chromium: 1.5.137
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.24.4)
callsites@3.1.0: {}
camelcase-css@2.0.1: {}
caniuse-lite@1.0.30001714: {}
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
supports-color: 7.2.0
chokidar@3.6.0:
dependencies:
anymatch: 3.1.3
braces: 3.0.3
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.3
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
commander@4.1.1: {}
concat-map@0.0.1: {}
convert-source-map@2.0.0: {}
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
cssesc@3.0.0: {}
csstype@3.1.3: {}
debug@4.4.0:
dependencies:
ms: 2.1.3
deep-is@0.1.4: {}
didyoumean@1.2.2: {}
dlv@1.1.3: {}
eastasianwidth@0.2.0: {}
electron-to-chromium@1.5.137: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
entities@4.5.0: {}
esbuild@0.21.5:
optionalDependencies:
'@esbuild/aix-ppc64': 0.21.5
'@esbuild/android-arm': 0.21.5
'@esbuild/android-arm64': 0.21.5
'@esbuild/android-x64': 0.21.5
'@esbuild/darwin-arm64': 0.21.5
'@esbuild/darwin-x64': 0.21.5
'@esbuild/freebsd-arm64': 0.21.5
'@esbuild/freebsd-x64': 0.21.5
'@esbuild/linux-arm': 0.21.5
'@esbuild/linux-arm64': 0.21.5
'@esbuild/linux-ia32': 0.21.5
'@esbuild/linux-loong64': 0.21.5
'@esbuild/linux-mips64el': 0.21.5
'@esbuild/linux-ppc64': 0.21.5
'@esbuild/linux-riscv64': 0.21.5
'@esbuild/linux-s390x': 0.21.5
'@esbuild/linux-x64': 0.21.5
'@esbuild/netbsd-x64': 0.21.5
'@esbuild/openbsd-x64': 0.21.5
'@esbuild/sunos-x64': 0.21.5
'@esbuild/win32-arm64': 0.21.5
'@esbuild/win32-ia32': 0.21.5
'@esbuild/win32-x64': 0.21.5
escalade@3.2.0: {}
escape-string-regexp@4.0.0: {}
eslint-plugin-vue@9.33.0(eslint@9.24.0(jiti@1.21.7)):
dependencies:
'@eslint-community/eslint-utils': 4.6.1(eslint@9.24.0(jiti@1.21.7))
eslint: 9.24.0(jiti@1.21.7)
globals: 13.24.0
natural-compare: 1.4.0
nth-check: 2.1.1
postcss-selector-parser: 6.1.2
semver: 7.7.1
vue-eslint-parser: 9.4.3(eslint@9.24.0(jiti@1.21.7))
xml-name-validator: 4.0.0
transitivePeerDependencies:
- supports-color
eslint-scope@7.2.2:
dependencies:
esrecurse: 4.3.0
estraverse: 5.3.0
eslint-scope@8.3.0:
dependencies:
esrecurse: 4.3.0
estraverse: 5.3.0
eslint-visitor-keys@3.4.3: {}
eslint-visitor-keys@4.2.0: {}
eslint@9.24.0(jiti@1.21.7):
dependencies:
'@eslint-community/eslint-utils': 4.6.1(eslint@9.24.0(jiti@1.21.7))
'@eslint-community/regexpp': 4.12.1
'@eslint/config-array': 0.20.0
'@eslint/config-helpers': 0.2.1
'@eslint/core': 0.12.0
'@eslint/eslintrc': 3.3.1
'@eslint/js': 9.24.0
'@eslint/plugin-kit': 0.2.8
'@humanfs/node': 0.16.6
'@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.2
'@types/estree': 1.0.7
'@types/json-schema': 7.0.15
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6
debug: 4.4.0
escape-string-regexp: 4.0.0
eslint-scope: 8.3.0
eslint-visitor-keys: 4.2.0
espree: 10.3.0
esquery: 1.6.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
file-entry-cache: 8.0.0
find-up: 5.0.0
glob-parent: 6.0.2
ignore: 5.3.2
imurmurhash: 0.1.4
is-glob: 4.0.3
json-stable-stringify-without-jsonify: 1.0.1
lodash.merge: 4.6.2
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.4
optionalDependencies:
jiti: 1.21.7
transitivePeerDependencies:
- supports-color
espree@10.3.0:
dependencies:
acorn: 8.14.1
acorn-jsx: 5.3.2(acorn@8.14.1)
eslint-visitor-keys: 4.2.0
espree@9.6.1:
dependencies:
acorn: 8.14.1
acorn-jsx: 5.3.2(acorn@8.14.1)
eslint-visitor-keys: 3.4.3
esquery@1.6.0:
dependencies:
estraverse: 5.3.0
esrecurse@4.3.0:
dependencies:
estraverse: 5.3.0
estraverse@5.3.0: {}
estree-walker@2.0.2: {}
esutils@2.0.3: {}
fast-deep-equal@3.1.3: {}
fast-glob@3.3.3:
dependencies:
'@nodelib/fs.stat': 2.0.5
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.8
fast-json-stable-stringify@2.1.0: {}
fast-levenshtein@2.0.6: {}
fastq@1.19.1:
dependencies:
reusify: 1.1.0
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
path-exists: 4.0.0
flat-cache@4.0.1:
dependencies:
flatted: 3.3.3
keyv: 4.5.4
flatted@3.3.3: {}
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
signal-exit: 4.1.0
fraction.js@4.3.7: {}
fsevents@2.3.3:
optional: true
function-bind@1.1.2: {}
gensync@1.0.0-beta.2: {}
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
glob-parent@6.0.2:
dependencies:
is-glob: 4.0.3
glob@10.4.5:
dependencies:
foreground-child: 3.3.1
jackspeak: 3.4.3
minimatch: 9.0.5
minipass: 7.1.2
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
globals@11.12.0: {}
globals@13.24.0:
dependencies:
type-fest: 0.20.2
globals@14.0.0: {}
has-flag@4.0.0: {}
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
ignore@5.3.2: {}
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
resolve-from: 4.0.0
imurmurhash@0.1.4: {}
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.3.0
is-core-module@2.16.1:
dependencies:
hasown: 2.0.2
is-extglob@2.1.1: {}
is-fullwidth-code-point@3.0.0: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
is-number@7.0.0: {}
isexe@2.0.0: {}
jackspeak@3.4.3:
dependencies:
'@isaacs/cliui': 8.0.2
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
jiti@1.21.7: {}
js-tokens@4.0.0: {}
js-yaml@4.1.0:
dependencies:
argparse: 2.0.1
jsesc@3.1.0: {}
json-buffer@3.0.1: {}
json-schema-traverse@0.4.1: {}
json-stable-stringify-without-jsonify@1.0.1: {}
json5@2.2.3: {}
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
type-check: 0.4.0
lilconfig@3.1.3: {}
lines-and-columns@1.2.4: {}
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
lodash.merge@4.6.2: {}
lodash@4.17.21: {}
lru-cache@10.4.3: {}
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
merge2@1.4.1: {}
micromatch@4.0.8:
dependencies:
braces: 3.0.3
picomatch: 2.3.1
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.1
minipass@7.1.2: {}
ms@2.1.3: {}
mz@2.7.0:
dependencies:
any-promise: 1.3.0
object-assign: 4.1.1
thenify-all: 1.6.0
nanoid@3.3.11: {}
natural-compare@1.4.0: {}
node-releases@2.0.19: {}
normalize-path@3.0.0: {}
normalize-range@0.1.2: {}
nth-check@2.1.1:
dependencies:
boolbase: 1.0.0
object-assign@4.1.1: {}
object-hash@3.0.0: {}
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
fast-levenshtein: 2.0.6
levn: 0.4.1
prelude-ls: 1.2.1
type-check: 0.4.0
word-wrap: 1.2.5
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
p-locate@5.0.0:
dependencies:
p-limit: 3.1.0
package-json-from-dist@1.0.1: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
path-exists@4.0.0: {}
path-key@3.1.1: {}
path-parse@1.0.7: {}
path-scurry@1.11.1:
dependencies:
lru-cache: 10.4.3
minipass: 7.1.2
picocolors@1.1.1: {}
picomatch@2.3.1: {}
pify@2.3.0: {}
pinia@2.3.1(vue@3.5.13):
dependencies:
'@vue/devtools-api': 6.6.4
vue: 3.5.13
vue-demi: 0.14.10(vue@3.5.13)
transitivePeerDependencies:
- '@vue/composition-api'
pirates@4.0.7: {}
postcss-import@15.1.0(postcss@8.5.3):
dependencies:
postcss: 8.5.3
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.10
postcss-js@4.0.1(postcss@8.5.3):
dependencies:
camelcase-css: 2.0.1
postcss: 8.5.3
postcss-load-config@4.0.2(postcss@8.5.3):
dependencies:
lilconfig: 3.1.3
yaml: 2.7.1
optionalDependencies:
postcss: 8.5.3
postcss-nested@6.2.0(postcss@8.5.3):
dependencies:
postcss: 8.5.3
postcss-selector-parser: 6.1.2
postcss-selector-parser@6.1.2:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
postcss-value-parser@4.2.0: {}
postcss@8.5.3:
dependencies:
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
prelude-ls@1.2.1: {}
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
read-cache@1.0.0:
dependencies:
pify: 2.3.0
readdirp@3.6.0:
dependencies:
picomatch: 2.3.1
resolve-from@4.0.0: {}
resolve@1.22.10:
dependencies:
is-core-module: 2.16.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
reusify@1.1.0: {}
rollup@4.40.0:
dependencies:
'@types/estree': 1.0.7
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.40.0
'@rollup/rollup-android-arm64': 4.40.0
'@rollup/rollup-darwin-arm64': 4.40.0
'@rollup/rollup-darwin-x64': 4.40.0
'@rollup/rollup-freebsd-arm64': 4.40.0
'@rollup/rollup-freebsd-x64': 4.40.0
'@rollup/rollup-linux-arm-gnueabihf': 4.40.0
'@rollup/rollup-linux-arm-musleabihf': 4.40.0
'@rollup/rollup-linux-arm64-gnu': 4.40.0
'@rollup/rollup-linux-arm64-musl': 4.40.0
'@rollup/rollup-linux-loongarch64-gnu': 4.40.0
'@rollup/rollup-linux-powerpc64le-gnu': 4.40.0
'@rollup/rollup-linux-riscv64-gnu': 4.40.0
'@rollup/rollup-linux-riscv64-musl': 4.40.0
'@rollup/rollup-linux-s390x-gnu': 4.40.0
'@rollup/rollup-linux-x64-gnu': 4.40.0
'@rollup/rollup-linux-x64-musl': 4.40.0
'@rollup/rollup-win32-arm64-msvc': 4.40.0
'@rollup/rollup-win32-ia32-msvc': 4.40.0
'@rollup/rollup-win32-x64-msvc': 4.40.0
fsevents: 2.3.3
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
semver@6.3.1: {}
semver@7.7.1: {}
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
signal-exit@4.1.0: {}
source-map-js@1.2.1: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
string-width@5.1.2:
dependencies:
eastasianwidth: 0.2.0
emoji-regex: 9.2.2
strip-ansi: 7.1.0
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-ansi@7.1.0:
dependencies:
ansi-regex: 6.1.0
strip-json-comments@3.1.1: {}
sucrase@3.35.0:
dependencies:
'@jridgewell/gen-mapping': 0.3.8
commander: 4.1.1
glob: 10.4.5
lines-and-columns: 1.2.4
mz: 2.7.0
pirates: 4.0.7
ts-interface-checker: 0.1.13
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
supports-preserve-symlinks-flag@1.0.0: {}
tailwindcss@3.4.17:
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
chokidar: 3.6.0
didyoumean: 1.2.2
dlv: 1.1.3
fast-glob: 3.3.3
glob-parent: 6.0.2
is-glob: 4.0.3
jiti: 1.21.7
lilconfig: 3.1.3
micromatch: 4.0.8
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.1.1
postcss: 8.5.3
postcss-import: 15.1.0(postcss@8.5.3)
postcss-js: 4.0.1(postcss@8.5.3)
postcss-load-config: 4.0.2(postcss@8.5.3)
postcss-nested: 6.2.0(postcss@8.5.3)
postcss-selector-parser: 6.1.2
resolve: 1.22.10
sucrase: 3.35.0
transitivePeerDependencies:
- ts-node
thenify-all@1.6.0:
dependencies:
thenify: 3.3.1
thenify@3.3.1:
dependencies:
any-promise: 1.3.0
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
ts-interface-checker@0.1.13: {}
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
type-fest@0.20.2: {}
update-browserslist-db@1.1.3(browserslist@4.24.4):
dependencies:
browserslist: 4.24.4
escalade: 3.2.0
picocolors: 1.1.1
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
util-deprecate@1.0.2: {}
vite@5.4.18:
dependencies:
esbuild: 0.21.5
postcss: 8.5.3
rollup: 4.40.0
optionalDependencies:
fsevents: 2.3.3
vue-demi@0.14.10(vue@3.5.13):
dependencies:
vue: 3.5.13
vue-eslint-parser@9.4.3(eslint@9.24.0(jiti@1.21.7)):
dependencies:
debug: 4.4.0
eslint: 9.24.0(jiti@1.21.7)
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
espree: 9.6.1
esquery: 1.6.0
lodash: 4.17.21
semver: 7.7.1
transitivePeerDependencies:
- supports-color
vue-router@4.5.0(vue@3.5.13):
dependencies:
'@vue/devtools-api': 6.6.4
vue: 3.5.13
vue@3.5.13:
dependencies:
'@vue/compiler-dom': 3.5.13
'@vue/compiler-sfc': 3.5.13
'@vue/runtime-dom': 3.5.13
'@vue/server-renderer': 3.5.13(vue@3.5.13)
'@vue/shared': 3.5.13
which@2.0.2:
dependencies:
isexe: 2.0.0
word-wrap@1.2.5: {}
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@8.1.0:
dependencies:
ansi-styles: 6.2.1
string-width: 5.1.2
strip-ansi: 7.1.0
xml-name-validator@4.0.0: {}
yallist@3.1.1: {}
yaml@2.7.1: {}
yocto-queue@0.1.0: {}
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
This file is too large to display.
[
{
"id": "A0001",
"title": "从《了不起的盖茨比》看世界变迁",
"organizer_id": "O0006",
"organizer_name": "百草园读书会",
"description": "与志同道合的读者一起,深入解析《了不起的盖茨比》。\n\n【活动内容】\n活动将邀请资深书评人进行导读,随后开展自由讨论与交流。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n活动全程录制,无法准时参加的书友可在后续获取回放。\n\n让我们在书香中相遇,在思想的海洋里遨游。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_1_online",
"start_time": "2025-04-02 06:13:57",
"end_time": "2025-04-02 07:13:57",
"registration_start": "2025-03-25 05:13:57",
"registration_end": "2025-04-02 05:13:57",
"max_participants": 36,
"participant_count": 30,
"activity_type": "online",
"location": null,
"online_link": "飞书线上会议,会议ID将提前发送",
"is_published": true,
"is_public": true,
"tags": [
"音乐"
],
"view_count": 515,
"volunteer_positions": [
"摄影记录",
"签到助手"
],
"created_at": "2025-03-23 05:13:57",
"updated_at": "2025-03-25 21:13:57",
"state": "past"
},
{
"id": "A0002",
"title": "与作者对话:《了不起的盖茨比》背后的故事",
"organizer_id": "O0003",
"organizer_name": "知行合一读书社",
"description": "想要探索《了不起的盖茨比》的深层含义?欢迎加入我们的读书会!\n\n【活动内容】\n我们将共同探讨书中的核心思想,分享阅读感悟,碰撞思维火花。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n场地座位有限,请提前报名,准时到场。\n\n每一次阅读都是新的开始,每一次讨论都是思想的盛宴。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_2_offline",
"start_time": "2025-03-24 06:13:57",
"end_time": "2025-03-24 07:13:57",
"registration_start": "2025-03-12 01:13:57",
"registration_end": "2025-03-24 01:13:57",
"max_participants": 56,
"participant_count": 50,
"activity_type": "offline",
"location": {
"name": "深蓝咖啡馆",
"address": "深圳市南山区科技园南区科苑路15号",
"city": "深圳"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"艺术",
"政治"
],
"view_count": 380,
"volunteer_positions": [
"设备维护",
"资料分发",
"场地布置",
"讨论主持"
],
"created_at": "2025-03-11 01:13:57",
"updated_at": "2025-03-12 22:13:57",
"state": "past"
},
{
"id": "A0003",
"title": "《瓦尔登湖》读书分享会",
"organizer_id": "O0010",
"organizer_name": "青禾读书会",
"description": "本次读书会将围绕《瓦尔登湖》展开深入讨论。\n\n【活动内容】\n除了对内容的讨论,我们还将探索作者创作背景与写作技巧。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n活动全程录制,无法准时参加的书友可在后续获取回放。\n\n让我们在书香中相遇,在思想的海洋里遨游。",
"cover_image": "/assets/images/activity_3_online.jpg",
"start_time": "2025-04-18 07:13:57",
"end_time": "2025-04-18 10:13:57",
"registration_start": "2025-03-25 09:13:57",
"registration_end": "2025-04-17 09:13:57",
"max_participants": 33,
"participant_count": 6,
"activity_type": "online",
"location": null,
"online_link": "腾讯会议线上会议,会议ID将提前发送",
"is_published": true,
"is_public": true,
"tags": [
"亲子阅读",
"政治"
],
"view_count": 97,
"volunteer_positions": [
"活动总结",
"设备维护",
"场地布置",
"直播助理"
],
"created_at": "2025-03-24 09:13:57",
"updated_at": "2025-03-26 02:13:57",
"state": "upcoming"
},
{
"id": "A0004",
"title": "从《围城》看世界变迁",
"organizer_id": "O0009",
"organizer_name": "四季阅读空间",
"description": "想要探索《围城》的深层含义?欢迎加入我们的读书会!\n\n【活动内容】\n我们将共同探讨书中的核心思想,分享阅读感悟,碰撞思维火花。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n现场提供免费茶点,请携带纸质书籍或笔记参加。\n\n期待您的参与,共同度过一个充满思想碰撞的美好时光!",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_4_offline",
"start_time": "2025-06-22 07:13:57",
"end_time": "2025-06-22 08:13:57",
"registration_start": "2025-06-12 03:13:57",
"registration_end": "2025-06-22 03:13:57",
"max_participants": 60,
"participant_count": 5,
"activity_type": "offline",
"location": {
"name": "大雁塔文化空间",
"address": "西安市雁塔区雁塔西路68号",
"city": "西安"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"推理"
],
"view_count": 68,
"volunteer_positions": [
"设备维护",
"场地布置",
"讨论主持"
],
"created_at": "2025-06-09 03:13:57",
"updated_at": "2025-06-12 18:13:57",
"state": "upcoming"
},
{
"id": "A0005",
"title": "青年读书会:共读《月亮与六便士》",
"organizer_id": "O0009",
"organizer_name": "四季阅读空间",
"description": "与志同道合的读者一起,深入解析《月亮与六便士》。\n\n【活动内容】\n通过分组讨论、角色扮演等互动形式,全方位解读这部作品。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n活动结束后有半小时自由交流时间,欢迎书友们互相认识。\n\n让我们在书香中相遇,在思想的海洋里遨游。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_5_offline",
"start_time": "2025-03-31 06:13:57",
"end_time": "2025-03-31 07:13:57",
"registration_start": "2025-03-21 23:13:57",
"registration_end": "2025-03-30 23:13:57",
"max_participants": 67,
"participant_count": 62,
"activity_type": "offline",
"location": {
"name": "大雁塔文化空间",
"address": "西安市雁塔区雁塔西路68号",
"city": "西安"
},
"online_link": null,
"is_published": true,
"is_public": false,
"tags": [
"健康生活",
"经济"
],
"view_count": 963,
"volunteer_positions": [
"设备维护"
],
"created_at": "2025-03-18 23:13:57",
"updated_at": "2025-03-22 03:13:57",
"state": "past"
},
{
"id": "A0006",
"title": "《三体》的艺术赏析",
"organizer_id": "O0008",
"organizer_name": "墨香文化传媒",
"description": "想要探索《三体》的深层含义?欢迎加入我们的读书会!\n\n【活动内容】\n除了对内容的讨论,我们还将探索作者创作背景与写作技巧。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n线上活动无地域限制,请提前调试好设备,确保网络流畅。\n\n每一次阅读都是新的开始,每一次讨论都是思想的盛宴。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_6_online",
"start_time": "2025-05-18 07:13:57",
"end_time": "2025-05-18 10:13:57",
"registration_start": "2025-05-01 15:13:57",
"registration_end": "2025-05-17 15:13:57",
"max_participants": 61,
"participant_count": 9,
"activity_type": "online",
"location": null,
"online_link": "微信视频线上会议,会议ID将提前发送",
"is_published": true,
"is_public": true,
"tags": [
"文学"
],
"view_count": 77,
"volunteer_positions": [
"秩序维持",
"场地布置",
"直播助理",
"活动总结",
"资料分发"
],
"created_at": "2025-04-28 15:13:57",
"updated_at": "2025-05-02 09:13:57",
"state": "upcoming"
},
{
"id": "A0007",
"title": "深度解读《活着》",
"organizer_id": "O0006",
"organizer_name": "百草园读书会",
"description": "与志同道合的读者一起,深入解析《活着》。\n\n【活动内容】\n活动将邀请资深书评人进行导读,随后开展自由讨论与交流。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n场地座位有限,请提前报名,准时到场。\n\n期待您的参与,共同度过一个充满思想碰撞的美好时光!",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_7_offline",
"start_time": "2025-04-03 05:13:57",
"end_time": "2025-04-03 07:13:57",
"registration_start": "2025-03-28 17:13:57",
"registration_end": "2025-04-02 17:13:57",
"max_participants": 30,
"participant_count": 28,
"activity_type": "offline",
"location": {
"name": "熊猫书房",
"address": "成都市锦江区红星路三段99号",
"city": "成都"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"科技",
"经济"
],
"view_count": 895,
"volunteer_positions": [
"秩序维持"
],
"created_at": "2025-03-25 17:13:57",
"updated_at": "2025-03-29 01:13:57",
"state": "past"
},
{
"id": "A0008",
"title": "《月亮与六便士》的哲学思考",
"organizer_id": "O0001",
"organizer_name": "光合作用文化空间",
"description": "本次读书会将围绕《月亮与六便士》展开深入讨论。\n\n【活动内容】\n除了对内容的讨论,我们还将探索作者创作背景与写作技巧。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n场地座位有限,请提前报名,准时到场。\n\n让我们在书香中相遇,在思想的海洋里遨游。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_8_offline",
"start_time": "2025-04-16 05:30:57",
"end_time": "2025-04-16 07:30:57",
"registration_start": "2025-04-11 05:30:57",
"registration_end": "2025-04-16 05:30:57",
"max_participants": 38,
"participant_count": 18,
"activity_type": "offline",
"location": {
"name": "紫金读书会",
"address": "南京市鼓楼区中山路99号",
"city": "南京"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"心灵成长"
],
"view_count": 207,
"volunteer_positions": [
"直播助理"
],
"created_at": "2025-04-10 05:30:57",
"updated_at": "2025-04-12 00:30:57",
"state": "ongoing"
},
{
"id": "A0009",
"title": "深度解读《瓦尔登湖》",
"organizer_id": "O0001",
"organizer_name": "光合作用文化空间",
"description": "与志同道合的读者一起,深入解析《瓦尔登湖》。\n\n【活动内容】\n活动将邀请资深书评人进行导读,随后开展自由讨论与交流。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n现场提供免费茶点,请携带纸质书籍或笔记参加。\n\n期待您的参与,共同度过一个充满思想碰撞的美好时光!",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_9_offline",
"start_time": "2025-05-30 07:13:57",
"end_time": "2025-05-30 10:13:57",
"registration_start": "2025-05-12 17:13:57",
"registration_end": "2025-05-29 17:13:57",
"max_participants": 52,
"participant_count": 14,
"activity_type": "offline",
"location": {
"name": "紫金读书会",
"address": "南京市鼓楼区中山路99号",
"city": "南京"
},
"online_link": null,
"is_published": true,
"is_public": false,
"tags": [
"哲学"
],
"view_count": 69,
"volunteer_positions": [
"直播助理",
"场地布置",
"资料分发",
"秩序维持",
"签到助手"
],
"created_at": "2025-05-07 17:13:57",
"updated_at": "2025-05-13 07:13:57",
"state": "upcoming"
},
{
"id": "A0010",
"title": "从《月亮与六便士》看世界变迁",
"organizer_id": "O0002",
"organizer_name": "四季阅读空间",
"description": "与志同道合的读者一起,深入解析《月亮与六便士》。\n\n【活动内容】\n通过分组讨论、角色扮演等互动形式,全方位解读这部作品。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n现场提供免费茶点,请携带纸质书籍或笔记参加。\n\n每一次阅读都是新的开始,每一次讨论都是思想的盛宴。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_10_offline",
"start_time": "2025-04-30 07:13:57",
"end_time": "2025-04-30 10:13:57",
"registration_start": "2025-04-16 13:13:57",
"registration_end": "2025-04-29 13:13:57",
"max_participants": 76,
"participant_count": 7,
"activity_type": "offline",
"location": {
"name": "城市中心图书馆",
"address": "北京市海淀区中关村南大街5号",
"city": "北京"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"古典文学",
"政治"
],
"view_count": 59,
"volunteer_positions": [
"签到助手",
"茶点准备",
"设备维护",
"资料分发"
],
"created_at": "2025-04-14 13:13:57",
"updated_at": "2025-04-17 06:13:57",
"state": "upcoming"
},
{
"id": "A0011",
"title": "《人类简史》的艺术赏析",
"organizer_id": "O0009",
"organizer_name": "四季阅读空间",
"description": "想要探索《人类简史》的深层含义?欢迎加入我们的读书会!\n\n【活动内容】\n通过分组讨论、角色扮演等互动形式,全方位解读这部作品。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n场地座位有限,请提前报名,准时到场。\n\n期待您的参与,共同度过一个充满思想碰撞的美好时光!",
"cover_image": "/assets/images/t2Sai-AqIpI_activity11.jpg",
"start_time": "2025-04-17 07:13:57",
"end_time": "2025-04-17 08:13:57",
"registration_start": "2025-03-22 19:13:57",
"registration_end": "2025-04-16 19:13:57",
"max_participants": 47,
"participant_count": 12,
"activity_type": "offline",
"location": {
"name": "大雁塔文化空间",
"address": "西安市雁塔区雁塔西路68号",
"city": "西安"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"政治",
"健康",
"传记"
],
"view_count": 61,
"volunteer_positions": [],
"created_at": "2025-03-21 19:13:57",
"updated_at": "2025-03-23 05:13:57",
"state": "upcoming"
},
{
"id": "A0012",
"title": "与作者对话:《小王子》背后的故事",
"organizer_id": "O0001",
"organizer_name": "光合作用文化空间",
"description": "本次读书会将围绕《小王子》展开深入讨论。\n\n【活动内容】\n除了对内容的讨论,我们还将探索作者创作背景与写作技巧。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n活动全程录制,无法准时参加的书友可在后续获取回放。\n\n每一次阅读都是新的开始,每一次讨论都是思想的盛宴。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_12_online",
"start_time": "2025-04-04 05:13:57",
"end_time": "2025-04-04 07:13:57",
"registration_start": "2025-03-27 21:13:57",
"registration_end": "2025-04-03 21:13:57",
"max_participants": 93,
"participant_count": 92,
"activity_type": "online",
"location": null,
"online_link": "飞书线上会议,会议ID将提前发送",
"is_published": true,
"is_public": true,
"tags": [
"环保",
"哲学"
],
"view_count": 342,
"volunteer_positions": [
"秩序维持",
"茶点准备",
"签到助手",
"摄影记录",
"资料分发"
],
"created_at": "2025-03-23 21:13:57",
"updated_at": "2025-03-28 17:13:57",
"state": "past"
},
{
"id": "A0013",
"title": "与作者对话:《红楼梦》背后的故事",
"organizer_id": "O0003",
"organizer_name": "知行合一读书社",
"description": "本次读书会将围绕《红楼梦》展开深入讨论。\n\n【活动内容】\n通过分组讨论、角色扮演等互动形式,全方位解读这部作品。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n现场提供免费茶点,请携带纸质书籍或笔记参加。\n\n让我们在书香中相遇,在思想的海洋里遨游。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_13_offline",
"start_time": "2025-04-16 06:02:57",
"end_time": "2025-04-16 08:02:57",
"registration_start": "2025-04-10 06:02:57",
"registration_end": "2025-04-16 06:02:57",
"max_participants": 89,
"participant_count": 55,
"activity_type": "offline",
"location": {
"name": "深蓝咖啡馆",
"address": "深圳市南山区科技园南区科苑路15号",
"city": "深圳"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"管理",
"哲学"
],
"view_count": 248,
"volunteer_positions": [],
"created_at": "2025-04-06 06:02:57",
"updated_at": "2025-04-10 15:02:57",
"state": "ongoing"
},
{
"id": "A0014",
"title": "与作者对话:《白夜行》背后的故事",
"organizer_id": "O0003",
"organizer_name": "知行合一读书社",
"description": "本次读书会将围绕《白夜行》展开深入讨论。\n\n【活动内容】\n除了对内容的讨论,我们还将探索作者创作背景与写作技巧。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n参与者将通过视频会议软件连接,链接将在活动开始前发送。\n\n期待您的参与,共同度过一个充满思想碰撞的美好时光!",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_14_online",
"start_time": "2025-05-23 07:13:57",
"end_time": "2025-05-23 08:13:57",
"registration_start": "2025-05-09 20:13:57",
"registration_end": "2025-05-22 20:13:57",
"max_participants": 28,
"participant_count": 5,
"activity_type": "online",
"location": null,
"online_link": "微信视频线上会议,会议ID将提前发送",
"is_published": true,
"is_public": true,
"tags": [
"哲学",
"外国文学"
],
"view_count": 53,
"volunteer_positions": [
"设备维护",
"秩序维持"
],
"created_at": "2025-05-07 20:13:57",
"updated_at": "2025-05-09 21:13:57",
"state": "upcoming"
},
{
"id": "A0015",
"title": "《思考,快与慢》的哲学思考",
"organizer_id": "O0003",
"organizer_name": "知行合一读书社",
"description": "本次读书会将围绕《思考,快与慢》展开深入讨论。\n\n【活动内容】\n我们将共同探讨书中的核心思想,分享阅读感悟,碰撞思维火花。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n参与者将通过视频会议软件连接,链接将在活动开始前发送。\n\n每一次阅读都是新的开始,每一次讨论都是思想的盛宴。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_15_online",
"start_time": "2025-04-16 06:02:57",
"end_time": "2025-04-16 09:02:57",
"registration_start": "2025-04-12 06:02:57",
"registration_end": "2025-04-16 06:02:57",
"max_participants": 21,
"participant_count": 12,
"activity_type": "online",
"location": null,
"online_link": "腾讯会议线上会议,会议ID将提前发送",
"is_published": true,
"is_public": true,
"tags": [
"心理学",
"环保"
],
"view_count": 103,
"volunteer_positions": [],
"created_at": "2025-04-07 06:02:57",
"updated_at": "2025-04-12 13:02:57",
"state": "ongoing"
},
{
"id": "A0016",
"title": "《自控力》的艺术赏析",
"organizer_id": "O0002",
"organizer_name": "四季阅读空间",
"description": "本次读书会将围绕《自控力》展开深入讨论。\n\n【活动内容】\n活动将邀请资深书评人进行导读,随后开展自由讨论与交流。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n现场提供免费茶点,请携带纸质书籍或笔记参加。\n\n期待您的参与,共同度过一个充满思想碰撞的美好时光!",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_16_offline",
"start_time": "2025-07-01 07:13:57",
"end_time": "2025-07-01 08:13:57",
"registration_start": "2025-06-15 13:13:57",
"registration_end": "2025-06-30 13:13:57",
"max_participants": 48,
"participant_count": 2,
"activity_type": "offline",
"location": {
"name": "城市中心图书馆",
"address": "北京市海淀区中关村南大街5号",
"city": "北京"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"历史文化"
],
"view_count": 90,
"volunteer_positions": [
"茶点准备",
"场地布置",
"活动总结",
"设备维护",
"签到助手"
],
"created_at": "2025-06-11 13:13:57",
"updated_at": "2025-06-16 10:13:57",
"state": "upcoming"
},
{
"id": "A0017",
"title": "经典重温:《解忧杂货店》",
"organizer_id": "O0006",
"organizer_name": "百草园读书会",
"description": "诚挚邀请书友们参加《解忧杂货店》读书分享会。\n\n【活动内容】\n通过分组讨论、角色扮演等互动形式,全方位解读这部作品。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n线上活动无地域限制,请提前调试好设备,确保网络流畅。\n\n每一次阅读都是新的开始,每一次讨论都是思想的盛宴。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_17_online",
"start_time": "2025-06-15 07:13:57",
"end_time": "2025-06-15 10:13:57",
"registration_start": "2025-05-27 02:13:57",
"registration_end": "2025-06-15 02:13:57",
"max_participants": 75,
"participant_count": 9,
"activity_type": "online",
"location": null,
"online_link": "飞书线上会议,会议ID将提前发送",
"is_published": true,
"is_public": true,
"tags": [
"文学",
"科技",
"健康"
],
"view_count": 34,
"volunteer_positions": [],
"created_at": "2025-05-26 02:13:57",
"updated_at": "2025-05-27 15:13:57",
"state": "upcoming"
},
{
"id": "A0018",
"title": "从《未来简史》看世界变迁",
"organizer_id": "O0003",
"organizer_name": "知行合一读书社",
"description": "与志同道合的读者一起,深入解析《未来简史》。\n\n【活动内容】\n我们将共同探讨书中的核心思想,分享阅读感悟,碰撞思维火花。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n线上活动无地域限制,请提前调试好设备,确保网络流畅。\n\n期待您的参与,共同度过一个充满思想碰撞的美好时光!",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_18_online",
"start_time": "2025-06-14 07:13:57",
"end_time": "2025-06-14 09:13:57",
"registration_start": "2025-06-04 02:13:57",
"registration_end": "2025-06-14 02:13:57",
"max_participants": 72,
"participant_count": 12,
"activity_type": "online",
"location": null,
"online_link": "飞书线上会议,会议ID将提前发送",
"is_published": true,
"is_public": true,
"tags": [
"诗歌"
],
"view_count": 27,
"volunteer_positions": [],
"created_at": "2025-05-31 02:13:57",
"updated_at": "2025-06-04 07:13:57",
"state": "upcoming"
},
{
"id": "A0019",
"title": "与作者对话:《月亮与六便士》背后的故事",
"organizer_id": "O0004",
"organizer_name": "百草园读书会",
"description": "诚挚邀请书友们参加《月亮与六便士》读书分享会。\n\n【活动内容】\n除了对内容的讨论,我们还将探索作者创作背景与写作技巧。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n线上活动无地域限制,请提前调试好设备,确保网络流畅。\n\n每一次阅读都是新的开始,每一次讨论都是思想的盛宴。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_19_online",
"start_time": "2025-04-16 05:34:57",
"end_time": "2025-04-16 08:34:57",
"registration_start": "2025-04-11 05:34:57",
"registration_end": "2025-04-16 05:34:57",
"max_participants": 29,
"participant_count": 15,
"activity_type": "online",
"location": null,
"online_link": "钉钉线上会议,会议ID将提前发送",
"is_published": true,
"is_public": false,
"tags": [
"健康",
"科学普及"
],
"view_count": 74,
"volunteer_positions": [
"摄影记录",
"签到助手",
"设备维护"
],
"created_at": "2025-04-07 05:34:57",
"updated_at": "2025-04-11 13:34:57",
"state": "ongoing"
},
{
"id": "A0020",
"title": "《心理学与生活》的哲学思考",
"organizer_id": "O0001",
"organizer_name": "光合作用文化空间",
"description": "与志同道合的读者一起,深入解析《心理学与生活》。\n\n【活动内容】\n我们将共同探讨书中的核心思想,分享阅读感悟,碰撞思维火花。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n参与者将通过视频会议软件连接,链接将在活动开始前发送。\n\n每一次阅读都是新的开始,每一次讨论都是思想的盛宴。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_20_online",
"start_time": "2025-06-17 07:13:57",
"end_time": "2025-06-17 10:13:57",
"registration_start": "2025-06-01 10:13:57",
"registration_end": "2025-06-16 10:13:57",
"max_participants": 38,
"participant_count": 2,
"activity_type": "online",
"location": null,
"online_link": "钉钉线上会议,会议ID将提前发送",
"is_published": true,
"is_public": true,
"tags": [
"历史",
"文学",
"管理"
],
"view_count": 87,
"volunteer_positions": [
"摄影记录",
"资料分发"
],
"created_at": "2025-05-27 10:13:57",
"updated_at": "2025-06-02 04:13:57",
"state": "upcoming"
},
{
"id": "A0021",
"title": "深度解读《了不起的盖茨比》",
"organizer_id": "O0007",
"organizer_name": "百草园读书会",
"description": "诚挚邀请书友们参加《了不起的盖茨比》读书分享会。\n\n【活动内容】\n我们将共同探讨书中的核心思想,分享阅读感悟,碰撞思维火花。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n参与者将通过视频会议软件连接,链接将在活动开始前发送。\n\n每一次阅读都是新的开始,每一次讨论都是思想的盛宴。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_21_online",
"start_time": "2025-05-04 07:13:57",
"end_time": "2025-05-04 09:13:57",
"registration_start": "2025-04-16 21:13:57",
"registration_end": "2025-05-03 21:13:57",
"max_participants": 75,
"participant_count": 15,
"activity_type": "online",
"location": null,
"online_link": "Zoom线上会议,会议ID将提前发送",
"is_published": true,
"is_public": false,
"tags": [
"历史",
"心灵成长"
],
"view_count": 49,
"volunteer_positions": [],
"created_at": "2025-04-12 21:13:57",
"updated_at": "2025-04-17 08:13:57",
"state": "upcoming"
},
{
"id": "A0022",
"title": "青年读书会:共读《未来简史》",
"organizer_id": "O0005",
"organizer_name": "鹿鸣书店",
"description": "诚挚邀请书友们参加《未来简史》读书分享会。\n\n【活动内容】\n我们将共同探讨书中的核心思想,分享阅读感悟,碰撞思维火花。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n场地座位有限,请提前报名,准时到场。\n\n每一次阅读都是新的开始,每一次讨论都是思想的盛宴。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_22_offline",
"start_time": "2025-04-16 06:31:57",
"end_time": "2025-04-16 07:31:57",
"registration_start": "2025-04-02 06:31:57",
"registration_end": "2025-04-16 06:31:57",
"max_participants": 78,
"participant_count": 46,
"activity_type": "offline",
"location": {
"name": "大雁塔文化空间",
"address": "西安市雁塔区雁塔西路68号",
"city": "西安"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"管理"
],
"view_count": 74,
"volunteer_positions": [
"设备维护",
"直播助理",
"资料分发"
],
"created_at": "2025-04-01 06:31:57",
"updated_at": "2025-04-02 23:31:57",
"state": "ongoing"
},
{
"id": "A0023",
"title": "《万物简史》的哲学思考",
"organizer_id": "O0007",
"organizer_name": "百草园读书会",
"description": "诚挚邀请书友们参加《万物简史》读书分享会。\n\n【活动内容】\n我们将共同探讨书中的核心思想,分享阅读感悟,碰撞思维火花。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n参与者将通过视频会议软件连接,链接将在活动开始前发送。\n\n期待您的参与,共同度过一个充满思想碰撞的美好时光!",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_23_online",
"start_time": "2025-03-27 05:13:57",
"end_time": "2025-03-27 07:13:57",
"registration_start": "2025-03-22 02:13:57",
"registration_end": "2025-03-27 02:13:57",
"max_participants": 47,
"participant_count": 38,
"activity_type": "online",
"location": null,
"online_link": "飞书线上会议,会议ID将提前发送",
"is_published": true,
"is_public": true,
"tags": [
"外国文学",
"历史",
"历史文化"
],
"view_count": 552,
"volunteer_positions": [
"设备维护",
"秩序维持",
"摄影记录",
"活动总结"
],
"created_at": "2025-03-19 02:13:57",
"updated_at": "2025-03-23 00:13:57",
"state": "past"
},
{
"id": "A0024",
"title": "跨界思考:《活着》与现代社会",
"organizer_id": "O0003",
"organizer_name": "知行合一读书社",
"description": "想要探索《活着》的深层含义?欢迎加入我们的读书会!\n\n【活动内容】\n除了对内容的讨论,我们还将探索作者创作背景与写作技巧。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n场地座位有限,请提前报名,准时到场。\n\n让我们在书香中相遇,在思想的海洋里遨游。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_24_offline",
"start_time": "2025-05-21 07:13:57",
"end_time": "2025-05-21 09:13:57",
"registration_start": "2025-04-22 23:13:57",
"registration_end": "2025-05-20 23:13:57",
"max_participants": 77,
"participant_count": 11,
"activity_type": "offline",
"location": {
"name": "深蓝咖啡馆",
"address": "深圳市南山区科技园南区科苑路15号",
"city": "深圳"
},
"online_link": null,
"is_published": true,
"is_public": false,
"tags": [
"环保"
],
"view_count": 61,
"volunteer_positions": [
"茶点准备",
"秩序维持"
],
"created_at": "2025-04-20 23:13:57",
"updated_at": "2025-04-23 12:13:57",
"state": "upcoming"
},
{
"id": "A0025",
"title": "经典重温:《活着》",
"organizer_id": "O0009",
"organizer_name": "四季阅读空间",
"description": "与志同道合的读者一起,深入解析《活着》。\n\n【活动内容】\n活动将邀请资深书评人进行导读,随后开展自由讨论与交流。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n场地座位有限,请提前报名,准时到场。\n\n每一次阅读都是新的开始,每一次讨论都是思想的盛宴。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_25_offline",
"start_time": "2025-05-08 07:13:57",
"end_time": "2025-05-08 09:13:57",
"registration_start": "2025-04-22 09:13:57",
"registration_end": "2025-05-07 09:13:57",
"max_participants": 83,
"participant_count": 11,
"activity_type": "offline",
"location": {
"name": "大雁塔文化空间",
"address": "西安市雁塔区雁塔西路68号",
"city": "西安"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"经济"
],
"view_count": 30,
"volunteer_positions": [
"讨论主持",
"资料分发",
"摄影记录",
"秩序维持",
"场地布置"
],
"created_at": "2025-04-20 09:13:57",
"updated_at": "2025-04-23 06:13:57",
"state": "upcoming"
},
{
"id": "A0026",
"title": "《未来简史》的哲学思考",
"organizer_id": "O0007",
"organizer_name": "百草园读书会",
"description": "想要探索《未来简史》的深层含义?欢迎加入我们的读书会!\n\n【活动内容】\n活动将邀请资深书评人进行导读,随后开展自由讨论与交流。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n现场提供免费茶点,请携带纸质书籍或笔记参加。\n\n让我们在书香中相遇,在思想的海洋里遨游。",
"cover_image": "/assets/images/XqXJJhK-c08.jpg",
"start_time": "2025-04-29 07:13:57",
"end_time": "2025-04-29 09:13:57",
"registration_start": "2025-04-03 10:13:57",
"registration_end": "2025-04-28 10:13:57",
"max_participants": 90,
"participant_count": 9,
"activity_type": "offline",
"location": {
"name": "紫金读书会",
"address": "南京市鼓楼区中山路99号",
"city": "南京"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"健康生活"
],
"view_count": 60,
"volunteer_positions": [],
"created_at": "2025-03-29 10:13:57",
"updated_at": "2025-04-03 22:13:57",
"state": "upcoming"
},
{
"id": "A0027",
"title": "跨界思考:《百年孤独》与现代社会",
"organizer_id": "O0005",
"organizer_name": "鹿鸣书店",
"description": "诚挚邀请书友们参加《百年孤独》读书分享会。\n\n【活动内容】\n通过分组讨论、角色扮演等互动形式,全方位解读这部作品。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n场地座位有限,请提前报名,准时到场。\n\n期待您的参与,共同度过一个充满思想碰撞的美好时光!",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_27_offline",
"start_time": "2025-04-16 05:21:57",
"end_time": "2025-04-16 08:21:57",
"registration_start": "2025-04-11 05:21:57",
"registration_end": "2025-04-16 05:21:57",
"max_participants": 23,
"participant_count": 15,
"activity_type": "offline",
"location": {
"name": "大雁塔文化空间",
"address": "西安市雁塔区雁塔西路68号",
"city": "西安"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"社会科学",
"传记"
],
"view_count": 248,
"volunteer_positions": [],
"created_at": "2025-04-10 05:21:57",
"updated_at": "2025-04-11 06:21:57",
"state": "ongoing"
},
{
"id": "A0028",
"title": "跨界思考:《追风筝的人》与现代社会",
"organizer_id": "O0008",
"organizer_name": "墨香文化传媒",
"description": "想要探索《追风筝的人》的深层含义?欢迎加入我们的读书会!\n\n【活动内容】\n除了对内容的讨论,我们还将探索作者创作背景与写作技巧。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n现场提供免费茶点,请携带纸质书籍或笔记参加。\n\n期待您的参与,共同度过一个充满思想碰撞的美好时光!",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_28_offline",
"start_time": "2025-05-27 07:13:57",
"end_time": "2025-05-27 10:13:57",
"registration_start": "2025-05-06 17:13:57",
"registration_end": "2025-05-26 17:13:57",
"max_participants": 86,
"participant_count": 15,
"activity_type": "offline",
"location": {
"name": "大雁塔文化空间",
"address": "西安市雁塔区雁塔西路68号",
"city": "西安"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"健康",
"音乐"
],
"view_count": 98,
"volunteer_positions": [
"场地布置"
],
"created_at": "2025-05-04 17:13:57",
"updated_at": "2025-05-06 18:13:57",
"state": "upcoming"
},
{
"id": "A0029",
"title": "《了不起的盖茨比》的艺术赏析",
"organizer_id": "O0005",
"organizer_name": "鹿鸣书店",
"description": "想要探索《了不起的盖茨比》的深层含义?欢迎加入我们的读书会!\n\n【活动内容】\n通过分组讨论、角色扮演等互动形式,全方位解读这部作品。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n线上活动无地域限制,请提前调试好设备,确保网络流畅。\n\n让我们在书香中相遇,在思想的海洋里遨游。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_29_online",
"start_time": "2025-03-18 05:13:57",
"end_time": "2025-03-18 07:13:57",
"registration_start": "2025-03-06 01:13:57",
"registration_end": "2025-03-18 01:13:57",
"max_participants": 42,
"participant_count": 40,
"activity_type": "online",
"location": null,
"online_link": "Zoom线上会议,会议ID将提前发送",
"is_published": true,
"is_public": false,
"tags": [
"古典文学",
"哲学",
"健康"
],
"view_count": 772,
"volunteer_positions": [],
"created_at": "2025-03-05 01:13:57",
"updated_at": "2025-03-06 07:13:57",
"state": "past"
},
{
"id": "A0030",
"title": "从《活着》看世界变迁",
"organizer_id": "O0005",
"organizer_name": "鹿鸣书店",
"description": "本次读书会将围绕《活着》展开深入讨论。\n\n【活动内容】\n我们将共同探讨书中的核心思想,分享阅读感悟,碰撞思维火花。\n\n【活动安排】\n1. 开场与活动介绍(15分钟)2. 作品导读(30分钟)3. 分组讨论(45分钟)4. 交流分享(30分钟)5. 问答与总结(15分钟)\n\n【参与须知】\n活动结束后有半小时自由交流时间,欢迎书友们互相认识。\n\n让我们在书香中相遇,在思想的海洋里遨游。",
"cover_image": "https://via.placeholder.com/1600x900.png?text=Activity_30_offline",
"start_time": "2025-03-24 06:13:57",
"end_time": "2025-03-24 07:13:57",
"registration_start": "2025-03-13 02:13:57",
"registration_end": "2025-03-24 02:13:57",
"max_participants": 64,
"participant_count": 60,
"activity_type": "offline",
"location": {
"name": "大雁塔文化空间",
"address": "西安市雁塔区雁塔西路68号",
"city": "西安"
},
"online_link": null,
"is_published": true,
"is_public": true,
"tags": [
"教育"
],
"view_count": 881,
"volunteer_positions": [
"签到助手",
"茶点准备",
"场地布置",
"活动总结"
],
"created_at": "2025-03-11 02:13:57",
"updated_at": "2025-03-13 12:13:57",
"state": "past"
}
]
\ No newline at end of file
[
{
"id": "C0001",
"activity_id": "A0001",
"user_id": "U0010",
"registration_id": "R0001",
"checkin_time": "2025-04-02 06:15:17",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0002",
"activity_id": "A0001",
"user_id": "U0020",
"registration_id": "R0002",
"checkin_time": "2025-04-02 05:59:08",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0003",
"activity_id": "A0001",
"user_id": "U0032",
"registration_id": "R0004",
"checkin_time": "2025-04-02 06:22:39",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0004",
"activity_id": "A0001",
"user_id": "U0025",
"registration_id": "R0005",
"checkin_time": "2025-04-02 06:27:22",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0005",
"activity_id": "A0001",
"user_id": "U0012",
"registration_id": "R0006",
"checkin_time": "2025-04-02 06:20:42",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0006",
"activity_id": "A0001",
"user_id": "U0034",
"registration_id": "R0008",
"checkin_time": "2025-04-02 06:31:33",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0007",
"activity_id": "A0001",
"user_id": "U0035",
"registration_id": "R0010",
"checkin_time": "2025-04-02 06:43:04",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0008",
"activity_id": "A0001",
"user_id": "U0008",
"registration_id": "R0011",
"checkin_time": "2025-04-02 06:24:37",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "带了书本",
"is_late": false
},
{
"id": "C0009",
"activity_id": "A0001",
"user_id": "U0016",
"registration_id": "R0012",
"checkin_time": "2025-04-02 06:09:02",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0010",
"activity_id": "A0001",
"user_id": "U0009",
"registration_id": "R0014",
"checkin_time": "2025-04-02 06:40:18",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "带了书本",
"is_late": false
},
{
"id": "C0011",
"activity_id": "A0001",
"user_id": "U0015",
"registration_id": "R0015",
"checkin_time": "2025-04-02 05:59:41",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0012",
"activity_id": "A0001",
"user_id": "U0019",
"registration_id": "R0016",
"checkin_time": "2025-04-02 06:11:07",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0013",
"activity_id": "A0001",
"user_id": "U0033",
"registration_id": "R0017",
"checkin_time": "2025-04-02 06:24:37",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0014",
"activity_id": "A0001",
"user_id": "U0001",
"registration_id": "R0018",
"checkin_time": "2025-04-02 05:59:19",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "热情参与讨论",
"is_late": false
},
{
"id": "C0015",
"activity_id": "A0001",
"user_id": "U0036",
"registration_id": "R0019",
"checkin_time": "2025-04-02 06:06:21",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "按时到达",
"is_late": false
},
{
"id": "C0016",
"activity_id": "A0001",
"user_id": "U0004",
"registration_id": "R0020",
"checkin_time": "2025-04-02 06:20:20",
"checkin_type": "manual",
"location": null,
"staff_id": "O0006",
"status": "failed",
"notes": "网络连接不稳定",
"is_late": false
},
{
"id": "C0017",
"activity_id": "A0001",
"user_id": "U0029",
"registration_id": "R0022",
"checkin_time": "2025-04-02 06:26:13",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0018",
"activity_id": "A0001",
"user_id": "U0031",
"registration_id": "R0023",
"checkin_time": "2025-04-02 06:01:47",
"checkin_type": "self-service",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0019",
"activity_id": "A0001",
"user_id": "U0011",
"registration_id": "R0024",
"checkin_time": "2025-04-02 06:01:25",
"checkin_type": "self-service",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0020",
"activity_id": "A0001",
"user_id": "U0040",
"registration_id": "R0027",
"checkin_time": "2025-04-02 06:14:06",
"checkin_type": "self-service",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0021",
"activity_id": "A0001",
"user_id": "U0003",
"registration_id": "R0028",
"checkin_time": "2025-04-02 05:59:24",
"checkin_type": "self-service",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0022",
"activity_id": "A0001",
"user_id": "U0017",
"registration_id": "R0030",
"checkin_time": "2025-04-02 06:18:33",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0023",
"activity_id": "A0001",
"user_id": "U0038",
"registration_id": "R0031",
"checkin_time": "2025-04-02 06:10:30",
"checkin_type": "manual",
"location": null,
"staff_id": "O0006",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0024",
"activity_id": "A0001",
"user_id": "U0030",
"registration_id": "R0032",
"checkin_time": "2025-04-02 06:15:42",
"checkin_type": "manual",
"location": null,
"staff_id": "O0006",
"status": "successful",
"notes": "热情参与讨论",
"is_late": false
},
{
"id": "C0025",
"activity_id": "A0001",
"user_id": "U0022",
"registration_id": "R0033",
"checkin_time": "2025-04-02 06:39:18",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "带了书本",
"is_late": false
},
{
"id": "C0026",
"activity_id": "A0001",
"user_id": "U0014",
"registration_id": "R0034",
"checkin_time": "2025-04-02 06:36:11",
"checkin_type": "self-service",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": false
},
{
"id": "C0027",
"activity_id": "A0001",
"user_id": "U0005",
"registration_id": "R0035",
"checkin_time": "2025-04-02 06:27:14",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": false
},
{
"id": "C0028",
"activity_id": "A0001",
"user_id": "U0039",
"registration_id": "R0037",
"checkin_time": "2025-04-02 06:43:29",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0029",
"activity_id": "A0002",
"user_id": "U0035",
"registration_id": "R0038",
"checkin_time": "2025-03-24 06:26:56",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "37.181720,114.507089"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0030",
"activity_id": "A0002",
"user_id": "U0031",
"registration_id": "R0039",
"checkin_time": "2025-03-24 06:40:17",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "38.496785,121.640291"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0031",
"activity_id": "A0002",
"user_id": "U0011",
"registration_id": "R0040",
"checkin_time": "2025-03-24 06:06:14",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "29.197808,115.186227"
},
"staff_id": null,
"status": "failed",
"notes": "网络连接不稳定",
"is_late": false
},
{
"id": "C0032",
"activity_id": "A0002",
"user_id": "U0032",
"registration_id": "R0041",
"checkin_time": "2025-03-24 06:17:34",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "36.369072,107.966159"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0033",
"activity_id": "A0002",
"user_id": "U0009",
"registration_id": "R0043",
"checkin_time": "2025-03-24 06:37:51",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "24.090606,121.419792"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0034",
"activity_id": "A0002",
"user_id": "U0018",
"registration_id": "R0044",
"checkin_time": "2025-03-24 06:37:28",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "24.317278,113.834895"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0035",
"activity_id": "A0002",
"user_id": "U0008",
"registration_id": "R0045",
"checkin_time": "2025-03-24 06:06:55",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "28.173906,120.708227"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0036",
"activity_id": "A0002",
"user_id": "U0025",
"registration_id": "R0046",
"checkin_time": "2025-03-24 06:26:03",
"checkin_type": "manual",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "32.612174,103.031492"
},
"staff_id": "O0003",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0037",
"activity_id": "A0002",
"user_id": "U0030",
"registration_id": "R0047",
"checkin_time": "2025-03-24 06:24:16",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "26.712645,111.630994"
},
"staff_id": null,
"status": "successful",
"notes": "按时到达",
"is_late": false
},
{
"id": "C0038",
"activity_id": "A0002",
"user_id": "U0022",
"registration_id": "R0048",
"checkin_time": "2025-03-24 06:12:56",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "24.159576,109.678518"
},
"staff_id": null,
"status": "successful",
"notes": "带了书本",
"is_late": false
},
{
"id": "C0039",
"activity_id": "A0002",
"user_id": "U0026",
"registration_id": "R0049",
"checkin_time": "2025-03-24 06:36:47",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "23.120311,112.843124"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0040",
"activity_id": "A0002",
"user_id": "U0016",
"registration_id": "R0050",
"checkin_time": "2025-03-24 06:08:26",
"checkin_type": "manual",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "39.064207,125.051377"
},
"staff_id": "O0003",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0041",
"activity_id": "A0002",
"user_id": "U0006",
"registration_id": "R0051",
"checkin_time": "2025-03-24 06:38:14",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "39.771853,103.340475"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0042",
"activity_id": "A0002",
"user_id": "U0017",
"registration_id": "R0052",
"checkin_time": "2025-03-24 06:38:41",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "28.109123,117.999368"
},
"staff_id": null,
"status": "failed",
"notes": "未携带所需材料",
"is_late": false
},
{
"id": "C0043",
"activity_id": "A0002",
"user_id": "U0023",
"registration_id": "R0053",
"checkin_time": "2025-03-24 06:28:37",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "26.399555,107.803150"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0044",
"activity_id": "A0002",
"user_id": "U0020",
"registration_id": "R0054",
"checkin_time": "2025-03-24 06:26:01",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "35.961689,105.194244"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0045",
"activity_id": "A0002",
"user_id": "U0002",
"registration_id": "R0055",
"checkin_time": "2025-03-24 06:17:24",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "38.805465,124.606647"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0046",
"activity_id": "A0002",
"user_id": "U0003",
"registration_id": "R0057",
"checkin_time": "2025-03-24 06:23:32",
"checkin_type": "manual",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "26.842865,115.187406"
},
"staff_id": "O0003",
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0047",
"activity_id": "A0002",
"user_id": "U0021",
"registration_id": "R0058",
"checkin_time": "2025-03-24 06:01:19",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "31.048369,122.562894"
},
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": false
},
{
"id": "C0048",
"activity_id": "A0002",
"user_id": "U0024",
"registration_id": "R0059",
"checkin_time": "2025-03-24 06:07:08",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "36.666997,102.016997"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0049",
"activity_id": "A0002",
"user_id": "U0037",
"registration_id": "R0061",
"checkin_time": "2025-03-24 06:39:21",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "26.657286,112.659843"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0050",
"activity_id": "A0002",
"user_id": "U0012",
"registration_id": "R0063",
"checkin_time": "2025-03-24 06:24:17",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "39.694284,109.014524"
},
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": true
},
{
"id": "C0051",
"activity_id": "A0002",
"user_id": "U0014",
"registration_id": "R0064",
"checkin_time": "2025-03-24 06:19:52",
"checkin_type": "manual",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "39.330172,105.342768"
},
"staff_id": "O0003",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0052",
"activity_id": "A0002",
"user_id": "U0010",
"registration_id": "R0065",
"checkin_time": "2025-03-24 06:51:43",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "27.822307,110.880438"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0053",
"activity_id": "A0002",
"user_id": "U0034",
"registration_id": "R0066",
"checkin_time": "2025-03-24 06:24:07",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "37.010299,118.129043"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0054",
"activity_id": "A0002",
"user_id": "U0039",
"registration_id": "R0067",
"checkin_time": "2025-03-24 06:39:58",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "24.617110,119.003828"
},
"staff_id": null,
"status": "successful",
"notes": "热情参与讨论",
"is_late": false
},
{
"id": "C0055",
"activity_id": "A0002",
"user_id": "U0001",
"registration_id": "R0068",
"checkin_time": "2025-03-24 06:35:33",
"checkin_type": "self-service",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "32.183967,110.892548"
},
"staff_id": null,
"status": "successful",
"notes": "准备充分",
"is_late": true
},
{
"id": "C0056",
"activity_id": "A0002",
"user_id": "U0028",
"registration_id": "R0069",
"checkin_time": "2025-03-24 06:22:47",
"checkin_type": "self-service",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "37.102393,104.723842"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0057",
"activity_id": "A0002",
"user_id": "U0027",
"registration_id": "R0070",
"checkin_time": "2025-03-24 06:22:44",
"checkin_type": "manual",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "35.022102,120.845595"
},
"staff_id": "O0003",
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0058",
"activity_id": "A0002",
"user_id": "U0013",
"registration_id": "R0071",
"checkin_time": "2025-03-24 06:29:54",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "27.520250,106.998565"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0059",
"activity_id": "A0002",
"user_id": "U0036",
"registration_id": "R0072",
"checkin_time": "2025-03-24 06:15:52",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "22.586642,106.806636"
},
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": false
},
{
"id": "C0060",
"activity_id": "A0002",
"user_id": "U0004",
"registration_id": "R0073",
"checkin_time": "2025-03-24 06:14:19",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "34.054279,106.738680"
},
"staff_id": null,
"status": "successful",
"notes": "按时到达",
"is_late": false
},
{
"id": "C0061",
"activity_id": "A0002",
"user_id": "U0040",
"registration_id": "R0074",
"checkin_time": "2025-03-24 06:26:57",
"checkin_type": "manual",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "35.010409,123.112389"
},
"staff_id": "O0003",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0062",
"activity_id": "A0002",
"user_id": "U0005",
"registration_id": "R0075",
"checkin_time": "2025-03-24 06:34:07",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "29.606431,116.433917"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0063",
"activity_id": "A0002",
"user_id": "U0033",
"registration_id": "R0077",
"checkin_time": "2025-03-24 06:27:57",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "33.496844,120.165656"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0064",
"activity_id": "A0007",
"user_id": "U0031",
"registration_id": "R0101",
"checkin_time": "2025-04-03 05:12:17",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "37.519897,120.666728"
},
"staff_id": null,
"status": "successful",
"notes": "准备充分",
"is_late": false
},
{
"id": "C0065",
"activity_id": "A0007",
"user_id": "U0039",
"registration_id": "R0102",
"checkin_time": "2025-04-03 05:43:42",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "23.325053,124.389998"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0066",
"activity_id": "A0007",
"user_id": "U0040",
"registration_id": "R0103",
"checkin_time": "2025-04-03 05:36:39",
"checkin_type": "manual",
"location": {
"name": "熊猫书房",
"coordinates": "29.006041,121.944115"
},
"staff_id": "O0006",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0067",
"activity_id": "A0007",
"user_id": "U0022",
"registration_id": "R0104",
"checkin_time": "2025-04-03 05:27:17",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "23.882683,105.402504"
},
"staff_id": null,
"status": "successful",
"notes": "准备充分",
"is_late": false
},
{
"id": "C0068",
"activity_id": "A0007",
"user_id": "U0015",
"registration_id": "R0106",
"checkin_time": "2025-04-03 05:27:01",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "37.561985,116.624272"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0069",
"activity_id": "A0007",
"user_id": "U0020",
"registration_id": "R0107",
"checkin_time": "2025-04-03 05:08:09",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "36.575256,121.636347"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0070",
"activity_id": "A0007",
"user_id": "U0038",
"registration_id": "R0108",
"checkin_time": "2025-04-03 05:21:29",
"checkin_type": "self-service",
"location": {
"name": "熊猫书房",
"coordinates": "27.958669,101.467384"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0071",
"activity_id": "A0007",
"user_id": "U0014",
"registration_id": "R0109",
"checkin_time": "2025-04-03 05:22:20",
"checkin_type": "self-service",
"location": {
"name": "熊猫书房",
"coordinates": "37.434346,114.067180"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0072",
"activity_id": "A0007",
"user_id": "U0011",
"registration_id": "R0110",
"checkin_time": "2025-04-03 05:08:27",
"checkin_type": "manual",
"location": {
"name": "熊猫书房",
"coordinates": "35.202228,124.219237"
},
"staff_id": "O0006",
"status": "successful",
"notes": "热情参与讨论",
"is_late": false
},
{
"id": "C0073",
"activity_id": "A0007",
"user_id": "U0034",
"registration_id": "R0111",
"checkin_time": "2025-04-03 05:35:07",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "30.140811,113.054793"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0074",
"activity_id": "A0007",
"user_id": "U0010",
"registration_id": "R0112",
"checkin_time": "2025-04-03 05:18:04",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "26.209453,123.344462"
},
"staff_id": null,
"status": "failed",
"notes": "未携带所需材料",
"is_late": false
},
{
"id": "C0075",
"activity_id": "A0007",
"user_id": "U0017",
"registration_id": "R0113",
"checkin_time": "2025-04-03 05:30:44",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "39.520586,111.230061"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0076",
"activity_id": "A0007",
"user_id": "U0006",
"registration_id": "R0115",
"checkin_time": "2025-04-03 05:35:32",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "22.854964,103.303204"
},
"staff_id": null,
"status": "successful",
"notes": "按时到达",
"is_late": false
},
{
"id": "C0077",
"activity_id": "A0007",
"user_id": "U0030",
"registration_id": "R0116",
"checkin_time": "2025-04-03 05:22:00",
"checkin_type": "self-service",
"location": {
"name": "熊猫书房",
"coordinates": "33.919211,117.039781"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0078",
"activity_id": "A0007",
"user_id": "U0036",
"registration_id": "R0117",
"checkin_time": "2025-04-03 05:14:00",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "22.389473,121.064134"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0079",
"activity_id": "A0007",
"user_id": "U0025",
"registration_id": "R0118",
"checkin_time": "2025-04-03 05:34:15",
"checkin_type": "manual",
"location": {
"name": "熊猫书房",
"coordinates": "23.279344,121.133817"
},
"staff_id": "O0006",
"status": "successful",
"notes": "准备充分",
"is_late": false
},
{
"id": "C0080",
"activity_id": "A0007",
"user_id": "U0008",
"registration_id": "R0120",
"checkin_time": "2025-04-03 05:29:55",
"checkin_type": "self-service",
"location": {
"name": "熊猫书房",
"coordinates": "26.897514,110.258579"
},
"staff_id": null,
"status": "failed",
"notes": "未携带所需材料",
"is_late": false
},
{
"id": "C0081",
"activity_id": "A0007",
"user_id": "U0009",
"registration_id": "R0122",
"checkin_time": "2025-04-03 05:19:05",
"checkin_type": "manual",
"location": {
"name": "熊猫书房",
"coordinates": "37.908705,105.332370"
},
"staff_id": "O0006",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0082",
"activity_id": "A0007",
"user_id": "U0033",
"registration_id": "R0123",
"checkin_time": "2025-04-03 05:06:33",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "27.330438,106.622710"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0083",
"activity_id": "A0007",
"user_id": "U0028",
"registration_id": "R0124",
"checkin_time": "2025-04-03 05:06:45",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "22.012082,114.991471"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0084",
"activity_id": "A0007",
"user_id": "U0035",
"registration_id": "R0125",
"checkin_time": "2025-04-03 05:24:04",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "36.148959,100.254513"
},
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": false
},
{
"id": "C0085",
"activity_id": "A0007",
"user_id": "U0032",
"registration_id": "R0127",
"checkin_time": "2025-04-03 05:25:34",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "26.064327,114.415219"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0086",
"activity_id": "A0007",
"user_id": "U0021",
"registration_id": "R0129",
"checkin_time": "2025-04-03 05:39:03",
"checkin_type": "manual",
"location": {
"name": "熊猫书房",
"coordinates": "32.406524,103.926679"
},
"staff_id": "O0006",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0087",
"activity_id": "A0007",
"user_id": "U0002",
"registration_id": "R0130",
"checkin_time": "2025-04-03 05:16:57",
"checkin_type": "self-service",
"location": {
"name": "熊猫书房",
"coordinates": "34.129245,115.222974"
},
"staff_id": null,
"status": "successful",
"notes": "带了书本",
"is_late": false
},
{
"id": "C0088",
"activity_id": "A0007",
"user_id": "U0003",
"registration_id": "R0131",
"checkin_time": "2025-04-03 05:15:34",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "22.591087,120.597131"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0089",
"activity_id": "A0007",
"user_id": "U0019",
"registration_id": "R0132",
"checkin_time": "2025-04-03 05:50:38",
"checkin_type": "QR code",
"location": {
"name": "熊猫书房",
"coordinates": "36.712114,101.658613"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0090",
"activity_id": "A0008",
"user_id": "U0003",
"registration_id": "R0135",
"checkin_time": "2025-04-16 05:25:35",
"checkin_type": "self-service",
"location": {
"name": "紫金读书会",
"coordinates": "33.547392,111.581963"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0091",
"activity_id": "A0008",
"user_id": "U0031",
"registration_id": "R0136",
"checkin_time": "2025-04-16 05:32:36",
"checkin_type": "QR code",
"location": {
"name": "紫金读书会",
"coordinates": "26.534463,122.247619"
},
"staff_id": null,
"status": "successful",
"notes": "带了书本",
"is_late": false
},
{
"id": "C0092",
"activity_id": "A0008",
"user_id": "U0025",
"registration_id": "R0137",
"checkin_time": "2025-04-16 05:30:42",
"checkin_type": "QR code",
"location": {
"name": "紫金读书会",
"coordinates": "37.697400,124.784329"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0093",
"activity_id": "A0008",
"user_id": "U0010",
"registration_id": "R0139",
"checkin_time": "2025-04-16 05:52:05",
"checkin_type": "QR code",
"location": {
"name": "紫金读书会",
"coordinates": "37.840360,125.457487"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0094",
"activity_id": "A0008",
"user_id": "U0033",
"registration_id": "R0140",
"checkin_time": "2025-04-16 05:57:41",
"checkin_type": "self-service",
"location": {
"name": "紫金读书会",
"coordinates": "26.522240,113.936005"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0095",
"activity_id": "A0008",
"user_id": "U0038",
"registration_id": "R0143",
"checkin_time": "2025-04-16 05:34:49",
"checkin_type": "QR code",
"location": {
"name": "紫金读书会",
"coordinates": "32.237890,101.465566"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0096",
"activity_id": "A0008",
"user_id": "U0027",
"registration_id": "R0145",
"checkin_time": "2025-04-16 05:32:21",
"checkin_type": "QR code",
"location": {
"name": "紫金读书会",
"coordinates": "26.943107,117.949832"
},
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": true
},
{
"id": "C0097",
"activity_id": "A0008",
"user_id": "U0030",
"registration_id": "R0146",
"checkin_time": "2025-04-16 05:24:26",
"checkin_type": "QR code",
"location": {
"name": "紫金读书会",
"coordinates": "26.658257,117.440716"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0098",
"activity_id": "A0008",
"user_id": "U0021",
"registration_id": "R0148",
"checkin_time": "2025-04-16 05:41:35",
"checkin_type": "QR code",
"location": {
"name": "紫金读书会",
"coordinates": "28.532310,111.835896"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0099",
"activity_id": "A0008",
"user_id": "U0004",
"registration_id": "R0149",
"checkin_time": "2025-04-16 05:16:49",
"checkin_type": "QR code",
"location": {
"name": "紫金读书会",
"coordinates": "24.072484,125.678064"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0100",
"activity_id": "A0012",
"user_id": "U0026",
"registration_id": "R0178",
"checkin_time": "2025-04-04 05:04:24",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0101",
"activity_id": "A0012",
"user_id": "U0009",
"registration_id": "R0181",
"checkin_time": "2025-04-04 05:09:34",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0102",
"activity_id": "A0012",
"user_id": "U0025",
"registration_id": "R0182",
"checkin_time": "2025-04-04 05:25:45",
"checkin_type": "self-service",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0103",
"activity_id": "A0012",
"user_id": "U0002",
"registration_id": "R0183",
"checkin_time": "2025-04-04 05:27:11",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0104",
"activity_id": "A0012",
"user_id": "U0031",
"registration_id": "R0184",
"checkin_time": "2025-04-04 05:31:22",
"checkin_type": "manual",
"location": null,
"staff_id": "O0001",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0105",
"activity_id": "A0012",
"user_id": "U0015",
"registration_id": "R0185",
"checkin_time": "2025-04-04 05:36:53",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "带了书本",
"is_late": false
},
{
"id": "C0106",
"activity_id": "A0012",
"user_id": "U0039",
"registration_id": "R0187",
"checkin_time": "2025-04-04 05:48:01",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0107",
"activity_id": "A0012",
"user_id": "U0006",
"registration_id": "R0188",
"checkin_time": "2025-04-04 05:18:54",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "带了书本",
"is_late": false
},
{
"id": "C0108",
"activity_id": "A0012",
"user_id": "U0020",
"registration_id": "R0189",
"checkin_time": "2025-04-04 05:55:49",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "准备充分",
"is_late": true
},
{
"id": "C0109",
"activity_id": "A0012",
"user_id": "U0034",
"registration_id": "R0190",
"checkin_time": "2025-04-04 05:08:51",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": false
},
{
"id": "C0110",
"activity_id": "A0012",
"user_id": "U0010",
"registration_id": "R0191",
"checkin_time": "2025-04-04 05:48:49",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0111",
"activity_id": "A0012",
"user_id": "U0005",
"registration_id": "R0192",
"checkin_time": "2025-04-04 05:09:19",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "准备充分",
"is_late": false
},
{
"id": "C0112",
"activity_id": "A0012",
"user_id": "U0032",
"registration_id": "R0193",
"checkin_time": "2025-04-04 05:00:54",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0113",
"activity_id": "A0012",
"user_id": "U0012",
"registration_id": "R0194",
"checkin_time": "2025-04-04 05:44:20",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0114",
"activity_id": "A0012",
"user_id": "U0023",
"registration_id": "R0195",
"checkin_time": "2025-04-04 05:24:39",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0115",
"activity_id": "A0012",
"user_id": "U0021",
"registration_id": "R0196",
"checkin_time": "2025-04-04 05:00:48",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0116",
"activity_id": "A0012",
"user_id": "U0004",
"registration_id": "R0199",
"checkin_time": "2025-04-04 05:47:11",
"checkin_type": "manual",
"location": null,
"staff_id": "O0001",
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0117",
"activity_id": "A0012",
"user_id": "U0033",
"registration_id": "R0200",
"checkin_time": "2025-04-04 05:26:28",
"checkin_type": "manual",
"location": null,
"staff_id": "O0001",
"status": "successful",
"notes": "准备充分",
"is_late": false
},
{
"id": "C0118",
"activity_id": "A0012",
"user_id": "U0018",
"registration_id": "R0201",
"checkin_time": "2025-04-04 05:27:24",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0119",
"activity_id": "A0012",
"user_id": "U0017",
"registration_id": "R0202",
"checkin_time": "2025-04-04 05:15:29",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0120",
"activity_id": "A0012",
"user_id": "U0030",
"registration_id": "R0203",
"checkin_time": "2025-04-04 06:00:53",
"checkin_type": "manual",
"location": null,
"staff_id": "O0001",
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0121",
"activity_id": "A0012",
"user_id": "U0040",
"registration_id": "R0204",
"checkin_time": "2025-04-04 05:39:19",
"checkin_type": "self-service",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0122",
"activity_id": "A0012",
"user_id": "U0035",
"registration_id": "R0205",
"checkin_time": "2025-04-04 04:59:03",
"checkin_type": "manual",
"location": null,
"staff_id": "O0001",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0123",
"activity_id": "A0012",
"user_id": "U0008",
"registration_id": "R0206",
"checkin_time": "2025-04-04 05:18:37",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0124",
"activity_id": "A0012",
"user_id": "U0029",
"registration_id": "R0207",
"checkin_time": "2025-04-04 05:05:57",
"checkin_type": "manual",
"location": null,
"staff_id": "O0001",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0125",
"activity_id": "A0012",
"user_id": "U0028",
"registration_id": "R0208",
"checkin_time": "2025-04-04 05:12:11",
"checkin_type": "manual",
"location": null,
"staff_id": "O0001",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0126",
"activity_id": "A0012",
"user_id": "U0027",
"registration_id": "R0209",
"checkin_time": "2025-04-04 05:25:32",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "带了书本",
"is_late": true
},
{
"id": "C0127",
"activity_id": "A0012",
"user_id": "U0022",
"registration_id": "R0211",
"checkin_time": "2025-04-04 05:37:40",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "热情参与讨论",
"is_late": true
},
{
"id": "C0128",
"activity_id": "A0012",
"user_id": "U0016",
"registration_id": "R0213",
"checkin_time": "2025-04-04 05:02:22",
"checkin_type": "self-service",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "热情参与讨论",
"is_late": false
},
{
"id": "C0129",
"activity_id": "A0012",
"user_id": "U0011",
"registration_id": "R0214",
"checkin_time": "2025-04-04 05:29:01",
"checkin_type": "self-service",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "按时到达",
"is_late": false
},
{
"id": "C0130",
"activity_id": "A0012",
"user_id": "U0036",
"registration_id": "R0215",
"checkin_time": "2025-04-04 05:42:57",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0131",
"activity_id": "A0012",
"user_id": "U0019",
"registration_id": "R0216",
"checkin_time": "2025-04-04 05:12:01",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0132",
"activity_id": "A0012",
"user_id": "U0037",
"registration_id": "R0217",
"checkin_time": "2025-04-04 05:43:15",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "准备充分",
"is_late": false
},
{
"id": "C0133",
"activity_id": "A0013",
"user_id": "U0021",
"registration_id": "R0219",
"checkin_time": "2025-04-16 06:30:48",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "31.314481,112.706910"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0134",
"activity_id": "A0013",
"user_id": "U0034",
"registration_id": "R0220",
"checkin_time": "2025-04-16 06:28:10",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "27.233821,118.026442"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0135",
"activity_id": "A0013",
"user_id": "U0024",
"registration_id": "R0222",
"checkin_time": "2025-04-16 06:19:07",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "26.304584,117.374957"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0136",
"activity_id": "A0013",
"user_id": "U0006",
"registration_id": "R0226",
"checkin_time": "2025-04-16 06:47:15",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "26.656372,107.313507"
},
"staff_id": null,
"status": "successful",
"notes": "带了书本",
"is_late": true
},
{
"id": "C0137",
"activity_id": "A0013",
"user_id": "U0016",
"registration_id": "R0228",
"checkin_time": "2025-04-16 06:02:25",
"checkin_type": "manual",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "22.619492,111.165082"
},
"staff_id": "O0003",
"status": "successful",
"notes": "带了书本",
"is_late": false
},
{
"id": "C0138",
"activity_id": "A0013",
"user_id": "U0039",
"registration_id": "R0230",
"checkin_time": "2025-04-16 06:19:57",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "28.583453,100.061289"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0139",
"activity_id": "A0013",
"user_id": "U0033",
"registration_id": "R0231",
"checkin_time": "2025-04-16 05:48:13",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "39.928726,103.865093"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0140",
"activity_id": "A0013",
"user_id": "U0013",
"registration_id": "R0232",
"checkin_time": "2025-04-16 06:22:37",
"checkin_type": "manual",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "30.206263,116.407620"
},
"staff_id": "O0003",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0141",
"activity_id": "A0013",
"user_id": "U0017",
"registration_id": "R0237",
"checkin_time": "2025-04-16 05:51:41",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "26.353654,112.348033"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0142",
"activity_id": "A0013",
"user_id": "U0040",
"registration_id": "R0238",
"checkin_time": "2025-04-16 05:59:42",
"checkin_type": "manual",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "29.536559,121.926857"
},
"staff_id": "O0003",
"status": "successful",
"notes": "按时到达",
"is_late": false
},
{
"id": "C0143",
"activity_id": "A0013",
"user_id": "U0028",
"registration_id": "R0239",
"checkin_time": "2025-04-16 06:03:43",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "38.445453,111.849643"
},
"staff_id": null,
"status": "successful",
"notes": "热情参与讨论",
"is_late": false
},
{
"id": "C0144",
"activity_id": "A0013",
"user_id": "U0015",
"registration_id": "R0240",
"checkin_time": "2025-04-16 06:15:58",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "38.987819,109.828606"
},
"staff_id": null,
"status": "failed",
"notes": "网络连接不稳定",
"is_late": false
},
{
"id": "C0145",
"activity_id": "A0013",
"user_id": "U0023",
"registration_id": "R0242",
"checkin_time": "2025-04-16 06:21:50",
"checkin_type": "manual",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "38.259928,108.439322"
},
"staff_id": "O0003",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0146",
"activity_id": "A0013",
"user_id": "U0012",
"registration_id": "R0243",
"checkin_time": "2025-04-16 06:22:16",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "30.354202,113.504246"
},
"staff_id": null,
"status": "successful",
"notes": "准备充分",
"is_late": false
},
{
"id": "C0147",
"activity_id": "A0013",
"user_id": "U0020",
"registration_id": "R0244",
"checkin_time": "2025-04-16 06:14:18",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "38.643924,120.520059"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0148",
"activity_id": "A0013",
"user_id": "U0022",
"registration_id": "R0246",
"checkin_time": "2025-04-16 06:03:06",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "31.559993,119.096261"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0149",
"activity_id": "A0013",
"user_id": "U0003",
"registration_id": "R0247",
"checkin_time": "2025-04-16 05:52:52",
"checkin_type": "self-service",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "39.656725,112.033980"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0150",
"activity_id": "A0013",
"user_id": "U0001",
"registration_id": "R0249",
"checkin_time": "2025-04-16 05:53:42",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "35.419700,117.373079"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0151",
"activity_id": "A0013",
"user_id": "U0036",
"registration_id": "R0250",
"checkin_time": "2025-04-16 06:22:22",
"checkin_type": "self-service",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "22.718087,103.176777"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0152",
"activity_id": "A0013",
"user_id": "U0037",
"registration_id": "R0251",
"checkin_time": "2025-04-16 06:04:16",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "36.437377,116.319798"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0153",
"activity_id": "A0013",
"user_id": "U0010",
"registration_id": "R0252",
"checkin_time": "2025-04-16 06:06:45",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "25.502640,110.555487"
},
"staff_id": null,
"status": "failed",
"notes": "设备问题",
"is_late": false
},
{
"id": "C0154",
"activity_id": "A0013",
"user_id": "U0004",
"registration_id": "R0254",
"checkin_time": "2025-04-16 06:00:23",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "38.473438,101.763058"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0155",
"activity_id": "A0013",
"user_id": "U0005",
"registration_id": "R0255",
"checkin_time": "2025-04-16 06:50:21",
"checkin_type": "QR code",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "25.433843,111.547821"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0156",
"activity_id": "A0013",
"user_id": "U0025",
"registration_id": "R0256",
"checkin_time": "2025-04-16 06:06:05",
"checkin_type": "manual",
"location": {
"name": "深蓝咖啡馆",
"coordinates": "33.246812,102.422719"
},
"staff_id": "O0003",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0157",
"activity_id": "A0015",
"user_id": "U0035",
"registration_id": "R0263",
"checkin_time": "2025-04-16 06:21:26",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0158",
"activity_id": "A0015",
"user_id": "U0010",
"registration_id": "R0264",
"checkin_time": "2025-04-16 06:35:32",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0159",
"activity_id": "A0015",
"user_id": "U0015",
"registration_id": "R0265",
"checkin_time": "2025-04-16 05:52:07",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0160",
"activity_id": "A0015",
"user_id": "U0005",
"registration_id": "R0266",
"checkin_time": "2025-04-16 06:18:17",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0161",
"activity_id": "A0015",
"user_id": "U0001",
"registration_id": "R0267",
"checkin_time": "2025-04-16 06:05:44",
"checkin_type": "self-service",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "准备充分",
"is_late": true
},
{
"id": "C0162",
"activity_id": "A0015",
"user_id": "U0025",
"registration_id": "R0270",
"checkin_time": "2025-04-16 06:32:13",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": false
},
{
"id": "C0163",
"activity_id": "A0015",
"user_id": "U0018",
"registration_id": "R0274",
"checkin_time": "2025-04-16 06:25:01",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": true
},
{
"id": "C0164",
"activity_id": "A0015",
"user_id": "U0019",
"registration_id": "R0275",
"checkin_time": "2025-04-16 06:13:07",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0165",
"activity_id": "A0022",
"user_id": "U0026",
"registration_id": "R0304",
"checkin_time": "2025-04-16 07:15:52",
"checkin_type": "manual",
"location": {
"name": "大雁塔文化空间",
"coordinates": "26.580414,125.344607"
},
"staff_id": "O0005",
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0166",
"activity_id": "A0022",
"user_id": "U0016",
"registration_id": "R0307",
"checkin_time": "2025-04-16 06:48:22",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "24.470878,121.132080"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0167",
"activity_id": "A0022",
"user_id": "U0001",
"registration_id": "R0310",
"checkin_time": "2025-04-16 06:28:54",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "28.449496,109.032109"
},
"staff_id": null,
"status": "successful",
"notes": "按时到达",
"is_late": false
},
{
"id": "C0168",
"activity_id": "A0022",
"user_id": "U0008",
"registration_id": "R0312",
"checkin_time": "2025-04-16 06:55:34",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "36.733507,110.478652"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0169",
"activity_id": "A0022",
"user_id": "U0021",
"registration_id": "R0313",
"checkin_time": "2025-04-16 06:27:02",
"checkin_type": "self-service",
"location": {
"name": "大雁塔文化空间",
"coordinates": "38.460924,120.561687"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0170",
"activity_id": "A0022",
"user_id": "U0035",
"registration_id": "R0314",
"checkin_time": "2025-04-16 06:31:10",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "26.603106,100.136011"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0171",
"activity_id": "A0022",
"user_id": "U0009",
"registration_id": "R0316",
"checkin_time": "2025-04-16 06:23:40",
"checkin_type": "manual",
"location": {
"name": "大雁塔文化空间",
"coordinates": "34.533322,104.144383"
},
"staff_id": "O0005",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0172",
"activity_id": "A0022",
"user_id": "U0015",
"registration_id": "R0317",
"checkin_time": "2025-04-16 06:55:20",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "23.284204,106.163339"
},
"staff_id": null,
"status": "successful",
"notes": "热情参与讨论",
"is_late": false
},
{
"id": "C0173",
"activity_id": "A0022",
"user_id": "U0036",
"registration_id": "R0318",
"checkin_time": "2025-04-16 06:47:27",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "32.940263,122.500088"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0174",
"activity_id": "A0022",
"user_id": "U0012",
"registration_id": "R0319",
"checkin_time": "2025-04-16 06:35:18",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "23.633884,125.935592"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0175",
"activity_id": "A0022",
"user_id": "U0020",
"registration_id": "R0320",
"checkin_time": "2025-04-16 06:44:24",
"checkin_type": "manual",
"location": {
"name": "大雁塔文化空间",
"coordinates": "30.891166,113.054445"
},
"staff_id": "O0005",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0176",
"activity_id": "A0022",
"user_id": "U0017",
"registration_id": "R0321",
"checkin_time": "2025-04-16 06:18:29",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "38.287921,119.065611"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0177",
"activity_id": "A0022",
"user_id": "U0004",
"registration_id": "R0324",
"checkin_time": "2025-04-16 06:48:23",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "30.466273,121.305050"
},
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": false
},
{
"id": "C0178",
"activity_id": "A0022",
"user_id": "U0031",
"registration_id": "R0327",
"checkin_time": "2025-04-16 06:22:20",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "30.379818,116.809350"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0179",
"activity_id": "A0022",
"user_id": "U0037",
"registration_id": "R0328",
"checkin_time": "2025-04-16 06:28:39",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "26.477816,122.575710"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0180",
"activity_id": "A0022",
"user_id": "U0007",
"registration_id": "R0329",
"checkin_time": "2025-04-16 06:37:26",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "25.742254,108.270197"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0181",
"activity_id": "A0022",
"user_id": "U0018",
"registration_id": "R0331",
"checkin_time": "2025-04-16 06:59:00",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "27.938128,105.012614"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0182",
"activity_id": "A0022",
"user_id": "U0030",
"registration_id": "R0332",
"checkin_time": "2025-04-16 07:01:36",
"checkin_type": "manual",
"location": {
"name": "大雁塔文化空间",
"coordinates": "39.677261,117.105251"
},
"staff_id": "O0005",
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0183",
"activity_id": "A0022",
"user_id": "U0006",
"registration_id": "R0333",
"checkin_time": "2025-04-16 06:51:37",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "26.249063,116.245331"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0184",
"activity_id": "A0022",
"user_id": "U0014",
"registration_id": "R0336",
"checkin_time": "2025-04-16 06:33:41",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "32.162403,115.813070"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0185",
"activity_id": "A0022",
"user_id": "U0024",
"registration_id": "R0338",
"checkin_time": "2025-04-16 06:54:14",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "25.927681,123.295939"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0186",
"activity_id": "A0022",
"user_id": "U0003",
"registration_id": "R0339",
"checkin_time": "2025-04-16 06:37:10",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "24.967249,100.833877"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0187",
"activity_id": "A0022",
"user_id": "U0033",
"registration_id": "R0340",
"checkin_time": "2025-04-16 06:40:31",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "34.843743,113.933664"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0188",
"activity_id": "A0022",
"user_id": "U0011",
"registration_id": "R0342",
"checkin_time": "2025-04-16 06:44:31",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "38.968901,123.655330"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0189",
"activity_id": "A0022",
"user_id": "U0005",
"registration_id": "R0343",
"checkin_time": "2025-04-16 06:18:06",
"checkin_type": "self-service",
"location": {
"name": "大雁塔文化空间",
"coordinates": "22.517659,116.229674"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0190",
"activity_id": "A0023",
"user_id": "U0014",
"registration_id": "R0344",
"checkin_time": "2025-03-27 05:16:40",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": false
},
{
"id": "C0191",
"activity_id": "A0023",
"user_id": "U0006",
"registration_id": "R0346",
"checkin_time": "2025-03-27 05:35:36",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": false
},
{
"id": "C0192",
"activity_id": "A0023",
"user_id": "U0030",
"registration_id": "R0347",
"checkin_time": "2025-03-27 05:23:26",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0193",
"activity_id": "A0023",
"user_id": "U0035",
"registration_id": "R0348",
"checkin_time": "2025-03-27 05:11:26",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0194",
"activity_id": "A0023",
"user_id": "U0007",
"registration_id": "R0350",
"checkin_time": "2025-03-27 05:05:40",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "failed",
"notes": "签到后临时离开",
"is_late": false
},
{
"id": "C0195",
"activity_id": "A0023",
"user_id": "U0016",
"registration_id": "R0351",
"checkin_time": "2025-03-27 05:30:46",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0196",
"activity_id": "A0023",
"user_id": "U0011",
"registration_id": "R0352",
"checkin_time": "2025-03-27 05:32:44",
"checkin_type": "manual",
"location": null,
"staff_id": "O0007",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0197",
"activity_id": "A0023",
"user_id": "U0003",
"registration_id": "R0353",
"checkin_time": "2025-03-27 05:31:42",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": false
},
{
"id": "C0198",
"activity_id": "A0023",
"user_id": "U0029",
"registration_id": "R0354",
"checkin_time": "2025-03-27 04:59:34",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0199",
"activity_id": "A0023",
"user_id": "U0034",
"registration_id": "R0355",
"checkin_time": "2025-03-27 05:32:02",
"checkin_type": "manual",
"location": null,
"staff_id": "O0007",
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0200",
"activity_id": "A0023",
"user_id": "U0002",
"registration_id": "R0356",
"checkin_time": "2025-03-27 05:26:35",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0201",
"activity_id": "A0023",
"user_id": "U0026",
"registration_id": "R0357",
"checkin_time": "2025-03-27 05:05:43",
"checkin_type": "manual",
"location": null,
"staff_id": "O0007",
"status": "successful",
"notes": "按时到达",
"is_late": false
},
{
"id": "C0202",
"activity_id": "A0023",
"user_id": "U0008",
"registration_id": "R0358",
"checkin_time": "2025-03-27 05:26:39",
"checkin_type": "manual",
"location": null,
"staff_id": "O0007",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0203",
"activity_id": "A0023",
"user_id": "U0020",
"registration_id": "R0360",
"checkin_time": "2025-03-27 05:57:08",
"checkin_type": "manual",
"location": null,
"staff_id": "O0007",
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0204",
"activity_id": "A0023",
"user_id": "U0038",
"registration_id": "R0361",
"checkin_time": "2025-03-27 05:23:01",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "热情参与讨论",
"is_late": false
},
{
"id": "C0205",
"activity_id": "A0023",
"user_id": "U0024",
"registration_id": "R0362",
"checkin_time": "2025-03-27 05:33:56",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0206",
"activity_id": "A0023",
"user_id": "U0025",
"registration_id": "R0363",
"checkin_time": "2025-03-27 05:23:06",
"checkin_type": "self-service",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0207",
"activity_id": "A0023",
"user_id": "U0001",
"registration_id": "R0365",
"checkin_time": "2025-03-27 05:18:27",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0208",
"activity_id": "A0023",
"user_id": "U0019",
"registration_id": "R0366",
"checkin_time": "2025-03-27 05:34:53",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0209",
"activity_id": "A0023",
"user_id": "U0027",
"registration_id": "R0370",
"checkin_time": "2025-03-27 05:33:23",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": true
},
{
"id": "C0210",
"activity_id": "A0023",
"user_id": "U0036",
"registration_id": "R0371",
"checkin_time": "2025-03-27 05:22:50",
"checkin_type": "manual",
"location": null,
"staff_id": "O0007",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0211",
"activity_id": "A0023",
"user_id": "U0031",
"registration_id": "R0373",
"checkin_time": "2025-03-27 05:34:33",
"checkin_type": "manual",
"location": null,
"staff_id": "O0007",
"status": "successful",
"notes": "提前到达",
"is_late": false
},
{
"id": "C0212",
"activity_id": "A0023",
"user_id": "U0037",
"registration_id": "R0374",
"checkin_time": "2025-03-27 05:27:34",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "热情参与讨论",
"is_late": false
},
{
"id": "C0213",
"activity_id": "A0023",
"user_id": "U0013",
"registration_id": "R0375",
"checkin_time": "2025-03-27 05:23:16",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "带了书本",
"is_late": false
},
{
"id": "C0214",
"activity_id": "A0023",
"user_id": "U0005",
"registration_id": "R0376",
"checkin_time": "2025-03-27 05:11:53",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "failed",
"notes": "网络连接不稳定",
"is_late": false
},
{
"id": "C0215",
"activity_id": "A0023",
"user_id": "U0039",
"registration_id": "R0377",
"checkin_time": "2025-03-27 05:01:36",
"checkin_type": "manual",
"location": null,
"staff_id": "O0007",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0216",
"activity_id": "A0023",
"user_id": "U0017",
"registration_id": "R0379",
"checkin_time": "2025-03-27 05:31:07",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0217",
"activity_id": "A0023",
"user_id": "U0004",
"registration_id": "R0380",
"checkin_time": "2025-03-27 05:42:59",
"checkin_type": "manual",
"location": null,
"staff_id": "O0007",
"status": "successful",
"notes": "准备充分",
"is_late": false
},
{
"id": "C0218",
"activity_id": "A0023",
"user_id": "U0015",
"registration_id": "R0381",
"checkin_time": "2025-03-27 05:45:54",
"checkin_type": "self-service",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0219",
"activity_id": "A0023",
"user_id": "U0009",
"registration_id": "R0382",
"checkin_time": "2025-03-27 05:26:18",
"checkin_type": "manual",
"location": null,
"staff_id": "O0007",
"status": "successful",
"notes": "带了书本",
"is_late": true
},
{
"id": "C0220",
"activity_id": "A0023",
"user_id": "U0040",
"registration_id": "R0383",
"checkin_time": "2025-03-27 05:30:24",
"checkin_type": "QR code",
"location": null,
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0221",
"activity_id": "A0027",
"user_id": "U0017",
"registration_id": "R0407",
"checkin_time": "2025-04-16 05:32:53",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "38.833814,114.514317"
},
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": true
},
{
"id": "C0222",
"activity_id": "A0027",
"user_id": "U0031",
"registration_id": "R0408",
"checkin_time": "2025-04-16 05:46:08",
"checkin_type": "self-service",
"location": {
"name": "大雁塔文化空间",
"coordinates": "27.781089,102.822173"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0223",
"activity_id": "A0027",
"user_id": "U0029",
"registration_id": "R0410",
"checkin_time": "2025-04-16 05:47:53",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "27.308599,114.704434"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0224",
"activity_id": "A0027",
"user_id": "U0007",
"registration_id": "R0411",
"checkin_time": "2025-04-16 05:40:07",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "38.156809,110.024987"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0225",
"activity_id": "A0027",
"user_id": "U0022",
"registration_id": "R0414",
"checkin_time": "2025-04-16 05:55:30",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "30.231849,102.497485"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0226",
"activity_id": "A0027",
"user_id": "U0027",
"registration_id": "R0417",
"checkin_time": "2025-04-16 05:54:23",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "30.564601,104.408038"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0227",
"activity_id": "A0027",
"user_id": "U0005",
"registration_id": "R0418",
"checkin_time": "2025-04-16 05:47:45",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "24.122715,109.827973"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0228",
"activity_id": "A0027",
"user_id": "U0013",
"registration_id": "R0419",
"checkin_time": "2025-04-16 05:27:24",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "25.549267,111.522543"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0229",
"activity_id": "A0027",
"user_id": "U0037",
"registration_id": "R0421",
"checkin_time": "2025-04-16 05:22:07",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "26.680011,113.731917"
},
"staff_id": null,
"status": "successful",
"notes": "准备充分",
"is_late": true
},
{
"id": "C0230",
"activity_id": "A0027",
"user_id": "U0012",
"registration_id": "R0422",
"checkin_time": "2025-04-16 05:08:30",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "30.777704,110.012542"
},
"staff_id": null,
"status": "successful",
"notes": "按时到达",
"is_late": false
},
{
"id": "C0231",
"activity_id": "A0027",
"user_id": "U0024",
"registration_id": "R0423",
"checkin_time": "2025-04-16 05:24:15",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "30.010222,103.940968"
},
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": true
},
{
"id": "C0232",
"activity_id": "A0027",
"user_id": "U0006",
"registration_id": "R0424",
"checkin_time": "2025-04-16 05:08:05",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "33.278353,101.387988"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0233",
"activity_id": "A0030",
"user_id": "U0022",
"registration_id": "R0441",
"checkin_time": "2025-03-24 06:13:41",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "28.787895,119.435720"
},
"staff_id": null,
"status": "successful",
"notes": "按时到达",
"is_late": false
},
{
"id": "C0234",
"activity_id": "A0030",
"user_id": "U0031",
"registration_id": "R0442",
"checkin_time": "2025-03-24 06:14:40",
"checkin_type": "manual",
"location": {
"name": "大雁塔文化空间",
"coordinates": "38.127912,104.147554"
},
"staff_id": "O0005",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0235",
"activity_id": "A0030",
"user_id": "U0003",
"registration_id": "R0443",
"checkin_time": "2025-03-24 06:12:05",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "36.552893,123.319240"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0236",
"activity_id": "A0030",
"user_id": "U0030",
"registration_id": "R0444",
"checkin_time": "2025-03-24 06:35:57",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "37.606920,110.862144"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0237",
"activity_id": "A0030",
"user_id": "U0028",
"registration_id": "R0445",
"checkin_time": "2025-03-24 06:21:00",
"checkin_type": "manual",
"location": {
"name": "大雁塔文化空间",
"coordinates": "36.680083,106.083682"
},
"staff_id": "O0005",
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0238",
"activity_id": "A0030",
"user_id": "U0007",
"registration_id": "R0446",
"checkin_time": "2025-03-24 06:14:08",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "39.916319,120.798035"
},
"staff_id": null,
"status": "failed",
"notes": "未携带所需材料",
"is_late": false
},
{
"id": "C0239",
"activity_id": "A0030",
"user_id": "U0034",
"registration_id": "R0447",
"checkin_time": "2025-03-24 06:21:39",
"checkin_type": "manual",
"location": {
"name": "大雁塔文化空间",
"coordinates": "35.329186,107.028931"
},
"staff_id": "O0005",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0240",
"activity_id": "A0030",
"user_id": "U0032",
"registration_id": "R0449",
"checkin_time": "2025-03-24 06:19:22",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "30.944494,124.919253"
},
"staff_id": null,
"status": "failed",
"notes": "迟到超过30分钟",
"is_late": false
},
{
"id": "C0241",
"activity_id": "A0030",
"user_id": "U0016",
"registration_id": "R0450",
"checkin_time": "2025-03-24 06:31:18",
"checkin_type": "self-service",
"location": {
"name": "大雁塔文化空间",
"coordinates": "30.156234,121.488007"
},
"staff_id": null,
"status": "failed",
"notes": "网络连接不稳定",
"is_late": false
},
{
"id": "C0242",
"activity_id": "A0030",
"user_id": "U0002",
"registration_id": "R0451",
"checkin_time": "2025-03-24 06:32:07",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "36.425545,115.307617"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0243",
"activity_id": "A0030",
"user_id": "U0029",
"registration_id": "R0453",
"checkin_time": "2025-03-24 06:04:37",
"checkin_type": "self-service",
"location": {
"name": "大雁塔文化空间",
"coordinates": "30.531905,106.376309"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0244",
"activity_id": "A0030",
"user_id": "U0038",
"registration_id": "R0454",
"checkin_time": "2025-03-24 06:16:33",
"checkin_type": "self-service",
"location": {
"name": "大雁塔文化空间",
"coordinates": "30.017986,114.083724"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0245",
"activity_id": "A0030",
"user_id": "U0021",
"registration_id": "R0455",
"checkin_time": "2025-03-24 06:11:23",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "27.571855,119.718238"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0246",
"activity_id": "A0030",
"user_id": "U0027",
"registration_id": "R0456",
"checkin_time": "2025-03-24 06:24:43",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "38.706085,113.113853"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0247",
"activity_id": "A0030",
"user_id": "U0012",
"registration_id": "R0457",
"checkin_time": "2025-03-24 06:31:03",
"checkin_type": "manual",
"location": {
"name": "大雁塔文化空间",
"coordinates": "36.627228,124.621240"
},
"staff_id": "O0005",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0248",
"activity_id": "A0030",
"user_id": "U0009",
"registration_id": "R0458",
"checkin_time": "2025-03-24 06:24:40",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "28.284668,120.174935"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0249",
"activity_id": "A0030",
"user_id": "U0035",
"registration_id": "R0459",
"checkin_time": "2025-03-24 06:22:22",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "25.338185,120.506832"
},
"staff_id": null,
"status": "successful",
"notes": "提前到达",
"is_late": true
},
{
"id": "C0250",
"activity_id": "A0030",
"user_id": "U0036",
"registration_id": "R0460",
"checkin_time": "2025-03-24 06:10:36",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "39.576489,123.708963"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0251",
"activity_id": "A0030",
"user_id": "U0010",
"registration_id": "R0461",
"checkin_time": "2025-03-24 06:30:17",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "25.560215,113.066388"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0252",
"activity_id": "A0030",
"user_id": "U0011",
"registration_id": "R0462",
"checkin_time": "2025-03-24 06:53:26",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "24.201619,102.148571"
},
"staff_id": null,
"status": "successful",
"notes": "热情参与讨论",
"is_late": true
},
{
"id": "C0253",
"activity_id": "A0030",
"user_id": "U0040",
"registration_id": "R0464",
"checkin_time": "2025-03-24 06:28:18",
"checkin_type": "manual",
"location": {
"name": "大雁塔文化空间",
"coordinates": "27.166876,110.472657"
},
"staff_id": "O0005",
"status": "successful",
"notes": "准备充分",
"is_late": false
},
{
"id": "C0254",
"activity_id": "A0030",
"user_id": "U0005",
"registration_id": "R0465",
"checkin_time": "2025-03-24 06:16:32",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "39.765965,102.449461"
},
"staff_id": null,
"status": "successful",
"notes": "准备充分",
"is_late": false
},
{
"id": "C0255",
"activity_id": "A0030",
"user_id": "U0017",
"registration_id": "R0466",
"checkin_time": "2025-03-24 06:26:07",
"checkin_type": "manual",
"location": {
"name": "大雁塔文化空间",
"coordinates": "35.381179,100.310767"
},
"staff_id": "O0005",
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0256",
"activity_id": "A0030",
"user_id": "U0006",
"registration_id": "R0467",
"checkin_time": "2025-03-24 06:08:21",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "30.564468,110.716545"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0257",
"activity_id": "A0030",
"user_id": "U0013",
"registration_id": "R0468",
"checkin_time": "2025-03-24 06:34:50",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "38.552870,102.806985"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0258",
"activity_id": "A0030",
"user_id": "U0014",
"registration_id": "R0469",
"checkin_time": "2025-03-24 06:11:44",
"checkin_type": "manual",
"location": {
"name": "大雁塔文化空间",
"coordinates": "28.615959,124.683004"
},
"staff_id": "O0005",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0259",
"activity_id": "A0030",
"user_id": "U0024",
"registration_id": "R0470",
"checkin_time": "2025-03-24 06:21:59",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "32.650324,122.567416"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0260",
"activity_id": "A0030",
"user_id": "U0023",
"registration_id": "R0471",
"checkin_time": "2025-03-24 06:19:00",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "33.643347,118.704871"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0261",
"activity_id": "A0030",
"user_id": "U0018",
"registration_id": "R0472",
"checkin_time": "2025-03-24 06:11:01",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "29.846041,119.659244"
},
"staff_id": null,
"status": "successful",
"notes": "热情参与讨论",
"is_late": false
},
{
"id": "C0262",
"activity_id": "A0030",
"user_id": "U0020",
"registration_id": "R0474",
"checkin_time": "2025-03-24 06:35:07",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "25.709511,106.061221"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": true
},
{
"id": "C0263",
"activity_id": "A0030",
"user_id": "U0008",
"registration_id": "R0475",
"checkin_time": "2025-03-24 06:39:17",
"checkin_type": "manual",
"location": {
"name": "大雁塔文化空间",
"coordinates": "32.647541,101.108562"
},
"staff_id": "O0005",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0264",
"activity_id": "A0030",
"user_id": "U0039",
"registration_id": "R0476",
"checkin_time": "2025-03-24 06:08:49",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "23.752600,113.568037"
},
"staff_id": null,
"status": "successful",
"notes": "准备充分",
"is_late": false
},
{
"id": "C0265",
"activity_id": "A0030",
"user_id": "U0025",
"registration_id": "R0477",
"checkin_time": "2025-03-24 06:14:21",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "27.951622,124.171448"
},
"staff_id": null,
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0266",
"activity_id": "A0030",
"user_id": "U0004",
"registration_id": "R0478",
"checkin_time": "2025-03-24 06:28:35",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "39.931468,121.020079"
},
"staff_id": null,
"status": "successful",
"notes": "准备充分",
"is_late": false
},
{
"id": "C0267",
"activity_id": "A0030",
"user_id": "U0037",
"registration_id": "R0479",
"checkin_time": "2025-03-24 06:07:58",
"checkin_type": "manual",
"location": {
"name": "大雁塔文化空间",
"coordinates": "34.700797,103.276532"
},
"staff_id": "O0005",
"status": "successful",
"notes": "",
"is_late": false
},
{
"id": "C0268",
"activity_id": "A0030",
"user_id": "U0001",
"registration_id": "R0480",
"checkin_time": "2025-03-24 06:28:46",
"checkin_type": "QR code",
"location": {
"name": "大雁塔文化空间",
"coordinates": "39.529239,117.178983"
},
"staff_id": null,
"status": "successful",
"notes": "带了书本",
"is_late": false
}
]
\ No newline at end of file
{
"data": [
{
"title": "Travel Blog",
"description": "Here is json data for travel blog"
}
]
}
\ No newline at end of file
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
[
{
"id": "U0001",
"username": "philosopher915",
"name": "陈静",
"avatar": "https://randomuser.me/api/portraits/women/33.jpg",
"phone": "15242619085",
"email": "philosopher915@outlook.com",
"gender": "男",
"age_group": "31-35岁",
"education": "高中",
"registration_date": "2024-10-15 07:11:49",
"location": "南京",
"bio": "观点,交流,生活,发现,认知,情感,情感,交流,兴趣",
"interests": [
"历史",
"古典文学",
"教育",
"社会科学",
"政治"
],
"is_organizer": false,
"organization": null,
"activity_count": 10,
"follow_count": 7,
"followers_count": 13,
"is_new_user": false
},
{
"id": "U0002",
"username": "bibliophile888",
"name": "林杰",
"avatar": "https://randomuser.me/api/portraits/men/71.jpg",
"phone": "18639598797",
"email": "bibliophile888@foxmail.com",
"gender": "女",
"age_group": "31-35岁",
"education": "本科",
"registration_date": "2023-11-12 07:11:49",
"location": "西安",
"bio": "生活,未来,读书,思考,知识,思考,感悟,视野,思想,坚持,热爱,工作,沟通,坚持,知识,生活,理解,坚持",
"interests": [
"推理",
"诗歌",
"音乐",
"政治",
"旅行"
],
"is_organizer": false,
"organization": null,
"activity_count": 9,
"follow_count": 37,
"followers_count": 14,
"is_new_user": false
},
{
"id": "U0003",
"username": "sage560",
"name": "胡秀英",
"avatar": "https://randomuser.me/api/portraits/men/25.jpg",
"phone": "13039456917",
"email": "sage560@gmail.com",
"gender": "女",
"age_group": "41-50岁",
"education": "硕士",
"registration_date": "2024-03-12 07:11:49",
"location": "北京",
"bio": "思想,学习,沟通,坚持,启发,认知,经验,创造,理解,观点,精神,认知,思想,视野,探索,知识,文化,",
"interests": [
"艺术",
"哲学",
"旅行",
"政治",
"教育"
],
"is_organizer": false,
"organization": null,
"activity_count": 5,
"follow_count": 13,
"followers_count": 15,
"is_new_user": false
},
{
"id": "U0004",
"username": "reader777",
"name": "陈勇",
"avatar": "https://randomuser.me/api/portraits/men/92.jpg",
"phone": "13678802250",
"email": "reader777@163.com",
"gender": "女",
"age_group": "36-40岁",
"education": "硕士",
"registration_date": "2023-05-28 07:11:49",
"location": "重庆",
"bio": "生活,教育,分享,交流,启发,观点,沟通,情感,观点,发现,认知,价值,视野",
"interests": [
"旅行",
"外国文学",
"古典文学",
"推理",
"经济"
],
"is_organizer": false,
"organization": null,
"activity_count": 9,
"follow_count": 18,
"followers_count": 22,
"is_new_user": false
},
{
"id": "U0005",
"username": "scholar823",
"name": "李霞",
"avatar": "https://randomuser.me/api/portraits/women/24.jpg",
"phone": "15765356756",
"email": "scholar823@outlook.com",
"gender": "女",
"age_group": "41-50岁",
"education": "高中",
"registration_date": "2023-07-05 07:11:49",
"location": "北京",
"bio": "思考,兴趣,观点,兴趣,观点,经验,观点,观点,文化,智慧",
"interests": [
"艺术",
"旅行",
"古典文学",
"音乐",
"外国文学"
],
"is_organizer": false,
"organization": null,
"activity_count": 3,
"follow_count": 2,
"followers_count": 27,
"is_new_user": false
},
{
"id": "U0006",
"username": "thinker926",
"name": "胡超",
"avatar": "https://randomuser.me/api/portraits/men/40.jpg",
"phone": "15926853615",
"email": "thinker926@126.com",
"gender": "女",
"age_group": "36-40岁",
"education": "博士",
"registration_date": "2025-04-10 07:11:49",
"location": "北京",
"bio": "情感,未来,发现,创造,灵感,灵感,读书,灵感,灵感",
"interests": [
"心理学",
"哲学",
"管理",
"科技"
],
"is_organizer": false,
"organization": null,
"activity_count": 15,
"follow_count": 10,
"followers_count": 21,
"is_new_user": true
},
{
"id": "U0007",
"username": "reader206",
"name": "何秀英",
"avatar": "https://randomuser.me/api/portraits/men/33.jpg",
"phone": "13028502918",
"email": "reader206@hotmail.com",
"gender": "女",
"age_group": "18-24岁",
"education": "大专",
"registration_date": "2024-05-18 07:11:49",
"location": "深圳",
"bio": "坚持,文化,教育,智慧,专注,情感,探索,成长,知识,沟通,生活,认知",
"interests": [
"外国文学",
"古典文学",
"传记",
"科技",
"社会科学"
],
"is_organizer": false,
"organization": null,
"activity_count": 13,
"follow_count": 1,
"followers_count": 25,
"is_new_user": false
},
{
"id": "U0008",
"username": "reader520",
"name": "周霞",
"avatar": "https://randomuser.me/api/portraits/women/77.jpg",
"phone": "13954151629",
"email": "reader520@126.com",
"gender": "男",
"age_group": "31-35岁",
"education": "本科",
"registration_date": "2024-08-24 07:11:49",
"location": "北京",
"bio": "读书,专注,观点,兴趣,知识,探索,智慧,情感,理解,创造,心灵,思想,观点,创造,视野,认知,学习,文化,探索,情感,知识,视野,工作",
"interests": [
"健康",
"推理"
],
"is_organizer": false,
"organization": null,
"activity_count": 15,
"follow_count": 43,
"followers_count": 16,
"is_new_user": false
},
{
"id": "U0009",
"username": "literati391",
"name": "何敏",
"avatar": "https://randomuser.me/api/portraits/women/26.jpg",
"phone": "18778721591",
"email": "literati391@gmail.com",
"gender": "女",
"age_group": "41-50岁",
"education": "博士",
"registration_date": "2024-08-06 07:11:49",
"location": "重庆",
"bio": "成长,工作,启发,文化,认知,观点,创造,感悟,学习,情感,坚持,成长,视野,思考,生活,启发,热爱,热爱",
"interests": [
"健康",
"经济",
"科幻",
"外国文学",
"社会科学"
],
"is_organizer": false,
"organization": null,
"activity_count": 5,
"follow_count": 14,
"followers_count": 5,
"is_new_user": false
},
{
"id": "U0010",
"username": "scholar137",
"name": "黄伟",
"avatar": "https://randomuser.me/api/portraits/women/46.jpg",
"phone": "15554784272",
"email": "scholar137@qq.com",
"gender": "女",
"age_group": "50岁以上",
"education": "博士",
"registration_date": "2023-04-21 07:11:49",
"location": "深圳",
"bio": "未来,心灵,知识,沟通,交流,感悟,未来,情感,知识,表达,理解,沟通,表达,观点,价值,工作,交流,教育,知识,发现,发现,读书,学习,认知,启发,工作,价值,智慧,沟通,启发,表达,感悟,",
"interests": [
"政治",
"经济",
"推理"
],
"is_organizer": false,
"organization": null,
"activity_count": 5,
"follow_count": 44,
"followers_count": 10,
"is_new_user": false
},
{
"id": "U0011",
"username": "thinker327",
"name": "陈洋",
"avatar": "https://randomuser.me/api/portraits/men/44.jpg",
"phone": "13252122058",
"email": "thinker327@gmail.com",
"gender": "女",
"age_group": "41-50岁",
"education": "大专",
"registration_date": "2024-06-27 07:11:49",
"location": "北京",
"bio": "工作,启发,工作,表达,发现,观点,读书,经验,沟通,探索,智慧,理解,文化,探索,兴趣,兴趣,思考,灵感,",
"interests": [
"健康",
"历史",
"文学",
"经济",
"心理学"
],
"is_organizer": false,
"organization": null,
"activity_count": 3,
"follow_count": 15,
"followers_count": 11,
"is_new_user": false
},
{
"id": "U0012",
"username": "scholar730",
"name": "陈超",
"avatar": "https://randomuser.me/api/portraits/men/67.jpg",
"phone": "13152364083",
"email": "scholar730@126.com",
"gender": "男",
"age_group": "41-50岁",
"education": "博士",
"registration_date": "2023-10-07 07:11:49",
"location": "北京",
"bio": "交流,探索,灵感,发现,未来,分享,专注,读书,工作,未来,思考,观点",
"interests": [
"传记"
],
"is_organizer": false,
"organization": null,
"activity_count": 2,
"follow_count": 34,
"followers_count": 5,
"is_new_user": false
},
{
"id": "U0013",
"username": "booklover923",
"name": "黄超",
"avatar": "https://randomuser.me/api/portraits/men/48.jpg",
"phone": "15670614516",
"email": "booklover923@126.com",
"gender": "女",
"age_group": "36-40岁",
"education": "硕士",
"registration_date": "2024-10-25 07:11:49",
"location": "重庆",
"bio": "思考,精神,经验,价值,经验,精神,知识,启发,表达,学习,思考,生活,探索,经验,坚持,创造,思想,情感,专注,精神,知识,工作,探索,知识,思想",
"interests": [
"社会科学",
"科技",
"哲学",
"推理",
"心理学"
],
"is_organizer": false,
"organization": null,
"activity_count": 3,
"follow_count": 15,
"followers_count": 10,
"is_new_user": false
},
{
"id": "U0014",
"username": "reader948",
"name": "李敏",
"avatar": "https://randomuser.me/api/portraits/men/85.jpg",
"phone": "18128797547",
"email": "reader948@qq.com",
"gender": "女",
"age_group": "18-24岁",
"education": "高中",
"registration_date": "2023-07-12 07:11:49",
"location": "广州",
"bio": "价值,坚持,认知,读书,灵感,探索,知识,坚持,专注,灵感,未来,文化,探索,教育,未来,教育,认知,学习,探索,教育",
"interests": [
"科技",
"政治",
"科幻"
],
"is_organizer": false,
"organization": null,
"activity_count": 9,
"follow_count": 50,
"followers_count": 9,
"is_new_user": false
},
{
"id": "U0015",
"username": "literati987",
"name": "徐磊",
"avatar": "https://randomuser.me/api/portraits/women/29.jpg",
"phone": "15585365059",
"email": "literati987@yahoo.com",
"gender": "女",
"age_group": "25-30岁",
"education": "博士",
"registration_date": "2024-06-18 07:11:49",
"location": "上海",
"bio": "理解,心灵,表达,感悟,启发,读书,文化,交流,兴趣,思想,文化,未来,分享,兴趣,启发,交流,生活,知识,专注,文化,情感,兴趣",
"interests": [
"教育",
"社会科学",
"哲学",
"管理",
"古典文学"
],
"is_organizer": false,
"organization": null,
"activity_count": 4,
"follow_count": 40,
"followers_count": 18,
"is_new_user": false
},
{
"id": "U0016",
"username": "literati913",
"name": "刘磊",
"avatar": "https://randomuser.me/api/portraits/men/41.jpg",
"phone": "15126570482",
"email": "literati913@qq.com",
"gender": "女",
"age_group": "36-40岁",
"education": "本科",
"registration_date": "2023-09-06 07:11:49",
"location": "南京",
"bio": "思考,未来,感悟,探索,坚持,发现,感悟,思想,学习,沟通,坚持,文化,探索,未来",
"interests": [
"经济",
"管理"
],
"is_organizer": false,
"organization": null,
"activity_count": 10,
"follow_count": 19,
"followers_count": 26,
"is_new_user": false
},
{
"id": "U0017",
"username": "bibliophile286",
"name": "林杰",
"avatar": "https://randomuser.me/api/portraits/men/12.jpg",
"phone": "18598074151",
"email": "bibliophile286@foxmail.com",
"gender": "男",
"age_group": "36-40岁",
"education": "大专",
"registration_date": "2023-09-02 07:11:49",
"location": "杭州",
"bio": "兴趣,启发,坚持,观点,情感,沟通,生活,学习,交流,发现,精神",
"interests": [
"文学",
"音乐"
],
"is_organizer": false,
"organization": null,
"activity_count": 4,
"follow_count": 32,
"followers_count": 12,
"is_new_user": false
},
{
"id": "U0018",
"username": "sage162",
"name": "高明",
"avatar": "https://randomuser.me/api/portraits/women/73.jpg",
"phone": "18339690960",
"email": "sage162@126.com",
"gender": "女",
"age_group": "25-30岁",
"education": "本科",
"registration_date": "2024-06-14 07:11:49",
"location": "北京",
"bio": "热爱,思想,教育,学习,启发,教育,启发,生活,专注,知识,成长,热爱,热爱,学习,思想,视野,启发,思考,认知,精神,专注,坚持,读书,教育,思想,学习,读书,价值,理解,工作",
"interests": [
"社会科学",
"历史",
"音乐",
"心理学"
],
"is_organizer": false,
"organization": null,
"activity_count": 5,
"follow_count": 37,
"followers_count": 1,
"is_new_user": false
},
{
"id": "U0019",
"username": "thinker676",
"name": "张芳",
"avatar": "https://randomuser.me/api/portraits/men/31.jpg",
"phone": "15710859650",
"email": "thinker676@qq.com",
"gender": "男",
"age_group": "36-40岁",
"education": "本科",
"registration_date": "2025-03-20 07:11:49",
"location": "上海",
"bio": "坚持,成长,思想,情感,工作,心灵,经验,探索,分享,知识,创造,兴趣,表达,思考,分享,发现,情感,视野,思考,创造,文化,思想,",
"interests": [
"教育",
"旅行"
],
"is_organizer": false,
"organization": null,
"activity_count": 3,
"follow_count": 12,
"followers_count": 8,
"is_new_user": true
},
{
"id": "U0020",
"username": "bibliophile942",
"name": "杨勇",
"avatar": "https://randomuser.me/api/portraits/men/9.jpg",
"phone": "13970324817",
"email": "bibliophile942@foxmail.com",
"gender": "男",
"age_group": "41-50岁",
"education": "硕士",
"registration_date": "2025-02-25 07:11:49",
"location": "西安",
"bio": "发现,成长,知识,智慧,理解,观点,坚持,创造,学习",
"interests": [
"推理",
"科幻",
"诗歌",
"管理",
"外国文学"
],
"is_organizer": false,
"organization": null,
"activity_count": 9,
"follow_count": 49,
"followers_count": 29,
"is_new_user": false
},
{
"id": "U0021",
"username": "bibliophile998",
"name": "刘秀英",
"avatar": "https://randomuser.me/api/portraits/women/17.jpg",
"phone": "15844639209",
"email": "bibliophile998@126.com",
"gender": "女",
"age_group": "31-35岁",
"education": "硕士",
"registration_date": "2023-12-06 07:11:49",
"location": "武汉",
"bio": "精神,发现,心灵,交流,视野,情感,思考,情感,坚持,发现,心灵,教育,感悟,",
"interests": [
"外国文学",
"艺术",
"健康",
"政治",
"心理学"
],
"is_organizer": false,
"organization": null,
"activity_count": 6,
"follow_count": 17,
"followers_count": 25,
"is_new_user": false
},
{
"id": "U0022",
"username": "bibliophile730",
"name": "徐娜",
"avatar": "https://randomuser.me/api/portraits/women/5.jpg",
"phone": "18820345218",
"email": "bibliophile730@foxmail.com",
"gender": "男",
"age_group": "36-40岁",
"education": "硕士",
"registration_date": "2024-09-23 07:11:49",
"location": "北京",
"bio": "思考,创造,感悟,思想,学习,成长,情感,生活,启发,创造,学习,理解,感悟,生活,分享,沟通,智慧,探索,学习,智慧,交流,成长,",
"interests": [
"健康",
"诗歌",
"科幻",
"心理学"
],
"is_organizer": false,
"organization": null,
"activity_count": 3,
"follow_count": 11,
"followers_count": 3,
"is_new_user": false
},
{
"id": "U0023",
"username": "booklover552",
"name": "徐娜",
"avatar": "https://randomuser.me/api/portraits/women/54.jpg",
"phone": "18161956034",
"email": "booklover552@foxmail.com",
"gender": "女",
"age_group": "31-35岁",
"education": "高中",
"registration_date": "2023-12-19 07:11:49",
"location": "成都",
"bio": "沟通,思考,价值,教育,分享,心灵,交流,生活,经验,启发,专注,情感",
"interests": [
"社会科学",
"音乐",
"古典文学"
],
"is_organizer": false,
"organization": null,
"activity_count": 5,
"follow_count": 39,
"followers_count": 14,
"is_new_user": false
},
{
"id": "U0024",
"username": "bookworm707",
"name": "胡敏",
"avatar": "https://randomuser.me/api/portraits/men/80.jpg",
"phone": "13799073465",
"email": "bookworm707@yahoo.com",
"gender": "女",
"age_group": "31-35岁",
"education": "博士",
"registration_date": "2024-03-23 07:11:49",
"location": "武汉",
"bio": "沟通,情感,思考,未来,知识,生活,知识,工作,经验,教育,文化,专注,专注,情感,认知,未来,经验,精神,教育,认知,坚持,视野,精神,情感,交流,观点,表达,专注,分享,经验,视野,知识,认知",
"interests": [
"外国文学",
"音乐",
"管理",
"艺术"
],
"is_organizer": false,
"organization": null,
"activity_count": 4,
"follow_count": 17,
"followers_count": 6,
"is_new_user": false
},
{
"id": "U0025",
"username": "philosopher564",
"name": "朱洋",
"avatar": "https://randomuser.me/api/portraits/women/71.jpg",
"phone": "18654998138",
"email": "philosopher564@foxmail.com",
"gender": "男",
"age_group": "50岁以上",
"education": "硕士",
"registration_date": "2023-05-23 07:11:49",
"location": "深圳",
"bio": "思考,经验,观点,学习,理解,智慧,交流,表达,专注,",
"interests": [
"传记",
"社会科学",
"经济",
"音乐"
],
"is_organizer": false,
"organization": null,
"activity_count": 3,
"follow_count": 20,
"followers_count": 27,
"is_new_user": false
},
{
"id": "U0026",
"username": "sage490",
"name": "吴洋",
"avatar": "https://randomuser.me/api/portraits/men/71.jpg",
"phone": "13415330923",
"email": "sage490@163.com",
"gender": "女",
"age_group": "36-40岁",
"education": "大专",
"registration_date": "2025-01-10 07:11:49",
"location": "广州",
"bio": "成长,心灵,精神,交流,分享,观点,认知,坚持,交流,创造,思考,经验,兴趣",
"interests": [
"音乐",
"健康"
],
"is_organizer": false,
"organization": null,
"activity_count": 13,
"follow_count": 49,
"followers_count": 20,
"is_new_user": false
},
{
"id": "U0027",
"username": "bookworm606",
"name": "王勇",
"avatar": "https://randomuser.me/api/portraits/women/55.jpg",
"phone": "18785082868",
"email": "bookworm606@126.com",
"gender": "女",
"age_group": "41-50岁",
"education": "高中",
"registration_date": "2024-12-05 07:11:49",
"location": "武汉",
"bio": "坚持,价值,思考,视野,观点,精神,感悟,分享,理解,启发,思考,文化,精神,思想,思考,热爱,",
"interests": [
"历史",
"健康",
"管理",
"古典文学"
],
"is_organizer": false,
"organization": null,
"activity_count": 3,
"follow_count": 29,
"followers_count": 24,
"is_new_user": false
},
{
"id": "U0028",
"username": "sage811",
"name": "罗强",
"avatar": "https://randomuser.me/api/portraits/men/30.jpg",
"phone": "15294749899",
"email": "sage811@hotmail.com",
"gender": "男",
"age_group": "25-30岁",
"education": "大专",
"registration_date": "2024-08-05 07:11:49",
"location": "北京",
"bio": "成长,视野,视野,读书,思想,思想,沟通,知识,启发,精神,成长,感悟,认知,探索,表达,感悟,启发,热爱,文化,专注,思想,感悟,专注,情感,思想,发现,兴趣",
"interests": [
"艺术",
"文学",
"管理"
],
"is_organizer": false,
"organization": null,
"activity_count": 15,
"follow_count": 13,
"followers_count": 17,
"is_new_user": false
},
{
"id": "U0029",
"username": "scholar879",
"name": "朱强",
"avatar": "https://randomuser.me/api/portraits/men/24.jpg",
"phone": "15552442116",
"email": "scholar879@yahoo.com",
"gender": "女",
"age_group": "31-35岁",
"education": "本科",
"registration_date": "2024-04-26 07:11:49",
"location": "西安",
"bio": "生活,发现,沟通,感悟,情感,探索,生活,精神,教育,知识,思考,交流,成长,心灵,价值,教育,工作,心灵,发现,思想,发现,观点,经验,经验,成长,表达,表达,视野,知识,生活,教育",
"interests": [
"推理",
"管理"
],
"is_organizer": false,
"organization": null,
"activity_count": 3,
"follow_count": 4,
"followers_count": 14,
"is_new_user": false
},
{
"id": "U0030",
"username": "booklover385",
"name": "张涛",
"avatar": "https://randomuser.me/api/portraits/women/38.jpg",
"phone": "15189425224",
"email": "booklover385@gmail.com",
"gender": "女",
"age_group": "41-50岁",
"education": "高中",
"registration_date": "2024-05-14 07:11:49",
"location": "成都",
"bio": "认知,经验,心灵,经验,观点,读书,热爱,思考,思考",
"interests": [
"哲学",
"文学",
"科幻",
"社会科学",
"外国文学"
],
"is_organizer": false,
"organization": null,
"activity_count": 8,
"follow_count": 16,
"followers_count": 19,
"is_new_user": false
},
{
"id": "U0031",
"username": "bibliophile693",
"name": "李芳",
"avatar": "https://randomuser.me/api/portraits/women/21.jpg",
"phone": "18658709476",
"email": "bibliophile693@hotmail.com",
"gender": "男",
"age_group": "36-40岁",
"education": "高中",
"registration_date": "2023-05-29 07:11:49",
"location": "杭州",
"bio": "观点,交流,启发,思想,兴趣,生活,精神,思想,交流",
"interests": [
"外国文学",
"哲学",
"经济",
"科技",
"历史"
],
"is_organizer": false,
"organization": null,
"activity_count": 7,
"follow_count": 21,
"followers_count": 9,
"is_new_user": false
},
{
"id": "U0032",
"username": "scholar444",
"name": "王敏",
"avatar": "https://randomuser.me/api/portraits/men/69.jpg",
"phone": "18353657373",
"email": "scholar444@163.com",
"gender": "女",
"age_group": "25-30岁",
"education": "高中",
"registration_date": "2024-01-10 07:11:49",
"location": "重庆",
"bio": "读书,创造,价值,发现,工作,探索,未来,理解,视野,经验,成长,思考,思想,价值,文化,思想,观点,教育,沟通,坚持,表达,生活,经验,分享,心灵,探索,智慧,智慧,知识,",
"interests": [
"推理",
"健康",
"社会科学"
],
"is_organizer": false,
"organization": null,
"activity_count": 9,
"follow_count": 18,
"followers_count": 28,
"is_new_user": false
},
{
"id": "U0033",
"username": "bibliophile775",
"name": "林秀英",
"avatar": "https://randomuser.me/api/portraits/men/39.jpg",
"phone": "13786169274",
"email": "bibliophile775@outlook.com",
"gender": "女",
"age_group": "25-30岁",
"education": "高中",
"registration_date": "2025-03-20 07:11:49",
"location": "西安",
"bio": "知识,认知,坚持,精神,文化,读书,未来,分享,智慧,专注,分享,经验,热爱,专注,未来,兴趣,探索,智慧",
"interests": [
"科幻"
],
"is_organizer": false,
"organization": null,
"activity_count": 14,
"follow_count": 27,
"followers_count": 1,
"is_new_user": true
},
{
"id": "U0034",
"username": "philosopher472",
"name": "何平",
"avatar": "https://randomuser.me/api/portraits/women/22.jpg",
"phone": "13967679665",
"email": "philosopher472@gmail.com",
"gender": "女",
"age_group": "25-30岁",
"education": "本科",
"registration_date": "2023-11-02 07:11:49",
"location": "广州",
"bio": "知识,思考,坚持,学习,专注,学习,发现,分享,智慧,经验,心灵,工作,专注,生活,未来,成长,价值,精神,热爱,精神,工作,专注,智慧,心灵,智慧,感悟",
"interests": [
"心理学",
"哲学",
"古典文学",
"音乐"
],
"is_organizer": false,
"organization": null,
"activity_count": 10,
"follow_count": 4,
"followers_count": 27,
"is_new_user": false
},
{
"id": "U0035",
"username": "thinker448",
"name": "罗芳",
"avatar": "https://randomuser.me/api/portraits/men/51.jpg",
"phone": "18474979217",
"email": "thinker448@hotmail.com",
"gender": "女",
"age_group": "36-40岁",
"education": "本科",
"registration_date": "2024-03-12 07:11:49",
"location": "武汉",
"bio": "沟通,发现,表达,表达,读书,热爱,热爱,观点,理解,创造,观点,感悟,观点,交流,探索,心灵,表达,思想,专注,思想,探索,学习,理解,坚持,理解,坚持,热爱,探索,价值,感悟,文化,探索,经验,成",
"interests": [
"科幻"
],
"is_organizer": false,
"organization": null,
"activity_count": 15,
"follow_count": 16,
"followers_count": 29,
"is_new_user": false
},
{
"id": "U0036",
"username": "scholar345",
"name": "刘杰",
"avatar": "https://randomuser.me/api/portraits/women/49.jpg",
"phone": "18492551516",
"email": "scholar345@qq.com",
"gender": "女",
"age_group": "36-40岁",
"education": "博士",
"registration_date": "2024-10-14 07:11:49",
"location": "上海",
"bio": "文化,工作,表达,理解,未来,沟通,启发,坚持,工作,认知,学习,未来,热爱,专注,价值,兴趣,工作",
"interests": [
"历史",
"心理学"
],
"is_organizer": false,
"organization": null,
"activity_count": 3,
"follow_count": 0,
"followers_count": 20,
"is_new_user": false
},
{
"id": "U0037",
"username": "bookworm118",
"name": "黄静",
"avatar": "https://randomuser.me/api/portraits/women/25.jpg",
"phone": "18027065768",
"email": "bookworm118@126.com",
"gender": "女",
"age_group": "41-50岁",
"education": "硕士",
"registration_date": "2024-06-03 07:11:49",
"location": "深圳",
"bio": "启发,学习,分享,探索,工作,思想,经验,创造,认知,理解,理解,专注,智慧,兴趣,思想,灵感",
"interests": [
"诗歌",
"社会科学",
"哲学",
"艺术",
"政治"
],
"is_organizer": false,
"organization": null,
"activity_count": 15,
"follow_count": 33,
"followers_count": 21,
"is_new_user": false
},
{
"id": "U0038",
"username": "sage197",
"name": "杨伟",
"avatar": "https://randomuser.me/api/portraits/men/33.jpg",
"phone": "15524305177",
"email": "sage197@outlook.com",
"gender": "女",
"age_group": "50岁以上",
"education": "高中",
"registration_date": "2024-12-24 07:11:49",
"location": "深圳",
"bio": "心灵,学习,文化,读书,思考,文化,表达,教育,灵感,价值,表达,启发,创造,专注,表达,感悟,文化,兴趣,探索,价值,灵感,价值,探索,精神,价值,经验,坚持,表达,",
"interests": [
"外国文学"
],
"is_organizer": false,
"organization": null,
"activity_count": 15,
"follow_count": 13,
"followers_count": 1,
"is_new_user": false
},
{
"id": "U0039",
"username": "reader235",
"name": "罗芳",
"avatar": "https://randomuser.me/api/portraits/men/35.jpg",
"phone": "13189382815",
"email": "reader235@126.com",
"gender": "女",
"age_group": "25-30岁",
"education": "大专",
"registration_date": "2024-06-30 07:11:49",
"location": "南京",
"bio": "视野,兴趣,知识,认知,思考,学习,灵感,教育,读书,专注,智慧,文化,发现,成长,学习,灵感,生活,思想,工作,读书,热爱,价值,感悟,视野,智慧,精神,教育,交流,学习,坚持",
"interests": [
"科幻",
"经济"
],
"is_organizer": false,
"organization": null,
"activity_count": 13,
"follow_count": 39,
"followers_count": 12,
"is_new_user": false
},
{
"id": "U0040",
"username": "scholar946",
"name": "孙敏",
"avatar": "https://randomuser.me/api/portraits/men/96.jpg",
"phone": "13191009314",
"email": "scholar946@gmail.com",
"gender": "女",
"age_group": "18-24岁",
"education": "博士",
"registration_date": "2024-10-30 07:11:49",
"location": "深圳",
"bio": "认知,坚持,启发,情感,情感,生活,思想,感悟,坚持,发现,读书,工作,成长,视野,灵感,经验,探索,启发,表达,发现,观点,成长",
"interests": [
"古典文学",
"历史"
],
"is_organizer": false,
"organization": null,
"activity_count": 14,
"follow_count": 13,
"followers_count": 12,
"is_new_user": false
},
{
"id": "O0001",
"username": "sage841",
"name": "王超",
"avatar": "https://randomuser.me/api/portraits/men/49.jpg",
"phone": "13039676712",
"email": "sage841@hotmail.com",
"gender": "女",
"age_group": "31-35岁",
"education": "博士",
"registration_date": "2022-11-07 07:11:49",
"location": "南京",
"bio": "学习,认知,理解,成长,兴趣,视野,兴趣,情感,交流,智慧,交流,智慧,交流,启发,生活,启发,知识,启发,智慧,学习,理解,创造,价值,生活,思想,学习,生活,读书,坚持,热爱,思考,感悟,读书,交流,思想,成长,学习,工作,视野,专注,认知,心灵,心灵,探索,文化",
"interests": [
"艺术",
"科幻",
"推理",
"历史"
],
"is_organizer": true,
"organization": "光合作用文化空间",
"activity_count": 31,
"follow_count": 40,
"followers_count": 50,
"is_new_user": false
},
{
"id": "O0002",
"username": "bibliophile236",
"name": "罗芳",
"avatar": "https://randomuser.me/api/portraits/women/83.jpg",
"phone": "15760214355",
"email": "bibliophile236@163.com",
"gender": "女",
"age_group": "41-50岁",
"education": "本科",
"registration_date": "2023-01-08 07:11:49",
"location": "北京",
"bio": "学习,观点,经验,观点,创造,精神,沟通,心灵,启发,兴趣,创造,理解,思考,经验,文化,坚持,热爱,沟通,教育,认知,成长,分享,理解,思想,智慧,探索,表达,教育,观点,观点,教育,思想,发现,生活,视野,思想,情感,探索,成长,交流,观点,成长,文化,探索,感悟,文化,智慧,学习,思考",
"interests": [
"文学",
"古典文学",
"诗歌"
],
"is_organizer": true,
"organization": "四季阅读空间",
"activity_count": 16,
"follow_count": 161,
"followers_count": 235,
"is_new_user": false
},
{
"id": "O0003",
"username": "booklover483",
"name": "李秀英",
"avatar": "https://randomuser.me/api/portraits/women/77.jpg",
"phone": "15043716500",
"email": "booklover483@hotmail.com",
"gender": "女",
"age_group": "31-35岁",
"education": "硕士",
"registration_date": "2023-05-17 07:11:49",
"location": "深圳",
"bio": "精神,教育,视野,心灵,交流,未来,认知,文化,热爱,学习,经验,工作,启发,价值,学习,成长,交流,成长,工作,生活,分享,创造,文化,工作,表达,视野,工作",
"interests": [
"古典文学",
"健康",
"历史",
"诗歌",
"艺术",
"教育"
],
"is_organizer": true,
"organization": "知行合一读书社",
"activity_count": 31,
"follow_count": 37,
"followers_count": 490,
"is_new_user": false
},
{
"id": "O0004",
"username": "sage327",
"name": "赵磊",
"avatar": "https://randomuser.me/api/portraits/women/32.jpg",
"phone": "18591156244",
"email": "sage327@163.com",
"gender": "女",
"age_group": "41-50岁",
"education": "硕士",
"registration_date": "2023-03-12 07:11:49",
"location": "武汉",
"bio": "经验,读书,创造,思考,工作,价值,视野,交流,表达,教育,分享,价值,分享,创造,知识,热爱,经验,工作",
"interests": [
"经济",
"古典文学",
"哲学",
"教育",
"艺术",
"音乐",
"社会科学",
"旅行"
],
"is_organizer": true,
"organization": "百草园读书会",
"activity_count": 44,
"follow_count": 21,
"followers_count": 150,
"is_new_user": false
},
{
"id": "O0005",
"username": "bookworm479",
"name": "朱明",
"avatar": "https://randomuser.me/api/portraits/men/45.jpg",
"phone": "13581054570",
"email": "bookworm479@163.com",
"gender": "男",
"age_group": "50岁以上",
"education": "硕士",
"registration_date": "2022-09-04 07:11:49",
"location": "西安",
"bio": "智慧,认知,心灵,价值,经验,智慧,视野,认知,专注,启发,智慧,心灵,沟通,经验,交流,智慧,交流,坚持,精神,智慧,教育,发现,探索,精神,坚持,经验,知识,探索,经验,认知,专注,",
"interests": [
"哲学",
"心理学",
"科幻",
"传记",
"经济",
"政治",
"科技"
],
"is_organizer": true,
"organization": "鹿鸣书店",
"activity_count": 26,
"follow_count": 96,
"followers_count": 454,
"is_new_user": false
},
{
"id": "O0006",
"username": "booklover785",
"name": "李敏",
"avatar": "https://randomuser.me/api/portraits/women/51.jpg",
"phone": "15828129990",
"email": "booklover785@foxmail.com",
"gender": "女",
"age_group": "50岁以上",
"education": "硕士",
"registration_date": "2024-07-16 07:11:49",
"location": "成都",
"bio": "分享,灵感,分享,视野,心灵,分享,视野,创造,认知,读书,专注,理解,视野,探索,视野,感悟,发现,精神,工作,精神,情感,表达,交流,生活,沟通,观点,启发,热爱",
"interests": [
"文学",
"心理学",
"科技",
"历史"
],
"is_organizer": true,
"organization": "百草园读书会",
"activity_count": 37,
"follow_count": 133,
"followers_count": 425,
"is_new_user": false
},
{
"id": "O0007",
"username": "bookworm995",
"name": "刘明",
"avatar": "https://randomuser.me/api/portraits/men/35.jpg",
"phone": "18484363974",
"email": "bookworm995@163.com",
"gender": "女",
"age_group": "31-35岁",
"education": "博士",
"registration_date": "2024-03-02 07:11:49",
"location": "南京",
"bio": "专注,情感,未来,专注,文化,思考,生活,心灵,精神,启发,分享,工作,感悟,思想,创造,理解,思想,工作,思考,热爱,认知,启发,精神,交流,启发,知识,坚持,认知,知识,读书,认知,学习,坚持,精神,兴趣,智慧,理解,发现",
"interests": [
"教育",
"经济",
"文学",
"健康",
"传记",
"科技",
"心理学",
"政治"
],
"is_organizer": true,
"organization": "百草园读书会",
"activity_count": 28,
"follow_count": 73,
"followers_count": 319,
"is_new_user": false
},
{
"id": "O0008",
"username": "literati792",
"name": "吴军",
"avatar": "https://randomuser.me/api/portraits/women/25.jpg",
"phone": "18764703997",
"email": "literati792@hotmail.com",
"gender": "女",
"age_group": "31-35岁",
"education": "本科",
"registration_date": "2023-09-21 07:11:49",
"location": "西安",
"bio": "认知,未来,沟通,灵感,思想,坚持,启发,思想,思考,生活,思考,未来,成长,学习,表达,精神,理解,未来,认知,坚持,表达,价值,沟通,精神,思想,分享,观点,视野,知识,灵感,生活,文化,学习,思想,观点,智慧,创造,兴趣,学习,坚持,精神,成长",
"interests": [
"哲学",
"经济",
"古典文学"
],
"is_organizer": true,
"organization": "墨香文化传媒",
"activity_count": 41,
"follow_count": 175,
"followers_count": 306,
"is_new_user": false
},
{
"id": "O0009",
"username": "reader617",
"name": "陈艳",
"avatar": "https://randomuser.me/api/portraits/women/25.jpg",
"phone": "15658916066",
"email": "reader617@foxmail.com",
"gender": "女",
"age_group": "41-50岁",
"education": "本科",
"registration_date": "2024-06-26 07:11:49",
"location": "西安",
"bio": "思考,读书,精神,创造,热爱,情感,读书,创造,工作,灵感,热爱,创造,认知,沟通,读书,精神,生活,心灵,学习,热爱,交流,发现,知识,经验,感悟,创造,读书,学习,探索,文化,沟通,发现",
"interests": [
"科技",
"诗歌",
"教育"
],
"is_organizer": true,
"organization": "四季阅读空间",
"activity_count": 24,
"follow_count": 35,
"followers_count": 84,
"is_new_user": false
},
{
"id": "O0010",
"username": "bibliophile948",
"name": "李伟",
"avatar": "https://randomuser.me/api/portraits/women/58.jpg",
"phone": "18696049013",
"email": "bibliophile948@hotmail.com",
"gender": "男",
"age_group": "50岁以上",
"education": "硕士",
"registration_date": "2024-02-21 07:11:49",
"location": "南京",
"bio": "工作,教育,视野,教育,认知,创造,生活,价值,学习,教育,专注,情感,价值,沟通,热爱,未来,知识,知识,思想,表达,知识,创造,专注,交流,视野,思考,感悟,",
"interests": [
"社会科学",
"音乐",
"教育",
"旅行"
],
"is_organizer": true,
"organization": "青禾读书会",
"activity_count": 17,
"follow_count": 135,
"followers_count": 269,
"is_new_user": false
}
]
\ No newline at end of file
<!--
* @Date: 2025-04-17 08:14:12
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-04-17 13:15:27
* @FilePath: /reading-club-app/src/App.vue
* @Description: 文件描述
-->
/*
* @Date: 2025-04-17 08:14:12
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-04-17 13:15:05
* @FilePath: /reading-club-app/src/App.jsx
* @Description: 文件描述
*/
<template>
<div class="flex flex-col min-h-screen">
<Header />
<main class="flex-grow">
<router-view></router-view>
</main>
<Footer />
</div>
</template>
<script setup>
import { onMounted } from 'vue'
import Header from './components/layout/Header.vue'
import Footer from './components/layout/Footer.vue'
// 更新文档标题
onMounted(() => {
document.title = '读书会 - 连接爱读书的人'
})
</script>
<!--
* @Date: 2025-04-17 13:22:07
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-04-17 13:22:09
* @FilePath: /reading-club-app/src/components/layout/Footer.vue
* @Description: 文件描述
-->
<template>
<footer class="mt-8 py-6 bg-gradient-to-r from-green-50 to-blue-50">
<div class="container mx-auto px-4">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="mb-4 md:mb-0">
<h3 class="text-xl font-bold bg-gradient-to-r from-green-500 to-blue-400 bg-clip-text text-transparent">读书会</h3>
<p class="text-gray-600 mt-2">连接爱读书的人,共享知识的力量</p>
</div>
<div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-8">
<div>
<h4 class="font-semibold text-gray-800 mb-3">关于我们</h4>
<ul class="space-y-2">
<li><a href="#" class="text-gray-600 hover:text-green-500">平台介绍</a></li>
<li><a href="#" class="text-gray-600 hover:text-green-500">使用指南</a></li>
<li><a href="#" class="text-gray-600 hover:text-green-500">联系我们</a></li>
</ul>
</div>
<div>
<h4 class="font-semibold text-gray-800 mb-3">热门分类</h4>
<ul class="space-y-2">
<li><a href="#" class="text-gray-600 hover:text-green-500">文学小说</a></li>
<li><a href="#" class="text-gray-600 hover:text-green-500">社科人文</a></li>
<li><a href="#" class="text-gray-600 hover:text-green-500">商业财经</a></li>
</ul>
</div>
<div>
<h4 class="font-semibold text-gray-800 mb-3">关注我们</h4>
<div class="flex space-x-4">
<a href="#" class="text-gray-600 hover:text-green-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 8l-8 8m0-8l8 8" />
</svg>
</a>
<a href="#" class="text-gray-600 hover:text-green-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</a>
<a href="#" class="text-gray-600 hover:text-green-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</a>
</div>
</div>
</div>
</div>
<div class="border-t border-gray-200 mt-8 pt-6 text-center">
<p class="text-gray-500 text-sm">© 2024 读书会平台 All Rights Reserved</p>
</div>
</div>
</footer>
</template>
<script setup>
// Footer组件不需要任何响应式数据或方法
</script>
<template>
<header class="sticky top-0 z-50">
<!-- Frosted glass effect background -->
<div class="backdrop-blur-xl bg-white/70 shadow-sm">
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<!-- Logo and Title -->
<div class="flex items-center">
<router-link to="/" class="flex items-center">
<span class="text-2xl font-bold bg-gradient-to-r from-green-500 to-blue-400 bg-clip-text text-transparent">
读书会
</span>
</router-link>
</div>
<!-- Navigation - Desktop -->
<nav class="hidden md:flex items-center space-x-6">
<router-link to="/" class="text-gray-600 hover:text-green-500 font-medium">
首页
</router-link>
<router-link to="/activities" class="text-gray-600 hover:text-green-500 font-medium">
全部活动
</router-link>
<router-link to="/create-activity" class="text-gray-600 hover:text-green-500 font-medium">
创建活动
</router-link>
</nav>
<!-- User Profile and Actions -->
<div class="flex items-center">
<!-- Messages Icon -->
<router-link to="/messages" class="relative p-2 mr-4 text-gray-600 hover:text-green-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
</svg>
<span class="absolute top-1 right-1 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-white transform translate-x-1/2 -translate-y-1/2 bg-red-500 rounded-full">
2
</span>
</router-link>
<!-- User Profile -->
<div class="relative">
<button @click="toggleProfileDropdown" class="flex items-center space-x-2">
<div class="h-8 w-8 rounded-full overflow-hidden border-2 border-green-500">
<img :src="currentUser?.avatar || '/assets/images/avatars/default_avatar.png'" alt="User Avatar" class="h-full w-full object-cover" />
</div>
<span class="hidden lg:block text-sm font-medium text-gray-700">{{ currentUser?.name || 'User' }}</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<!-- Profile Dropdown -->
<div v-if="isProfileDropdownOpen" class="absolute right-0 mt-2 w-48 py-2 bg-white rounded-md shadow-lg backdrop-blur-xl bg-white/90 z-20">
<router-link to="/profile" class="block px-4 py-2 text-sm text-gray-700 hover:bg-green-50 hover:text-green-500">
个人资料
</router-link>
<router-link to="/my-activities" class="block px-4 py-2 text-sm text-gray-700 hover:bg-green-50 hover:text-green-500">
我的活动
</router-link>
<router-link to="/settings" class="block px-4 py-2 text-sm text-gray-700 hover:bg-green-50 hover:text-green-500">
设置
</router-link>
<button class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-green-50 hover:text-green-500">
退出登录
</button>
</div>
</div>
<!-- Mobile Menu Button -->
<div class="ml-4 md:hidden">
<button @click="toggleMenu" type="button" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-green-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-green-500">
<span class="sr-only">Open main menu</span>
<svg :class="{ 'hidden': isMenuOpen, 'block': !isMenuOpen }" class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
<svg :class="{ 'block': isMenuOpen, 'hidden': !isMenuOpen }" class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile Menu -->
<div :class="{ 'block': isMenuOpen, 'hidden': !isMenuOpen }" class="md:hidden">
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3">
<router-link to="/" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-green-500 hover:bg-green-50">
首页
</router-link>
<router-link to="/activities" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-green-500 hover:bg-green-50">
全部活动
</router-link>
<router-link to="/create-activity" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-green-500 hover:bg-green-50">
创建活动
</router-link>
<router-link to="/profile" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-green-500 hover:bg-green-50">
个人资料
</router-link>
<router-link to="/messages" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-green-500 hover:bg-green-50">
消息通知
</router-link>
</div>
</div>
</div>
</div>
</header>
</template>
<script setup>
import { ref } from 'vue'
import { useAppStore } from '../../stores/app'
const appStore = useAppStore()
const currentUser = appStore.currentUser
const isMenuOpen = ref(false)
const isProfileDropdownOpen = ref(false)
const toggleMenu = () => {
isMenuOpen.value = !isMenuOpen.value
}
const toggleProfileDropdown = () => {
isProfileDropdownOpen.value = !isProfileDropdownOpen.value
}
</script>
<!--
* @Date: 2025-04-17 13:16:20
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-04-17 13:16:22
* @FilePath: /reading-club-app/src/components/shared/ActivityCard.vue
* @Description: 文件描述
-->
<template>
<router-link
:to="`/activity/${activity.id}`"
class="block bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition duration-200"
>
<div class="relative pb-48 overflow-hidden">
<img
:src="activity.cover_image"
:alt="activity.title"
class="absolute inset-0 h-full w-full object-cover"
/>
<div
v-if="activity.tags && activity.tags.length"
class="absolute top-0 left-0 p-4 flex flex-wrap gap-2"
>
<span
v-for="tag in activity.tags"
:key="tag"
class="px-3 py-1 text-sm bg-green-500 text-white rounded-full"
>
{{ tag }}
</span>
</div>
</div>
<div class="p-6">
<h3 class="text-xl font-semibold text-gray-800 mb-2">{{ activity.title }}</h3>
<p class="text-gray-600 text-sm mb-4 line-clamp-2">{{ activity.description }}</p>
<div class="flex items-center text-sm text-gray-500 mb-4">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span>{{ formatDateTime(activity.start_time) }}</span>
</div>
<div class="flex items-center text-sm text-gray-500 mb-4">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span>{{ activity.location }}</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<img
:src="activity.organizer_avatar"
:alt="activity.organizer_name"
class="w-8 h-8 rounded-full mr-2"
/>
<span class="text-sm text-gray-600">{{ activity.organizer_name }}</span>
</div>
<span class="text-sm font-medium text-green-500">
{{ activity.participant_count }}人参与
</span>
</div>
</div>
</router-link>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
activity: {
type: Object,
required: true
}
})
// 格式化日期时间
const formatDateTime = (dateTimeStr) => {
const date = new Date(dateTimeStr.replace(' ', 'T'))
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
}).format(date)
}
</script>
<template>
<button
:type="type"
:class="[baseClasses, variantClass, sizeClass, roundedClass, block ? buttonBlock : '', className]"
:disabled="disabled"
@click="$emit('click', $event)"
>
<span v-if="leftIcon" class="mr-2">
<slot name="leftIcon">
{{ leftIcon }}
</slot>
</span>
<slot></slot>
<span v-if="rightIcon" class="ml-2">
<slot name="rightIcon">
{{ rightIcon }}
</slot>
</span>
</button>
</template>
<script setup>
import { computed } from 'vue'
const buttonVariants = {
primary: 'bg-gradient-to-r from-green-500 to-blue-500 hover:from-green-600 hover:to-blue-600 text-white border-transparent',
secondary: 'bg-white hover:bg-gray-50 text-gray-700 border-gray-300',
success: 'bg-green-600 hover:bg-green-700 text-white border-transparent',
danger: 'bg-red-600 hover:bg-red-700 text-white border-transparent',
warning: 'bg-yellow-500 hover:bg-yellow-600 text-white border-transparent',
info: 'bg-blue-500 hover:bg-blue-600 text-white border-transparent',
ghost: 'bg-transparent hover:bg-gray-100 text-gray-700 hover:text-gray-900 border-transparent'
}
const buttonSizes = {
xs: 'px-2 py-1 text-xs',
sm: 'px-2 py-1 text-sm',
md: 'px-4 py-2',
lg: 'px-6 py-3 text-lg',
xl: 'px-8 py-4 text-xl'
}
const buttonRounded = {
none: 'rounded-none',
sm: 'rounded-sm',
md: 'rounded-md',
lg: 'rounded-lg',
full: 'rounded-full'
}
const buttonBlock = 'w-full flex justify-center'
const props = defineProps({
variant: {
type: String,
default: 'primary',
validator: (value) => Object.keys(buttonVariants).includes(value)
},
size: {
type: String,
default: 'md',
validator: (value) => Object.keys(buttonSizes).includes(value)
},
rounded: {
type: String,
default: 'md',
validator: (value) => Object.keys(buttonRounded).includes(value)
},
block: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
className: {
type: String,
default: ''
},
leftIcon: {
type: [String, Object],
default: null
},
rightIcon: {
type: [String, Object],
default: null
},
type: {
type: String,
default: 'button'
}
})
defineEmits(['click'])
const baseClasses = 'flex items-center justify-center font-medium border shadow-sm transition duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed'
const variantClass = computed(() => buttonVariants[props.variant] || buttonVariants.primary)
const sizeClass = computed(() => buttonSizes[props.size] || buttonSizes.md)
const roundedClass = computed(() => buttonRounded[props.rounded] || buttonRounded.md)
</script>
<template>
<div :class="['w-full', className]">
<label v-if="label" :for="inputId" class="block text-sm font-medium text-gray-700 mb-1">
{{ label }}
<span v-if="required" class="text-red-500 ml-1">*</span>
</label>
<div class="relative rounded-md shadow-sm">
<div v-if="leftIcon" class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span class="text-gray-500 sm:text-sm">
<slot name="leftIcon">
{{ leftIcon }}
</slot>
</span>
</div>
<input
:id="inputId"
:type="type"
:name="name"
:placeholder="placeholder"
:value="modelValue"
:required="required"
:disabled="disabled"
@input="$emit('update:modelValue', $event.target.value)"
@blur="$emit('blur', $event)"
:class="[
'block w-full border-gray-300 rounded-md shadow-sm',
'focus:ring-green-500 focus:border-green-500 sm:text-sm',
'disabled:bg-gray-100 disabled:text-gray-500 disabled:cursor-not-allowed',
sizeClass,
error ? 'border-red-300 text-red-900 placeholder-red-300 focus:ring-red-500 focus:border-red-500' : '',
leftIcon ? 'pl-10' : '',
rightIcon ? 'pr-10' : ''
]"
v-bind="$attrs"
/>
<div v-if="rightIcon" class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span class="text-gray-500 sm:text-sm">
<slot name="rightIcon">
{{ rightIcon }}
</slot>
</span>
</div>
</div>
<p v-if="error || helperText" :class="['mt-1 text-sm', error ? 'text-red-600' : 'text-gray-500']">
{{ error || helperText }}
</p>
</div>
</template>
<script setup>
import { computed } from 'vue'
const inputSizes = {
sm: 'px-2 py-1 text-sm',
md: 'px-3 py-2',
lg: 'px-4 py-3 text-lg'
}
const props = defineProps({
modelValue: {
type: [String, Number],
default: ''
},
label: {
type: String,
default: ''
},
name: {
type: String,
required: true
},
type: {
type: String,
default: 'text'
},
placeholder: {
type: String,
default: ''
},
error: {
type: String,
default: ''
},
size: {
type: String,
default: 'md',
validator: (value) => Object.keys(inputSizes).includes(value)
},
required: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
className: {
type: String,
default: ''
},
helperText: {
type: String,
default: ''
},
leftIcon: {
type: [String, Object],
default: null
},
rightIcon: {
type: [String, Object],
default: null
}
})
defineEmits(['update:modelValue', 'blur'])
const inputId = computed(() => `input-${props.name}`)
const sizeClass = computed(() => inputSizes[props.size] || inputSizes.md)
</script>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
const props = defineProps({
isOpen: {
type: Boolean,
required: true
},
title: {
type: String,
required: true
},
size: {
type: String,
default: 'md'
},
closeOnClickOutside: {
type: Boolean,
default: true
},
showCloseButton: {
type: Boolean,
default: true
},
contentClassName: {
type: String,
default: ''
}
});
const emit = defineEmits(['close']);
const modalRef = ref(null);
const isMounted = ref(false);
// Set sizes based on the size prop
const sizeClasses = {
sm: 'max-w-md',
md: 'max-w-lg',
lg: 'max-w-2xl',
xl: 'max-w-4xl',
full: 'max-w-full mx-4'
};
const modalSize = computed(() => sizeClasses[props.size] || sizeClasses.md);
// Handle ESC key press
const handleKeyDown = (event) => {
if (event.key === 'Escape' && props.isOpen) {
emit('close');
}
};
// Handle click outside
const handleClickOutside = (event) => {
if (modalRef.value && !modalRef.value.contains(event.target) && props.closeOnClickOutside) {
emit('close');
}
};
// Handle mounting/unmounting and event listeners
onMounted(() => {
isMounted.value = true;
document.addEventListener('keydown', handleKeyDown);
});
onUnmounted(() => {
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('mousedown', handleClickOutside);
document.body.style.overflow = 'auto';
});
// Watch isOpen changes
watch(() => props.isOpen, (newValue) => {
if (newValue) {
document.body.style.overflow = 'hidden';
document.addEventListener('mousedown', handleClickOutside);
} else {
document.body.style.overflow = 'auto';
document.removeEventListener('mousedown', handleClickOutside);
}
});
</script>
<template>
<Teleport to="body">
<div v-if="isOpen && isMounted" class="fixed inset-0 z-50 overflow-y-auto">
<!-- Backdrop with semi-transparent background -->
<div class="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm transition-opacity"></div>
<!-- Modal container -->
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center">
<div
:class="['relative inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle w-full', modalSize]"
ref="modalRef"
>
<!-- Modal header -->
<div class="bg-white px-4 py-4 border-b border-gray-200 sm:px-6">
<div class="flex items-center justify-between">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{{ title }}
</h3>
<button
v-if="showCloseButton"
type="button"
class="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none"
@click="emit('close')"
>
<span class="sr-only">关闭</span>
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<!-- Modal content -->
<div :class="['bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4', contentClassName]">
<slot></slot>
</div>
<!-- Modal footer -->
<div v-if="$slots.footer" class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<slot name="footer"></slot>
</div>
</div>
</div>
</div>
</Teleport>
</template>
<template>
<div :class="className">
<!-- Tab headers -->
<div :class="getContainerClasses()">
<button
v-for="(tab, index) in tabs"
:key="index"
:class="getTabHeaderClasses(index)"
@click="handleTabChange(index)"
role="tab"
:aria-selected="currentTab === index"
>
{{ tab.label }}
</button>
</div>
<!-- Tab content -->
<div class="pt-4">
<component :is="tabs[currentTab]?.content" />
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const props = defineProps({
tabs: {
type: Array,
required: true
},
activeTab: {
type: Number,
default: 0
},
className: {
type: String,
default: ''
},
variant: {
type: String,
default: 'underline', // 'underline', 'pills', 'bordered'
validator: (value) => ['underline', 'pills', 'bordered'].includes(value)
},
size: {
type: String,
default: 'md', // 'sm', 'md', 'lg'
validator: (value) => ['sm', 'md', 'lg'].includes(value)
}
});
const emit = defineEmits(['change']);
const currentTab = ref(props.activeTab);
const handleTabChange = (index) => {
currentTab.value = index;
emit('change', index);
};
const getSizeClasses = () => {
switch (props.size) {
case 'sm': return 'text-sm py-1 px-2';
case 'lg': return 'text-lg py-3 px-5';
case 'md':
default: return 'text-base py-2 px-4';
}
};
const getTabHeaderClasses = (index) => {
const isActive = index === currentTab.value;
const sizeClasses = getSizeClasses();
const baseClasses = 'font-medium transition-all duration-200 focus:outline-none';
switch (props.variant) {
case 'pills':
return `${baseClasses} ${sizeClasses} rounded-md ${
isActive
? 'bg-green-500 text-white'
: 'text-gray-700 hover:text-green-500 hover:bg-green-50'
}`;
case 'bordered':
return `${baseClasses} ${sizeClasses} border-b-2 ${
isActive
? 'border-green-500 text-green-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`;
case 'underline':
default:
return `${baseClasses} ${sizeClasses} border-b-2 ${
isActive
? 'border-green-500 text-green-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`;
}
};
const getContainerClasses = () => {
switch (props.variant) {
case 'pills':
return 'flex p-1 space-x-1 bg-gray-100 rounded-lg';
case 'bordered':
return 'flex border-b border-gray-200';
case 'underline':
default:
return 'flex border-b border-gray-200';
}
};
</script>
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
const AppContext = createContext();
export const AppProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [activities, setActivities] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [registrations, setRegistrations] = useState([]);
const [userMessages, setUserMessages] = useState([]);
// Initialize app data
useEffect(() => {
const fetchInitialData = async () => {
try {
setLoading(true);
// Fetch users data - In a real app, this would be a real API call
const usersResponse = await fetch('/data/users.json');
const usersData = await usersResponse.json();
// For demo purposes, set the first user as current user
setCurrentUser(usersData[0]);
// Fetch activities
const activitiesResponse = await fetch('/data/activities.json');
const activitiesData = await activitiesResponse.json();
setActivities(activitiesData);
// Fetch registrations
const registrationsResponse = await fetch('/data/registrations.json');
const registrationsData = await registrationsResponse.json();
setRegistrations(registrationsData);
// Fetch messages
const messagesResponse = await fetch('/data/messages.json');
const messagesData = await messagesResponse.json();
setUserMessages(messagesData);
setLoading(false);
} catch (err) {
console.error('Failed to fetch initial data:', err);
setError('Failed to load application data. Please try again later.');
setLoading(false);
}
};
fetchInitialData();
}, []);
// Get activity by ID
const getActivityById = useCallback((activityId) => {
return activities.find(activity => activity.id === activityId) || null;
}, [activities]);
// Get user's registrations
const getUserRegistrations = useCallback(() => {
if (!currentUser) return [];
return registrations.filter(reg => reg.user_id === currentUser.id);
}, [currentUser, registrations]);
// Register for an activity
const registerForActivity = useCallback((activityId, formData) => {
if (!currentUser) return { success: false, error: 'User not logged in' };
// In a real app, this would make an API call
const newRegistration = {
id: `R${Math.floor(Math.random() * 10000).toString().padStart(4, '0')}`,
activity_id: activityId,
user_id: currentUser.id,
registration_time: new Date().toISOString().replace('T', ' ').substring(0, 19),
status: 'pending',
custom_fields: formData.fields,
custom_answers: formData.answers
};
setRegistrations(prev => [...prev, newRegistration]);
return { success: true, registrationId: newRegistration.id };
}, [currentUser]);
// Create a new activity
const createActivity = useCallback((activityData) => {
if (!currentUser) return { success: false, error: 'User not logged in' };
// In a real app, this would make an API call
const newActivity = {
id: `A${Math.floor(Math.random() * 10000).toString().padStart(4, '0')}`,
organizer_id: currentUser.id,
organizer_name: currentUser.name,
...activityData,
participant_count: 0,
is_published: activityData.is_published || true,
is_public: activityData.is_public || true,
};
setActivities(prev => [...prev, newActivity]);
return { success: true, activityId: newActivity.id };
}, [currentUser]);
// Check in for an activity
const checkInForActivity = useCallback((activityId, registrationId, method = 'manual') => {
if (!currentUser) return { success: false, error: 'User not logged in' };
// In a real app, this would make an API call
const checkinData = {
id: `C${Math.floor(Math.random() * 10000).toString().padStart(4, '0')}`,
activity_id: activityId,
user_id: currentUser.id,
registration_id: registrationId,
checkin_time: new Date().toISOString().replace('T', ' ').substring(0, 19),
checkin_type: method,
status: 'successful',
is_late: false
};
// In a real app, we would update the backend
return { success: true, checkin: checkinData };
}, [currentUser]);
// Get user messages
const getUserMessages = useCallback(() => {
if (!currentUser) return [];
return userMessages.filter(msg => msg.recipient_id === currentUser.id);
}, [currentUser, userMessages]);
// Toggle read status of a message
const toggleMessageReadStatus = useCallback((messageId) => {
setUserMessages(prev =>
prev.map(msg =>
msg.id === messageId
? { ...msg, read_status: !msg.read_status }
: msg
)
);
}, []);
const contextValue = {
currentUser,
activities,
loading,
error,
registrations,
userMessages,
getActivityById,
getUserRegistrations,
registerForActivity,
createActivity,
checkInForActivity,
getUserMessages,
toggleMessageReadStatus
};
return (
<AppContext.Provider value={contextValue}>
{children}
</AppContext.Provider>
);
};
export const useApp = () => {
const context = useContext(AppContext);
if (!context) {
throw new Error('useApp must be used within an AppProvider');
}
return context;
};
export default AppContext;
\ No newline at end of file
import { useState, useEffect } from 'react';
import { fetchUsers } from '../utils/api';
// A custom hook to manage authentication state
const useAuth = () => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// In a real app, this would check if the user is logged in via JWT, cookies, etc.
// For this demo, we'll just load the first user from the mock data
const checkAuth = async () => {
try {
const users = await fetchUsers();
// For demo purposes, use the first user as the logged-in user
if (users && users.length > 0) {
// Check if there's a stored user ID in local storage (for persistence)
const storedUserId = localStorage.getItem('currentUserId');
if (storedUserId) {
const storedUser = users.find(u => u.id === storedUserId);
if (storedUser) {
setUser(storedUser);
} else {
setUser(users[0]); // Fallback to first user
}
} else {
setUser(users[0]);
}
}
setLoading(false);
} catch (err) {
console.error('Authentication error:', err);
setError('Failed to authenticate. Please try again.');
setLoading(false);
}
};
checkAuth();
}, []);
// Login function - in a real app, this would validate credentials
const login = async (userId) => {
try {
setLoading(true);
const users = await fetchUsers();
const foundUser = users.find(u => u.id === userId);
if (foundUser) {
setUser(foundUser);
localStorage.setItem('currentUserId', userId);
setLoading(false);
return { success: true };
} else {
setError('User not found');
setLoading(false);
return { success: false, error: 'User not found' };
}
} catch (err) {
console.error('Login error:', err);
setError('Failed to log in. Please try again.');
setLoading(false);
return { success: false, error: err.message };
}
};
// Logout function
const logout = () => {
setUser(null);
localStorage.removeItem('currentUserId');
};
// Switch user (for demo purposes)
const switchUser = async (userId) => {
return await login(userId);
};
return {
user,
loading,
error,
login,
logout,
switchUser,
isAuthenticated: !!user
};
};
export default useAuth;
\ No newline at end of file
@tailwind base;
@tailwind components;
@tailwind utilities;
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background-color: transparent;
}
::-webkit-scrollbar-thumb {
border-radius: 25px;
transition: all 0.3s;
background-color: rgba(106, 115, 125, 0.2);
&:hover {
background-color: rgba(106, 115, 125, 0.27);
}
}
::-webkit-scrollbar-corner {
display: none;
}
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
import './index.css'
// 创建Vue应用实例
const app = createApp(App)
// 创建Pinia状态管理实例
const pinia = createPinia()
app.use(pinia)
// 创建路由实例
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: () => import('./pages/HomePage.vue') },
{ path: '/activity/:activityId', component: () => import('./pages/ActivityDetail.vue') },
{ path: '/create-activity', component: () => import('./pages/CreateActivity.vue') },
{ path: '/profile', component: () => import('./pages/UserProfile.vue') },
{ path: '/registration/:activityId', component: () => import('./pages/Registration.vue') },
{ path: '/check-in/:activityId', component: () => import('./pages/CheckIn.vue') },
{ path: '/messages/:activityId', component: () => import('./pages/Messages.vue') },
{ path: '/:pathMatch(.*)*', redirect: '/' }
]
})
app.use(router)
// 挂载应用
app.mount('#root')
<template>
<div class="min-h-screen bg-gray-50">
<!-- Activity Header -->
<div class="bg-white shadow-sm">
<div class="container mx-auto px-4 py-4">
<button @click="goBack" class="flex items-center text-gray-600 hover:text-green-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z"
clip-rule="evenodd" />
</svg>
返回
</button>
</div>
</div>
<!-- Loading State -->
<div v-if="loading" class="min-h-screen flex justify-center items-center">
<div class="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-green-500"></div>
</div>
<!-- Error State -->
<div v-else-if="error || !activity"
class="min-h-screen flex flex-col justify-center items-center bg-gray-50 px-4">
<div class="text-red-500 text-6xl mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h1 class="text-2xl font-bold mb-2">活动不存在</h1>
<p class="text-gray-600 mb-6">{{ error || '该活动可能已被删除或不存在' }}</p>
<Button @click="goBack" variant="primary">返回上一页</Button>
</div>
<!-- Activity Content -->
<template v-else>
<!-- Activity Cover -->
<div class="relative h-64 md:h-80 bg-gray-300 overflow-hidden">
<img :src="getActivityImage(activity.id)" :alt="activity.title" class="w-full h-full object-cover" />
<div class="absolute inset-0 bg-black bg-opacity-40"></div>
<div class="absolute bottom-0 left-0 right-0 p-6">
<div class="container mx-auto">
<span
:class="['inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium mb-2', activityStatus.class]">
{{ activityStatus.text }}
</span>
<h1 class="text-2xl md:text-3xl font-bold text-white">{{ activity.title }}</h1>
</div>
</div>
</div>
<!-- Activity Content -->
<div class="container mx-auto px-4 py-8">
<div class="flex flex-col md:flex-row gap-8">
<!-- Main content -->
<div class="w-full md:w-2/3">
<!-- Organizer -->
<div class="mb-8 flex items-center">
<div class="flex items-center">
<div class="h-12 w-12 rounded-full overflow-hidden bg-gray-200 mr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-full w-full text-gray-400"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
<div>
<div class="font-medium">主办方:{{ activity.organizer_name }}</div>
<div class="text-sm text-gray-500">活动组织者</div>
</div>
</div>
</div>
<!-- Activity Tabs -->
<Tabs :tabs="tabs" />
</div>
<!-- Sidebar -->
<div class="w-full md:w-1/3 mt-8 md:mt-0">
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">活动信息</h3>
<div class="space-y-4">
<!-- Date and Time -->
<div class="flex">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500 mt-0.5 mr-3"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<div>
<div class="font-medium">活动时间</div>
<div class="text-gray-600">{{ formatDate(activity.start_time) }}</div>
</div>
</div>
<!-- Location -->
<div class="flex">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500 mt-0.5 mr-3"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<div>
<div class="font-medium">活动地点</div>
<div class="text-gray-600">
{{ activity.activity_type === 'online'
? '线上活动' + (activity.online_link ? `(${activity.online_link})` : '')
: (activity.location ? (typeof activity.location === 'object' ?
activity.location.name : activity.location) : '地点未设置')
}}
</div>
</div>
</div>
<!-- Registration -->
<div class="flex">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500 mt-0.5 mr-3"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
<div>
<div class="font-medium">报名时间</div>
<div class="text-gray-600">{{ formatDate(activity.registration_start) }} - {{
formatDate(activity.registration_end) }}</div>
</div>
</div>
<!-- Participants -->
<div class="flex">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500 mt-0.5 mr-3"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
<div>
<div class="font-medium">参与人数</div>
<div class="text-gray-600">{{ activity.participant_count }}/{{
activity.max_participants }}</div>
</div>
</div>
<!-- Registration progress -->
<div class="mt-2">
<div class="flex items-center justify-between text-sm mb-1">
<span>报名进度</span>
<span>{{ Math.round((activity.participant_count / activity.max_participants) *
100) }}%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="bg-gradient-to-r from-green-400 to-blue-500 h-2 rounded-full"
:style="{ width: `${Math.min((activity.participant_count / activity.max_participants) * 100, 100)}%` }">
</div>
</div>
</div>
<!-- Registration Status -->
<div v-if="hasRegistered"
class="mt-2 bg-green-50 border border-green-100 rounded-md p-4">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-500 mr-2"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
<span class="font-medium text-green-800">您已报名此活动</span>
</div>
<div class="mt-2 flex items-center">
<span class="text-sm text-green-700 mr-2">报名状态:</span>
<span :class="registrationStatusBadge.class">{{ registrationStatusBadge.text
}}</span>
</div>
</div>
<!-- Registration button or message -->
<template v-if="!hasRegistered">
<Button variant="primary" size="lg" block
:disabled="!isRegistrationOpen || activity.participant_count >= activity.max_participants"
@click="handleRegistration" class="mt-4">
{{ isRegistrationOpen
? activity.participant_count >= activity.max_participants
? '名额已满'
: '立即报名'
: '报名已截止' }}
</Button>
</template>
<RouterLink v-if="hasRegistered" :to="`/check-in/${activity.id}`">
<Button variant="secondary" size="md" block class="mt-2">
活动签到
</Button>
</RouterLink>
</div>
</div>
<!-- Similar Activities -->
<div v-if="similarActivities.length > 0" class="bg-white rounded-lg shadow-sm p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">相似活动推荐</h3>
<div class="space-y-4">
<ActivityCard v-for="activity in similarActivities" :key="activity.id"
:activity="activity" variant="compact" />
</div>
</div>
</div>
</div>
</div>
</template>
<!-- Register Modal -->
<Modal v-if="showRegisterModal" @close="showRegisterModal = false">
<template #title>活动报名</template>
<template #content>
<form @submit.prevent="submitRegistration">
<!-- Registration form fields -->
</form>
</template>
</Modal>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAppStore } from '../stores/app'
import Button from '../components/shared/Button.vue'
import Modal from '../components/shared/Modal.vue'
import Tabs from '../components/shared/Tabs.vue'
import ActivityCard from '../components/shared/ActivityCard.vue'
const route = useRoute()
const router = useRouter()
const store = useAppStore()
// State
const activity = ref(null)
const loading = ref(true)
const error = ref(null)
const showRegisterModal = ref(false)
const similarActivities = ref([])
const hasRegistered = ref(false)
const registrationStatus = ref(null)
// Computed
const activityStatus = computed(() => {
if (!activity.value) return {}
const now = new Date()
const startTime = new Date(activity.value.start_time.replace(' ', 'T'))
const endTime = new Date(activity.value.end_time.replace(' ', 'T'))
const registrationEnd = new Date(activity.value.registration_end.replace(' ', 'T'))
if (now > endTime) {
return {
text: '已结束',
class: 'bg-gray-100 text-gray-800'
}
} else if (now >= startTime && now <= endTime) {
return {
text: '进行中',
class: 'bg-green-100 text-green-800'
}
} else if (now <= registrationEnd) {
return {
text: '报名中',
class: 'bg-blue-100 text-blue-800'
}
} else {
return {
text: '即将开始',
class: 'bg-yellow-100 text-yellow-800'
}
}
})
const isRegistrationOpen = computed(() => {
if (!activity.value) return false
const now = new Date()
return now >= new Date(activity.value.registration_start.replace(' ', 'T')) &&
now <= new Date(activity.value.registration_end.replace(' ', 'T'))
})
const registrationStatusBadge = computed(() => {
if (!registrationStatus.value) return null
const statusMap = {
pending: { text: '审核中', class: 'bg-yellow-100 text-yellow-800' },
approved: { text: '已通过', class: 'bg-green-100 text-green-800' },
rejected: { text: '已拒绝', class: 'bg-red-100 text-red-800' },
waitlist: { text: '候补', class: 'bg-purple-100 text-purple-800' }
}
return statusMap[registrationStatus.value] || {
text: registrationStatus.value,
class: 'bg-gray-100 text-gray-800'
}
})
const tabs = computed(() => [
{
label: '活动详情',
content: activity.value?.description.split('\n').map((paragraph, idx) => (
`<p key="${idx}" class="mb-4">${paragraph}</p>`
)).join('')
},
{
label: '参与须知',
content: `
<h3 class="font-medium text-lg mb-4">参与须知</h3>
<ul class="list-disc pl-5 space-y-2">
<li>请准时到达活动地点或登录线上会议</li>
<li>请提前阅读相关书籍或材料</li>
<li>活动开始后,请将手机调至静音模式</li>
<li>尊重他人发言,不打断他人</li>
<li>可携带笔记本进行记录</li>
<li>如需取消参与,请提前24小时通知主办方</li>
</ul>
`
},
{
label: '常见问题',
content: `
<div className="py-4">
<div className="space-y-6">
<div>
<h4 className="font-medium text-gray-900">如何取消报名?</h4>
<p className="mt-2 text-gray-600">您可以在"我的活动"页面找到已报名的活动,点击"取消报名"按钮即可。请注意,活动开始前24小时内取消将无法获得退款。</p>
</div>
<div>
<h4 className="font-medium text-gray-900">活动材料如何获取?</h4>
<p className="mt-2 text-gray-600">报名成功后,您将在"我的活动"页面看到活动详情,相关材料可在页面底部下载或通过邮件接收。</p>
</div>
<div>
<h4 className="font-medium text-gray-900">线上活动如何参加?</h4>
<p className="mt-2 text-gray-600">线上活动将在活动开始前30分钟发送会议链接到您的邮箱和手机短信,您也可以在"我的活动"页面找到入口链接。</p>
</div>
</div>
</div>
`}
])
// Functions
const goBack = () => {
router.back()
}
const getActivityImage = (activityId) => {
return `/ assets / images / activities / ${activityId}.jpg`
}
const formatDate = (dateString) => {
if (!dateString) return ''
const date = new Date(dateString.replace(' ', 'T'))
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
}
const handleRegistration = () => {
showRegisterModal.value = true
}
const submitRegistration = async () => {
// TODO: Implement registration submission
showRegisterModal.value = false
}
// Fetch activity data
onMounted(async () => {
try {
const activityId = route.params.id
const response = await store.fetchActivity(activityId)
activity.value = response
loading.value = false
// Check registration status
const registration = await store.checkRegistration(activityId)
hasRegistered.value = registration !== null
registrationStatus.value = registration?.status
// Fetch similar activities
similarActivities.value = await store.fetchSimilarActivities(activityId)
} catch (err) {
error.value = err.message
loading.value = false
}
})
</script>
<template>
<div class="min-h-screen bg-gray-50">
<!-- Loading State -->
<div v-if="loading" class="min-h-screen flex justify-center items-center bg-gray-50">
<div class="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-green-500"></div>
</div>
<!-- Error State -->
<div v-else-if="error" class="min-h-screen flex flex-col justify-center items-center bg-gray-50 px-4">
<div class="text-red-500 text-6xl mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h1 class="text-2xl font-bold mb-2">无法签到</h1>
<p class="text-gray-600 mb-6">{{ error }}</p>
<Button @click="goBack" variant="primary">返回上一页</Button>
</div>
<!-- Already Checked In -->
<div v-else-if="userCheckIn" class="py-8">
<div class="container mx-auto px-4">
<div class="max-w-md mx-auto bg-white rounded-lg shadow-sm overflow-hidden">
<div class="bg-gradient-to-r from-green-500 to-blue-500 px-6 py-4">
<h1 class="text-2xl font-bold text-white">签到成功</h1>
</div>
<div class="p-6 text-center">
<div class="flex justify-center">
<div class="rounded-full bg-green-100 p-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-green-500" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M5 13l4 4L19 7" />
</svg>
</div>
</div>
<h2 class="text-2xl font-bold text-gray-800 mt-6">您已完成签到</h2>
<p class="text-gray-600 mt-2">签到时间:{{ formatDate(userCheckIn.check_in_time) }}</p>
<p class="text-gray-600">祝您阅读愉快!</p>
<div class="mt-8">
<h3 class="font-semibold text-gray-800 mb-2">活动信息</h3>
<div class="bg-gray-50 rounded-lg p-4 text-left">
<p class="font-medium text-gray-900">{{ activity.title }}</p>
<p class="text-gray-600 text-sm mt-1">
时间:{{ formatDate(activity.start_time) }} - {{ formatDate(activity.end_time) }}
</p>
<p class="text-gray-600 text-sm mt-1">
地点:{{ activity.activity_type === 'online' ? '线上活动' : (activity.location ? (typeof
activity.location === 'object' ? activity.location.name : activity.location) :
'地点未设置') }}
</p>
</div>
</div>
<Button @click="goToActivityDetail" variant="primary" class="mt-8">
返回活动详情
</Button>
</div>
</div>
</div>
</div>
<!-- Activity Not Now -->
<div v-else-if="!isActivityNow()" class="py-8">
<div class="container mx-auto px-4">
<div class="max-w-md mx-auto bg-white rounded-lg shadow-sm overflow-hidden">
<div class="bg-gradient-to-r from-green-500 to-blue-500 px-6 py-4">
<h1 class="text-2xl font-bold text-white">暂不可签到</h1>
</div>
<div class="p-6">
<div class="text-center">
<div class="inline-block rounded-full bg-blue-100 p-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-blue-500" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h2 class="text-xl font-bold text-gray-800 mt-6">签到尚未开始</h2>
<p class="text-gray-600 mt-2">
{{ new Date() < new Date(activity.start_time.replace(' ', ' T')) ? '活动签到将于活动开始前30分钟开放'
: '活动已结束,签到已关闭' }} </p>
<div class="mt-8">
<h3 class="font-semibold text-gray-800 mb-2">活动信息</h3>
<div class="bg-gray-50 rounded-lg p-4 text-left">
<p class="font-medium text-gray-900">{{ activity.title }}</p>
<p class="text-gray-600 text-sm mt-1">
时间:{{ formatDate(activity.start_time) }} - {{
formatDate(activity.end_time) }}
</p>
<p class="text-gray-600 text-sm mt-1">
地点:{{ activity.activity_type === 'online' ? '线上活动' : (activity.location
? (typeof activity.location === 'object' ? activity.location.name :
activity.location) : '地点未设置') }}
</p>
</div>
</div>
<Button @click="goToActivityDetail" variant="primary" class="mt-6">
返回活动详情
</Button>
</div>
</div>
</div>
</div>
</div>
<!-- Main Check-in Flow -->
<div v-else class="py-8">
<div class="container mx-auto px-4">
<div class="max-w-md mx-auto bg-white rounded-lg shadow-sm overflow-hidden">
<div class="bg-gradient-to-r from-green-500 to-blue-500 px-6 py-4">
<h1 class="text-2xl font-bold text-white">活动签到</h1>
</div>
<div class="p-6">
<!-- Activity Info -->
<div class="mb-6">
<h2 class="text-xl font-medium text-gray-900 mb-2">{{ activity.title }}</h2>
<p class="text-gray-600 text-sm">
时间:{{ formatDate(activity.start_time) }} - {{ formatDate(activity.end_time) }}
</p>
<p class="text-gray-600 text-sm mt-1">
地点:{{ activity.activity_type === 'online' ? '线上活动' : (activity.location ? (typeof
activity.location === 'object' ? activity.location.name : activity.location) : '地点未设置')
}}
</p>
<div v-if="userRegistration.status === 'approved'"
class="bg-green-50 border border-green-200 rounded-md p-3 mt-4">
<div class="flex">
<svg class="h-5 w-5 text-green-500 mr-2" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
<span class="text-green-800 font-medium">您的报名已通过审核</span>
</div>
</div>
</div>
<!-- Check-in Steps -->
<div v-if="checkInStep === 'initial'" class="text-center">
<div class="inline-block bg-blue-100 rounded-full p-4 mb-6">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-blue-600" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
</div>
<h3 class="text-lg font-medium text-gray-900 mb-2">活动签到</h3>
<p class="text-gray-600 mb-6">
点击下方按钮生成签到码,请向工作人员出示
</p>
<Button @click="generateCheckInCode" variant="primary" size="lg" block>
生成签到码
</Button>
</div>
<div v-else-if="checkInStep === 'code'" class="text-center">
<h3 class="text-lg font-medium text-gray-900 mb-2">您的签到码</h3>
<p class="text-gray-600 mb-4">请向工作人员出示以下签到码</p>
<div class="border-2 border-gray-300 rounded-lg p-4 mb-6">
<div class="mb-4">
<div class="w-48 h-48 mx-auto bg-gray-100 flex items-center justify-center">
<div class="text-center">
<div class="text-xl font-bold mb-2">签到码</div>
<div class="text-3xl font-bold">{{ checkInCode }}</div>
</div>
</div>
</div>
<div class="flex justify-center">
<div
class="text-3xl font-bold tracking-wide text-gray-800 py-2 px-4 border-2 border-gray-300 rounded-lg">
{{ checkInCode }}
</div>
</div>
</div>
<p class="text-sm text-gray-600">
{{ countdown > 0 ? `签到码有效期: ${countdown} 秒` : '签到码已过期,请重新生成' }}
</p>
<div class="mt-6 text-sm">
<p class="text-gray-700">或输入工作人员提供的活动码</p>
<div class="flex mt-2">
<input type="text" v-model="inputCode" placeholder="请输入6位签到码"
class="flex-1 block border-gray-300 rounded-l-md shadow-sm focus:ring-green-500 focus:border-green-500 sm:text-sm" />
<Button @click="handleSubmitCode" variant="primary" class="rounded-l-none"
:disabled="isSubmitting || !inputCode">
验证
</Button>
</div>
</div>
<Button @click="setCheckInStep('initial')" variant="secondary" size="md" class="mt-6">
返回
</Button>
</div>
<div v-else-if="checkInStep === 'success'" class="text-center">
<div class="flex justify-center">
<div class="rounded-full bg-green-100 p-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-green-500" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M5 13l4 4L19 7" />
</svg>
</div>
</div>
<h2 class="text-2xl font-bold text-gray-800 mt-6">签到成功</h2>
<p class="text-gray-600 mt-2">祝您阅读愉快!</p>
<Button @click="goToActivityDetail" variant="primary" class="mt-8">
返回活动详情
</Button>
</div>
<div v-else-if="checkInStep === 'error'" class="text-center">
<div class="flex justify-center">
<div class="rounded-full bg-red-100 p-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-red-500" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
<h2 class="text-xl font-bold text-gray-800 mt-6">签到失败</h2>
<p class="text-red-500 mt-2">{{ error }}</p>
<Button @click="setCheckInStep('initial')" variant="primary" class="mt-6">
重新签到
</Button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAppStore } from '../stores/app'
import Button from '../components/shared/Button.vue'
const route = useRoute()
const router = useRouter()
const appStore = useAppStore()
const activityId = route.params.activityId
const activity = ref(null)
const loading = ref(true)
const error = ref(null)
const userRegistration = ref(null)
const userCheckIn = ref(null)
const checkInStep = ref('initial')
const checkInCode = ref('')
const countdown = ref(0)
const inputCode = ref('')
const isSubmitting = ref(false)
// Fetch activity details and user registration
const fetchData = async () => {
try {
if (!appStore.currentUser) {
error.value = '请先登录'
loading.value = false
return
}
if (appStore.activities.length > 0) {
const foundActivity = appStore.getActivityById(activityId)
if (foundActivity) {
activity.value = foundActivity
// Find user registration for this activity
const registration = appStore.registrations.find(
reg => reg.activity_id === activityId && reg.user_id === appStore.currentUser.id
)
if (registration) {
userRegistration.value = registration
// Check if user already checked in
const checkIn = appStore.checkIns.find(
check => check.activity_id === activityId && check.user_id === appStore.currentUser.id
)
if (checkIn) {
userCheckIn.value = checkIn
}
} else {
error.value = '您尚未报名该活动'
}
} else {
error.value = '未找到活动信息'
}
}
loading.value = false
} catch (err) {
console.error('Failed to fetch data:', err)
error.value = '加载数据失败'
loading.value = false
}
}
// Generate a random check-in code
const generateCheckInCode = () => {
checkInCode.value = Math.floor(100000 + Math.random() * 900000).toString()
checkInStep.value = 'code'
countdown.value = 60
}
// Handle check-in code submission
const handleSubmitCode = async () => {
if (!inputCode.value.trim()) return
isSubmitting.value = true
try {
if (inputCode.value === checkInCode.value) {
const now = new Date().toISOString().replace('T', ' ').substring(0, 19)
const checkInResult = await appStore.addCheckIn({
user_id: appStore.currentUser.id,
activity_id: activityId,
check_in_time: now,
status: 'checked_in'
})
if (checkInResult.success) {
userCheckIn.value = {
user_id: appStore.currentUser.id,
activity_id: activityId,
check_in_time: now,
status: 'checked_in'
}
checkInStep.value = 'success'
} else {
error.value = '签到失败,请稍后重试'
checkInStep.value = 'error'
}
} else {
error.value = '签到码不正确,请重试'
checkInStep.value = 'error'
}
} catch (err) {
console.error('Check-in error:', err)
error.value = '签到失败,请稍后重试'
checkInStep.value = 'error'
} finally {
isSubmitting.value = false
}
}
// Format date for display
const formatDate = (dateString) => {
if (!dateString) return ''
const date = new Date(dateString.replace(' ', 'T'))
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
}).format(date)
}
// Check if the activity is happening now
const isActivityNow = () => {
if (!activity.value) return false
const now = new Date()
const startTime = new Date(activity.value.start_time.replace(' ', 'T'))
const endTime = new Date(activity.value.end_time.replace(' ', 'T'))
// Allow check-in from 30 minutes before start until end time
const checkInStartTime = new Date(startTime.getTime() - 30 * 60 * 1000)
return now >= checkInStartTime && now <= endTime
}
// Navigation
const goBack = () => router.back()
const goToActivityDetail = () => router.push(`/activity/${activityId}`)
// Watch countdown
watch(countdown, (newValue) => {
if (newValue > 0 && checkInStep.value === 'code') {
setTimeout(() => {
countdown.value--
}, 1000)
} else if (newValue === 0 && checkInStep.value === 'code') {
checkInStep.value = 'initial'
}
})
// Initial data fetch
onMounted(fetchData)
</script>
<template>
<div class="min-h-screen bg-gray-50 py-8">
<div class="container mx-auto px-4">
<div class="max-w-4xl mx-auto bg-white rounded-lg shadow-sm overflow-hidden">
<!-- Header -->
<div class="bg-gradient-to-r from-green-500 to-blue-500 px-6 py-4">
<h1 class="text-2xl font-bold text-white">创建读书会活动</h1>
</div>
<!-- Form -->
<form class="p-6" @submit.prevent="handlePreview">
<div class="space-y-8">
<!-- Basic Information Section -->
<div>
<h2 class="text-xl font-medium text-gray-900 border-b pb-2">基本信息</h2>
<div class="mt-4 space-y-6">
<!-- Title -->
<Input id="title" label="活动标题" name="title" v-model="formState.title"
placeholder="例如:《百年孤独》读书分享会" :error="formErrors.title" required />
<!-- Description -->
<div>
<label for="description" class="block text-sm font-medium text-gray-700 mb-1">
活动描述 <span class="text-red-500">*</span>
</label>
<textarea id="description" name="description" rows="8"
v-model="formState.description" :class="[
'block w-full border-gray-300 rounded-md shadow-sm focus:ring-green-500 focus:border-green-500 sm:text-sm',
formErrors.description ? 'border-red-300' : ''
]" placeholder="请描述活动内容、流程安排、参与须知等信息..."></textarea>
<p v-if="formErrors.description" class="mt-1 text-sm text-red-600">{{
formErrors.description }}</p>
</div>
<!-- Cover Image -->
<div>
<label for="cover_image" class="block text-sm font-medium text-gray-700 mb-1">
封面图片
</label>
<div class="mt-1 flex items-center space-x-4">
<div class="w-48 h-32 bg-gray-100 rounded-md overflow-hidden">
<img :src="formState.cover_image" alt="活动封面预览"
class="w-full h-full object-cover" />
</div>
<Input id="cover_image" name="cover_image" v-model="formState.cover_image"
placeholder="输入图片URL或上传图片" />
</div>
<p class="mt-1 text-sm text-gray-500">
建议尺寸:1200 x 675 像素,格式:JPG、PNG
</p>
</div>
<!-- Tags -->
<div>
<label for="tagInput" class="block text-sm font-medium text-gray-700 mb-1">
活动标签
</label>
<div class="flex flex-wrap items-center gap-2 mb-2">
<div v-for="(tag, index) in formState.tags" :key="index"
class="bg-green-50 text-green-700 rounded-full px-3 py-1 text-sm flex items-center">
<span>{{ tag }}</span>
<button type="button" @click="removeTag(tag)"
class="ml-1 text-green-500 hover:text-green-700 focus:outline-none">
&times;
</button>
</div>
<Input id="tagInput" name="tagInput" v-model="formState.tagInput"
@keydown="handleTagKeyDown" placeholder="输入标签并按回车" class="flex-1" />
</div>
<p class="text-sm text-gray-500">
添加相关标签,使活动更容易被发现,如:小说、科普、心理学等
</p>
</div>
</div>
</div>
<!-- Time and Location Section -->
<div>
<h2 class="text-xl font-medium text-gray-900 border-b pb-2">时间与地点</h2>
<div class="mt-4 space-y-6">
<!-- Activity Time -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input id="start_time" type="datetime-local" label="活动开始时间" name="start_time"
v-model="formState.start_time" :error="formErrors.start_time" required />
<Input id="end_time" type="datetime-local" label="活动结束时间" name="end_time"
v-model="formState.end_time" :error="formErrors.end_time" required />
</div>
<!-- Registration Time -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input id="registration_start" type="datetime-local" label="报名开始时间"
name="registration_start" v-model="formState.registration_start"
:error="formErrors.registration_start" required />
<Input id="registration_end" type="datetime-local" label="报名结束时间"
name="registration_end" v-model="formState.registration_end"
:error="formErrors.registration_end" required />
</div>
<!-- Activity Type -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
活动形式 <span class="text-red-500">*</span>
</label>
<div class="flex space-x-4">
<label class="inline-flex items-center">
<input type="radio" name="activity_type" value="offline"
v-model="formState.activity_type"
class="h-4 w-4 text-green-600 border-gray-300 focus:ring-green-500" />
<span class="ml-2">线下活动</span>
</label>
<label class="inline-flex items-center">
<input type="radio" name="activity_type" value="online"
v-model="formState.activity_type"
class="h-4 w-4 text-green-600 border-gray-300 focus:ring-green-500" />
<span class="ml-2">线上活动</span>
</label>
</div>
</div>
<!-- Location or Online Link -->
<Input v-if="formState.activity_type === 'offline'" id="location" label="活动地点"
name="location" v-model="formState.location" placeholder="例如:北京市海淀区中关村图书馆三层读书区"
:error="formErrors.location" required />
<Input v-else id="online_link" label="线上会议信息" name="online_link"
v-model="formState.online_link" placeholder="例如:Zoom会议ID或飞书会议链接"
:error="formErrors.online_link" required />
</div>
</div>
<!-- Settings Section -->
<div>
<h2 class="text-xl font-medium text-gray-900 border-b pb-2">活动设置</h2>
<div class="mt-4 space-y-6">
<!-- Maximum Participants -->
<div class="max-w-xs">
<Input id="max_participants" type="number" label="最大参与人数" name="max_participants"
v-model="formState.max_participants" min="1" max="500"
:error="formErrors.max_participants" />
</div>
<!-- Visibility -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
活动可见性
</label>
<label class="inline-flex items-center">
<input type="checkbox" name="is_public" v-model="formState.is_public"
class="h-4 w-4 text-green-600 border-gray-300 rounded focus:ring-green-500" />
<span class="ml-2">公开活动(所有人可见)</span>
</label>
<p class="mt-1 text-sm text-gray-500">
{{ formState.is_public ? '活动将在首页和搜索结果中显示' : '活动仅对邀请的用户可见' }}
</p>
</div>
</div>
</div>
<!-- Submit Buttons -->
<div class="flex justify-end space-x-4 pt-6">
<Button type="button" variant="secondary" @click="goBack">
取消
</Button>
<Button type="submit" variant="primary" :disabled="isLoading">
预览活动
</Button>
</div>
</div>
</form>
</div>
</div>
<!-- Preview Modal -->
<Modal v-model:isOpen="showPreviewModal" title="活动预览" size="lg">
<template #default>
<div class="py-4">
<div class="space-y-6">
<!-- Preview Cover -->
<div class="relative h-48 rounded-lg overflow-hidden bg-gray-200">
<img :src="formState.cover_image" :alt="formState.title"
class="w-full h-full object-cover" />
</div>
<!-- Preview Title -->
<h2 class="text-2xl font-bold text-gray-900">{{ formState.title }}</h2>
<!-- Preview Tags -->
<div v-if="formState.tags.length > 0" class="flex flex-wrap gap-2">
<span v-for="(tag, index) in formState.tags" :key="index"
class="bg-green-50 text-green-700 rounded-full px-3 py-1 text-sm">
{{ tag }}
</span>
</div>
<!-- Preview Description -->
<div class="prose max-w-none text-gray-700">
<p v-for="(paragraph, idx) in formState.description.split('\n')" :key="idx" class="mb-2">
{{ paragraph }}
</p>
</div>
</div>
</div>
</template>
<template #footer>
<Button @click="showPreviewModal = false" variant="secondary" class="mr-3">
返回编辑
</Button>
<Button @click="handleSubmit" variant="primary" :disabled="isLoading">
{{ isLoading ? '创建中...' : '创建活动' }}
</Button>
</template>
</Modal>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '../stores/app'
import Button from '../components/shared/Button.vue'
import Input from '../components/shared/Input.vue'
import Modal from '../components/shared/Modal.vue'
const router = useRouter()
const appStore = useAppStore()
const { currentUser } = appStore
const isLoading = ref(false)
const showPreviewModal = ref(false)
const formErrors = reactive({})
// Form state
const formState = reactive({
title: '',
description: '',
cover_image: '/assets/images/LEVRaos2Ero.jpg',
start_time: '',
end_time: '',
registration_start: '',
registration_end: '',
activity_type: 'offline', // 'online' or 'offline'
location: '',
online_link: '',
max_participants: 20,
is_public: true,
tags: [],
tagInput: '' // For managing tag input
})
// Check if user is logged in
onMounted(() => {
if (!currentUser) {
// In a real app, redirect to login
alert('请先登录再创建活动')
router.push('/')
}
})
// Handle tag input
const handleTagKeyDown = (e) => {
if (e.key === 'Enter') {
e.preventDefault()
addTag()
}
}
// Add tag to the list
const addTag = () => {
const tag = formState.tagInput.trim()
if (tag && !formState.tags.includes(tag)) {
formState.tags.push(tag)
formState.tagInput = ''
}
}
// Remove tag from the list
const removeTag = (tagToRemove) => {
formState.tags = formState.tags.filter(tag => tag !== tagToRemove)
}
// Validate form before submission
const validateForm = () => {
const errors = {}
if (!formState.title.trim()) {
errors.title = '请输入活动标题'
}
if (!formState.description.trim()) {
errors.description = '请输入活动描述'
}
if (!formState.start_time) {
errors.start_time = '请选择活动开始时间'
}
if (!formState.end_time) {
errors.end_time = '请选择活动结束时间'
} else if (new Date(formState.end_time) <= new Date(formState.start_time)) {
errors.end_time = '结束时间必须晚于开始时间'
}
if (!formState.registration_start) {
errors.registration_start = '请选择报名开始时间'
}
if (!formState.registration_end) {
errors.registration_end = '请选择报名结束时间'
} else if (new Date(formState.registration_end) <= new Date(formState.registration_start)) {
errors.registration_end = '报名结束时间必须晚于开始时间'
}
if (formState.activity_type === 'offline' && !formState.location.trim()) {
errors.location = '请输入活动地点'
}
if (formState.activity_type === 'online' && !formState.online_link.trim()) {
errors.online_link = '请输入线上会议链接'
}
if (formState.max_participants <= 0) {
errors.max_participants = '参与人数必须大于0'
}
Object.assign(formErrors, errors)
return Object.keys(errors).length === 0
}
// Preview activity before submission
const handlePreview = (e) => {
e.preventDefault()
if (validateForm()) {
showPreviewModal.value = true
} else {
// Scroll to the first error
const firstErrorField = Object.keys(formErrors)[0]
const element = document.getElementById(firstErrorField)
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
}
}
// Submit form to create activity
const handleSubmit = async () => {
if (validateForm()) {
isLoading.value = true
try {
// Create form data for submission
const activityData = { ...formState }
delete activityData.tagInput
activityData.participant_count = 0
activityData.is_published = true
const result = await appStore.createActivity(activityData)
if (result.success) {
alert('活动创建成功!')
router.push(`/activity/${result.activityId}`)
} else {
alert(`创建失败: ${result.error || '未知错误'}`)
isLoading.value = false
}
} catch (error) {
console.error('Failed to create activity:', error)
alert('创建活动失败,请稍后再试')
isLoading.value = false
}
}
}
// Format datetime for preview
const formatDatetime = (datetimeStr) => {
if (!datetimeStr) return ''
const date = new Date(datetimeStr)
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
}).format(date)
}
// Navigation
const goBack = () => {
router.back()
}
</script>
<template>
<div class="min-h-screen">
<!-- 加载状态 -->
<div v-if="loading" class="min-h-screen flex justify-center items-center">
<div class="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-green-500"></div>
</div>
<template v-else>
<!-- Hero Section -->
<section class="relative bg-gradient-to-br from-green-50 to-blue-50 py-16">
<div class="container mx-auto px-4">
<div class="max-w-3xl mx-auto text-center">
<h1 class="text-4xl md:text-5xl font-bold mb-6 text-gray-800">
连接爱读书的人,<span
class="bg-gradient-to-r from-green-500 to-blue-400 bg-clip-text text-transparent">共享知识的力量</span>
</h1>
<p class="text-xl text-gray-600 mb-8">
发现丰富的读书活动,结识志同道合的朋友,让阅读成为一种享受
</p>
<!-- 搜索表单 -->
<form @submit.prevent="handleSearch" class="relative max-w-xl mx-auto mb-8">
<div class="flex rounded-full overflow-hidden shadow-lg">
<input type="text" v-model="searchTerm" class="flex-grow px-6 py-4 focus:outline-none"
placeholder="搜索读书会活动、主题或城市..." />
<button type="submit"
class="px-8 py-4 bg-green-500 text-white font-semibold hover:bg-green-600 transition duration-200">
搜索
</button>
</div>
</form>
</div>
</div>
</section>
<!-- 搜索结果 -->
<section v-if="isSearching" class="container mx-auto px-4 py-12">
<div class="flex justify-between items-center mb-8">
<h2 class="text-2xl font-bold text-gray-800">搜索结果</h2>
<button @click="clearSearch" class="text-gray-600 hover:text-gray-800 transition duration-200">
清除搜索
</button>
</div>
<div v-if="searchResults.length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<ActivityCard v-for="activity in searchResults" :key="activity.id" :activity="activity" />
</div>
<div v-else class="text-center py-12 text-gray-600">
未找到相关活动
</div>
</section>
<!-- 活动列表 -->
<template v-else>
<!-- 进行中的活动 -->
<section v-if="ongoingActivities.length > 0" class="container mx-auto px-4 py-12">
<h2 class="text-2xl font-bold text-gray-800 mb-8">正在进行的活动</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<ActivityCard v-for="activity in ongoingActivities" :key="activity.id" :activity="activity" />
</div>
</section>
<!-- 即将开始的活动 -->
<section class="container mx-auto px-4 py-12">
<div class="flex justify-between items-center mb-8">
<h2 class="text-2xl font-bold text-gray-800">即将开始的活动</h2>
<router-link to="/create-activity"
class="px-6 py-2 bg-green-500 text-white rounded-full font-semibold hover:bg-green-600 transition duration-200">
创建活动
</router-link>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<ActivityCard v-for="activity in upcomingActivities" :key="activity.id" :activity="activity" />
</div>
</section>
<!-- 往期活动 -->
<section class="container mx-auto px-4 py-12">
<h2 class="text-2xl font-bold text-gray-800 mb-8">往期活动</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<ActivityCard v-for="activity in pastActivities" :key="activity.id" :activity="activity" />
</div>
</section>
</template>
</template>
</div>
</template>
<script setup>
import { ref, computed, watchEffect } from 'vue'
import { useAppStore } from '../stores/app'
import ActivityCard from '../components/shared/ActivityCard.vue'
const store = useAppStore()
const { activities, loading } = store
const upcomingActivities = ref([])
const ongoingActivities = ref([])
const pastActivities = ref([])
const searchTerm = ref('')
const searchResults = ref([])
const isSearching = ref(false)
// 监听活动数据变化并分类
watchEffect(() => {
if (activities.value.length > 0) {
const now = new Date()
// 筛选即将开始的活动
upcomingActivities.value = activities.value
.filter(activity => {
const startTime = new Date(activity.start_time.replace(' ', 'T'))
return startTime > now
})
.sort((a, b) => {
return new Date(a.start_time.replace(' ', 'T')) - new Date(b.start_time.replace(' ', 'T'))
})
.slice(0, 6)
// 筛选进行中的活动
ongoingActivities.value = activities.value
.filter(activity => {
const startTime = new Date(activity.start_time.replace(' ', 'T'))
const endTime = new Date(activity.end_time.replace(' ', 'T'))
return startTime <= now && endTime >= now
})
// 筛选往期活动
pastActivities.value = activities.value
.filter(activity => {
const endTime = new Date(activity.end_time.replace(' ', 'T'))
return endTime < now
})
.sort((a, b) => {
return new Date(b.end_time.replace(' ', 'T')) - new Date(a.end_time.replace(' ', 'T'))
})
.slice(0, 6)
}
})
// 搜索处理
const handleSearch = () => {
if (!searchTerm.value.trim()) return
isSearching.value = true
searchResults.value = activities.value.filter(activity =>
activity.title.toLowerCase().includes(searchTerm.value.toLowerCase()) ||
activity.description.toLowerCase().includes(searchTerm.value.toLowerCase()) ||
(activity.tags && activity.tags.some(tag => tag.toLowerCase().includes(searchTerm.value.toLowerCase())))
)
}
// 清除搜索
const clearSearch = () => {
searchTerm.value = ''
isSearching.value = false
}
</script>
<template>
<div class="min-h-screen bg-gray-50">
<!-- Header -->
<div class="bg-white shadow-sm sticky top-0 z-10">
<div class="container mx-auto px-4 py-3">
<div class="flex items-center justify-between">
<div class="flex items-center">
<button @click="goBack" class="mr-3 text-gray-600 hover:text-gray-900">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
fill="currentColor">
<path fill-rule="evenodd"
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
clip-rule="evenodd" />
</svg>
</button>
<div>
<h1 class="text-lg font-medium text-gray-900 truncate max-w-xs">{{ activity?.title }}</h1>
<p class="text-sm text-gray-500">消息交流</p>
</div>
</div>
<router-link :to="`/activity/${activityId}`"
class="text-sm text-green-600 hover:text-green-700 font-medium">
活动详情
</router-link>
</div>
</div>
</div>
<!-- Message Tabs -->
<div class="bg-white shadow-sm">
<div class="container mx-auto px-4">
<div class="flex overflow-x-auto hide-scrollbar">
<button v-for="tab in tabs" :key="tab.value" :class="[
'py-3 px-4 text-sm font-medium border-b-2 whitespace-nowrap',
activeTab === tab.value
? 'border-green-500 text-green-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
]" @click="activeTab = tab.value">
{{ tab.label }}
</button>
</div>
</div>
</div>
<!-- Messages Container -->
<div class="container mx-auto px-4 py-4 flex flex-col h-[calc(100vh-13rem)]">
<!-- Message List -->
<div class="flex-grow overflow-y-auto mb-4 pr-1">
<div v-if="filteredMessages.length === 0" class="flex flex-col items-center justify-center h-full">
<div class="text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
</div>
<p class="text-gray-500 mt-2">暂无消息</p>
<button v-if="activeTab !== 'all'" @click="activeTab = 'all'"
class="mt-4 text-green-600 hover:text-green-700 font-medium text-sm">
查看全部消息
</button>
</div>
<div v-else>
<div v-for="date in sortedDates" :key="date">
<div class="flex items-center justify-center my-4">
<div class="border-t border-gray-200 flex-grow"></div>
<span class="mx-4 text-xs text-gray-500">{{ formatDate(date) }}</span>
<div class="border-t border-gray-200 flex-grow"></div>
</div>
<div v-for="message in groupedMessages[date]" :key="message.id">
<component :is="messageComponent(message)" :message="message" />
</div>
</div>
<div ref="messageEndRef" />
</div>
</div>
<!-- Message Input -->
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden">
<div v-if="isOrganizer" class="p-2 bg-gray-50 border-b border-gray-200">
<div class="flex items-center">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
组织者
</span>
<span class="text-xs text-gray-500 ml-2">
您可以发送公告消息
</span>
</div>
</div>
<form @submit.prevent="handleSendMessage" class="flex p-2">
<input type="text" v-model="messageInput" placeholder="输入消息..."
class="flex-grow border-none focus:ring-0 focus:outline-none" :disabled="isSubmitting" />
<Button type="submit" variant="primary" size="sm" :disabled="!messageInput.trim() || isSubmitting">
发送
</Button>
</form>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAppStore } from '../stores/app'
import Button from '../components/shared/Button.vue'
// Route and store setup
const route = useRoute()
const router = useRouter()
const store = useAppStore()
// Props and refs
const activityId = route.params.activityId
const messageEndRef = ref(null)
const messageInput = ref('')
const activeTab = ref('all')
const isSubmitting = ref(false)
const activity = ref(null)
const filteredMessages = ref([])
const loading = ref(true)
const error = ref(null)
// Computed properties
const isOrganizer = computed(() => {
return store.currentUser && store.currentUser.id === activity.value?.organizer_id
})
// Tabs configuration
const tabs = [
{ label: '全部消息', value: 'all' },
{ label: '活动公告', value: 'announcements' },
{ label: '问答交流', value: 'questions' },
{ label: '我的消息', value: 'personal' }
]
// Methods
const goBack = () => router.back()
const formatTime = (dateString) => {
const date = new Date(dateString.replace(' ', 'T'))
return new Intl.DateTimeFormat('zh-CN', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
}).format(date)
}
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString('zh-CN')
}
const handleSendMessage = async () => {
if (!messageInput.value.trim()) return
if (!store.currentUser) {
alert('请先登录再发送消息')
return
}
isSubmitting.value = true
try {
const now = new Date().toISOString().replace('T', ' ').substring(0, 19)
const newMessage = {
activity_id: activityId,
user_id: store.currentUser.id,
type: 'question',
content: messageInput.value,
timestamp: now,
user_name: store.currentUser.name,
user_avatar: store.currentUser.avatar
}
const result = await store.addMessage(newMessage)
if (result.success) {
messageInput.value = ''
} else {
alert(`发送失败: ${result.error}`)
}
} catch (error) {
console.error('Failed to send message:', error)
alert('发送消息失败,请稍后重试')
} finally {
isSubmitting.value = false
}
}
// Message components
const AnnouncementMessage = {
props: ['message'],
template: `
<div class="bg-blue-50 border border-blue-100 rounded-lg p-4 mb-4">
<div class="flex items-center mb-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-blue-500 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 3a1 1 0 00-1.447-.894L8.763 6H5a3 3 0 000 6h.28l1.771 5.316A1 1 0 008 18h1a1 1 0 001-1v-4.382l6.553 3.276A1 1 0 0018 15V3z" clip-rule="evenodd" />
</svg>
<span class="font-semibold text-blue-800">活动公告</span>
</div>
<p class="text-gray-700">{{ message.content }}</p>
<div class="mt-2 text-xs text-gray-500 flex items-center justify-between">
<span>{{ message.user_name }}</span>
<span>{{ formatTime(message.timestamp) }}</span>
</div>
</div>
`
}
const AnswerMessage = {
props: ['message'],
template: `
<div class="bg-green-50 border border-green-100 rounded-lg p-4 mb-4">
<div class="flex items-center mb-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-500 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 13V5a2 2 0 00-2-2H4a2 2 0 00-2 2v8a2 2 0 002 2h3l3 3 3-3h3a2 2 0 002-2zM5 7a1 1 0 011-1h8a1 1 0 110 2H6a1 1 0 01-1-1zm1 3a1 1 0 100 2h3a1 1 0 100-2H6z" clip-rule="evenodd" />
</svg>
<span class="font-semibold text-green-800">回复</span>
</div>
<div v-if="message.reply_to" class="bg-white rounded p-2 mb-2 text-sm text-gray-600 border-l-2 border-gray-300">
<p class="truncate">{{ message.reply_to }}</p>
</div>
<p class="text-gray-700">{{ message.content }}</p>
<div class="mt-2 text-xs text-gray-500 flex items-center justify-between">
<span>{{ message.user_name }}</span>
<span>{{ formatTime(message.timestamp) }}</span>
</div>
</div>
`
}
const DefaultMessage = {
props: ['message'],
setup(props) {
const store = useAppStore()
const isCurrentUser = computed(() => props.message.user_id === store.currentUser?.id)
return { isCurrentUser, formatTime }
},
template: `
<div :class="['flex', isCurrentUser ? 'justify-end' : 'justify-start', 'mb-4']">
<div :class="['max-w-[80%]', isCurrentUser ? 'order-1' : 'order-2']">
<div :class="['flex items-center', isCurrentUser ? 'justify-end' : 'justify-start', 'mb-1']">
<span class="text-xs text-gray-500 mr-2">{{ message.user_name }}</span>
<div class="h-6 w-6 rounded-full overflow-hidden bg-gray-200">
<img v-if="message.user_avatar" :src="message.user_avatar" :alt="message.user_name" class="h-full w-full object-cover" />
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-full w-full text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</div>
</div>
<div :class="['rounded-lg p-3', isCurrentUser ? 'bg-green-100' : 'bg-white border border-gray-200']">
<p class="text-gray-700">{{ message.content }}</p>
</div>
<div :class="['text-xs text-gray-500 mt-1', isCurrentUser ? 'text-right' : 'text-left']">
{{ formatTime(message.timestamp) }}
</div>
</div>
</div>
`
}
const messageComponent = (message) => {
switch (message.type) {
case 'announcement':
return AnnouncementMessage
case 'answer':
return AnswerMessage
default:
return DefaultMessage
}
}
// Computed properties for message grouping
const groupedMessages = computed(() => {
return filteredMessages.value.reduce((groups, message) => {
const date = message.timestamp.split(' ')[0]
if (!groups[date]) {
groups[date] = []
}
groups[date].push(message)
return groups
}, {})
})
const sortedDates = computed(() => {
return Object.keys(groupedMessages.value).sort()
})
// Watch for changes in messages and scroll to bottom
watch(filteredMessages, () => {
if (messageEndRef.value) {
messageEndRef.value.scrollIntoView({ behavior: 'smooth' })
}
})
// Filter messages based on active tab
watch([() => store.messages, activeTab], () => {
if (!store.messages) return
let filtered = store.messages.filter(msg => msg.activity_id === activityId)
switch (activeTab.value) {
case 'announcements':
filtered = filtered.filter(msg => msg.type === 'announcement')
break
case 'questions':
filtered = filtered.filter(msg => msg.type === 'question' || msg.type === 'answer')
break
case 'personal':
filtered = filtered.filter(msg =>
msg.user_id === store.currentUser?.id || msg.to_user_id === store.currentUser?.id
)
break
}
filteredMessages.value = filtered.sort((a, b) => {
return new Date(a.timestamp.replace(' ', 'T')) - new Date(b.timestamp.replace(' ', 'T'))
})
})
// Initial data fetch
onMounted(async () => {
try {
if (!store.currentUser) {
error.value = '请先登录'
loading.value = false
return
}
if (store.activities.length > 0) {
const foundActivity = store.getActivityById(activityId)
if (foundActivity) {
activity.value = foundActivity
// Filter messages for this activity
const activityMessages = store.messages.filter(
msg => msg.activity_id === activityId
).sort((a, b) => {
return new Date(a.timestamp.replace(' ', 'T')) - new Date(b.timestamp.replace(' ', 'T'))
})
filteredMessages.value = activityMessages
} else {
error.value = '未找到活动信息'
}
}
loading.value = false
} catch (err) {
console.error('Failed to fetch data:', err)
error.value = '加载数据失败'
loading.value = false
}
})
</script>
<style scoped>
.hide-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
</style>
<template>
<div class="min-h-screen bg-gray-50">
<!-- Loading State -->
<div v-if="loading" class="min-h-screen flex justify-center items-center bg-gray-50">
<div class="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-green-500"></div>
</div>
<!-- Error State -->
<div v-else-if="error || !activity"
class="min-h-screen flex flex-col justify-center items-center bg-gray-50 px-4">
<div class="text-red-500 text-6xl mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h1 class="text-2xl font-bold mb-2">出错了</h1>
<p class="text-gray-600 mb-6">{{ error || '无法加载活动信息' }}</p>
<Button @click="$router.push('/')" variant="primary">返回首页</Button>
</div>
<!-- Registration Closed State -->
<div v-else-if="!isRegistrationOpen"
class="min-h-screen flex flex-col justify-center items-center bg-gray-50 px-4">
<div class="text-yellow-500 text-6xl mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h1 class="text-2xl font-bold mb-2">报名已截止</h1>
<p class="text-gray-600 mb-6">该活动的报名时间为 {{ formatDate(activity.registration_start) }} 至 {{
formatDate(activity.registration_end) }}</p>
<Button @click="$router.push(`/activity/${activityId}`)" variant="primary">查看活动详情</Button>
</div>
<!-- Full State -->
<div v-else-if="isFull" class="min-h-screen flex flex-col justify-center items-center bg-gray-50 px-4">
<div class="text-blue-500 text-6xl mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<h1 class="text-2xl font-bold mb-2">名额已满</h1>
<p class="text-gray-600 mb-6">该活动名额已满,请关注其他精彩活动</p>
<div class="flex space-x-4">
<Button @click="$router.push(`/activity/${activityId}`)" variant="secondary">查看活动详情</Button>
<Button @click="$router.push('/')" variant="primary">浏览更多活动</Button>
</div>
</div>
<!-- Registration Form -->
<div v-else class="py-8">
<div class="container mx-auto px-4">
<div class="max-w-3xl mx-auto bg-white rounded-lg shadow-sm overflow-hidden">
<!-- Header -->
<div class="bg-gradient-to-r from-green-500 to-blue-500 px-6 py-4">
<h1 class="text-2xl font-bold text-white">活动报名</h1>
</div>
<!-- Activity Info -->
<div class="border-b border-gray-200 bg-gray-50 px-6 py-4">
<h2 class="text-xl font-semibold text-gray-800 mb-2">{{ activity.title }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-gray-600">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500 mr-2" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span>活动时间:{{ formatDate(activity.start_time) }}</span>
</div>
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500 mr-2" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span>{{ activity.activity_type === 'online' ? '线上活动' : `活动地点:${activity.location}`
}}</span>
</div>
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500 mr-2" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
</svg>
<span>已报名:{{ activity.participant_count }}/{{ activity.max_participants }}</span>
</div>
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500 mr-2" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
</svg>
<span>报名截止:{{ formatDate(activity.registration_end) }}</span>
</div>
</div>
</div>
<!-- Registration Form -->
<form class="p-6" @submit.prevent="handleSubmit">
<h3 class="text-lg font-medium text-gray-900 mb-4">填写报名信息</h3>
<div class="space-y-6">
<Input id="name" label="姓名" v-model="formState.name" :error="formErrors.name"
placeholder="请输入您的姓名" required />
<Input id="phone" label="手机号码" v-model="formState.phone" :error="formErrors.phone"
placeholder="请输入您的手机号码" required />
<Input id="email" label="电子邮箱" type="email" v-model="formState.email"
:error="formErrors.email" placeholder="请输入您的电子邮箱" required />
<div>
<label for="reason" class="block text-sm font-medium text-gray-700 mb-1">
参加原因 <span class="text-red-500">*</span>
</label>
<textarea id="reason" v-model="formState.reason" rows="4" :class="[
'block w-full border-gray-300 rounded-md shadow-sm focus:ring-green-500 focus:border-green-500 sm:text-sm',
formErrors.reason ? 'border-red-300' : ''
]" placeholder="请简要说明您参加本次活动的原因或期望..."></textarea>
<p v-if="formErrors.reason" class="mt-1 text-sm text-red-600">{{ formErrors.reason }}
</p>
</div>
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="agreeTerms" type="checkbox" v-model="formState.agreeTerms"
class="focus:ring-green-500 h-4 w-4 text-green-600 border-gray-300 rounded" />
</div>
<div class="ml-3 text-sm">
<label for="agreeTerms"
:class="['font-medium', formErrors.agreeTerms ? 'text-red-600' : 'text-gray-700']">
我同意活动条款和隐私政策
</label>
<p class="text-gray-500">
注册即表示您同意我们的
<a href="#" class="text-green-600 hover:text-green-500">服务条款</a>和
<a href="#" class="text-green-600 hover:text-green-500">隐私政策</a>。
</p>
<p v-if="formErrors.agreeTerms" class="mt-1 text-sm text-red-600">{{
formErrors.agreeTerms }}</p>
</div>
</div>
</div>
<div class="mt-8 flex justify-between">
<Button type="button" variant="secondary" @click="$router.push(`/activity/${activityId}`)">
返回活动详情
</Button>
<Button type="submit" variant="primary" :disabled="isSubmitting">
{{ isSubmitting ? '提交中...' : '确认报名' }}
</Button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAppStore } from '../stores/app'
import Button from '../components/shared/Button.vue'
import Input from '../components/shared/Input.vue'
const route = useRoute()
const router = useRouter()
const store = useAppStore()
const activityId = route.params.activityId
const activity = ref(null)
const loading = ref(true)
const error = ref(null)
const isSubmitting = ref(false)
const formState = ref({
name: '',
phone: '',
email: '',
reason: '',
agreeTerms: false
})
const formErrors = ref({})
// Fetch activity details
onMounted(async () => {
try {
if (store.activities.length > 0) {
const foundActivity = store.getActivityById(activityId)
if (foundActivity) {
activity.value = foundActivity
// Pre-fill form with user data if available
if (store.currentUser) {
formState.value = {
...formState.value,
name: store.currentUser.name || '',
phone: store.currentUser.phone || '',
email: store.currentUser.email || ''
}
}
} else {
error.value = '未找到活动信息'
}
}
loading.value = false
} catch (err) {
console.error('Failed to fetch activity details:', err)
error.value = '加载活动详情失败'
loading.value = false
}
})
// Validate form
const validateForm = () => {
const errors = {}
if (!formState.value.name.trim()) {
errors.name = '请输入您的姓名'
}
if (!formState.value.phone.trim()) {
errors.phone = '请输入您的手机号码'
} else if (!/^1[3-9]\d{9}$/.test(formState.value.phone)) {
errors.phone = '请输入有效的手机号码'
}
if (!formState.value.email.trim()) {
errors.email = '请输入您的电子邮箱'
} else if (!/\S+@\S+\.\S+/.test(formState.value.email)) {
errors.email = '请输入有效的电子邮箱'
}
if (!formState.value.reason.trim()) {
errors.reason = '请简要说明参加原因'
}
if (!formState.value.agreeTerms) {
errors.agreeTerms = '请同意活动条款和隐私政策'
}
formErrors.value = errors
return Object.keys(errors).length === 0
}
// Handle form submission
const handleSubmit = async () => {
if (!store.currentUser) {
alert('请先登录再进行报名')
// Redirect to login page in a real app
return
}
if (!validateForm()) {
// Focus on first error field
const firstErrorField = Object.keys(formErrors.value)[0]
const element = document.getElementById(firstErrorField)
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
return
}
isSubmitting.value = true
try {
const registrationData = {
fields: {
姓名: true,
手机号: true,
邮箱: true,
参加原因: true
},
answers: {
姓名: formState.value.name,
手机号: formState.value.phone,
邮箱: formState.value.email,
参加原因: formState.value.reason
}
}
const result = await store.registerForActivity(activityId, registrationData)
if (result.success) {
alert('报名成功,请等待审核')
router.push(`/activity/${activityId}`)
} else {
alert(`报名失败: ${result.error}`)
isSubmitting.value = false
}
} catch (error) {
console.error('Registration error:', error)
alert('报名失败,请稍后再试')
isSubmitting.value = false
}
}
// Format date for display
const formatDate = (dateString) => {
if (!dateString) return ''
const date = new Date(dateString.replace(' ', 'T'))
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
}).format(date)
}
// Check if registration is still open
const isRegistrationOpen = computed(() => {
if (!activity.value) return false
return (
new Date() >= new Date(activity.value.registration_start.replace(' ', 'T')) &&
new Date() <= new Date(activity.value.registration_end.replace(' ', 'T'))
)
})
// Check if activity is full
const isFull = computed(() => {
if (!activity.value) return false
return activity.value.participant_count >= activity.value.max_participants
})
</script>
<template>
<div class="min-h-screen bg-gray-50">
<!-- Loading UI if no current user -->
<div v-if="!currentUser" class="min-h-screen flex justify-center items-center bg-gray-50">
<div class="text-center">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-green-500 mx-auto"></div>
<p class="mt-4 text-gray-600">加载用户资料...</p>
</div>
</div>
<template v-else>
<!-- Profile Header -->
<div class="bg-gradient-to-r from-green-500 to-blue-500 py-12 px-4">
<div class="container mx-auto">
<div class="flex flex-col md:flex-row items-center justify-between">
<!-- User Info -->
<div class="flex flex-col md:flex-row items-center">
<div
class="h-24 w-24 md:h-32 md:w-32 rounded-full overflow-hidden border-4 border-white mb-4 md:mb-0 md:mr-6">
<img :src="currentUser.avatar || '/assets/images/avatars/default_avatar.png'"
:alt="currentUser.name" class="h-full w-full object-cover" />
</div>
<div class="text-center md:text-left">
<h1 class="text-2xl md:text-3xl font-bold text-white">{{ currentUser.name }}</h1>
<p class="text-white mt-2 opacity-90">@{{ currentUser.username }}</p>
<div class="flex flex-wrap justify-center md:justify-start mt-3 gap-2">
<span v-for="(interest, index) in displayedInterests" :key="index"
class="bg-white bg-opacity-20 text-white px-3 py-1 rounded-full text-sm">
{{ interest }}
</span>
<span v-if="currentUser.interests && currentUser.interests.length > 3"
class="bg-white bg-opacity-20 text-white px-3 py-1 rounded-full text-sm">
+{{ currentUser.interests.length - 3 }}
</span>
</div>
</div>
</div>
<!-- Actions -->
<div class="mt-6 md:mt-0 flex gap-2">
<Button @click="isEditModalOpen = true" variant="secondary">
<template #leftIcon>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
fill="currentColor">
<path
d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</template>
编辑资料
</Button>
</div>
</div>
</div>
</div>
<!-- Profile Content -->
<div class="container mx-auto py-8 px-4">
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
<Tabs :tabs="profileTabs" />
</div>
</div>
<!-- Edit Profile Modal -->
<Modal v-model:show="isEditModalOpen" title="编辑个人资料">
<div class="space-y-4">
<Input v-model="editForm.name" label="姓名" placeholder="请输入姓名" />
<Input v-model="editForm.email" label="电子邮箱" type="email" placeholder="请输入电子邮箱" />
<Input v-model="editForm.phone" label="手机号码" placeholder="请输入手机号码" />
<Input v-model="editForm.location" label="所在地" placeholder="请输入所在地" />
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">个人简介</label>
<textarea v-model="editForm.bio" rows="4"
class="w-full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-500"
placeholder="请输入个人简介"></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">兴趣标签</label>
<div class="flex flex-wrap gap-2 mb-2">
<span v-for="interest in editForm.interests" :key="interest"
class="bg-green-50 text-green-700 px-3 py-1 rounded-full text-sm flex items-center">
{{ interest }}
<button @click="handleRemoveInterest(interest)"
class="ml-2 text-green-500 hover:text-green-700">
×
</button>
</span>
</div>
<div class="flex gap-2">
<Input v-model="interestInput" placeholder="添加兴趣标签" @keyup.enter="handleAddInterest" />
<Button @click="handleAddInterest" variant="secondary">添加</Button>
</div>
</div>
</div>
<template #footer>
<div class="flex justify-end gap-2">
<Button @click="isEditModalOpen = false" variant="ghost">取消</Button>
<Button @click="handleSubmitEdit" variant="primary">保存</Button>
</div>
</template>
</Modal>
</template>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useAppStore } from '../stores/app'
import Button from '../components/shared/Button.vue'
import Input from '../components/shared/Input.vue'
import Tabs from '../components/shared/Tabs.vue'
import ActivityCard from '../components/shared/ActivityCard.vue'
import Modal from '../components/shared/Modal.vue'
const store = useAppStore()
const { currentUser, activities, registrations } = store
const userRegistrations = ref([])
const registeredActivities = ref([])
const pastActivities = ref([])
const upcomingActivities = ref([])
const isEditModalOpen = ref(false)
const editForm = ref({
name: '',
bio: '',
email: '',
phone: '',
location: '',
interests: []
})
const interestInput = ref('')
// Computed properties
const displayedInterests = computed(() => {
return currentUser.value?.interests?.slice(0, 3) || []
})
// Methods
const handleInputChange = (e) => {
const { name, value } = e.target
editForm.value[name] = value
}
const handleAddInterest = () => {
const interest = interestInput.value.trim()
if (interest && !editForm.value.interests.includes(interest)) {
editForm.value.interests.push(interest)
interestInput.value = ''
}
}
const handleRemoveInterest = (interestToRemove) => {
editForm.value.interests = editForm.value.interests.filter(
interest => interest !== interestToRemove
)
}
const handleSubmitEdit = () => {
// In a real app, this would make an API call to update the user profile
console.log('Profile update submitted:', editForm.value)
isEditModalOpen.value = false
// Update would be handled through context in a real app
}
const formatRegistrationDate = (dateString) => {
const date = new Date(dateString.replace(' ', 'T'))
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
}).format(date)
}
// Profile tabs configuration
const profileTabs = computed(() => [
{
label: "个人资料",
content: {
component: 'div',
class: 'p-6',
children: [
// Basic Info section
{
component: 'div',
class: 'grid grid-cols-1 md:grid-cols-2 gap-8',
children: [
// Left column
{
component: 'div',
children: [
{
component: 'h3',
class: 'text-lg font-medium text-gray-900 mb-4',
text: '基本信息'
},
// User info fields
{
component: 'div',
class: 'space-y-4',
children: [
// Name
{
component: 'div',
children: [
{
component: 'h4',
class: 'text-sm font-medium text-gray-500',
text: '姓名'
},
{
component: 'p',
class: 'mt-1 text-gray-900',
text: currentUser.value?.name
}
]
},
// Location
{
component: 'div',
children: [
{
component: 'h4',
class: 'text-sm font-medium text-gray-500',
text: '所在地'
},
{
component: 'p',
class: 'mt-1 text-gray-900',
text: currentUser.value?.location || '未设置'
}
]
}
]
}
]
},
// Right column
{
component: 'div',
children: [
{
component: 'h3',
class: 'text-lg font-medium text-gray-900 mb-4',
text: '联系方式'
},
// Contact info
{
component: 'div',
class: 'space-y-4',
children: [
// Email
{
component: 'div',
children: [
{
component: 'h4',
class: 'text-sm font-medium text-gray-500',
text: '电子邮箱'
},
{
component: 'p',
class: 'mt-1 text-gray-900',
text: currentUser.value?.email
}
]
},
// Phone
{
component: 'div',
children: [
{
component: 'h4',
class: 'text-sm font-medium text-gray-500',
text: '手机号码'
},
{
component: 'p',
class: 'mt-1 text-gray-900',
text: currentUser.value?.phone
}
]
}
]
}
]
}
]
}
]
}
},
{
label: "我的活动",
content: {
component: 'div',
class: 'p-6',
children: [
// Upcoming activities section
{
component: 'div',
class: 'mb-8',
children: [
{
component: 'h3',
class: 'text-lg font-medium text-gray-900 mb-4',
children: [
{
component: 'span',
class: 'inline-block w-3 h-3 bg-green-500 rounded-full mr-2'
},
{
component: 'span',
text: '即将参加的活动'
}
]
},
// Activity cards
{
component: 'div',
class: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6',
children: upcomingActivities.value.map(activity => ({
component: ActivityCard,
props: { activity }
}))
}
]
}
]
}
},
{
label: "报名记录",
content: {
component: 'div',
class: 'p-6',
children: [
{
component: 'div',
class: 'overflow-x-auto',
children: [
// Registration table
{
component: 'table',
class: 'min-w-full divide-y divide-gray-200',
children: [
// Table header
{
component: 'thead',
class: 'bg-gray-50',
children: [
{
component: 'tr',
children: [
{
component: 'th',
class: 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider',
text: '活动信息'
},
{
component: 'th',
class: 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider',
text: '报名时间'
},
{
component: 'th',
class: 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider',
text: '状态'
}
]
}
]
},
// Table body
{
component: 'tbody',
class: 'bg-white divide-y divide-gray-200',
children: userRegistrations.value.map(registration => ({
component: 'tr',
key: registration.id,
children: [
// Activity info
{
component: 'td',
class: 'px-6 py-4 whitespace-nowrap',
children: [
{
component: 'div',
class: 'flex items-center',
children: [
{
component: 'div',
class: 'text-sm font-medium text-gray-900',
text: activities.value.find(a => a.id === registration.activity_id)?.title || '未知活动'
}
]
}
]
},
// Registration time
{
component: 'td',
class: 'px-6 py-4 whitespace-nowrap',
children: [
{
component: 'div',
class: 'text-sm text-gray-900',
text: formatRegistrationDate(registration.registration_time)
}
]
},
// Status
{
component: 'td',
class: 'px-6 py-4 whitespace-nowrap',
children: [
{
component: 'span',
class: `inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${registration.status === 'approved'
? 'bg-green-100 text-green-800'
: registration.status === 'pending'
? 'bg-yellow-100 text-yellow-800'
: 'bg-gray-100 text-gray-800'
}`,
text: registration.status === 'approved'
? '已通过'
: registration.status === 'pending'
? '审核中'
: registration.status
}
]
}
]
}))
}
]
}
]
}
]
}
}
])
// Lifecycle hooks
onMounted(() => {
if (currentUser.value) {
editForm.value = {
name: currentUser.value.name || '',
bio: currentUser.value.bio || '',
email: currentUser.value.email || '',
phone: currentUser.value.phone || '',
location: currentUser.value.location || '',
interests: currentUser.value.interests || []
}
}
if (currentUser.value && registrations.value && activities.value) {
// Filter user registrations
const userRegs = registrations.value.filter(reg => reg.user_id === currentUser.value.id)
userRegistrations.value = userRegs
// Get activities the user has registered for
const regActivityIds = userRegs.map(reg => reg.activity_id)
const regActivities = activities.value.filter(act => regActivityIds.includes(act.id))
registeredActivities.value = regActivities
const now = new Date()
// Split activities into past and upcoming
pastActivities.value = regActivities.filter(act => {
return new Date(act.end_time.replace(' ', 'T')) < now
})
upcomingActivities.value = regActivities.filter(act => {
return new Date(act.start_time.replace(' ', 'T')) > now
})
}
})
</script>
<!--
* @Date: 2025-04-17 13:41:17
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-04-17 13:41:18
* @FilePath: /reading-club-app/src/providers/AppProvider.vue
* @Description: 文件描述
-->
<template>
<slot></slot>
</template>
<script setup>
import { onMounted } from 'vue'
import { useAppStore } from '../stores/app'
const store = useAppStore()
onMounted(() => {
store.fetchInitialData()
})
</script>
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useAppStore = defineStore('app', () => {
const currentUser = ref(null)
const activities = ref([])
const loading = ref(true)
const error = ref(null)
const registrations = ref([])
const userMessages = ref([])
// 初始化应用数据
async function fetchInitialData() {
try {
loading.value = true
// 获取用户数据
const usersResponse = await fetch('/data/users.json')
const usersData = await usersResponse.json()
currentUser.value = usersData[0]
// 获取活动数据
const activitiesResponse = await fetch('/data/activities.json')
const activitiesData = await activitiesResponse.json()
activities.value = activitiesData
// 获取报名数据
const registrationsResponse = await fetch('/data/registrations.json')
const registrationsData = await registrationsResponse.json()
registrations.value = registrationsData
// 获取消息数据
const messagesResponse = await fetch('/data/messages.json')
const messagesData = await messagesResponse.json()
userMessages.value = messagesData
loading.value = false
} catch (err) {
console.error('Failed to fetch initial data:', err)
error.value = '加载应用数据失败,请稍后重试。'
loading.value = false
}
}
// 根据ID获取活动
const getActivityById = computed(() => {
return (activityId) => activities.value.find(activity => activity.id === activityId) || null
})
// 获取用户的报名记录
const getUserRegistrations = computed(() => {
return () => currentUser.value ? registrations.value.filter(reg => reg.user_id === currentUser.value.id) : []
})
// 报名活动
function registerForActivity(activityId, formData) {
if (!currentUser.value) return { success: false, error: '用户未登录' }
const newRegistration = {
id: `R${Math.floor(Math.random() * 10000).toString().padStart(4, '0')}`,
activity_id: activityId,
user_id: currentUser.value.id,
registration_time: new Date().toISOString().replace('T', ' ').substring(0, 19),
status: 'pending',
custom_fields: formData.fields,
custom_answers: formData.answers
}
registrations.value.push(newRegistration)
return { success: true, registrationId: newRegistration.id }
}
// 创建新活动
function createActivity(activityData) {
if (!currentUser.value) return { success: false, error: '用户未登录' }
const newActivity = {
id: `A${Math.floor(Math.random() * 10000).toString().padStart(4, '0')}`,
organizer_id: currentUser.value.id,
organizer_name: currentUser.value.name,
...activityData,
participant_count: 0,
is_published: activityData.is_published || true,
is_public: activityData.is_public || true,
}
activities.value.push(newActivity)
return { success: true, activityId: newActivity.id }
}
// 活动签到
function checkInForActivity(activityId, registrationId, method = 'manual') {
if (!currentUser.value) return { success: false, error: '用户未登录' }
const checkinData = {
id: `C${Math.floor(Math.random() * 10000).toString().padStart(4, '0')}`,
activity_id: activityId,
user_id: currentUser.value.id,
registration_id: registrationId,
checkin_time: new Date().toISOString().replace('T', ' ').substring(0, 19),
checkin_type: method,
status: 'successful',
is_late: false
}
return { success: true, checkin: checkinData }
}
// 获取用户消息
const getUserMessages = computed(() => {
return () => currentUser.value ? userMessages.value.filter(msg => msg.recipient_id === currentUser.value.id) : []
})
// 切换消息已读状态
function toggleMessageReadStatus(messageId) {
userMessages.value = userMessages.value.map(msg =>
msg.id === messageId
? { ...msg, read_status: !msg.read_status }
: msg
)
}
return {
currentUser,
activities,
loading,
error,
registrations,
userMessages,
fetchInitialData,
getActivityById,
getUserRegistrations,
registerForActivity,
createActivity,
checkInForActivity,
getUserMessages,
toggleMessageReadStatus
}
})
// Mock API functions to fetch data from JSON files
// Fetch activities from mock data
export const fetchActivities = async () => {
try {
const response = await fetch('/data/activities.json');
if (!response.ok) throw new Error('Failed to fetch activities');
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching activities:', error);
throw error;
}
};
// Fetch a single activity by ID
export const fetchActivityById = async (activityId) => {
try {
const response = await fetch('/data/activities.json');
if (!response.ok) throw new Error('Failed to fetch activity');
const activities = await response.json();
const activity = activities.find(act => act.id === activityId);
if (!activity) throw new Error('Activity not found');
return activity;
} catch (error) {
console.error(`Error fetching activity ${activityId}:`, error);
throw error;
}
};
// Fetch users data
export const fetchUsers = async () => {
try {
const response = await fetch('/data/users.json');
if (!response.ok) throw new Error('Failed to fetch users');
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching users:', error);
throw error;
}
};
// Fetch a single user by ID
export const fetchUserById = async (userId) => {
try {
const response = await fetch('/data/users.json');
if (!response.ok) throw new Error('Failed to fetch user');
const users = await response.json();
const user = users.find(u => u.id === userId);
if (!user) throw new Error('User not found');
return user;
} catch (error) {
console.error(`Error fetching user ${userId}:`, error);
throw error;
}
};
// Fetch registrations data
export const fetchRegistrations = async (activityId = null, userId = null) => {
try {
const response = await fetch('/data/registrations.json');
if (!response.ok) throw new Error('Failed to fetch registrations');
let registrations = await response.json();
// Filter by activity ID if provided
if (activityId) {
registrations = registrations.filter(reg => reg.activity_id === activityId);
}
// Filter by user ID if provided
if (userId) {
registrations = registrations.filter(reg => reg.user_id === userId);
}
return registrations;
} catch (error) {
console.error('Error fetching registrations:', error);
throw error;
}
};
// Fetch check-in data
export const fetchCheckins = async (activityId = null, userId = null) => {
try {
const response = await fetch('/data/checkins.json');
if (!response.ok) throw new Error('Failed to fetch check-ins');
let checkins = await response.json();
// Filter by activity ID if provided
if (activityId) {
checkins = checkins.filter(check => check.activity_id === activityId);
}
// Filter by user ID if provided
if (userId) {
checkins = checkins.filter(check => check.user_id === userId);
}
return checkins;
} catch (error) {
console.error('Error fetching check-ins:', error);
throw error;
}
};
// Fetch messages data
export const fetchMessages = async (recipientId = null) => {
try {
const response = await fetch('/data/messages.json');
if (!response.ok) throw new Error('Failed to fetch messages');
let messages = await response.json();
// Filter by recipient ID if provided
if (recipientId) {
messages = messages.filter(msg => msg.recipient_id === recipientId);
}
return messages;
} catch (error) {
console.error('Error fetching messages:', error);
throw error;
}
};
// Mock function for creating an activity (would be an API POST in a real app)
export const createActivity = async (activityData) => {
// In a real app, this would send a POST request
console.log('Creating activity with data:', activityData);
return {
success: true,
id: `A${Math.floor(Math.random() * 10000).toString().padStart(4, '0')}`,
...activityData
};
};
// Mock function for registering to an activity
export const registerForActivity = async (activityId, userData) => {
// In a real app, this would send a POST request
console.log(`Registering for activity ${activityId} with data:`, userData);
return {
success: true,
id: `R${Math.floor(Math.random() * 10000).toString().padStart(4, '0')}`,
activity_id: activityId,
...userData
};
};
// Mock function for checking in to an activity
export const checkInForActivity = async (registrationId, checkinData) => {
// In a real app, this would send a POST request
console.log(`Checking in for registration ${registrationId} with data:`, checkinData);
return {
success: true,
id: `C${Math.floor(Math.random() * 10000).toString().padStart(4, '0')}`,
registration_id: registrationId,
...checkinData
};
};
\ No newline at end of file
/*
* @Date: 2025-04-16 22:22:18
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-04-17 14:09:27
* @FilePath: /reading-club-app/tailwind.config.js
* @Description: 文件描述
*/
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
theme: {
extend: {
colors: {
primary: '#4caf50',
secondary: '#2196f3',
'background-start': '#f0f9ef',
'background-end': '#e6f4ff',
'text-base': '#333333',
'text-light': '#6e6e6e'
}
},
},
plugins: [],
};
{
"description": " - This is a template for general web app, game, common application,client,tool,component development. It is based on React framework with Tailwind CSS. The template includes the basic structure of a React project, including an index.html file and a src directory with an App.jsx file.\n - Modify index.html to change web title, create new jsx files under src if needed, and rewrite src/App.jsx to meet the user's requirements. You should NOT modify src/main.jsx and src/index.css. Modify vite.config.js only if extra config is absolutely necessary.\n - Style your elements with Tailwind CSS classes directly in the jsx files.\n - If the user's requirements do not specify a technology stack, development language, development tools, components, etc., this template will be used by default. For example, developing an XXX application, XXX client, etc. all use this template.",
"required_fields": [],
"required_files": [
"index.html",
"src/main.jsx",
"src/App.jsx",
"src/index.css",
"vite.config.js"
],
"lang": "JavaScript",
"framework": "React",
"style": "react_template",
"scene": "default_web_project"
}
/*
* @Date: 2025-04-16 22:22:18
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-04-17 14:11:13
* @FilePath: /reading-club-app/vite.config.js
* @Description: 文件描述
*/
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
vueJsx(),
],
})