小程序初始提交

This commit is contained in:
jdc
2025-11-13 10:36:23 +08:00
parent f26b4f9a2f
commit 5db3b180eb
447 changed files with 83351 additions and 0 deletions

View File

@@ -0,0 +1,222 @@
<template>
<cl-page title="日报详情">
<view v-if="loading" class="flex justify-center items-center py-20">
<cl-loading />
</view>
<view v-else-if="report" class="p-4">
<!-- 头部信息 -->
<view class="mb-6">
<view class="flex justify-between items-center mb-2">
<text class="text-2xl font-bold">{{ formatDate(report.reportDate) }}</text>
<cl-tag :type="report.status === 1 ? 'success' : 'warning'">
{{ report.status === 1 ? '已提交' : '草稿' }}
</cl-tag>
</view>
<text class="text-sm text-gray-500">
{{ report.inputType === 1 ? "🎤 语音输入" : "⌨️ 文字输入" }} ·
{{ report.submitTime ? ('提交于 ' + formatDateTime(report.submitTime)) : ('创建于 ' + formatDateTime(report.createTime)) }}
</text>
</view>
<!-- 最终日报内容 -->
<view class="mb-6">
<text class="text-base font-bold mb-3 block">📝 日报内容</text>
<view class="p-4 bg-white rounded-lg shadow-sm">
<text class="text-sm text-gray-800">{{ report.userEditedContent || "暂无内容" }}</text>
</view>
</view>
<!-- 折叠面板查看原始内容和AI生成内容 -->
<cl-collapse v-model="activeNames">
<!-- 原始内容 -->
<cl-collapse-item v-if="report.originalText" name="original" title="📄 原始输入内容">
<view class="p-3 bg-gray-50 rounded">
<text class="text-sm text-gray-700">{{ report.originalText }}</text>
</view>
</cl-collapse-item>
<!-- AI格式化内容 -->
<cl-collapse-item v-if="report.aiFormattedContent" name="ai" title="🤖 AI生成内容">
<view class="p-3 bg-blue-50 rounded">
<text class="text-sm text-gray-800">{{ report.aiFormattedContent }}</text>
</view>
</cl-collapse-item>
</cl-collapse>
<!-- 操作按钮 -->
<view class="flex gap-3 mt-6">
<cl-button type="primary" size="large" :flex="1" @tap="editReport">
编辑日报
</cl-button>
<cl-button v-if="report.status === 0" type="success" size="large" :flex="1" @tap="submitReport" :loading="isSubmitting">
{{ isSubmitting ? "提交中..." : "提交日报" }}
</cl-button>
</view>
</view>
<view v-else class="flex flex-col items-center justify-center py-20">
<text class="text-6xl mb-4">📝</text>
<text class="text-gray-400 text-base">日报不存在</text>
<cl-button type="primary" size="small" class="mt-4" @tap="router.back()">
返回列表
</cl-button>
</view>
</cl-page>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { request, router, useStore } from "@/cool";
import { useUi } from "@/uni_modules/cool-ui";
import { onLoad } from "@dcloudio/uni-app";
const ui = useUi();
const { user } = useStore();
// 日报数据
const report = ref<any>(null);
const loading = ref(true);
const isSubmitting = ref(false);
// 折叠面板展开项
const activeNames = ref<string[]>([]);
// 用户ID从登录状态获取
const userId = ref(0);
// 路由参数onLoad 中获取)
const reportId = ref("");
const reportDateParam = ref("");
onLoad(async (options: any) => {
// 读取路由参数
reportId.value = (options?.id ?? "").toString();
reportDateParam.value = (options?.date ?? "").toString();
// 刷新用户信息
if (user.token) {
try { await user.get(); } catch (e) { console.error("【日报详情】获取用户信息失败:", e); }
}
// 获取当前登录用户ID
if (user.info.value && user.info.value.id) {
userId.value = user.info.value.id;
} else {
ui.showToast({ message: "请先登录", type: "error" });
setTimeout(() => { router.to("/pages/user/login"); }, 1000);
loading.value = false;
return;
}
if (reportId.value) {
await loadReportDetail();
} else if (reportDateParam.value) {
await loadReportDetailByDateFallback();
} else {
loading.value = false;
}
});
// 加载日报详情按ID
async function loadReportDetail() {
loading.value = true;
try {
const res = await request({
url: "/app/dailyreport/report/detail",
method: "GET",
params: { id: reportId.value, userId: userId.value }
});
if (res) { report.value = res; return; }
await loadReportDetailByDateFallback();
} catch (error: any) {
console.error("加载日报详情失败:", error);
await loadReportDetailByDateFallback();
} finally {
loading.value = false;
}
}
// 按日期回退查询
async function loadReportDetailByDateFallback() {
if (!reportDateParam.value) return;
loading.value = true;
try {
const res = await request({
url: "/app/dailyreport/report/myReports",
method: "GET",
params: {
userId: userId.value,
startDate: reportDateParam.value,
endDate: reportDateParam.value
}
});
if (Array.isArray(res) && res.length > 0) {
report.value = res[0];
}
} catch (error: any) {
console.error("按日期加载日报失败:", error);
} finally {
loading.value = false;
}
}
// 编辑日报
function editReport() {
const id = reportId.value || report.value?.id;
if (id) {
router.to(`/pages/dailyreport/submit?id=${id}`);
} else if (reportDateParam.value) {
router.to(`/pages/dailyreport/submit?date=${reportDateParam.value}`);
} else {
ui.showToast({ message: "无有效日报标识", type: "warn" });
}
}
// 提交日报(草稿转提交)
async function submitReport() {
if (!report.value) return;
isSubmitting.value = true;
try {
await request({
url: "/app/dailyreport/report/submit",
method: "POST",
data: {
userId: userId.value,
reportDate: report.value.reportDate,
originalText: report.value.originalText,
aiFormattedContent: report.value.aiFormattedContent,
userEditedContent: report.value.userEditedContent,
inputType: report.value.inputType
}
});
ui.showToast({ message: "日报提交成功", type: "success" });
setTimeout(() => { loadReportDetail(); }, 500);
} catch (error: any) {
console.error("提交日报失败:", error);
ui.showToast({ message: "提交失败: " + (error.message || "未知错误"), type: "error" });
} finally {
isSubmitting.value = false;
}
}
// 工具:日期与时间格式化
function formatDate(dateStr: string) {
if (!dateStr) return "";
const d = new Date(dateStr);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const dd = String(d.getDate()).padStart(2, "0");
return `${y}-${m}-${dd}`;
}
function formatDateTime(timeStr: string) {
if (!timeStr) return "";
const date = new Date(timeStr);
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, "0");
const d = String(date.getDate()).padStart(2, "0");
const hh = String(date.getHours()).padStart(2, "0");
const mm = String(date.getMinutes()).padStart(2, "0");
return `${y}-${m}-${d} ${hh}:${mm}`;
}
</script>

