提交 912a004e authored 作者: 王鹏飞's avatar 王鹏飞

bug fixes

上级 26a42e11
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -27,7 +27,9 @@
"easy-formula-editor": "^0.0.2-alpha.1",
"echarts": "^5.4.3",
"evit-gm-crypt": "^1.0.1",
"file-saver": "^2.0.5",
"highlight.js": "^11.11.1",
"html-docx-js-typescript": "^0.1.5",
"jquery": "^3.7.1",
"js-cookie": "^3.0.5",
"js-md5": "^0.8.3",
......
......@@ -32,7 +32,6 @@
overflow: hidden;
width: 430px;
border-radius: 30px;
}
.previee-container {
height: 100%;
......@@ -108,19 +107,34 @@
.preview-content-show {
overflow-x: hidden;
overflow-y: auto;
height: calc(100% - 45px);
height: calc(100% - 85px);
}
.preview-page-number {
height: 40px;
line-height: 40px;
text-align: center;
font-size: 14px;
color: #999;
border-top: 1px solid #efefef;
background: #fff;
flex-shrink: 0;
}
* {
line-height: 1.5;
font-family: "黑体";
font-family: '黑体';
}
.preview-content-html {
padding-bottom: 50px;
.chapter-practice, .chapter-expand, .chapter-item-section, .chapter-item-header, .chapter-gallery-container {
.chapter-practice,
.chapter-expand,
.chapter-item-section,
.chapter-item-header,
.chapter-gallery-container {
margin-top: 15px;
margin-bottom: 15px;
&.chapter-expand-inline, &.chapter-gallery-inline {
&.chapter-expand-inline,
&.chapter-gallery-inline {
margin-top: 0;
margin-bottom: 0;
margin-top: -3px !important;
......@@ -152,7 +166,7 @@
padding: 5px;
font-size: 18px;
}
div[data-w-e-type=video] {
div[data-w-e-type='video'] {
margin: 10px;
}
p[data-slate-node]:not(:empty) {
......@@ -361,7 +375,6 @@
width: 100%;
}
p {
}
img {
height: auto;
......@@ -383,7 +396,10 @@
}
}
.chapter-item-tooltip, .chapter-item-link, .chapter-gallery-inline, .chapter-expand-inline {
.chapter-item-tooltip,
.chapter-item-link,
.chapter-gallery-inline,
.chapter-expand-inline {
text-indent: 0;
svg {
text-indent: 0;
......@@ -410,7 +426,7 @@
text-align: center;
padding-top: 40px;
color: #999;
font-size: 18px;;
font-size: 18px;
}
.opa {
position: absolute;
......@@ -486,9 +502,12 @@
}
}
li, dd, dt, blockquote {
li,
dd,
dt,
blockquote {
font-size: 18px;
font-family: "黑体";
font-family: '黑体';
line-height: 1.5;
margin: 15px 0;
}
......@@ -656,5 +675,36 @@
height: 100%;
overflow-x: hidden;
overflow-y: auto;
.ant-tree .ant-tree-treenode {
position: relative;
// padding-right: 40px;
.ant-tree-node-content-wrapper {
overflow: hidden;
}
}
.tree-node-title {
display: inline-flex;
align-items: center;
width: 100%;
.tree-node-name {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tree-node-page {
// position: absolute;
// right: 0;
font-size: 12px;
color: #999;
// min-width: 32px;
text-align: right;
background: #f0f0f0;
border-radius: 4px;
padding: 0 6px;
line-height: 20px;
}
}
}
}
......@@ -38,9 +38,27 @@ const correctList = [
'W',
'X',
'Y',
'Z'
'Z',
]
// 递归遍历树,为叶子节点分配页码,返回 Map<key, pageNumber> 和总页数
const buildPageMap = (treeData) => {
const map = {}
let page = 0
const walk = (nodes) => {
nodes.forEach((node) => {
if (node.children && node.children.length > 0) {
walk(node.children)
} else {
page += 1
map[node.key] = page
}
})
}
walk(treeData)
return { map, total: page }
}
const PreviewScreen = (props, ref) => {
const { bookId } = props
const [gData, setGData] = useState([])
......@@ -48,6 +66,9 @@ const PreviewScreen = (props, ref) => {
const [prviewHtml, setPrviewHtml] = useState('') // 预览html
const [open, setOpen] = useState(false) // 抽屉
const [secondType, setSecondType] = useState(false)
const [pageMap, setPageMap] = useState({}) // 叶子节点页码映射
const [totalPages, setTotalPages] = useState(0) // 总页数
const [currentPage, setCurrentPage] = useState(0) // 当前页码
const [toolTip, setTooltip] = useState({})
......@@ -73,9 +94,9 @@ const PreviewScreen = (props, ref) => {
const newId = useRef(null)
// 递归函数,获取所有节点的key
const getAllNodeKeys = nodes => {
const getAllNodeKeys = (nodes) => {
let keys = []
nodes.forEach(node => {
nodes.forEach((node) => {
keys.push(node.key)
if (node.children && node.children.length > 0) {
keys = keys.concat(getAllNodeKeys(node.children))
......@@ -91,6 +112,9 @@ const PreviewScreen = (props, ref) => {
return
}
const arr = convertToAntdTreeData(data.data, 'name')
const { map, total } = buildPageMap(arr)
setPageMap(map)
setTotalPages(total)
setGData(arr)
const allKeys = getAllNodeKeys(arr)
setExpandedKeys(allKeys)
......@@ -98,6 +122,7 @@ const PreviewScreen = (props, ref) => {
const first = findFirstNotHasChildren(arr)
setOldTitle(first.title)
setNewChapterId(first.key)
setCurrentPage(map[first.key] || 0)
setLoading(false)
}
......@@ -105,11 +130,11 @@ const PreviewScreen = (props, ref) => {
const data = await getChapterTopic({
position: practicenum,
book_id: bookid,
chapter_id: chapterid
chapter_id: chapterid,
})
const temp = []
data.forEach(item => {
data.forEach((item) => {
let obj = { ...item }
if ([1, 2, 3].includes(parseInt(item.question_style))) {
try {
......@@ -120,11 +145,11 @@ const PreviewScreen = (props, ref) => {
}
temp.push(obj)
})
const tempRadio = temp.filter(item => parseInt(item.question_style) === 1)
const tempCheckbox = temp.filter(item => parseInt(item.question_style) === 2)
const tempJudge = temp.filter(item => parseInt(item.question_style) === 3)
const tempFill = temp.filter(item => parseInt(item.question_style) === 4)
const tempTextarea = temp.filter(item => parseInt(item.question_style) === 5)
const tempRadio = temp.filter((item) => parseInt(item.question_style) === 1)
const tempCheckbox = temp.filter((item) => parseInt(item.question_style) === 2)
const tempJudge = temp.filter((item) => parseInt(item.question_style) === 3)
const tempFill = temp.filter((item) => parseInt(item.question_style) === 4)
const tempTextarea = temp.filter((item) => parseInt(item.question_style) === 5)
setRadioList(tempRadio)
setCheckBoxList(tempCheckbox)
......@@ -138,7 +163,7 @@ const PreviewScreen = (props, ref) => {
const data = await expandReadInfo({
book_id: bookId,
chapter_id: chapterId,
position: position
position: position,
})
if (data) {
setExpandContent(data.content)
......@@ -162,7 +187,7 @@ const PreviewScreen = (props, ref) => {
document
.querySelectorAll('.preview-content-show ')[0]
.querySelectorAll('pre code')
.forEach(el => {
.forEach((el) => {
hljs.highlightElement(el)
})
}, 200)
......@@ -176,7 +201,7 @@ const PreviewScreen = (props, ref) => {
const parentOffset = $('.preview-content-it').offset() // 获取父元素相对于文档的位置
const parentRect = $('.preview-content-it')[0].getBoundingClientRect() // 获取父元素相对于文档的位置
$('.preview-content-html').on('click', async ev => {
$('.preview-content-html').on('click', async (ev) => {
ev.stopPropagation()
const target = ev.target
......@@ -224,7 +249,7 @@ const PreviewScreen = (props, ref) => {
let style1 = {
top: `${top}px`,
left: `${left}px`
left: `${left}px`,
}
setTooltip({
......@@ -235,7 +260,7 @@ const PreviewScreen = (props, ref) => {
tooltipType,
inSide,
style: style1,
squareStyle: squareStyle
squareStyle: squareStyle,
})
setTimeout(() => {
const childMoveHeight = $('.tooltip').outerHeight() // 获取元素的高度
......@@ -279,7 +304,7 @@ const PreviewScreen = (props, ref) => {
top: `${top}px`,
left: `${left}px`,
zIndex: 1000,
opacity: 1
opacity: 1,
}
setTooltip({
title,
......@@ -289,7 +314,7 @@ const PreviewScreen = (props, ref) => {
tooltipType,
inSide,
style: style2,
squareStyle: squareStyle
squareStyle: squareStyle,
})
}, 100)
......@@ -380,7 +405,7 @@ const PreviewScreen = (props, ref) => {
setGallery([])
})
$('.expand-content, .gallery-prview-container').on('click', ev => {
$('.expand-content, .gallery-prview-container').on('click', (ev) => {
const target = ev.target
if (target.nodeName.toLowerCase() === 'img') {
......@@ -412,7 +437,7 @@ const PreviewScreen = (props, ref) => {
const onClose = () => {
setOpen(false)
}
const openDrawer = e => {
const openDrawer = (e) => {
e.preventDefault()
e.stopPropagation()
setOpen(true)
......@@ -421,7 +446,7 @@ const PreviewScreen = (props, ref) => {
if (info.node.children && info.node.children.length > 0) {
const temp = JSON.parse(JSON.stringify(expandedKeys))
if (temp.includes(info.node.key)) {
const newExpands = temp.filter(item => item !== info.node.key)
const newExpands = temp.filter((item) => item !== info.node.key)
setExpandedKeys([...newExpands])
} else {
setExpandedKeys([...temp, info.node.key])
......@@ -431,6 +456,7 @@ const PreviewScreen = (props, ref) => {
setNewChapterId(info.node.key)
newId.current = info.node.key
setCheckedKeys(checkedKeys)
setCurrentPage(pageMap[info.node.key] || 0)
setOpen(false)
}
}
......@@ -498,6 +524,13 @@ const PreviewScreen = (props, ref) => {
<Spin spinning={loading} wrapperClassName="chapter-loading">
<div className="preview-content-html" dangerouslySetInnerHTML={{ __html: prviewHtml }}></div>
</Spin>
{currentPage > 0 && (
<div className="preview-page-number">
<span>
{currentPage} / {totalPages}
</span>
</div>
)}
</div>
{isType && (
<div className="preview-content-other">
......@@ -535,7 +568,7 @@ const PreviewScreen = (props, ref) => {
<div
className="topic-choose-title"
dangerouslySetInnerHTML={{
__html: `<span class="index">${index + 1}.</span>${item.titles}`
__html: `<span class="index">${index + 1}.</span>${item.titles}`,
}}></div>
<div className="topic_choose-list">
......@@ -549,7 +582,7 @@ const PreviewScreen = (props, ref) => {
<div
className="topic_content"
dangerouslySetInnerHTML={{
__html: `<span class="correct">${correctList[cindex]}.</span> ${citem.option}`
__html: `<span class="correct">${correctList[cindex]}.</span> ${citem.option}`,
}}></div>
</div>
)
......@@ -577,7 +610,7 @@ const PreviewScreen = (props, ref) => {
<div
className="topic-choose-title"
dangerouslySetInnerHTML={{
__html: `<span class="index">${index + 1}.</span>${item.titles}`
__html: `<span class="index">${index + 1}.</span>${item.titles}`,
}}></div>
<div className="topic_choose-list">
......@@ -591,7 +624,7 @@ const PreviewScreen = (props, ref) => {
<div
className="topic_content"
dangerouslySetInnerHTML={{
__html: `<span class="correct">${correctList[cindex]}.</span> ${citem.option}`
__html: `<span class="correct">${correctList[cindex]}.</span> ${citem.option}`,
}}></div>
</div>
)
......@@ -619,7 +652,7 @@ const PreviewScreen = (props, ref) => {
<div
className="topic-choose-title"
dangerouslySetInnerHTML={{
__html: `<span class="index">${index + 1}.</span>${item.titles}`
__html: `<span class="index">${index + 1}.</span>${item.titles}`,
}}></div>
<div className="topic_choose-list">
......@@ -633,7 +666,7 @@ const PreviewScreen = (props, ref) => {
<div
className="topic_content"
dangerouslySetInnerHTML={{
__html: `<span class="correct">${correctList[cindex]}.</span> ${citem.option}`
__html: `<span class="correct">${correctList[cindex]}.</span> ${citem.option}`,
}}></div>
</div>
)
......@@ -661,7 +694,7 @@ const PreviewScreen = (props, ref) => {
<div
className="topic-choose-title"
dangerouslySetInnerHTML={{
__html: `<span class="index">${index + 1}.</span>${item.titles}`
__html: `<span class="index">${index + 1}.</span>${item.titles}`,
}}></div>
<div className="topic_choose-list">
......@@ -675,7 +708,7 @@ const PreviewScreen = (props, ref) => {
<div
className="topic_content"
dangerouslySetInnerHTML={{
__html: `<span class="correct">${correctList[cindex]}.</span> ${citem.option}`
__html: `<span class="correct">${correctList[cindex]}.</span> ${citem.option}`,
}}></div>
</div>
)
......@@ -703,7 +736,7 @@ const PreviewScreen = (props, ref) => {
<div
className="topic-choose-title"
dangerouslySetInnerHTML={{
__html: `<span class="index">${index + 1}.</span>${item.titles}`
__html: `<span class="index">${index + 1}.</span>${item.titles}`,
}}></div>
<div className="topic_choose-list">
......@@ -717,7 +750,7 @@ const PreviewScreen = (props, ref) => {
<div
className="topic_content"
dangerouslySetInnerHTML={{
__html: `${correctList[cindex]}. ${citem.option}`
__html: `${correctList[cindex]}. ${citem.option}`,
}}></div>
</div>
)
......@@ -795,6 +828,15 @@ const PreviewScreen = (props, ref) => {
defaultExpandAll
blockNode
treeData={gData}
titleRender={(nodeData) => {
const page = pageMap[nodeData.key]
return (
<span className="tree-node-title">
<span className="tree-node-name">{nodeData.title}</span>
{page != null && <span className="tree-node-page">P{page}</span>}
</span>
)
}}
/>
</Drawer>
{/* </Spin> */}
......
......@@ -4,7 +4,8 @@ const { RangePicker } = DatePicker
const { TextArea } = Input
import { useNavigate } from 'react-router-dom'
import PaginationCom from '@/common/Pagination'
import { getList, delUser, exportBook, releaseExamine, importBook, exportToPdf } from '../request'
import { getList, delUser, exportBook, releaseExamine, importBook } from '../request'
import { exportHtmlToWord } from '@/utils/exportWordHtmlDocx'
import reset from '@/assets/images/icon/reset.png'
import filter from '@/assets/images/icon/filter.png'
import add from '@/assets/images/icon/add.png'
......@@ -14,7 +15,6 @@ import dayjs from 'dayjs'
import TableCom from '@/common/TableCom/index'
import { useSelector } from 'react-redux'
import { downloadFile } from '@/utils/common'
import $ from 'jquery'
const Audit = () => {
const [data, setData] = useState([])
......@@ -34,12 +34,12 @@ const Audit = () => {
authors: '',
audit_status: null,
start_time: '',
end_time: ''
end_time: '',
})
const [dateVal, setDateVal] = useState('')
// 获取操作权限
const { operationPermissionsList } = useSelector(state => state.user)
const { userInfo } = useSelector(state => state.user)
const { operationPermissionsList } = useSelector((state) => state.user)
const { userInfo } = useSelector((state) => state.user)
// 重置
const handleReset = () => {
setFilterObj({ name: '', authors: '', audit_status: null })
......@@ -85,22 +85,25 @@ const Audit = () => {
const exportSuccess = async () => {
setExportModal(false)
setLoading(true)
try {
const { name, content } = await exportBook({ id })
if (!name) return
const parser = new DOMParser()
const doc = parser.parseFromString(content, 'text/html')
$(doc.body)
.find(
".chapter-gallery-container, .chapter-expand, .chapter-practice, .chapter-item-link, .chapter-item-tooltip, div[data-w-e-type='video']"
)
.remove()
const data = await exportToPdf({ name, content: $(doc.body).html() })
await exportHtmlToWord(name, content, [
'.chapter-gallery-container',
'.chapter-expand',
'.chapter-practice',
'.chapter-item-link',
'.chapter-item-tooltip',
"div[data-w-e-type='video']",
])
} catch (e) {
console.error('导出Word失败', e)
} finally {
setLoading(false)
downloadFile(`temp/${data.name}`)
}
const importSuccess = async obj => {
}
const importSuccess = async (obj) => {
setUploading(true)
const bool = await importBook({ ...obj, file: bookFile })
// if (!bool) return;
......@@ -124,9 +127,9 @@ const Audit = () => {
}
setBookFile(file)
return false
},
}
}
const handleReleaseExamine = async obj => {
const handleReleaseExamine = async (obj) => {
const bool = await releaseExamine({ ...obj, id })
if (bool) {
window.location.replace(window.location.href) // 几乎完美
......@@ -147,7 +150,7 @@ const Audit = () => {
title: '书籍名称',
key: 'name',
align: 'center',
dataIndex: 'name'
dataIndex: 'name',
},
{
title: '书籍图片',
......@@ -161,13 +164,13 @@ const Audit = () => {
src={img}
fallback="data:image/png;base64,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"
/>
)
),
},
{
title: '作者',
key: 'authors',
align: 'center',
dataIndex: 'authors'
dataIndex: 'authors',
},
{
title: '审核状态',
......@@ -201,26 +204,26 @@ const Audit = () => {
</Button>
)
}
}
},
},
{
title: '发布次数',
key: 'release_num',
dataIndex: 'release_num',
width: 150,
align: 'center'
align: 'center',
},
{
title: '创建时间',
key: 'create_time',
align: 'center',
dataIndex: 'create_time'
dataIndex: 'create_time',
},
{
title: '更新时间',
key: 'update_time',
align: 'center',
dataIndex: 'update_time'
dataIndex: 'update_time',
},
{
title: '操作',
......@@ -287,8 +290,8 @@ const Audit = () => {
)}
</Space>
)
}
}
},
},
]
return (
<>
......@@ -300,7 +303,7 @@ const Audit = () => {
autoComplete="off"
allowClear
value={filterObj.name}
onChange={ev => handleFilter('name', ev.target.value)}
onChange={(ev) => handleFilter('name', ev.target.value)}
placeholder="请输入书籍名称"
id="name"></Input>
</Form.Item>
......@@ -309,7 +312,7 @@ const Audit = () => {
autoComplete="off"
allowClear
value={filterObj.authors}
onChange={ev => handleFilter('authors', ev.target.value)}
onChange={(ev) => handleFilter('authors', ev.target.value)}
placeholder="请输入作者"
id="author"></Input>
</Form.Item>
......@@ -318,7 +321,7 @@ const Audit = () => {
style={{ width: 160 }}
allowClear
value={filterObj.audit_status}
onChange={ev => handleFilter('audit_status', ev)}
onChange={(ev) => handleFilter('audit_status', ev)}
placeholder="请选择审核状态"
id="status">
<Select.Option value="1">待发布</Select.Option>
......@@ -330,7 +333,7 @@ const Audit = () => {
<Form.Item label="创建时间">
<RangePicker
value={dateVal}
onChange={ev => handleFilter('startandend', ev)}
onChange={(ev) => handleFilter('startandend', ev)}
id="createTime"></RangePicker>
</Form.Item>
<Space>
......@@ -344,7 +347,7 @@ const Audit = () => {
display: 'inline-block',
width: '11px',
height: '10px',
pointerEvents: 'none'
pointerEvents: 'none',
}}>
<Image src={reset} />
</span>
......@@ -360,7 +363,7 @@ const Audit = () => {
display: 'inline-block',
width: '13px',
height: '12px',
pointerEvents: 'none'
pointerEvents: 'none',
}}>
<Image src={filter} />
</span>
......@@ -369,7 +372,7 @@ const Audit = () => {
onClick={() => {
setPage(1)
console.log(filterObj)
Object.values(filterObj).some(item => item) ? init(filterObj) : init()
Object.values(filterObj).some((item) => item) ? init(filterObj) : init()
}}>
筛选
</Button>
......@@ -382,13 +385,13 @@ const Audit = () => {
display: 'inline-block',
width: '10px',
height: '12px',
pointerEvents: 'none'
pointerEvents: 'none',
}}>
<Image src={reload} />
</span>
}
onClick={() => {
Object.values(filterObj).some(item => item) ? init(filterObj) : init()
Object.values(filterObj).some((item) => item) ? init(filterObj) : init()
}}>
刷新
</Button>
......@@ -406,7 +409,7 @@ const Audit = () => {
display: 'inline-block',
width: '11px',
height: '12px',
pointerEvents: 'none'
pointerEvents: 'none',
}}>
<Image src={imports} />
</span>
......@@ -425,7 +428,7 @@ const Audit = () => {
display: 'inline-block',
width: '13px',
height: '12px',
pointerEvents: 'none'
pointerEvents: 'none',
}}>
<Image src={add} />
</span>
......@@ -501,7 +504,7 @@ const Audit = () => {
color: '#999999',
fontSize: 12,
marginBottom: 30,
paddingLeft: 75
paddingLeft: 75,
}}>
目录需设置为word中的标题样式或按照示例编写,防止导入失败
</p>
......@@ -546,7 +549,7 @@ const Audit = () => {
placeholder="请输入发布信息"
autoSize={{
minRows: 5,
maxRows: 8
maxRows: 8,
}}
/>
</Form.Item>
......
......@@ -209,7 +209,7 @@ const Examine = () => {
})
if (data) {
if ((data.code && data.code === 3000) || data.code === '3000') {
navigate('/books/management/list')
// navigate('/books/management/list')
return
} else {
setContentMd5(newMd5)
......
import { asBlob } from 'html-docx-js-typescript'
import { saveAs } from 'file-saver'
import $ from 'jquery'
/**
* 将图片 URL 转为 base64 Data URI
* 优先使用 fetch,失败后尝试 Image + Canvas 方式
*/
async function imgToBase64(src) {
// 方式1:fetch
try {
const res = await fetch(src)
if (res.ok) {
const blob = await res.blob()
return await blobToDataUri(blob)
}
} catch (e) {
console.warn('fetch 图片失败,尝试 canvas 方式:', src, e)
}
// 方式2:Image + Canvas(可绕过部分限制)
try {
return await loadImageAsBase64(src)
} catch (e) {
console.warn('canvas 转换图片失败:', src, e)
}
return null
}
function blobToDataUri(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onloadend = () => resolve(reader.result)
reader.onerror = reject
reader.readAsDataURL(blob)
})
}
function loadImageAsBase64(src) {
return new Promise((resolve, reject) => {
const img = new Image()
img.crossOrigin = 'anonymous'
img.onload = () => {
try {
const canvas = document.createElement('canvas')
canvas.width = img.naturalWidth
canvas.height = img.naturalHeight
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0)
resolve(canvas.toDataURL('image/png'))
} catch (e) {
reject(e)
}
}
img.onerror = reject
img.src = src
})
}
/**
* 将 DOM 中所有 img 的 src 替换为 base64 Data URI
*/
async function convertImagesToBase64(docEl) {
const imgs = docEl.querySelectorAll('img')
const tasks = Array.from(imgs).map(async (img) => {
const src = img.getAttribute('src')
if (!src || src.startsWith('data:')) return
// 处理相对路径和协议相对路径
let absoluteSrc = src
if (src.startsWith('//')) {
absoluteSrc = window.location.protocol + src
} else if (src.startsWith('/')) {
absoluteSrc = window.location.origin + src
} else if (!src.startsWith('http')) {
absoluteSrc = new URL(src, window.location.href).href
}
const dataUri = await imgToBase64(absoluteSrc)
if (dataUri) {
img.setAttribute('src', dataUri)
} else {
console.warn('图片转 base64 失败,已跳过:', absoluteSrc)
img.remove()
}
})
await Promise.all(tasks)
}
/**
* 使用 html-docx-js-typescript 将 HTML 内容导出为 Word 文档(.docx)
* 图片会自动转为 base64 内嵌到文档中
* @param {string} name - 文件名(不含扩展名)
* @param {string} htmlString - 原始 HTML 字符串
* @param {string[]} removeSelectors - 需要移除的 CSS 选择器列表
*/
export async function exportHtmlToWord(name, htmlString, removeSelectors = []) {
const parser = new DOMParser()
const doc = parser.parseFromString(htmlString, 'text/html')
if (removeSelectors.length) {
$(doc.body).find(removeSelectors.join(', ')).remove()
}
// 将所有图片转为 base64 内嵌
await convertImagesToBase64(doc.body)
// 使用原生 innerHTML 避免 jQuery 序列化可能编码 base64 特殊字符
const htmlContent = doc.body.innerHTML
const fullHtml = `<!DOCTYPE html><html><head><meta charset="utf-8"><style>
@page { size: A4; margin: 2cm; }
body { font-family: SimSun, serif; font-size: 14px; line-height: 1.6; }
table { border-collapse: collapse; width: 100%; }
td, th { border: 1px solid #000; padding: 5px; }
img { max-width: 100%; }
</style></head><body>${htmlContent}</body></html>`
const blob = await asBlob(fullHtml, { orientation: 'portrait' })
saveAs(blob, `${name}.docx`)
}
......@@ -38,7 +38,8 @@ export default defineConfig(() => {
// rewrite: (path) => path.replace(/^\/api\/wenku/, '/'),
// },
'/api': {
target: 'https://zijingebook.ezijing.com',
// target: 'https://zijingebook.ezijing.com',
target: 'http://localhost:7419',
changeOrigin: true,
secure: false,
},
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论