+ {{ record.response_text || record.error_message || '无响应内容' }} +
++ 触发方式:{{ statusLabel(record.trigger_type) }} +
+diff --git a/.env.example b/.env.example index 9da157c..5b921d7 100644 --- a/.env.example +++ b/.env.example @@ -60,4 +60,3 @@ TOKEN_CHECK_INTERVAL_MINUTES=30 # 会话数据清理间隔(小时) SESSION_CLEANUP_INTERVAL_HOURS=24 - diff --git a/.gitignore b/.gitignore index f827d11..6a76f8c 100644 --- a/.gitignore +++ b/.gitignore @@ -53,9 +53,10 @@ Thumbs.db apps/frontend/node_modules/ apps/frontend/dist/ apps/frontend/.vite/ -apps/new-frontend/node_modules/ -apps/new-frontend/dist/ -apps/new-frontend/.vite/ + +# 本地验证产物 +.playwright-cli/ +output/ .claude .codex diff --git a/README.md b/README.md index 318fd22..23613e2 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ ## 技术栈 **后端**: FastAPI + SQLAlchemy + APScheduler + Selenium -**前端**: Vue 3 + Ant Design Vue + Pinia +**前端**: Vue 3 + TypeScript + shadcn-vue + Tailwind **数据库**: SQLite ## 快速开始 @@ -28,7 +28,8 @@ - Python 3.9+ - uv -- Node.js 16+ +- Node.js 20+ +- pnpm - Chrome 浏览器 ### 安装运行 @@ -40,8 +41,8 @@ uv run python main.py backend # 前端 cd apps/frontend -npm install -npm run dev +pnpm install +pnpm dev # 创建管理员 uv run python apps/backend/scripts/create_admin.py diff --git a/apps/frontend/.env.development b/apps/frontend/.env.development deleted file mode 100644 index 6d45ba0..0000000 --- a/apps/frontend/.env.development +++ /dev/null @@ -1,2 +0,0 @@ -# API Base URL (Development) -VITE_API_BASE_URL=http://localhost:8000 diff --git a/apps/frontend/.env.production b/apps/frontend/.env.production deleted file mode 100644 index 7d536cd..0000000 --- a/apps/frontend/.env.production +++ /dev/null @@ -1,3 +0,0 @@ -# API Base URL (Production) -# 留空,让 API 请求使用相对路径(由 Nginx 转发) -VITE_API_BASE_URL= diff --git a/apps/frontend/.prettierignore b/apps/frontend/.prettierignore index d70bb9c..661aec8 100644 --- a/apps/frontend/.prettierignore +++ b/apps/frontend/.prettierignore @@ -1,4 +1,4 @@ -node_modules dist -.DS_Store -*.local +node_modules +pnpm-lock.yaml +components.json diff --git a/apps/frontend/.prettierrc b/apps/frontend/.prettierrc deleted file mode 100644 index 4fe916c..0000000 --- a/apps/frontend/.prettierrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "semi": true, - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "es5", - "printWidth": 100, - "arrowParens": "avoid", - "endOfLine": "auto" -} diff --git a/apps/new-frontend/.prettierrc.json b/apps/frontend/.prettierrc.json similarity index 100% rename from apps/new-frontend/.prettierrc.json rename to apps/frontend/.prettierrc.json diff --git a/apps/frontend/README.md b/apps/frontend/README.md new file mode 100644 index 0000000..06bb320 --- /dev/null +++ b/apps/frontend/README.md @@ -0,0 +1,26 @@ +# CheckIn App Frontend + +Vue 3 + TypeScript frontend for CheckIn App. This is the supported frontend source tree. + +## Commands + +```bash +pnpm install +pnpm dev +pnpm lint:check +pnpm test +pnpm build +``` + +The development server runs on port `3000` and proxies `/api` to `http://127.0.0.1:8000`. + +## Structure + +- `src/app/`: auth state, router, theme state +- `src/api/`: typed API helpers and fetch client +- `src/components/ui/`: shadcn-vue primitives +- `src/components/templates/`: structured template field editor +- `src/views/`: route-owned pages +- `src/style.css`: Tailwind v4 and shadcn token setup + +Prefer shadcn-vue primitives, lucide icons, semantic tokens, and the local helpers in `src/components/ui.ts` for repeated surfaces. Keep route-local Tailwind where it expresses one-off layout or dense operational structure. diff --git a/apps/new-frontend/components.json b/apps/frontend/components.json similarity index 100% rename from apps/new-frontend/components.json rename to apps/frontend/components.json diff --git a/apps/frontend/eslint.config.js b/apps/frontend/eslint.config.js index 2dc6d8a..58078c1 100644 --- a/apps/frontend/eslint.config.js +++ b/apps/frontend/eslint.config.js @@ -1,38 +1,65 @@ -import js from '@eslint/js'; -import pluginVue from 'eslint-plugin-vue'; -import prettierConfig from '@vue/eslint-config-prettier'; +import js from '@eslint/js' +import prettierConfig from '@vue/eslint-config-prettier' +import tsParser from '@typescript-eslint/parser' +import pluginVue from 'eslint-plugin-vue' +import vueParser from 'vue-eslint-parser' + +const browserGlobals = { + AbortController: 'readonly', + DOMException: 'readonly', + Headers: 'readonly', + URL: 'readonly', + URLSearchParams: 'readonly', + console: 'readonly', + document: 'readonly', + fetch: 'readonly', + localStorage: 'readonly', + window: 'readonly', +} export default [ { - ignores: ['node_modules', 'dist', '*.local'], + ignores: ['dist/**', 'node_modules/**', 'coverage/**', '*.local', 'pnpm-lock.yaml'], }, js.configs.recommended, - ...pluginVue.configs['flat/recommended'], - prettierConfig, { + files: ['**/*.ts'], languageOptions: { - globals: { - // 浏览器环境 - window: 'readonly', - document: 'readonly', - localStorage: 'readonly', - console: 'readonly', - setTimeout: 'readonly', - clearTimeout: 'readonly', - setInterval: 'readonly', - clearInterval: 'readonly', - navigator: 'readonly', - // Node.js 环境(用于配置文件) - process: 'readonly', - __dirname: 'readonly', - }, + ecmaVersion: 'latest', + sourceType: 'module', + parser: tsParser, + globals: browserGlobals, }, rules: { - 'vue/multi-word-component-names': 'off', - 'vue/no-v-html': 'warn', - 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', - 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', - 'no-unused-vars': 'warn', + 'no-undef': 'off', }, }, -]; + ...pluginVue.configs['flat/recommended'], + { + files: ['**/*.vue'], + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + parser: vueParser, + parserOptions: { + parser: tsParser, + ecmaVersion: 'latest', + sourceType: 'module', + }, + globals: browserGlobals, + }, + rules: { + 'no-undef': 'off', + }, + }, + prettierConfig, + { + files: ['**/*.{js,ts,vue}'], + rules: { + 'no-console': 'warn', + 'no-unused-vars': 'off', + 'vue/multi-word-component-names': 'off', + 'vue/no-v-html': 'warn', + }, + }, +] diff --git a/apps/frontend/index.html b/apps/frontend/index.html index f60ac5a..33bd005 100644 --- a/apps/frontend/index.html +++ b/apps/frontend/index.html @@ -1,14 +1,13 @@ - +
-输入JSON对象,会自动序列化为字符串
-如:{"key1":value1,"key2":value2}
- - 用户未填写时使用此值 - -用户必须填写此字段
-直接使用默认值,不在表单中显示
-- 隐藏字段将自动使用默认值,不会在创建任务表单中显示。请确保设置了合适的默认值。 -
- -- 💡 提示:显示文本是用户看到的内容,选项值是实际保存的数据 -
-数组为空
-对象为空
-正在获取二维码...
-请使用手机 QQ 扫描二维码登录
-{{ countdown }}s
-登录成功!
-二维码已过期
-{{ errorMessage }}
-- {{ description || '当前没有内容可显示' }} -
- - -{{ label }}
-- {{ formattedValue }} -
-- {{ subtitle }} -
-- {{ loginMode === 'qrcode' ? 'QQ 扫码登录/注册' : '用户名密码登录' }} -
-当前地址没有对应的新前端页面。
+ ++ 当前账号 + {{ auth.state.user?.alias ?? '未知用户' }} 已完成登录,但还需要管理员审批后才能访问工作台。 +
++ {{ record.response_text || record.error_message || '无响应内容' }} +
++ 触发方式:{{ statusLabel(record.trigger_type) }} +
+管理您的自动打卡任务
-总任务数
-{{ taskStore.taskStats.total }}
-启用中
-- {{ taskStore.taskStats.active }} -
-已禁用
-- {{ taskStore.taskStats.inactive }} -
-- 点击右上角的"创建任务"按钮开始添加您的第一个打卡任务 -
-任务 ID: {{ task.id }}
-加载模板中...
-暂无可用模板
-请联系管理员创建模板
-- {{ template.description || '无描述' }} -
-- 💡 此配置由模板自动生成,如需修改请删除任务后从模板重新创建 -
-JSON 映射架构 - 配置即结构
-创建第一个模板,让用户更轻松地创建打卡任务
- -- {{ template.description || '无描述' }} -
- - {{ template.is_active ? '已启用' : '已禁用' }} - -- 配置即结构:模板配置完全映射到生成的 Payload 结构 -
-字段名保持原样:不进行任何大小写转换
-ThreadId 由用户填写,无需在模板中配置
- -点击上方"添加字段"开始配置模板
-{{ JSON.stringify(formData.field_config, null, 2) }}
- {{ JSON.stringify(previewData.preview_payload, null, 2) }}
- {{ JSON.stringify(previewData.field_config, null, 2) }}
-