mirror of
https://github.com/fumiama/paper-manager.git
synced 2026-06-30 07:40:28 +08:00
finish 试卷生成
This commit is contained in:
@@ -4,10 +4,16 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/FloatTech/ttl"
|
||||||
|
"github.com/fumiama/go-docx"
|
||||||
"github.com/fumiama/paper-manager/backend/global"
|
"github.com/fumiama/paper-manager/backend/global"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var genfilecache = ttl.NewCache[int, *docx.Docx](time.Minute * 10)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
apimap["/api/genFile"] = &apihandler{"POST", func(w http.ResponseWriter, r *http.Request) {
|
apimap["/api/genFile"] = &apihandler{"POST", func(w http.ResponseWriter, r *http.Request) {
|
||||||
user := usertokens.Get(r.Header.Get("Authorization"))
|
user := usertokens.Get(r.Header.Get("Authorization"))
|
||||||
@@ -27,6 +33,21 @@ func init() {
|
|||||||
writeresult(w, codeError, nil, err.Error(), typeError)
|
writeresult(w, codeError, nil, err.Error(), typeError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
genfilecache.Set(*user.ID, docf)
|
||||||
|
writeresult(w, codeSuccess, "请在10分钟内下载, 且不要在下载完成前关闭页面, 云端不会保存", messageOk, typeSuccess)
|
||||||
|
}}
|
||||||
|
|
||||||
|
apimap["/api/dlGen"] = &apihandler{"GET", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user := usertokens.Get(r.Header.Get("Authorization"))
|
||||||
|
if user == nil {
|
||||||
|
writeresult(w, codeError, nil, errInvalidToken.Error(), typeError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
docf := genfilecache.Get(*user.ID)
|
||||||
|
if docf == nil {
|
||||||
|
writeresult(w, codeError, nil, os.ErrNotExist.Error(), typeError)
|
||||||
|
return
|
||||||
|
}
|
||||||
_, _ = io.Copy(w, docf)
|
_, _ = io.Copy(w, docf)
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func init() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
err = FileDB.db.Create(FileTableQuestion, &Question{},
|
err = FileDB.db.Create(FileTableQuestion, &Question{},
|
||||||
"FOREIGN KEY(FileID) REFERENCES "+FileTableFile+"(ID)",
|
"FOREIGN KEY(ListID) REFERENCES "+FileTableList+"(ID)",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import (
|
|||||||
|
|
||||||
sql "github.com/FloatTech/sqlite"
|
sql "github.com/FloatTech/sqlite"
|
||||||
"github.com/fumiama/go-docx"
|
"github.com/fumiama/go-docx"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidGenerateConfig = errors.New("invalid generate config")
|
ErrInvalidGenerateConfig = errors.New("invalid generate config")
|
||||||
ErrMajorTooLarge = errors.New("major too large")
|
ErrMajorTooLarge = errors.New("major too large")
|
||||||
|
ErrNoSuchMajor = errors.New("no such major")
|
||||||
ErrNoEnoughQuestionToMatchRequire = errors.New("no enough question to match require")
|
ErrNoEnoughQuestionToMatchRequire = errors.New("no enough question to match require")
|
||||||
ErrRateLimitExceeded = errors.New("rate limit exceeded")
|
ErrRateLimitExceeded = errors.New("rate limit exceeded")
|
||||||
)
|
)
|
||||||
@@ -26,21 +28,31 @@ type GenerateConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateFile 用一些限定条件生成新试卷, 云端不保存
|
// GenerateFile 用一些限定条件生成新试卷, 云端不保存
|
||||||
func (f *FileDatabase) GenerateFile(config *GenerateConfig) (*docx.Docx, error) {
|
func (f *FileDatabase) GenerateFile(config *GenerateConfig) (docf *docx.Docx, err error) {
|
||||||
if config == nil || config.Distribution == nil || len(config.Distribution) == 0 {
|
if config == nil || config.Distribution == nil || len(config.Distribution) == 0 {
|
||||||
return nil, ErrInvalidGenerateConfig
|
return nil, ErrInvalidGenerateConfig
|
||||||
}
|
}
|
||||||
if len(config.Distribution) > 10 {
|
if len(config.Distribution) > 10 {
|
||||||
return nil, ErrMajorTooLarge
|
return nil, ErrMajorTooLarge
|
||||||
}
|
}
|
||||||
docf := docx.NewA4()
|
mm := map[string]struct{}{}
|
||||||
|
for _, m := range f.GetMajors() {
|
||||||
|
mm[m] = struct{}{}
|
||||||
|
}
|
||||||
|
for n := range config.Distribution {
|
||||||
|
if _, ok := mm[n]; !ok {
|
||||||
|
return nil, ErrNoSuchMajor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
docf = docx.NewA4()
|
||||||
f.mu.RLock()
|
f.mu.RLock()
|
||||||
defer f.mu.RUnlock()
|
defer f.mu.RUnlock()
|
||||||
|
i := 0
|
||||||
for n, c := range config.Distribution {
|
for n, c := range config.Distribution {
|
||||||
if c == 0 {
|
if c <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
docf.AddParagraph().AddText(string([]rune("一二三四五六七八九十")[c]) + "、" + n).Size("44").Bold()
|
docf.AddParagraph().AddText(string([]rune("一二三四五六七八九十")[i]) + "、" + n).Size("30").Bold()
|
||||||
cond := " WHERE"
|
cond := " WHERE"
|
||||||
hasfront := false
|
hasfront := false
|
||||||
if config.YearStart > 0 {
|
if config.YearStart > 0 {
|
||||||
@@ -51,20 +63,32 @@ func (f *FileDatabase) GenerateFile(config *GenerateConfig) (*docx.Docx, error)
|
|||||||
if hasfront {
|
if hasfront {
|
||||||
cond += " AND"
|
cond += " AND"
|
||||||
}
|
}
|
||||||
cond += " Year<=" + strconv.Itoa(int(config.YearStart))
|
cond += " Year<=" + strconv.Itoa(int(config.YearEnd))
|
||||||
hasfront = true
|
hasfront = true
|
||||||
}
|
}
|
||||||
if hasfront {
|
if config.TypeMask > 0 {
|
||||||
cond += " AND"
|
if hasfront {
|
||||||
|
cond += " AND"
|
||||||
|
}
|
||||||
|
typmsk := strconv.Itoa(int(config.TypeMask))
|
||||||
|
cond += " (Type&" + typmsk + ")==" + typmsk
|
||||||
|
hasfront = true
|
||||||
|
}
|
||||||
|
var ques []*Question
|
||||||
|
q := ""
|
||||||
|
if hasfront {
|
||||||
|
q = "SELECT * FROM " + FileTableQuestion +
|
||||||
|
" WHERE Major='" + n + "' AND ListID IN (SELECT DISTINCT ListID FROM " +
|
||||||
|
FileTableFile + cond +
|
||||||
|
") ORDER BY RANDOM() limit " + strconv.Itoa(int(c)) + ";"
|
||||||
|
ques, err = sql.QueryAll[Question](&f.db, q)
|
||||||
|
} else {
|
||||||
|
q = "SELECT * FROM " + FileTableQuestion +
|
||||||
|
" WHERE Major='" + n + "' ORDER BY RANDOM() limit " + strconv.Itoa(int(c)) + ";"
|
||||||
|
ques, err = sql.QueryAll[Question](&f.db, q)
|
||||||
}
|
}
|
||||||
cond += " (Type&" + strconv.Itoa(int(config.TypeMask)) + ")!=0"
|
|
||||||
ques, err := sql.QueryAll[Question](&f.db,
|
|
||||||
"SELECT * FROM "+FileTableQuestion+
|
|
||||||
" WHERE FileID IN (SELECT FileID FROM "+
|
|
||||||
FileTableFile+cond+
|
|
||||||
") ORDER BY RANDOM() limit "+strconv.Itoa(int(c))+";",
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logrus.Warnln(err, q)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(ques) != int(c) {
|
if len(ques) != int(c) {
|
||||||
@@ -79,7 +103,7 @@ func (f *FileDatabase) GenerateFile(config *GenerateConfig) (*docx.Docx, error)
|
|||||||
return nil, ErrRateLimitExceeded
|
return nil, ErrRateLimitExceeded
|
||||||
}
|
}
|
||||||
for i, q := range ques {
|
for i, q := range ques {
|
||||||
lst, err := sql.Find[List](&f.db, FileTableFile, "WHERE ID="+strconv.Itoa(q.ListID))
|
lst, err := sql.Find[List](&f.db, FileTableList, "WHERE ID="+strconv.Itoa(q.ListID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -98,6 +122,7 @@ func (f *FileDatabase) GenerateFile(config *GenerateConfig) (*docx.Docx, error)
|
|||||||
docf.AddParagraph().AddText(strconv.Itoa(i+1) + ". (" + lst.Desc + ")")
|
docf.AddParagraph().AddText(strconv.Itoa(i+1) + ". (" + lst.Desc + ")")
|
||||||
docf.AppendFile(docq)
|
docf.AppendFile(docq)
|
||||||
}
|
}
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
return docf, nil
|
return docf, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { defHttp, paperHttp } from '/@/utils/http/axios'
|
import { defHttp, paperHttp } from '/@/utils/http/axios'
|
||||||
import { getFileListModel, AnalyzeFile, FileListGroupItem } from './model/fileListModel'
|
import {
|
||||||
|
getFileListModel,
|
||||||
|
AnalyzeFile,
|
||||||
|
FileListGroupItem,
|
||||||
|
GenerateConfig,
|
||||||
|
} from './model/fileListModel'
|
||||||
import { DownloadFile, FileStatus } from './model/fileModel'
|
import { DownloadFile, FileStatus } from './model/fileModel'
|
||||||
|
|
||||||
enum Api {
|
enum Api {
|
||||||
@@ -11,6 +16,8 @@ enum Api {
|
|||||||
DlFile = '/dlFile',
|
DlFile = '/dlFile',
|
||||||
GetFileStatus = '/getFileStatus',
|
GetFileStatus = '/getFileStatus',
|
||||||
GetMajors = '/getMajors',
|
GetMajors = '/getMajors',
|
||||||
|
GenFile = '/genFile',
|
||||||
|
DlGen = '/dlGen',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,3 +88,20 @@ export const getFileStatus = (id: number) => {
|
|||||||
export const getMajors = () => {
|
export const getMajors = () => {
|
||||||
return defHttp.get<string[]>({ url: Api.GetMajors })
|
return defHttp.get<string[]>({ url: Api.GetMajors })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Generate File
|
||||||
|
*/
|
||||||
|
export const generateFile = (config: GenerateConfig) => {
|
||||||
|
return defHttp.post<string>({ url: Api.GenFile, params: config }, { errorMessageMode: 'none' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Download generated file
|
||||||
|
*/
|
||||||
|
export const dlGeneratedFile = () => {
|
||||||
|
return paperHttp.get<any>(
|
||||||
|
{ url: '/api' + Api.DlGen, responseType: 'blob' },
|
||||||
|
{ errorMessageMode: 'none' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,3 +18,11 @@ export interface AnalyzeFile {
|
|||||||
code: number
|
code: number
|
||||||
msg: string
|
msg: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GenerateConfig {
|
||||||
|
Distribution: { [x: string]: any } // Distribution is map[majorname]subcount
|
||||||
|
RateLimit: number // RateLimit 重复率上限
|
||||||
|
YearStart: number // YearStart 起始年份(空则直到最旧)
|
||||||
|
YearEnd: number // YearEnd 截止年份(空则直到最新)
|
||||||
|
TypeMask: number // TypeMask & File.Type != 0 则匹配
|
||||||
|
}
|
||||||
|
|||||||
@@ -95,7 +95,12 @@
|
|||||||
async function customSubmitFunc() {
|
async function customSubmitFunc() {
|
||||||
try {
|
try {
|
||||||
const values = await validate()
|
const values = await validate()
|
||||||
emit('next', values)
|
const data = getDataSource()
|
||||||
|
if (data.length == 0) {
|
||||||
|
createMessage.error('必须指定至少一种题型!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emit('next', { values, data })
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="step2">
|
<div class="step2">
|
||||||
<a-alert message="确认转账后,资金将直接打入对方账户,无法退回。" show-icon />
|
<a-alert message="确认提交后,将进入下载页面,云端不保存。" show-icon />
|
||||||
<a-descriptions :column="1" class="mt-5">
|
<a-descriptions :column="1" class="mt-5">
|
||||||
<a-descriptions-item label="付款账户"> ant-design@alipay.com </a-descriptions-item>
|
<a-descriptions-item label="大题数">
|
||||||
<a-descriptions-item label="收款账户"> test@example.com </a-descriptions-item>
|
{{ state.step1Values.data.length }}
|
||||||
<a-descriptions-item label="收款人姓名"> Vben </a-descriptions-item>
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="转账金额"> 500元 </a-descriptions-item>
|
<a-descriptions-item label="小题数">
|
||||||
|
{{
|
||||||
|
state.step1Values.data
|
||||||
|
.map((record) => record.count || 0)
|
||||||
|
.reduce((sum, count) => sum + count, 0)
|
||||||
|
}}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="重复率上限">
|
||||||
|
{{ state.step1Values.values.RateLimit[1] / 100 }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="起止年份" v-if="state.step1Values.values['[YearStart, YearEnd]']">
|
||||||
|
{{ state.step1Values.values['[YearStart, YearEnd]'][0].$y }} -
|
||||||
|
{{ state.step1Values.values['[YearStart, YearEnd]'][1].$y }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="试卷类别" v-if="state.step1Values.values.AB">
|
||||||
|
{{ state.step1Values.values.AB }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="考试阶段" v-if="state.step1Values.values.MiddleFinal">
|
||||||
|
{{ state.step1Values.values.MiddleFinal }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="考试学期" v-if="state.step1Values.values.FirstSecond">
|
||||||
|
{{ state.step1Values.values.FirstSecond }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="考试类型" v-if="state.step1Values.values.OpenClose">
|
||||||
|
{{ state.step1Values.values.OpenClose }}
|
||||||
|
</a-descriptions-item>
|
||||||
</a-descriptions>
|
</a-descriptions>
|
||||||
<a-divider />
|
<a-divider />
|
||||||
<BasicForm @register="register" />
|
<BasicForm @register="register" />
|
||||||
@@ -14,7 +39,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import { BasicForm, useForm } from '/@/components/Form'
|
import { BasicForm, useForm } from '/@/components/Form'
|
||||||
import { step2Schemas } from './data'
|
import { state } from './data'
|
||||||
|
import { generateFile } from '/@/api/page'
|
||||||
|
import { useMessage } from '/@/hooks/web/useMessage'
|
||||||
import { Alert, Divider, Descriptions } from 'ant-design-vue'
|
import { Alert, Divider, Descriptions } from 'ant-design-vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -27,11 +54,10 @@
|
|||||||
},
|
},
|
||||||
emits: ['next', 'prev'],
|
emits: ['next', 'prev'],
|
||||||
setup(_, { emit }) {
|
setup(_, { emit }) {
|
||||||
const [register, { validate, setProps }] = useForm({
|
const [register, { setProps }] = useForm({
|
||||||
labelWidth: 80,
|
labelWidth: 80,
|
||||||
schemas: step2Schemas,
|
|
||||||
actionColOptions: {
|
actionColOptions: {
|
||||||
span: 14,
|
span: 16,
|
||||||
},
|
},
|
||||||
resetButtonOptions: {
|
resetButtonOptions: {
|
||||||
text: '上一步',
|
text: '上一步',
|
||||||
@@ -43,30 +69,74 @@
|
|||||||
submitFunc: customSubmitFunc,
|
submitFunc: customSubmitFunc,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { createMessage } = useMessage()
|
||||||
|
|
||||||
async function customResetFunc() {
|
async function customResetFunc() {
|
||||||
emit('prev')
|
emit('prev')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function customSubmitFunc() {
|
async function customSubmitFunc() {
|
||||||
try {
|
try {
|
||||||
const values = await validate()
|
|
||||||
setProps({
|
setProps({
|
||||||
submitButtonOptions: {
|
submitButtonOptions: {
|
||||||
loading: true,
|
loading: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
let ys = 0
|
||||||
setProps({
|
let ye = 0
|
||||||
submitButtonOptions: {
|
if (state.step1Values.values['[YearStart, YearEnd]']) {
|
||||||
loading: false,
|
ys = state.step1Values.values['[YearStart, YearEnd]'][0].$y
|
||||||
},
|
ye = state.step1Values.values['[YearStart, YearEnd]'][1].$y
|
||||||
})
|
}
|
||||||
emit('next', values)
|
let tm = 0
|
||||||
}, 1500)
|
if (state.step1Values.values.AB) {
|
||||||
} catch (error) {}
|
if (state.step1Values.values.AB == 'A') tm |= 1
|
||||||
|
else if (state.step1Values.values.AB == 'B') tm |= 2
|
||||||
|
}
|
||||||
|
if (state.step1Values.values.MiddleFinal) {
|
||||||
|
if (state.step1Values.values.MiddleFinal == '中') tm |= 1 << 4
|
||||||
|
else if (state.step1Values.values.MiddleFinal == '末') tm |= 2 << 4
|
||||||
|
}
|
||||||
|
if (state.step1Values.values.FirstSecond) {
|
||||||
|
if (state.step1Values.values.FirstSecond == '1') tm |= 1 << 8
|
||||||
|
else if (state.step1Values.values.FirstSecond == '2') tm |= 2 << 8
|
||||||
|
}
|
||||||
|
if (state.step1Values.values.OpenClose) {
|
||||||
|
if (state.step1Values.values.OpenClose == '开卷') tm |= 1 << 12
|
||||||
|
else if (state.step1Values.values.OpenClose == '一页纸开卷') tm |= 2 << 12
|
||||||
|
else if (state.step1Values.values.OpenClose == '闭卷') tm |= 4 << 12
|
||||||
|
}
|
||||||
|
const data = await generateFile({
|
||||||
|
Distribution: state.step1Values.data.reduce((acc, { major, count }) => {
|
||||||
|
console.log(major, count)
|
||||||
|
acc[major] = count
|
||||||
|
return acc
|
||||||
|
}, {}),
|
||||||
|
RateLimit: state.step1Values.values.RateLimit[1] / 100,
|
||||||
|
YearStart: ys,
|
||||||
|
YearEnd: ye,
|
||||||
|
TypeMask: tm,
|
||||||
|
})
|
||||||
|
setProps({
|
||||||
|
submitButtonOptions: {
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
emit('next', data)
|
||||||
|
} catch (error) {
|
||||||
|
createMessage.error((error as unknown as Error).message)
|
||||||
|
setProps({
|
||||||
|
submitButtonOptions: {
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { register }
|
return {
|
||||||
|
register,
|
||||||
|
state,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,36 +1,87 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="step3">
|
<div class="step3">
|
||||||
<a-result status="success" title="操作成功" sub-title="预计两小时内到账">
|
<a-result status="success" title="试卷生成成功" :sub-title="msg">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<a-button type="primary" @click="redo"> 再转一笔 </a-button>
|
<a-button type="primary" @click="redo"> 再次生成 </a-button>
|
||||||
<a-button> 查看账单 </a-button>
|
<a-button @click="downloadDocx"> 下载试卷 </a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-result>
|
</a-result>
|
||||||
<div class="desc-wrap">
|
<div class="docxWrap" :style="{ width }">
|
||||||
<a-descriptions :column="1" class="mt-5">
|
<div ref="docxRef"></div>
|
||||||
<a-descriptions-item label="付款账户"> ant-design@alipay.com </a-descriptions-item>
|
|
||||||
<a-descriptions-item label="收款账户"> test@example.com </a-descriptions-item>
|
|
||||||
<a-descriptions-item label="收款人姓名"> Vben </a-descriptions-item>
|
|
||||||
<a-descriptions-item label="转账金额"> 500元 </a-descriptions-item>
|
|
||||||
</a-descriptions>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent, ref } from 'vue'
|
||||||
import { Result, Descriptions } from 'ant-design-vue'
|
import { Result, Descriptions } from 'ant-design-vue'
|
||||||
|
import { renderAsync } from 'docx-preview'
|
||||||
|
import { state } from './data'
|
||||||
|
import { dlGeneratedFile } from '/@/api/page'
|
||||||
|
import { useMessage } from '/@/hooks/web/useMessage'
|
||||||
|
import { downloadByData } from '/@/utils/file/download'
|
||||||
|
|
||||||
|
let docxRef = ref(null)
|
||||||
|
|
||||||
|
let docxNameRef = ref('paper.docx')
|
||||||
|
let docxSizeRef = ref(0)
|
||||||
|
|
||||||
|
let docxBlob: Blob | null = null
|
||||||
|
|
||||||
|
function loadDocx(file: Blob) {
|
||||||
|
docxBlob = file
|
||||||
|
renderAsync(file, docxRef.value as unknown as HTMLElement, undefined, {
|
||||||
|
className: 'docx', // 默认和文档样式类的类名/前缀
|
||||||
|
inWrapper: false, // 启用围绕文档内容渲染包装器
|
||||||
|
ignoreWidth: false, // 禁止页面渲染宽度
|
||||||
|
ignoreHeight: false, // 禁止页面渲染高度
|
||||||
|
ignoreFonts: false, // 禁止字体渲染
|
||||||
|
breakPages: false, // 在分页符上启用分页
|
||||||
|
ignoreLastRenderedPageBreak: true, //禁用lastRenderedPageBreak元素的分页
|
||||||
|
experimental: true, // 启用实验性功能(制表符停止计算)
|
||||||
|
trimXmlDeclaration: true, // 如果为真,xml声明将在解析之前从xml文档中删除
|
||||||
|
debug: false, // 启用额外的日志记录
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadDocx() {
|
||||||
|
downloadByData(docxBlob as BlobPart, docxNameRef.value)
|
||||||
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
[Result.name]: Result,
|
[Result.name]: Result,
|
||||||
[Descriptions.name]: Descriptions,
|
[Descriptions.name]: Descriptions,
|
||||||
[Descriptions.Item.name]: Descriptions.Item,
|
[Descriptions.Item.name]: Descriptions.Item,
|
||||||
},
|
},
|
||||||
|
props: {
|
||||||
|
width: {
|
||||||
|
type: String as PropType<string>,
|
||||||
|
default: '100%',
|
||||||
|
},
|
||||||
|
},
|
||||||
emits: ['redo'],
|
emits: ['redo'],
|
||||||
setup(_, { emit }) {
|
setup(_, { emit }) {
|
||||||
|
const { createMessage } = useMessage()
|
||||||
|
;(async () => {
|
||||||
|
try {
|
||||||
|
const data = await dlGeneratedFile()
|
||||||
|
if (data) {
|
||||||
|
loadDocx(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
createMessage.error('加载docx错误: ' + (error as unknown as Error).message)
|
||||||
|
}
|
||||||
|
})()
|
||||||
return {
|
return {
|
||||||
|
msg: state.step2Values,
|
||||||
redo: () => {
|
redo: () => {
|
||||||
emit('redo')
|
emit('redo')
|
||||||
},
|
},
|
||||||
|
docxRef,
|
||||||
|
downloadDocx,
|
||||||
|
docxNameRef,
|
||||||
|
docxSizeRef,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -41,9 +92,12 @@
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.desc-wrap {
|
.docxWrap {
|
||||||
padding: 24px 40px;
|
padding-top: 0px;
|
||||||
margin-top: 24px;
|
margin: 0 auto;
|
||||||
background-color: @background-color-light;
|
overflow-x: auto;
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import { FormSchema } from '/@/components/Form'
|
import { FormSchema } from '/@/components/Form'
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
|
||||||
|
export const state = reactive({
|
||||||
|
initSetp2: false,
|
||||||
|
initSetp3: false,
|
||||||
|
step1Values: { values: {} as any, data: [] as Recordable<any>[] },
|
||||||
|
step2Values: {} as any,
|
||||||
|
})
|
||||||
|
|
||||||
export const step1Schemas: FormSchema[] = [
|
export const step1Schemas: FormSchema[] = [
|
||||||
{
|
{
|
||||||
@@ -119,16 +127,3 @@ export const step1Schemas: FormSchema[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const step2Schemas: FormSchema[] = [
|
|
||||||
{
|
|
||||||
field: 'pwd',
|
|
||||||
component: 'InputPassword',
|
|
||||||
label: '支付密码',
|
|
||||||
required: true,
|
|
||||||
defaultValue: '123456',
|
|
||||||
colProps: {
|
|
||||||
span: 24,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<PageWrapper
|
<PageWrapper
|
||||||
title="分步表单"
|
:title="t('routes.genfile.name')"
|
||||||
contentBackground
|
contentBackground
|
||||||
content=" 将一个冗长或用户不熟悉的表单任务分成多个步骤,指导用户完成。"
|
content="使用自定义限制条件生成试卷"
|
||||||
contentClass="p-4"
|
contentClass="p-4"
|
||||||
>
|
>
|
||||||
<div class="step-form-form">
|
<div class="step-form-form">
|
||||||
<a-steps :current="current">
|
<a-steps :current="current">
|
||||||
<a-step title="填写转账信息" />
|
<a-step title="填写信息" />
|
||||||
<a-step title="确认转账信息" />
|
<a-step title="确认生成" />
|
||||||
<a-step title="完成" />
|
<a-step title="下载" />
|
||||||
</a-steps>
|
</a-steps>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
@@ -25,12 +25,14 @@
|
|||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, reactive, toRefs } from 'vue'
|
import { defineComponent, ref, toRefs } from 'vue'
|
||||||
|
import { state } from './data'
|
||||||
import Step1 from './Step1.vue'
|
import Step1 from './Step1.vue'
|
||||||
import Step2 from './Step2.vue'
|
import Step2 from './Step2.vue'
|
||||||
import Step3 from './Step3.vue'
|
import Step3 from './Step3.vue'
|
||||||
import { PageWrapper } from '/@/components/Page'
|
import { PageWrapper } from '/@/components/Page'
|
||||||
import { Steps } from 'ant-design-vue'
|
import { Steps } from 'ant-design-vue'
|
||||||
|
import { useI18n } from '/@/hooks/web/useI18n'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'FormStepPage',
|
name: 'FormStepPage',
|
||||||
@@ -45,15 +47,12 @@
|
|||||||
setup() {
|
setup() {
|
||||||
const current = ref(0)
|
const current = ref(0)
|
||||||
|
|
||||||
const state = reactive({
|
const { t } = useI18n()
|
||||||
initSetp2: false,
|
|
||||||
initSetp3: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
function handleStep1Next(step1Values: any) {
|
function handleStep1Next(step1Values: any) {
|
||||||
current.value++
|
current.value++
|
||||||
state.initSetp2 = true
|
state.initSetp2 = true
|
||||||
console.log(step1Values)
|
state.step1Values = step1Values
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStepPrev() {
|
function handleStepPrev() {
|
||||||
@@ -63,7 +62,7 @@
|
|||||||
function handleStep2Next(step2Values: any) {
|
function handleStep2Next(step2Values: any) {
|
||||||
current.value++
|
current.value++
|
||||||
state.initSetp3 = true
|
state.initSetp3 = true
|
||||||
console.log(step2Values)
|
state.step2Values = step2Values
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRedo() {
|
function handleRedo() {
|
||||||
@@ -73,6 +72,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
t,
|
||||||
current,
|
current,
|
||||||
handleStep1Next,
|
handleStep1Next,
|
||||||
handleStep2Next,
|
handleStep2Next,
|
||||||
|
|||||||
Reference in New Issue
Block a user