feat: 完成全部11项优化需求 + 模板必填项标注 + 归档重算个税
- 高优先级: 花名册导入模板必填项标注、性别自动识别、导入结果反馈、证据链Excel导出、身份证搜索修复、试用期区分与转正提醒、薪税批次流程优化 - 中优先级: 专项附加扣除批量导入、文本模板库完善(Word下载/复制/使用说明)、用工体检评分标准说明、考勤页面导入入口 - 所有导入模板表头标注必填项(*后缀)并含示例行 - 导入逻辑统一改用getField兼容*后缀列名 - 批次归档时强制重算所有条目个税和社保,解决多未归档批次并存时累计计算不准问题 - 更新需求梳理文档
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { FileText, Copy, X, ChevronRight } from 'lucide-react'
|
||||
import { FileText, Copy, X, ChevronRight, Download, BookOpen, HelpCircle } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import api from '../lib/api'
|
||||
import { useAuthStore } from '../store/authStore'
|
||||
import Card from '../components/ui/Card'
|
||||
import Button from '../components/ui/Button'
|
||||
import EmptyState from '../components/ui/EmptyState'
|
||||
@@ -100,11 +101,40 @@ export default function Templates() {
|
||||
}
|
||||
}
|
||||
|
||||
const [showHelp, setShowHelp] = useState(false)
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(rendered)
|
||||
toast.success('已复制到剪贴板')
|
||||
}
|
||||
|
||||
const handleDownloadWord = async () => {
|
||||
if (!selected) return
|
||||
try {
|
||||
const token = useAuthStore.getState().accessToken
|
||||
const baseURL = import.meta.env.DEV ? 'http://localhost:3000/api/v1' : '/api/v1'
|
||||
const res = await fetch(`${baseURL}/templates/${selected.id}/download`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
})
|
||||
const blob = await res.blob()
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `${selected.name}.doc`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
toast.success('已下载 Word 文档')
|
||||
} catch {
|
||||
toast.error('下载失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopyAsNew = () => {
|
||||
const text = rendered || detail?.content || ''
|
||||
navigator.clipboard.writeText(text)
|
||||
toast.success('模板内容已复制,可粘贴到 Word 中编辑使用')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -113,6 +143,25 @@ export default function Templates() {
|
||||
</div>
|
||||
<p className="text-sm text-gray-500">合同、制度、通知等常用文本模板,支持变量替换</p>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="sm" variant="secondary" onClick={() => setShowHelp(!showHelp)}>
|
||||
<HelpCircle className="w-3.5 h-3.5 mr-1" />使用说明
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{showHelp && (
|
||||
<Card className="bg-blue-50/50">
|
||||
<div className="space-y-2 text-xs text-gray-600">
|
||||
<div className="flex items-center gap-1.5 font-medium text-gray-700"><BookOpen className="w-3.5 h-3.5" />使用说明</div>
|
||||
<div>1. 选择需要的模板分类(合同/制度/通知/协议),点击模板卡片打开详情</div>
|
||||
<div>2. 在弹窗中填写变量字段(如公司名称、员工姓名等),点击「渲染模板」生成完整文本</div>
|
||||
<div>3. 渲染后可「复制」到剪贴板,或「下载 Word」保存为 .doc 文件</div>
|
||||
<div>4. 「复制为新模板」将渲染结果复制到剪贴板,可粘贴到 Word 中进一步编辑</div>
|
||||
<div>5. 变量字段为选填,未填写的变量将保留 <code className="px-1 bg-gray-100 rounded">{'{{变量名}}'}</code> 占位符</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2">
|
||||
{['', 'CONTRACT', 'RULES', 'NOTICE', 'AGREEMENT', 'OTHER'].map(c => (
|
||||
<button
|
||||
@@ -187,15 +236,30 @@ export default function Templates() {
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-xs font-medium text-gray-600">渲染结果</span>
|
||||
<button onClick={handleCopy} className="flex items-center gap-1 text-xs text-primary hover:underline">
|
||||
<Copy className="w-3 h-3" />复制
|
||||
</button>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={handleCopyAsNew} className="flex items-center gap-1 text-xs text-primary hover:underline">
|
||||
<Copy className="w-3 h-3" />复制为新模板
|
||||
</button>
|
||||
<button onClick={handleDownloadWord} className="flex items-center gap-1 text-xs text-primary hover:underline">
|
||||
<Download className="w-3 h-3" />下载 Word
|
||||
</button>
|
||||
<button onClick={handleCopy} className="flex items-center gap-1 text-xs text-primary hover:underline">
|
||||
<Copy className="w-3 h-3" />复制
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre className="text-sm text-gray-700 whitespace-pre-wrap bg-gray-50 p-3 rounded-lg max-h-[50vh] overflow-y-auto">{rendered}</pre>
|
||||
</div>
|
||||
) : detail?.content ? (
|
||||
<div>
|
||||
<div className="text-xs font-medium text-gray-600 mb-2">模板原文</div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-xs font-medium text-gray-600">模板原文</span>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={handleDownloadWord} className="flex items-center gap-1 text-xs text-primary hover:underline">
|
||||
<Download className="w-3 h-3" />下载 Word
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre className="text-sm text-gray-700 whitespace-pre-wrap bg-gray-50 p-3 rounded-lg max-h-[50vh] overflow-y-auto">{detail.content}</pre>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user