diff --git a/index.html b/index.html
index c405e45..d3bb385 100644
--- a/index.html
+++ b/index.html
@@ -2,13 +2,46 @@
+
+
+
+
+
+
diff --git a/package.json b/package.json
index df5943a..0339bb7 100644
--- a/package.json
+++ b/package.json
@@ -22,8 +22,8 @@
"build:app-android": "uni build -p app-android",
"build:app-ios": "uni build -p app-ios",
"build:custom": "uni build -p",
- "build:h5": "uni build",
- "build:h5:ssr": "uni build --ssr",
+ "build:h5": "node scripts/update-version.js && uni build",
+ "build:h5:ssr": "node scripts/update-version.js && uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
diff --git a/scripts/update-version.js b/scripts/update-version.js
new file mode 100644
index 0000000..f5d0811
--- /dev/null
+++ b/scripts/update-version.js
@@ -0,0 +1,47 @@
+const fs = require('fs');
+const path = require('path');
+
+// 生成版本号(格式:YYYYMMDD-HHmmss)
+function generateVersion() {
+ const now = new Date();
+ const year = now.getFullYear();
+ const month = String(now.getMonth() + 1).padStart(2, '0');
+ const day = String(now.getDate()).padStart(2, '0');
+ const hours = String(now.getHours()).padStart(2, '0');
+ const minutes = String(now.getMinutes()).padStart(2, '0');
+ const seconds = String(now.getSeconds()).padStart(2, '0');
+ return `${year}${month}${day}-${hours}${minutes}${seconds}`;
+}
+
+// 更新 index.html 中的版本号
+function updateVersion() {
+ const htmlPath = path.join(__dirname, '../index.html');
+
+ if (!fs.existsSync(htmlPath)) {
+ console.error('❌ index.html 文件不存在:', htmlPath);
+ process.exit(1);
+ }
+
+ let html = fs.readFileSync(htmlPath, 'utf8');
+ const version = generateVersion();
+
+ // 替换版本号
+ if (html.includes('/,
+ ``
+ );
+ } else {
+ // 如果不存在版本号 meta 标签,在 charset 后面添加
+ html = html.replace(
+ //,
+ `\n \n `
+ );
+ }
+
+ fs.writeFileSync(htmlPath, html, 'utf8');
+ console.log(`✅ 版本号已更新为: ${version}`);
+}
+
+updateVersion();
+
diff --git a/src/api/base/wjApi.ts b/src/api/base/wjApi.ts
new file mode 100644
index 0000000..714ea7e
--- /dev/null
+++ b/src/api/base/wjApi.ts
@@ -0,0 +1,76 @@
+import { get, post } from "@/utils/request";
+
+/**
+ * 问卷相关API
+ */
+
+/**
+ * 根据ID获取问卷详情(包含验证权限)
+ */
+export const questionnaireFindByIdApi = async (params: { id: string }) => {
+ return await get(`/api/questionnaire/findById?id=${params.id}`);
+};
+
+/**
+ * 验证问卷填写权限
+ */
+export const questionnaireCheckPermissionApi = async (params: { questionnaireId: string }) => {
+ return await post("/api/questionnaire/checkPermission", params);
+};
+
+/**
+ * 获取问卷列表(分页)
+ */
+export const questionnaireFindPageApi = async (params: any) => {
+ return await get("/api/questionnaire/findPage", params);
+};
+
+/**
+ * 获取问卷题目列表
+ */
+export const questionnaireQuestionFindPageApi = async (params: any) => {
+ return await get("/api/questionnaireQuestion/findPage", params);
+};
+
+/**
+ * 保存问卷答案
+ */
+export const questionnaireAnswerSaveApi = async (params: any) => {
+ return await post("/api/questionnaireAnswer/save", params);
+};
+
+/**
+ * 查询用户是否已填写问卷
+ */
+export const questionnaireAnswerFindByUserApi = async (params: { questionnaireId: string }) => {
+ return await get("/api/questionnaireAnswer/findByUser", params);
+};
+
+/**
+ * 获取问卷填写人列表(分页)
+ */
+export const questionnaireAnswerFindPageApi = async (params: any) => {
+ return await get("/api/questionnaireAnswer/findPage", params);
+};
+
+/**
+ * 根据问卷ID获取填写人列表(按问卷ID和提交用户ID分组)
+ */
+export const questionnaireAnswerFindUserListApi = async (params: { questionnaireId: string }) => {
+ return await get("/api/questionnaireAnswer/findUserListByQuestionnaireId", params);
+};
+
+/**
+ * 根据问卷ID和用户ID获取问卷填写详情
+ */
+export const questionnaireAnswerFindDetailApi = async (params: { questionnaireId: string, userId?: string, answerId?: string }) => {
+ return await get("/api/questionnaireAnswer/findDetail", params);
+};
+
+/**
+ * 使用AI分析问卷内容
+ */
+export const questionnaireAnalyzeWithAIApi = async (params: any) => {
+ return await post("/api/questionnaireAnswer/analyzeWithAI", params);
+};
+
diff --git a/src/components/ImageVideoUpload/ImageVideoUpload.vue b/src/components/ImageVideoUpload/ImageVideoUpload.vue
new file mode 100644
index 0000000..4ca4d14
--- /dev/null
+++ b/src/components/ImageVideoUpload/ImageVideoUpload.vue
@@ -0,0 +1,1285 @@
+
+
+
+
+
+ 图片
+ ({{ imageList.length }}/{{ maxImageCount }})
+
+
+
+
+
+
+
+
+
+
+
+ 已压缩
+
+
+
+
+
+ 添加图片
+
+
+
+
+
+
+
+ 视频
+ ({{ videoList.length }}/{{ maxVideoCount }})
+
+
+
+
+
+
+
+
+
+
+ {{ formatDuration(video.duration) }}
+
+
+
+
+
+
+
+
+ {{ video.originalName || video.name }}
+ {{ formatFileSize(video.size) }}
+
+
+
+
+
+ 添加视频
+
+
+
+
+
+
+
+ 文件
+ ({{ fileList.length }}/{{ maxFileCount }})
+
+
+
+
+
+
+
+
+
+ {{ file.originalName || file.name }}
+ {{ formatFileSize(file.size) }}
+ {{ file.extension?.toUpperCase() || 'FILE' }}
+
+
+
+
+
+
+
+
+
+
+
+ 添加文件
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/ImageVideoUpload/index.ts b/src/components/ImageVideoUpload/index.ts
new file mode 100644
index 0000000..fcf56e1
--- /dev/null
+++ b/src/components/ImageVideoUpload/index.ts
@@ -0,0 +1,7 @@
+// 导出组件和类型
+export { default as ImageVideoUpload } from './ImageVideoUpload.vue'
+export * from './types'
+
+// 默认导出
+export { default } from './ImageVideoUpload.vue'
+
diff --git a/src/components/ImageVideoUpload/types.ts b/src/components/ImageVideoUpload/types.ts
new file mode 100644
index 0000000..1ef4420
--- /dev/null
+++ b/src/components/ImageVideoUpload/types.ts
@@ -0,0 +1,156 @@
+// 图片项接口
+export interface ImageItem {
+ tempPath?: string // 临时路径(用于预览)
+ url?: string // 服务器路径(上传成功后)
+ name?: string // 文件名(临时文件名或服务器文件名)
+ originalName?: string // 原始文件名(用户选择的真实文件名)
+ originalPath?: string // 原始路径(用于调试)
+ isCompressed?: boolean // 是否已压缩
+}
+
+// 视频项接口
+export interface VideoItem {
+ tempPath?: string // 临时路径(用于预览)
+ url?: string // 服务器路径(上传成功后)
+ name?: string // 文件名(临时文件名或服务器文件名)
+ originalName?: string // 原始文件名(用户选择的真实文件名)
+ duration?: number // 视频时长(秒)
+ size?: number // 文件大小(字节)
+ thumbnail?: string // 缩略图路径
+}
+
+// 文件项接口
+export interface FileItem {
+ tempPath?: string // 临时路径(用于预览)
+ url?: string // 服务器路径(上传成功后)
+ name?: string // 文件名(临时文件名或服务器文件名)
+ originalName?: string // 原始文件名(用户选择的真实文件名)
+ type?: string // 文件类型(image/video/audio/document等)
+ size?: number // 文件大小(字节)
+ extension?: string // 文件扩展名
+ mimeType?: string // MIME类型
+}
+
+// 压缩配置接口
+export interface CompressConfig {
+ image: {
+ quality: number // 压缩质量 (1-100)
+ maxWidth: number // 最大宽度
+ maxHeight: number // 最大高度
+ maxSize: number // 最大文件大小(字节)
+ minQuality: number // 最低压缩质量
+ }
+ video: {
+ maxDuration: number // 最大时长(秒)
+ maxSize: number // 最大文件大小(字节)
+ quality: string // 视频质量
+ }
+}
+
+// 组件Props接口
+export interface ImageVideoUploadProps {
+ // 图片相关
+ enableImage?: boolean
+ maxImageCount?: number
+ imageList?: ImageItem[]
+
+ // 视频相关
+ enableVideo?: boolean
+ maxVideoCount?: number
+ videoList?: VideoItem[]
+
+ // 文件相关
+ enableFile?: boolean
+ maxFileCount?: number
+ fileList?: FileItem[]
+ allowedFileTypes?: string[] // 允许的文件类型,如 ['pdf', 'doc', 'docx', 'mp3', 'wav']
+
+ // 压缩配置
+ compressConfig?: CompressConfig
+
+ // 上传配置
+ autoUpload?: boolean
+ uploadApi?: (file: any) => Promise
+}
+
+// 组件Emits接口
+export interface ImageVideoUploadEmits {
+ 'update:imageList': [images: ImageItem[]]
+ 'update:videoList': [videos: VideoItem[]]
+ 'update:fileList': [files: FileItem[]]
+ 'image-upload-success': [image: ImageItem, index: number]
+ 'image-upload-error': [error: any, index: number]
+ 'video-upload-success': [video: VideoItem, index: number]
+ 'video-upload-error': [error: any, index: number]
+ 'file-upload-success': [file: FileItem, index: number]
+ 'file-upload-error': [error: any, index: number]
+ 'upload-progress': [type: 'image' | 'video' | 'file', current: number, total: number]
+}
+
+// 默认压缩配置
+export const DEFAULT_COMPRESS_CONFIG: CompressConfig = {
+ image: {
+ quality: 60,
+ maxWidth: 1280,
+ maxHeight: 720,
+ maxSize: 200 * 1024, // 200KB
+ minQuality: 20
+ },
+ video: {
+ maxDuration: 60,
+ maxSize: 10 * 1024 * 1024, // 10MB
+ quality: 'medium'
+ }
+}
+
+// 预设压缩配置
+export const COMPRESS_PRESETS = {
+ // 高质量配置
+ high: {
+ image: {
+ quality: 80,
+ maxWidth: 1920,
+ maxHeight: 1080,
+ maxSize: 500 * 1024, // 500KB
+ minQuality: 40
+ },
+ video: {
+ maxDuration: 60,
+ maxSize: 20 * 1024 * 1024, // 20MB
+ quality: 'high'
+ }
+ },
+
+ // 平衡配置
+ medium: {
+ image: {
+ quality: 60,
+ maxWidth: 1280,
+ maxHeight: 720,
+ maxSize: 200 * 1024, // 200KB
+ minQuality: 20
+ },
+ video: {
+ maxDuration: 60,
+ maxSize: 10 * 1024 * 1024, // 10MB
+ quality: 'medium'
+ }
+ },
+
+ // 高压缩配置
+ low: {
+ image: {
+ quality: 40,
+ maxWidth: 960,
+ maxHeight: 540,
+ maxSize: 100 * 1024, // 100KB
+ minQuality: 15
+ },
+ video: {
+ maxDuration: 30,
+ maxSize: 5 * 1024 * 1024, // 5MB
+ quality: 'low'
+ }
+ }
+}
+
diff --git a/src/pages.json b/src/pages.json
index 162f32f..05769a3 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -150,7 +150,13 @@
"enablePullDownRefresh": false
}
},
-
+ {
+ "path": "pages/base/wj/indexwb",
+ "style": {
+ "navigationBarTitleText": "家长问卷",
+ "enablePullDownRefresh": false
+ }
+ },
{
"path": "pages/base/xk/index",
"style": {
diff --git a/src/pages/base/wj/indexwb.vue b/src/pages/base/wj/indexwb.vue
new file mode 100644
index 0000000..027619c
--- /dev/null
+++ b/src/pages/base/wj/indexwb.vue
@@ -0,0 +1,1761 @@
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+ 提交中,请稍候...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ { if (questionErrors[question.id]) questionErrors[question.id] = false; }"
+ type="text"
+ placeholder="请输入"
+ :maxlength="500"
+ class="input-field"
+ :class="{ 'input-field-error': questionErrors[question.id] }"
+ />
+
+
+
+
+
+
+
+
+ { if (questionErrors[question.id]) questionErrors[question.id] = false; }"
+ type="digit"
+ placeholder="请输入数字"
+ class="input-field"
+ :class="{ 'input-field-error': questionErrors[question.id] }"
+ />
+
+
+
+
+
+
+
+
+
+ {{ option.key }}.
+ {{ option.value }}
+
+
+
+
+
+
+
+
+
+ {{ getOptionText(question, answers[question.id].value) }}
+
+ 请选择
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ option.key }}.
+ {{ option.value }}
+
+
+
+
+
+
+
+
+ {{ answers[question.id].value }}
+ 请选择日期
+
+
+
+
+
+
+
+ onImageUploadSuccess(question.id, image, index)"
+ @file-upload-success="(file, index) => onFileUploadSuccess(question.id, file, index)"
+ />
+
+
+
+
+ onVideoUploadSuccess(question.id, video, index)"
+ />
+
+
+
+
+
+
+
+
+
+ 重新签名
+
+
+
+
+
+
+
+ 点击签名
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 重新签名
+
+
+
+
+
+
+
+ 点击签名
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交成功
+ {{ formatTime(new Date()) }}
+ 您已成功提交问卷
+
+
+
+
+
+
+
+ AI分析中,请稍候...
+
+
+
+
+
+
+ {{ analysisResult }}
+
+
+
+
+
+
+
+
+
+
+
+ 无填写权限
+ 抱歉,您没有填写此问卷的权限
+
+
+
+
+
+
+
+
+ 已填写
+ 您已经填写过此问卷
+
+
+
+
+
+
+
+
+ {{ timeInvalidMessage }}
+ {{ timeInvalidSubtitle }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/utils/filePreview.ts b/src/utils/filePreview.ts
new file mode 100644
index 0000000..904981e
--- /dev/null
+++ b/src/utils/filePreview.ts
@@ -0,0 +1,49 @@
+// 文件预览工具函数
+export const previewFile = (fileUrl: string, fileName: string, fileType: string) => {
+ return new Promise((resolve, reject) => {
+ const type = fileType.toLowerCase();
+
+ // 检查是否是压缩包文件,如果是则直接下载
+ if (['zip', 'rar', '7z', 'tar', 'gz'].includes(type)) {
+ uni.showToast({ title: '压缩包文件不支持预览,将为您下载', icon: 'none' });
+ downloadFile(fileUrl, fileName).then(resolve).catch(resolve);
+ return;
+ }
+
+ // 使用 kkview 预览
+ const previewUrl = fileUrl;
+ const needLandscape = ['ppt', 'pptx'].includes(type);
+
+ // 打开预览页面
+ uni.navigateTo({
+ url: `/pages/base/view/index?url=${encodeURIComponent(previewUrl)}&name=${encodeURIComponent(fileName)}&landscape=${needLandscape}`,
+ success: resolve,
+ fail: reject
+ });
+ });
+};
+
+// 下载文件
+const downloadFile = (fileUrl: string, fileName: string) => {
+ return new Promise((resolve, reject) => {
+ uni.downloadFile({
+ url: fileUrl,
+ success: (res) => {
+ if (res.statusCode === 200) {
+ uni.saveFile({
+ tempFilePath: res.tempFilePath,
+ success: (saveRes) => {
+ uni.showToast({ title: '文件已保存', icon: 'success' });
+ resolve(saveRes);
+ },
+ fail: reject
+ });
+ } else {
+ reject(new Error('下载失败'));
+ }
+ },
+ fail: reject
+ });
+ });
+};
+
diff --git a/vite.config.ts b/vite.config.ts
index b67c884..65f2541 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -17,6 +17,18 @@ export default defineConfig({
},
port: 5139,
},
+ build: {
+ // 确保文件名包含 hash,便于缓存失效控制
+ rollupOptions: {
+ output: {
+ entryFileNames: `assets/[name]-[hash].js`,
+ chunkFileNames: `assets/[name]-[hash].js`,
+ assetFileNames: `assets/[name]-[hash].[ext]`,
+ }
+ },
+ // 生成 manifest 方便版本追踪
+ manifest: true,
+ },
plugins: [
//c 为class 例如 class="wi-10"
//w 为样式style 例如 width:10rpx