View File

@@ -0,0 +1,327 @@
<template>
<cl-page title="我的日报">
<!-- 筛选栏 -->
<view class="px-4 py-3 bg-white border-b">
<view class="flex gap-2">
<cl-button
:type="filterType === 'all' ? 'primary' : 'default'"
size="small"
@tap="changeFilter('all')"
>
全部
</cl-button>
<cl-button
:type="filterType === 'draft' ? 'primary' : 'default'"
size="small"
@tap="changeFilter('draft')"
>
草稿
</cl-button>
<cl-button
:type="filterType === 'submitted' ? 'primary' : 'default'"
size="small"
@tap="changeFilter('submitted')"
>
已提交
</cl-button>
</view>
</view>
<!-- 日报列表 -->
<scroll-view scroll-y class="flex-1">
<!-- 加载中 -->
<view v-if="loading && list.length === 0" class="flex items-center justify-center py-20">
<text class="text-gray-400">加载中...</text>
</view>
<!-- 列表内容 -->
<view v-else-if="list.length > 0">
<view
v-for="(item, index) in list"
:key="item.id || index"
class="mx-4 my-2 p-4 bg-white rounded-lg shadow-sm"
@tap="toDetail(item)"
>
<!-- 头部:日期和状态 -->
<view class="flex justify-between items-center mb-3">
<view class="flex items-center gap-2">
<text class="text-lg font-bold">{{ formatDate(item.reportDate) }}</text>
<text class="text-xs text-gray-500">
{{ formatWeekday(item.reportDate) }}
</text>
</view>
<view
:class="[
'px-2 py-1 rounded text-xs',
item.status === 1 ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700'
]"
>
{{ item.status === 1 ? "已提交" : "草稿" }}
</view>
</view>
<!-- 内容摘要 -->
<view class="mb-3">
<text class="text-sm text-gray-700">
{{ getContentPreview(item.userEditedContent) }}
</text>
</view>
<!-- 底部信息 -->
<view class="flex justify-between items-center text-xs text-gray-500">
<view class="flex items-center gap-2">
<text>{{ item.inputType === 1 ? "🎤 语音输入" : "⌨️ 文字输入" }}</text>
</view>
<text v-if="item.submitTime">
提交于 {{ formatTime(item.submitTime) }}
</text>
<text v-else-if="item.createTime">
创建于 {{ formatTime(item.createTime) }}
</text>
</view>
</view>
<!-- 加载更多提示 -->
<view v-if="loading" class="flex items-center justify-center py-4">
<text class="text-gray-400 text-sm">加载中...</text>
</view>
<view v-else-if="finished" class="flex items-center justify-center py-4">
<text class="text-gray-400 text-sm">没有更多了</text>
</view>
</view>
<!-- 空状态 -->
<view v-else class="flex flex-col items-center justify-center py-20">
<text class="text-6xl mb-4">📝</text>
<text class="text-gray-400 text-base">暂无日报</text>
<view
class="mt-4 px-4 py-2 bg-blue-500 text-white rounded"
@tap="toSubmit"
>
<text>去提交日报</text>
</view>
</view>
</scroll-view>
<!-- 悬浮提交按钮 -->
<view class="fixed bottom-20 right-4">
<cl-button
type="primary"
size="large"
round
@tap="toSubmit"
>
<text class="text-2xl">✏️</text>
</cl-button>
</view>
</cl-page>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { request, router, useStore } from "@/cool";
import { useUi } from "@/uni_modules/cool-ui";
const ui = useUi();
const { user } = useStore();
// 列表相关
const list = ref<any[]>([]);
const loading = ref(false);
const finished = ref(false);
const page = ref(1);
const pageSize = ref(20);
// 筛选类型
const filterType = ref<"all" | "draft" | "submitted">("all");
// 用户ID从登录状态获取
const userId = ref(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);
if (user.info.value && user.info.value.id) {
userId.value = user.info.value.id;
console.log("【日报列表】设置userId:", userId.value);
loadReports();
} else {
console.error("【日报列表】用户未登录或用户信息为空");
ui.showToast({
message: "请先登录",
type: "error"
});
setTimeout(() => {
router.to("/pages/user/login");
}, 1000);
}
});
// 加载日报列表
async function loadReports() {
if (loading.value || finished.value) return;
loading.value = true;
try {
console.log("【日报列表】开始加载, userId:", userId.value);
if (!userId.value || userId.value === 0) {
console.error("【日报列表】userId无效:", userId.value);
throw new Error("用户ID无效请重新登录");
}
const params: any = {
userId: userId.value,
page: page.value,
size: pageSize.value
};
// 添加状态筛选
if (filterType.value === "draft") {
params.status = 0;
} else if (filterType.value === "submitted") {
params.status = 1;
}
console.log("【日报列表】请求参数:", params);
const res = await request({
url: "/app/dailyreport/report/myReports",
method: "GET",
params
});
console.log("【日报列表】响应数据:", res);
console.log("【日报列表】响应类型:", typeof res);
console.log("【日报列表】是否为数组:", Array.isArray(res));
console.log("【日报列表】数据长度:", res ? res.length : 0);
if (res && res.length > 0) {
console.log("【日报列表】开始填充列表数据");
if (page.value === 1) {
list.value = res;
} else {
list.value.push(...res);
}
page.value++;
console.log("【日报列表】列表已更新, 当前list长度:", list.value.length);
console.log("【日报列表】列表第一项数据:", list.value[0]);
// 如果返回数据少于每页大小,说明已经到底了
if (res.length < pageSize.value) {
finished.value = true;
}
} else {
console.log("【日报列表】无数据或数据为空");
// 即使没有数据也不应该标记为finished可能只是筛选条件没有匹配的数据
if (page.value === 1) {
list.value = [];
}
finished.value = true;
}
} catch (error: any) {
console.error("加载日报列表失败:", error);
ui.showToast({
message: "加载失败: " + (error.message || "未知错误"),
type: "error"
});
} finally {
loading.value = false;
}
}
// 下拉刷新
function onRefresh() {
console.log("【日报列表】下拉刷新");
page.value = 1;
finished.value = false;
list.value = [];
loadReports();
}
// 切换筛选条件
function changeFilter(type: "all" | "draft" | "submitted") {
console.log("【日报列表】切换筛选:", type);
filterType.value = type;
page.value = 1;
finished.value = false;
list.value = [];
loadReports();
}
// 跳转到详情
function toDetail(item: any) {
const date = item.reportDate ? encodeURIComponent(item.reportDate) : '';
router.to(`/pages/dailyreport/detail?id=${item.id}&date=${date}`);
}
// 跳转到提交页面
function toSubmit() {
router.to("/pages/dailyreport/submit");
}
// 格式化日期
function formatDate(dateStr: string) {
if (!dateStr) return "";
const date = new Date(dateStr);
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${month}月${day}日`;
}
// 格式化星期
function formatWeekday(dateStr: string) {
if (!dateStr) return "";
const date = new Date(dateStr);
const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
return weekdays[date.getDay()];
}
// 格式化时间
function formatTime(timeStr: string) {
if (!timeStr) return "";
const date = new Date(timeStr);
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
return `${month}-${day} ${hours}:${minutes}`;
}
// 获取内容摘要
function getContentPreview(content: string) {
if (!content) return "(无内容)";
// 移除 Markdown 标记
const plain = content.replace(/[#*`\[\]()]/g, "").trim();
// 限制长度
return plain.length > 100 ? plain.substring(0, 100) + "..." : plain;
}
</script>
<style scoped>
.gap-2 {
gap: 0.5rem;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@@ -0,0 +1,555 @@
<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>