小程序初始提交
This commit is contained in:
93
cool-unix/pages/monthlyreport/detail.uvue
Normal file
93
cool-unix/pages/monthlyreport/detail.uvue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<cl-page :title="'\u6708\u62A5\u8BE6\u60C5'">
|
||||
<view v-if="loading" class="flex justify-center items-center py-20">
|
||||
<cl-loading />
|
||||
</view>
|
||||
|
||||
<view v-else-if="report" class="p-4">
|
||||
<!-- header -->
|
||||
<view class="mb-6">
|
||||
<view class="flex justify-between items-center mb-2">
|
||||
<text class="text-2xl font-bold">{{ report.month || '-' }}</text>
|
||||
</view>
|
||||
<text class="text-sm text-gray-500">{{ report.submitTime ? ('\u63D0\u4EA4\u4E8E ' + formatDateTime(report.submitTime)) : (report.createTime ? ('\u521B\u5EFA\u4E8E ' + formatDateTime(report.createTime)) : '') }}</text>
|
||||
</view>
|
||||
|
||||
<!-- content blocks -->
|
||||
<view class="mb-6">
|
||||
<text class="text-base font-bold mb-3 block">{{ '\u6708\u62A5\u5185\u5BB9' }}</text>
|
||||
<view class="p-4 bg-white rounded-lg shadow-sm">
|
||||
<text class="text-sm text-gray-800">{{ report.userEditedContent || report.aiFormattedContent || report.originalText || '\u6682\u65E0\u5185\u5BB9' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="report.originalText" class="mb-4">
|
||||
<text class="text-base font-bold mb-2">{{ '\u539F\u59CB\u8F93\u5165\u5185\u5BB9' }}</text>
|
||||
<view class="p-3 bg-gray-50 rounded">
|
||||
<text class="text-sm text-gray-700">{{ report.originalText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="report.aiFormattedContent" class="mb-4">
|
||||
<text class="text-base font-bold mb-2">{{ 'AI\u751F\u6210\u5185\u5BB9' }}</text>
|
||||
<view class="p-3 bg-blue-50 rounded">
|
||||
<text class="text-sm text-gray-800">{{ report.aiFormattedContent }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="flex flex-col items-center justify-center py-20">
|
||||
<text class="text-6xl mb-4">{{ '\uD83D\uDCC5' }}</text>
|
||||
<text class="text-gray-400 text-base">{{ '\u6708\u62A5\u4E0D\u5B58\u5728' }}</text>
|
||||
<cl-button type="primary" size="small" class="mt-4" @tap="router.back()">{{ '\u8FD4\u56DE\u5217\u8868' }}</cl-button>
|
||||
</view>
|
||||
</cl-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { request, router } from '@/cool';
|
||||
import { useUi } from '@/uni_modules/cool-ui';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
|
||||
const ui = useUi();
|
||||
const report = ref<any>(null);
|
||||
const loading = ref(true);
|
||||
const reportId = ref('');
|
||||
|
||||
onLoad(async (options: any) => {
|
||||
reportId.value = (options?.id ?? '').toString();
|
||||
if (!reportId.value) {
|
||||
ui.showToast({ message: '\u53C2\u6570\u9519\u8BEF\uFF1A\u7F3A\u5C11id', type: 'error' });
|
||||
setTimeout(() => router.back(), 700);
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
await loadDetail();
|
||||
});
|
||||
|
||||
async function loadDetail() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await request({ url: '/app/monthlyreport/report/detail', method: 'GET', params: { id: reportId.value } });
|
||||
report.value = res || null;
|
||||
} catch (e: any) {
|
||||
ui.showToast({ message: '\u52A0\u8F7D\u5931\u8D25: ' + (e.message || '\u672A\u77E5\u9519\u8BEF'), type: 'error' });
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function formatDateTime(v: string) {
|
||||
if (!v) return '';
|
||||
const d = new Date(v);
|
||||
const Y = d.getFullYear();
|
||||
const M = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const D = String(d.getDate()).padStart(2, '0');
|
||||
const h = String(d.getHours()).padStart(2, '0');
|
||||
const m = String(d.getMinutes()).padStart(2, '0');
|
||||
return `${Y}-${M}-${D} ${h}:${m}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
152
cool-unix/pages/monthlyreport/list.uvue
Normal file
152
cool-unix/pages/monthlyreport/list.uvue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<cl-page title="我的月报">
|
||||
<view class="p-4">
|
||||
<!-- 筛选 -->
|
||||
<view class="flex items-center gap-3 mb-4">
|
||||
<cl-segmented
|
||||
v-model="filterType"
|
||||
:list="[
|
||||
{ label: '全部', value: 'all' },
|
||||
{ label: '草稿', value: 'draft' },
|
||||
{ label: '已提交', value: 'submitted' }
|
||||
]"
|
||||
/>
|
||||
<cl-button size="small" @tap="refresh">刷新</cl-button>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<scroll-view scroll-y style="height: calc(100vh - 160px)" @scrolltolower="loadMore">
|
||||
<view v-if="list.length > 0">
|
||||
<view v-for="item in list" :key="item.id" class="p-4 mb-3 bg-white rounded-2xl" @tap="toDetail(item)">
|
||||
<view class="flex items-center justify-between mb-1">
|
||||
<text class="text-base font-bold">{{ item.month }}</text>
|
||||
<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>
|
||||
<text class="text-gray-500 text-sm">{{ brief(item.userEditedContent || item.aiFormattedContent || item.originalText) }}</text>
|
||||
<view class="text-xs text-gray-400 mt-2">
|
||||
<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-3">
|
||||
<text class="text-gray-400">加载中...</text>
|
||||
</view>
|
||||
<view v-else-if="finished" class="flex items-center justify-center py-3">
|
||||
<text class="text-gray-400">没有更多了</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="flex flex-col items-center justify-center py-20">
|
||||
<text class="text-6xl mb-3">📅</text>
|
||||
<text class="text-gray-400">暂无月报</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>
|
||||
</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');
|
||||
const userId = ref(0);
|
||||
|
||||
onMounted(async () => {
|
||||
if (user.token) {
|
||||
try { await user.get(); } catch {}
|
||||
}
|
||||
if (user.info.value?.id) {
|
||||
userId.value = user.info.value.id;
|
||||
loadReports();
|
||||
} else {
|
||||
ui.showToast({ message: '请先登录', type: 'error' });
|
||||
setTimeout(() => router.to('/pages/user/login'), 1000);
|
||||
}
|
||||
});
|
||||
|
||||
function brief(s: string = '') {
|
||||
const t = s.replace(/\n/g, ' ');
|
||||
return t.length > 60 ? t.slice(0, 60) + '…' : t;
|
||||
}
|
||||
|
||||
function statusClass(status: number) {
|
||||
return status === 1 ? 'text-green-600' : 'text-orange-500';
|
||||
}
|
||||
|
||||
function statusText(status: number) {
|
||||
return status === 1 ? '已提交' : '草稿';
|
||||
}
|
||||
|
||||
function formatTime(v: string) {
|
||||
try {
|
||||
const d = new Date(v);
|
||||
const Y = d.getFullYear();
|
||||
const M = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const D = String(d.getDate()).padStart(2, '0');
|
||||
const h = String(d.getHours()).padStart(2, '0');
|
||||
const m = String(d.getMinutes()).padStart(2, '0');
|
||||
const s = String(d.getSeconds()).padStart(2, '0');
|
||||
return `${Y}-${M}-${D} ${h}:${m}:${s}`;
|
||||
} catch (e) { return v; }
|
||||
}
|
||||
|
||||
async function loadReports() {
|
||||
if (loading.value || finished.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const params: any = { userId: userId.value, page: page.value, size: pageSize.value };
|
||||
if (filterType.value === 'draft') params.status = 0;
|
||||
if (filterType.value === 'submitted') params.status = 1;
|
||||
const res = await request({ url: '/app/monthlyreport/report/myReports', method: 'GET', params });
|
||||
if (res && res.length > 0) {
|
||||
if (page.value === 1) list.value = res; else list.value.push(...res);
|
||||
if (res.length < pageSize.value) finished.value = true;
|
||||
page.value++;
|
||||
} else {
|
||||
if (page.value === 1) list.value = [];
|
||||
finished.value = true;
|
||||
}
|
||||
} catch (e: any) {
|
||||
ui.showToast({ message: e.message || '加载失败', type: 'error' });
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function loadMore() { loadReports(); }
|
||||
|
||||
function refresh() {
|
||||
page.value = 1; finished.value = false; list.value = []; loadReports();
|
||||
}
|
||||
|
||||
function toSubmit() { router.to('/pages/monthlyreport/submit'); }
|
||||
function toDetail(item: any) { router.to(`/pages/monthlyreport/detail?id=${item.id}`); }
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
173
cool-unix/pages/monthlyreport/submit.uvue
Normal file
173
cool-unix/pages/monthlyreport/submit.uvue
Normal file
@@ -0,0 +1,173 @@
|
||||
<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.month" placeholder="例如:2025-11" />
|
||||
</view>
|
||||
|
||||
<!-- 原始内容输入 -->
|
||||
<view class="mb-4">
|
||||
<text class="text-base font-bold mb-2">工作内容(可选)</text>
|
||||
<cl-input v-model="originalText" placeholder="请输入本月工作要点(可选)" :maxlength="4000" />
|
||||
</view>
|
||||
|
||||
<!-- 从周报生成 -->
|
||||
<view class="mb-4">
|
||||
<cl-button type="primary" size="large" block @tap="generateFromWeekly" :loading="isGenerating">
|
||||
{{ isGenerating ? '生成中...' : '🧩 从我的周报生成' }}
|
||||
</cl-button>
|
||||
</view>
|
||||
|
||||
<!-- AI格式化 -->
|
||||
<view v-if="originalText" 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>
|
||||
<cl-input v-model="userEditedContent" placeholder="请编辑最终月报内容" :maxlength="6000" />
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="flex gap-3">
|
||||
<cl-button type="default" class="flex-1" @tap="saveDraft" :loading="saving">保存草稿</cl-button>
|
||||
<cl-button type="primary" class="flex-1" @tap="submitReport" :loading="submitting">提交</cl-button>
|
||||
</view>
|
||||
</view>
|
||||
</cl-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { request, useStore, router } from '@/cool';
|
||||
import { useUi } from '@/uni_modules/cool-ui';
|
||||
|
||||
const ui = useUi();
|
||||
const { user } = useStore();
|
||||
|
||||
const form = ref<any>({ month: '' });
|
||||
const originalText = ref('');
|
||||
const aiFormattedContent = ref('');
|
||||
const userEditedContent = ref('');
|
||||
const isFormatting = ref(false);
|
||||
const isGenerating = ref(false);
|
||||
const saving = ref(false);
|
||||
const submitting = ref(false);
|
||||
|
||||
onMounted(async () => {
|
||||
if (user.token) {
|
||||
try { await user.get(); } catch {}
|
||||
}
|
||||
});
|
||||
|
||||
function ensureMonth(): string | null {
|
||||
const v = (form.value.month || '').trim();
|
||||
if (!/^\d{4}-\d{2}$/.test(v)) {
|
||||
ui.showToast({ message: '请输入有效月份,如 2025-11', type: 'error' });
|
||||
return null;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
async function generateFromWeekly() {
|
||||
const month = ensureMonth();
|
||||
if (!month) return;
|
||||
if (!user.info.value?.id) {
|
||||
ui.showToast({ message: '请先登录', type: 'error' });
|
||||
router.to('/pages/user/login');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isGenerating.value = true;
|
||||
const res = await request({
|
||||
url: '/app/monthlyreport/report/generateFromWeekly',
|
||||
method: 'POST',
|
||||
data: {
|
||||
userId: user.info.value.id,
|
||||
month,
|
||||
templateKey: 'monthly_report_format'
|
||||
}
|
||||
});
|
||||
aiFormattedContent.value = res.formattedContent || '';
|
||||
userEditedContent.value = aiFormattedContent.value;
|
||||
} catch (e: any) {
|
||||
ui.showToast({ message: e.message || '生成失败', type: 'error' });
|
||||
} finally {
|
||||
isGenerating.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function formatWithAI() {
|
||||
const month = ensureMonth();
|
||||
if (!month) return;
|
||||
try {
|
||||
isFormatting.value = true;
|
||||
const res = await request({
|
||||
url: '/app/monthlyreport/report/aiFormat',
|
||||
method: 'POST',
|
||||
data: { originalText: originalText.value, templateKey: 'monthly_report_format', month }
|
||||
});
|
||||
aiFormattedContent.value = res.formattedContent || '';
|
||||
userEditedContent.value = aiFormattedContent.value;
|
||||
} catch (e: any) {
|
||||
ui.showToast({ message: e.message || 'AI格式化失败', type: 'error' });
|
||||
} finally {
|
||||
isFormatting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveDraft() {
|
||||
const month = ensureMonth();
|
||||
if (!month || !user.info.value?.id) return;
|
||||
try {
|
||||
saving.value = true;
|
||||
await request({
|
||||
url: '/app/monthlyreport/report/saveDraft',
|
||||
method: 'POST',
|
||||
data: {
|
||||
userId: user.info.value.id,
|
||||
month,
|
||||
originalText: originalText.value,
|
||||
aiFormattedContent: aiFormattedContent.value,
|
||||
userEditedContent: userEditedContent.value,
|
||||
inputType: 0
|
||||
}
|
||||
});
|
||||
ui.showToast({ message: '已保存草稿', type: 'success' });
|
||||
} catch (e: any) {
|
||||
ui.showToast({ message: e.message || '保存失败', type: 'error' });
|
||||
} finally { saving.value = false; }
|
||||
}
|
||||
|
||||
async function submitReport() {
|
||||
const month = ensureMonth();
|
||||
if (!month || !user.info.value?.id) return;
|
||||
try {
|
||||
submitting.value = true;
|
||||
await request({
|
||||
url: '/app/monthlyreport/report/submit',
|
||||
method: 'POST',
|
||||
data: {
|
||||
userId: user.info.value.id,
|
||||
month,
|
||||
originalText: originalText.value,
|
||||
aiFormattedContent: aiFormattedContent.value,
|
||||
userEditedContent: userEditedContent.value,
|
||||
inputType: 0
|
||||
}
|
||||
});
|
||||
ui.showToast({ message: '提交成功', type: 'success' });
|
||||
router.back();
|
||||
} catch (e: any) {
|
||||
ui.showToast({ message: e.message || '提交失败', type: 'error' });
|
||||
} finally { submitting.value = false; }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
Reference in New Issue
Block a user