Files
jindengchen-ai-report/cool-unix/pages/dailyreport/submit.uvue
2025-11-13 10:36:23 +08:00

556 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<cl-page title="提交日报">
<view class="p-4">
<!-- 日期选择 -->
<view class="mb-4">
<text class="text-base font-bold mb-2">日报日期</text>
<cl-input
v-model="form.reportDate"
type="date"
placeholder="选择日报日期"
:max="todayDate"
/>
</view>
<!-- 输入方式选择 -->
<view class="mb-4">
<text class="text-base font-bold mb-2">输入方式</text>
<view class="flex gap-2">
<cl-button
:type="inputType === 0 ? 'primary' : 'default'"
size="small"
@tap="inputType = 0"
>
文字输入
</cl-button>
<cl-button
:type="inputType === 1 ? 'primary' : 'default'"
size="small"
@tap="inputType = 1"
>
语音输入
</cl-button>
</view>
</view>
<!-- 语音输入区域 -->
<view v-if="inputType === 1" class="mb-4">
<text class="text-base font-bold mb-2">语音录制</text>
<view class="flex flex-col items-center p-6 bg-gray-50 rounded-lg">
<view v-if="!isRecording && !audioFile" class="flex flex-col items-center">
<text class="text-gray-500 mb-4">按住按钮开始录音最长60秒</text>
<cl-button
type="primary"
size="large"
round
@touchstart="startRecording"
@touchend="stopRecording"
>
<text class="text-2xl">🎤</text>
</cl-button>
</view>
<view v-if="isRecording" class="flex flex-col items-center">
<text class="text-red-500 text-lg mb-2">录音中...</text>
<text class="text-3xl font-bold text-red-500 mb-4">{{ recordingTime }}s</text>
<cl-button type="danger" @tap="cancelRecording">取消录音</cl-button>
</view>
<view v-if="audioFile && !isRecording" class="flex flex-col items-center w-full">
<text class="text-green-500 mb-2">录音完成</text>
<text class="text-gray-600 mb-4">时长: {{ recordingTime }}秒</text>
<view class="flex gap-2">
<cl-button type="default" @tap="reRecord">重新录音</cl-button>
<cl-button type="primary" @tap="recognizeVoice" :loading="isRecognizing">
{{ isRecognizing ? '识别中...' : '语音识别' }}
</cl-button>
</view>
</view>
</view>
<!-- 识别结果 -->
<view v-if="recognizedText" class="mt-4">
<text class="text-base font-bold mb-2">识别结果</text>
<view class="p-3 bg-blue-50 rounded-lg">
<text class="text-gray-700">{{ recognizedText }}</text>
</view>
</view>
</view>
<!-- 文字输入区域 -->
<view v-if="inputType === 0" class="mb-4">
<view class="p-4 bg-white rounded-2xl">
<cl-form>
<cl-form-item label="工作内容" required>
<cl-input
v-model="originalText"
placeholder="请输入今天的工作内容"
:maxlength="1000"
/>
</cl-form-item>
</cl-form>
</view>
</view>
<!-- AI格式化按钮 -->
<view v-if="originalText || recognizedText" class="mb-4">
<cl-button
type="success"
size="large"
block
@tap="formatWithAI"
:loading="isFormatting"
>
{{ isFormatting ? 'AI生成中...' : '🤖 AI生成日报' }}
</cl-button>
</view>
<!-- AI生成的日报内容 -->
<view v-if="aiFormattedContent" class="mb-4">
<text class="text-base font-bold mb-2">AI生成的日报</text>
<view class="p-3 bg-green-50 rounded-lg mb-2">
<cl-markdown :content="aiFormattedContent" />
</view>
<text class="text-sm text-gray-500">您可以在下方编辑最终内容</text>
</view>
<!-- 最终编辑区域 -->
<view v-if="aiFormattedContent" class="mb-4">
<view class="p-4 bg-white rounded-2xl">
<cl-form>
<cl-form-item label="最终日报内容" required>
<cl-input
v-model="userEditedContent"
placeholder="请编辑最终的日报内容"
:maxlength="2000"
/>
</cl-form-item>
</cl-form>
</view>
</view>
<!-- 操作按钮 -->
<view v-if="userEditedContent" class="flex gap-2 mt-6">
<cl-button
type="default"
size="large"
:flex="1"
@tap="saveDraft"
:loading="isSavingDraft"
>
{{ isSavingDraft ? '保存中...' : '保存草稿' }}
</cl-button>
<cl-button
type="primary"
size="large"
:flex="1"
@tap="submitReport"
:loading="isSubmitting"
>
{{ isSubmitting ? '提交中...' : '提交日报' }}
</cl-button>
</view>
</view>
</cl-page>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from "vue";
import { request, router, useStore } from "@/cool";
import { useUi } from "@/uni_modules/cool-ui";
const ui = useUi();
const { user } = useStore();
// 表单数据
const form = ref({
reportDate: "",
userId: 0 // 将在onMounted中从登录用户信息获取
});
// 输入方式0-文字1-语音
const inputType = ref<number>(0);
// 语音录制相关
const isRecording = ref(false);
const recordingTime = ref(0);
const audioFile = ref<string | null>(null);
const recorderManager = ref<any>(null);
let recordTimer: number | null = null;
// 识别和格式化相关
const isRecognizing = ref(false);
const recognizedText = ref("");
const originalText = ref("");
const isFormatting = ref(false);
const aiFormattedContent = ref("");
const userEditedContent = ref("");
// 提交相关
const isSavingDraft = ref(false);
const isSubmitting = ref(false);
// 今天的日期
const todayDate = computed(() => {
const today = new Date();
return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
});
onMounted(async () => {
console.log("【日报提交】页面加载, user.info:", user.info.value);
// 先尝试获取最新用户信息
if (user.token) {
try {
await user.get();
} catch (e) {
console.error("【日报提交】获取用户信息失败:", e);
}
}
console.log("【日报提交】获取用户信息后, user.info:", user.info.value);
// 获取当前登录用户ID
if (user.info.value && user.info.value.id) {
form.value.userId = user.info.value.id;
console.log("【日报提交】设置userId:", form.value.userId);
} else {
// 如果未登录,跳转到登录页
console.error("【日报提交】用户未登录或用户信息为空");
ui.showToast({
message: "请先登录",
type: "error"
});
setTimeout(() => {
router.to("/pages/user/login");
}, 1000);
return;
}
// 设置默认日期为今天
form.value.reportDate = todayDate.value;
// 初始化录音管理器(仅在支持的平台)
try {
// @ts-ignore
const manager = uni.getRecorderManager();
if (manager) {
recorderManager.value = manager;
// 监听录音结束
recorderManager.value.onStop((res: any) => {
if (recordTimer) {
clearInterval(recordTimer);
recordTimer = null;
}
audioFile.value = res.tempFilePath;
isRecording.value = false;
});
// 监听录音错误
recorderManager.value.onError((err: any) => {
console.error("录音错误:", err);
ui.showToast({
message: "录音失败: " + err.errMsg,
type: "error"
});
isRecording.value = false;
if (recordTimer) {
clearInterval(recordTimer);
recordTimer = null;
}
});
console.log("录音管理器初始化成功");
} else {
console.warn("当前环境不支持录音功能H5环境请使用文字输入");
}
} catch (e) {
console.warn("录音管理器初始化失败:", e);
}
// 检查今天是否已有日报
checkTodayReport();
});
onUnmounted(() => {
if (recordTimer) {
clearInterval(recordTimer);
}
});
// 检查今天是否已有日报
async function checkTodayReport() {
try {
const res = await request({
url: "/app/dailyreport/report/todayReport",
method: "GET",
params: { userId: form.value.userId }
});
if (res && res.id) {
// 今天已有日报,询问是否继续编辑
uni.showModal({
title: "提示",
content: `您今天已经${res.status === 1 ? "提交" : "保存"}了日报,是否继续编辑?`,
success: (modalRes) => {
if (modalRes.confirm) {
// 加载已有日报
loadExistingReport(res);
} else {
// 跳转到列表页
router.back();
}
}
});
}
} catch (error) {
console.log("今天还没有日报,可以新建");
}
}
// 加载已有日报
function loadExistingReport(report: any) {
originalText.value = report.originalText || "";
aiFormattedContent.value = report.aiFormattedContent || "";
userEditedContent.value = report.userEditedContent || "";
inputType.value = report.inputType || 0;
}
// 开始录音
function startRecording() {
// 检查录音管理器是否可用
if (!recorderManager.value) {
return ui.showToast({
message: "当前环境不支持录音功能,请使用文字输入",
type: "error"
});
}
isRecording.value = true;
recordingTime.value = 0;
audioFile.value = null;
recognizedText.value = "";
// 开始录音
recorderManager.value.start({
duration: 60000, // 最长60秒
format: "mp3"
});
// 开始计时
recordTimer = setInterval(() => {
recordingTime.value++;
if (recordingTime.value >= 60) {
stopRecording();
}
}, 1000);
}
// 停止录音
function stopRecording() {
if (isRecording.value && recorderManager.value) {
recorderManager.value.stop();
}
}
// 取消录音
function cancelRecording() {
if (isRecording.value && recorderManager.value) {
recorderManager.value.stop();
audioFile.value = null;
recordingTime.value = 0;
}
}
// 重新录音
function reRecord() {
audioFile.value = null;
recognizedText.value = "";
recordingTime.value = 0;
}
// 语音识别
async function recognizeVoice() {
if (!audioFile.value) {
return ui.showToast({
message: "请先录音",
type: "error"
});
}
isRecognizing.value = true;
try {
// 上传音频文件
const uploadRes = await uni.uploadFile({
url: request.options.baseURL + "/app/dailyreport/report/uploadVoice",
filePath: audioFile.value,
name: "audio",
formData: {
userId: form.value.userId
}
});
const result = JSON.parse(uploadRes.data);
if (result.code === 1000) {
recognizedText.value = result.data;
originalText.value = result.data;
ui.showToast({
message: "识别成功",
type: "success"
});
} else {
throw new Error(result.message || "识别失败");
}
} catch (error: any) {
console.error("语音识别失败:", error);
ui.showToast({
message: "语音识别失败: " + (error.message || "未知错误"),
type: "error"
});
} finally {
isRecognizing.value = false;
}
}
// AI格式化
async function formatWithAI() {
const text = originalText.value || recognizedText.value;
if (!text) {
return ui.showToast({
message: "请先输入内容或录音",
type: "error"
});
}
isFormatting.value = true;
try {
const res = await request({
url: "/app/dailyreport/report/aiFormat",
method: "POST",
data: {
originalText: text,
reportDate: form.value.reportDate // 传递日报日期
}
});
// 后端返回的是对象 {formattedContent: "内容", length: 数字}
const formattedContent = res.formattedContent || res;
aiFormattedContent.value = formattedContent;
userEditedContent.value = formattedContent;
ui.showToast({
message: "AI生成成功",
type: "success"
});
} catch (error: any) {
console.error("AI格式化失败:", error);
ui.showToast({
message: "AI格式化失败: " + (error.message || "未知错误"),
type: "error"
});
} finally {
isFormatting.value = false;
}
}
// 保存草稿
async function saveDraft() {
if (!userEditedContent.value) {
return ui.showToast({
message: "请先生成日报内容",
type: "error"
});
}
isSavingDraft.value = true;
try {
await request({
url: "/app/dailyreport/report/saveDraft",
method: "POST",
data: {
userId: form.value.userId,
reportDate: form.value.reportDate,
originalText: originalText.value || recognizedText.value,
aiFormattedContent: aiFormattedContent.value,
userEditedContent: userEditedContent.value,
inputType: inputType.value
}
});
ui.showToast({
message: "草稿保存成功",
type: "success"
});
// 延迟跳转
setTimeout(() => {
router.back();
}, 1000);
} catch (error: any) {
console.error("保存草稿失败:", error);
ui.showToast({
message: "保存草稿失败: " + (error.message || "未知错误"),
type: "error"
});
} finally {
isSavingDraft.value = false;
}
}
// 提交日报
async function submitReport() {
if (!userEditedContent.value) {
return ui.showToast({
message: "请先生成日报内容",
type: "error"
});
}
isSubmitting.value = true;
try {
await request({
url: "/app/dailyreport/report/submit",
method: "POST",
data: {
userId: form.value.userId,
reportDate: form.value.reportDate,
originalText: originalText.value || recognizedText.value,
aiFormattedContent: aiFormattedContent.value,
userEditedContent: userEditedContent.value,
inputType: inputType.value
}
});
ui.showToast({
message: "日报提交成功",
type: "success"
});
// 延迟跳转
setTimeout(() => {
router.back();
}, 1000);
} catch (error: any) {
console.error("提交日报失败:", error);
ui.showToast({
message: "提交日报失败: " + (error.message || "未知错误"),
type: "error"
});
} finally {
isSubmitting.value = false;
}
}
</script>
<style scoped>
.gap-2 {
gap: 0.5rem;
}
</style>