mirror of
https://github.com/fumiama/paper-manager.git
synced 2026-06-04 23:40:24 +08:00
front: finish docx & back: init
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,3 +16,5 @@
|
||||
|
||||
# MacOS storage file
|
||||
.DS_Store
|
||||
|
||||
/data
|
||||
|
||||
15
backend/api/main.go
Normal file
15
backend/api/main.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/fumiama/paper-manager/backend/utils"
|
||||
)
|
||||
|
||||
// Handler serves all backend /api call
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.IsMethod("GET", w, r) {
|
||||
return
|
||||
}
|
||||
http.Error(w, "404 Not Found", http.StatusNotFound)
|
||||
}
|
||||
26
backend/file/provider.go
Normal file
26
backend/file/provider.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/fumiama/paper-manager/backend/global"
|
||||
"github.com/fumiama/paper-manager/backend/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Handler serves contents in global.FileFolder
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.IsMethod("GET", w, r) {
|
||||
return
|
||||
}
|
||||
i := strings.LastIndex(r.URL.Path, "/")
|
||||
fn := r.URL.Path[i+1:]
|
||||
if fn == "" {
|
||||
http.Error(w, "400 Bad Request: empty path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
name := global.FileFolder + fn
|
||||
logrus.Infoln("[file.Handler]\t serve", name)
|
||||
http.ServeFile(w, r, name)
|
||||
}
|
||||
36
backend/global/base.go
Normal file
36
backend/global/base.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// DataFolder stores all backend data in
|
||||
DataFolder = "./data/"
|
||||
// FileFolder stores all blob files
|
||||
FileFolder = DataFolder + "file/"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initdir(DataFolder)
|
||||
initdir(FileFolder)
|
||||
}
|
||||
|
||||
func initdir(folder string) {
|
||||
err := os.MkdirAll(folder, 0755)
|
||||
if err != nil {
|
||||
logrus.Errorln("[os.MkdirAll]\t", err)
|
||||
os.Exit(line())
|
||||
}
|
||||
}
|
||||
|
||||
func line() int {
|
||||
_, _, fileLine, ok := runtime.Caller(2)
|
||||
if ok {
|
||||
return fileLine
|
||||
}
|
||||
return -1
|
||||
}
|
||||
27
backend/utils/method.go
Normal file
27
backend/utils/method.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// IP gets ip from r.Header's X-FORWARDED-FOR or r.RemoteAddr
|
||||
func IP(r *http.Request) string {
|
||||
forwarded := r.Header.Get("X-FORWARDED-FOR")
|
||||
if forwarded != "" {
|
||||
return forwarded
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
// IsMethod check if the method meets the requirement
|
||||
// and response 405 Method Not Allowed if not matched
|
||||
func IsMethod(m string, w http.ResponseWriter, r *http.Request) bool {
|
||||
logrus.Infoln("[utils.IsMethod]\t accept", IP(r), r.Method, r.URL)
|
||||
if r.Method != m {
|
||||
http.Error(w, "405 Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -6,14 +6,14 @@ VITE_PUBLIC_PATH = /
|
||||
|
||||
# Cross-domain proxy, you can configure multiple
|
||||
# Please note that no line breaks
|
||||
VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","http://localhost:3000/upload"]]
|
||||
VITE_PROXY = [["/api","http://localhost:3000/api"],["/file","http://localhost:3000/file"],["/upload","http://localhost:3000/upload"]]
|
||||
# VITE_PROXY=[["/api","https://vvbin.cn/test"]]
|
||||
|
||||
# Delete console
|
||||
VITE_DROP_CONSOLE = false
|
||||
|
||||
# Basic interface address SPA
|
||||
VITE_GLOB_API_URL=/basic-api
|
||||
VITE_GLOB_API_URL=/api
|
||||
|
||||
# File upload address, optional
|
||||
VITE_GLOB_UPLOAD_URL=/upload
|
||||
|
||||
@@ -16,7 +16,7 @@ VITE_BUILD_COMPRESS = 'none'
|
||||
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
|
||||
|
||||
# Basic interface address SPA
|
||||
VITE_GLOB_API_URL=/basic-api
|
||||
VITE_GLOB_API_URL=/api
|
||||
|
||||
# File upload address, optional
|
||||
# It can be forwarded by nginx or write the actual address directly
|
||||
|
||||
@@ -45,7 +45,7 @@ const userInfo = {
|
||||
|
||||
export default [
|
||||
{
|
||||
url: '/basic-api/account/getAccountInfo',
|
||||
url: '/api/account/getAccountInfo',
|
||||
timeout: 1000,
|
||||
method: 'get',
|
||||
response: () => {
|
||||
@@ -53,7 +53,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/user/sessionTimeout',
|
||||
url: '/api/user/sessionTimeout',
|
||||
method: 'post',
|
||||
statusCode: 401,
|
||||
response: () => {
|
||||
@@ -61,7 +61,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/user/tokenExpired',
|
||||
url: '/api/user/tokenExpired',
|
||||
method: 'post',
|
||||
statusCode: 200,
|
||||
response: () => {
|
||||
|
||||
@@ -311,7 +311,7 @@ const areaList: any[] = [
|
||||
]
|
||||
export default [
|
||||
{
|
||||
url: '/basic-api/cascader/getAreaRecord',
|
||||
url: '/api/cascader/getAreaRecord',
|
||||
timeout: 1000,
|
||||
method: 'post',
|
||||
response: ({ body }) => {
|
||||
|
||||
@@ -16,7 +16,7 @@ const demoList = (keyword: string, count = 20) => {
|
||||
|
||||
export default [
|
||||
{
|
||||
url: '/basic-api/select/getDemoOptions',
|
||||
url: '/api/select/getDemoOptions',
|
||||
timeout: 1000,
|
||||
method: 'get',
|
||||
response: ({ query }) => {
|
||||
|
||||
@@ -136,7 +136,7 @@ const menuList = (() => {
|
||||
|
||||
export default [
|
||||
{
|
||||
url: '/basic-api/system/getAccountList',
|
||||
url: '/api/system/getAccountList',
|
||||
timeout: 100,
|
||||
method: 'get',
|
||||
response: ({ query }) => {
|
||||
@@ -145,7 +145,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/system/getRoleListByPage',
|
||||
url: '/api/system/getRoleListByPage',
|
||||
timeout: 100,
|
||||
method: 'get',
|
||||
response: ({ query }) => {
|
||||
@@ -154,7 +154,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/system/setRoleStatus',
|
||||
url: '/api/system/setRoleStatus',
|
||||
timeout: 500,
|
||||
method: 'post',
|
||||
response: ({ query }) => {
|
||||
@@ -163,7 +163,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/system/getAllRoleList',
|
||||
url: '/api/system/getAllRoleList',
|
||||
timeout: 100,
|
||||
method: 'get',
|
||||
response: () => {
|
||||
@@ -171,7 +171,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/system/getDeptList',
|
||||
url: '/api/system/getDeptList',
|
||||
timeout: 100,
|
||||
method: 'get',
|
||||
response: () => {
|
||||
@@ -179,7 +179,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/system/getMenuList',
|
||||
url: '/api/system/getMenuList',
|
||||
timeout: 100,
|
||||
method: 'get',
|
||||
response: () => {
|
||||
@@ -187,7 +187,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/system/accountExist',
|
||||
url: '/api/system/accountExist',
|
||||
timeout: 500,
|
||||
method: 'post',
|
||||
response: ({ body }) => {
|
||||
|
||||
@@ -41,7 +41,7 @@ const demoList = (() => {
|
||||
|
||||
export default [
|
||||
{
|
||||
url: '/basic-api/table/getDemoList',
|
||||
url: '/api/table/getDemoList',
|
||||
timeout: 100,
|
||||
method: 'get',
|
||||
response: ({ query }) => {
|
||||
|
||||
@@ -26,7 +26,7 @@ const demoTreeList = (keyword: string) => {
|
||||
|
||||
export default [
|
||||
{
|
||||
url: '/basic-api/tree/getDemoOptions',
|
||||
url: '/api/tree/getDemoOptions',
|
||||
timeout: 1000,
|
||||
method: 'get',
|
||||
response: ({ query }) => {
|
||||
|
||||
19
frontend/vben/mock/page/file.ts
Normal file
19
frontend/vben/mock/page/file.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
import { resultError, resultSuccess, getRequestToken, requestParams } from '../_util'
|
||||
|
||||
export default [
|
||||
{
|
||||
url: '/api/dlFile',
|
||||
timeout: 1000,
|
||||
method: 'get',
|
||||
response: (request: requestParams) => {
|
||||
const token = getRequestToken(request)
|
||||
if (!token) return resultError('Invalid token')
|
||||
const id = Number(request.query.id)
|
||||
if (!id || id < 0) return resultError('Invalid id')
|
||||
return resultSuccess({
|
||||
url: '/file/' + id + '.docx',
|
||||
})
|
||||
},
|
||||
},
|
||||
] as MockMethod[]
|
||||
@@ -26,7 +26,7 @@ function createFileList() {
|
||||
export default [
|
||||
// mock get filelist
|
||||
{
|
||||
url: '/basic-api/getFileList',
|
||||
url: '/api/getFileList',
|
||||
timeout: 200,
|
||||
method: 'get',
|
||||
response: (request: requestParams) => {
|
||||
@@ -42,7 +42,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/getFilePercent',
|
||||
url: '/api/getFilePercent',
|
||||
timeout: 200,
|
||||
method: 'get',
|
||||
response: (request: requestParams) => {
|
||||
@@ -71,7 +71,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/delFile',
|
||||
url: '/api/delFile',
|
||||
timeout: 200,
|
||||
method: 'get',
|
||||
response: (request: requestParams) => {
|
||||
@@ -86,7 +86,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/analyzeFile',
|
||||
url: '/api/analyzeFile',
|
||||
timeout: 1000,
|
||||
method: 'get',
|
||||
response: (request: requestParams) => {
|
||||
|
||||
@@ -227,7 +227,7 @@ const linkRoute = {
|
||||
|
||||
export default [
|
||||
{
|
||||
url: '/basic-api/getMenuList',
|
||||
url: '/api/getMenuList',
|
||||
timeout: 1000,
|
||||
method: 'get',
|
||||
response: (request: requestParams) => {
|
||||
|
||||
@@ -62,7 +62,7 @@ const fakeCodeList: any = {
|
||||
export default [
|
||||
// mock user login
|
||||
{
|
||||
url: '/basic-api/login',
|
||||
url: '/api/login',
|
||||
timeout: 200,
|
||||
method: 'post',
|
||||
response: ({ body }) => {
|
||||
@@ -86,7 +86,7 @@ export default [
|
||||
},
|
||||
// mock reset password
|
||||
{
|
||||
url: '/basic-api/resetPassword',
|
||||
url: '/api/resetPassword',
|
||||
timeout: 200,
|
||||
method: 'post',
|
||||
response: ({ body }) => {
|
||||
@@ -98,7 +98,7 @@ export default [
|
||||
},
|
||||
// mock register
|
||||
{
|
||||
url: '/basic-api/register',
|
||||
url: '/api/register',
|
||||
timeout: 200,
|
||||
method: 'post',
|
||||
response: ({ body }) => {
|
||||
@@ -109,7 +109,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/getUserInfo',
|
||||
url: '/api/getUserInfo',
|
||||
method: 'get',
|
||||
response: (request: requestParams) => {
|
||||
const token = getRequestToken(request)
|
||||
@@ -122,7 +122,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/getPermCode',
|
||||
url: '/api/getPermCode',
|
||||
timeout: 200,
|
||||
method: 'get',
|
||||
response: (request: requestParams) => {
|
||||
@@ -138,7 +138,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/logout',
|
||||
url: '/api/logout',
|
||||
timeout: 200,
|
||||
method: 'get',
|
||||
response: (request: requestParams) => {
|
||||
@@ -152,7 +152,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/basic-api/testRetry',
|
||||
url: '/api/testRetry',
|
||||
statusCode: 405,
|
||||
method: 'get',
|
||||
response: () => {
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"cropperjs": "^1.5.12",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dayjs": "^1.11.1",
|
||||
"docx-preview": "^0.1.15",
|
||||
"echarts": "^5.3.2",
|
||||
"intro.js": "^5.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { defHttp } from '/@/utils/http/axios'
|
||||
import { getFileListModel, FilePercent, DelFile, AnalyzeFile } from './model/fileListModel'
|
||||
import { DownloadFile } from './model/fileModel'
|
||||
|
||||
enum Api {
|
||||
GetFileList = '/getFileList',
|
||||
GetFilePercent = '/getFilePercent',
|
||||
DelFile = '/delFile',
|
||||
AnalyzeFile = '/analyzeFile',
|
||||
DlFile = '/dlFile',
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,3 +37,10 @@ export const delFile = (id: number) => {
|
||||
export const analyzeFile = (id: number) => {
|
||||
return defHttp.get<AnalyzeFile>({ url: Api.AnalyzeFile, params: { id: id } })
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: Download file
|
||||
*/
|
||||
export const downloadFile = (id: number) => {
|
||||
return defHttp.get<DownloadFile>({ url: Api.DlFile, params: { id: id } })
|
||||
}
|
||||
|
||||
3
frontend/vben/src/api/page/model/fileModel.ts
Normal file
3
frontend/vben/src/api/page/model/fileModel.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface DownloadFile {
|
||||
url: string
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export default {
|
||||
name: '试卷库',
|
||||
file: '试卷解析',
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { AppRouteModule } from '/@/router/types'
|
||||
|
||||
import { ExceptionEnum } from '/@/enums/exceptionEnum'
|
||||
import { LAYOUT } from '/@/router/constant'
|
||||
import { t } from '/@/hooks/web/useI18n'
|
||||
|
||||
const ExceptionPage = () => import('/@/views/sys/exception/Exception.vue')
|
||||
|
||||
const filelist: AppRouteModule = {
|
||||
path: '/filelist',
|
||||
name: 'FileList',
|
||||
@@ -25,6 +27,28 @@ const filelist: AppRouteModule = {
|
||||
hideMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'file/:id',
|
||||
name: 'FilePage',
|
||||
component: () => import('/@/views/page/file/index.vue'),
|
||||
meta: {
|
||||
title: t('routes.filelist.file'),
|
||||
carryParam: true,
|
||||
icon: 'bi:filetype-docx',
|
||||
hideMenu: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '404',
|
||||
name: 'PageNotFound',
|
||||
component: ExceptionPage,
|
||||
props: {
|
||||
status: ExceptionEnum.PAGE_NOT_FOUND,
|
||||
},
|
||||
meta: {
|
||||
title: '404',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
206
frontend/vben/src/views/page/file/index.vue
Normal file
206
frontend/vben/src/views/page/file/index.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<PageWrapper title="带参数标签页" content="支持带参数多tab缓存">
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
<div class="docxWrap" :style="{ width }">
|
||||
<div ref="docxRef"></div>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, unref, PropType, ref, Ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { PageWrapper } from '/@/components/Page'
|
||||
import { useECharts } from '/@/hooks/web/useECharts'
|
||||
import { renderAsync } from 'docx-preview'
|
||||
import { downloadFile } from '/@/api/page'
|
||||
import { DownloadFile } from '/@/api/page/model/fileModel'
|
||||
import { router } from '/@/router'
|
||||
import { PageEnum } from '/@/enums/pageEnum'
|
||||
import axios from 'axios'
|
||||
|
||||
let docxRef = ref(null)
|
||||
|
||||
function loadDocx(file: Blob) {
|
||||
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, // 启用额外的日志记录
|
||||
})
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TestTab',
|
||||
components: { PageWrapper },
|
||||
props: {
|
||||
width: {
|
||||
type: String as PropType<string>,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String as PropType<string>,
|
||||
default: 'calc(100vh - 78px)',
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { currentRoute } = useRouter()
|
||||
|
||||
const params = computed(() => {
|
||||
return unref(currentRoute).params
|
||||
})
|
||||
|
||||
if (!params.value || !params.value.id) {
|
||||
router.push(PageEnum.ERROR_PAGE)
|
||||
}
|
||||
|
||||
downloadFile(Number(params.value.id)).then((file: DownloadFile) => {
|
||||
if (file && file.url) {
|
||||
axios({
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
url: file.url,
|
||||
}).then(({ data }) => {
|
||||
loadDocx(data)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const chartRef = ref<HTMLDivElement | null>(null)
|
||||
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
|
||||
const dataAll = [389, 259, 262, 324, 232, 176, 196, 214, 133, 370]
|
||||
const yAxisData = [
|
||||
'原因1',
|
||||
'原因2',
|
||||
'原因3',
|
||||
'原因4',
|
||||
'原因5',
|
||||
'原因6',
|
||||
'原因7',
|
||||
'原因8',
|
||||
'原因9',
|
||||
'原因10',
|
||||
]
|
||||
onMounted(() => {
|
||||
setOptions({
|
||||
title: [
|
||||
{
|
||||
text: '题量占比',
|
||||
left: '2%',
|
||||
top: '1%',
|
||||
textStyle: {
|
||||
fontSize: 36,
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '重复率前十',
|
||||
left: '40%',
|
||||
top: '1%',
|
||||
textStyle: {
|
||||
fontSize: 36,
|
||||
},
|
||||
},
|
||||
{
|
||||
text: '分数占比',
|
||||
left: '2%',
|
||||
top: '50%',
|
||||
textStyle: {
|
||||
fontSize: 36,
|
||||
},
|
||||
},
|
||||
],
|
||||
grid: [{ left: '50%', top: '7%', width: '45%', height: '90%' }],
|
||||
tooltip: {
|
||||
formatter: '{b} ({c})',
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
gridIndex: 0,
|
||||
axisTick: { show: false },
|
||||
axisLabel: { show: false },
|
||||
splitLine: { show: false },
|
||||
axisLine: { show: false },
|
||||
},
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
gridIndex: 0,
|
||||
interval: 0,
|
||||
data: yAxisData.reverse(),
|
||||
axisTick: { show: false },
|
||||
axisLabel: { show: true },
|
||||
splitLine: { show: false },
|
||||
axisLine: { show: true },
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '各渠道投诉占比',
|
||||
type: 'pie',
|
||||
radius: '30%',
|
||||
center: ['22%', '25%'],
|
||||
data: [
|
||||
{ value: 335, name: '客服电话' },
|
||||
{ value: 310, name: '奥迪官网' },
|
||||
{ value: 234, name: '媒体曝光' },
|
||||
{ value: 135, name: '质检总局' },
|
||||
{ value: 105, name: '其他' },
|
||||
],
|
||||
labelLine: { show: false },
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b} \n ({d}%)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '各级别投诉占比',
|
||||
type: 'pie',
|
||||
radius: '30%',
|
||||
center: ['22%', '75%'],
|
||||
labelLine: { show: false },
|
||||
data: [
|
||||
{ value: 335, name: 'A级' },
|
||||
{ value: 310, name: 'B级' },
|
||||
{ value: 234, name: 'C级' },
|
||||
{ value: 135, name: 'D级' },
|
||||
],
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{b} \n ({d}%)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '投诉原因TOP10',
|
||||
type: 'bar',
|
||||
xAxisIndex: 0,
|
||||
yAxisIndex: 0,
|
||||
barWidth: '45%',
|
||||
itemStyle: { color: '#86c9f4' },
|
||||
label: { show: true, position: 'right' },
|
||||
data: dataAll.sort(),
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
return {
|
||||
chartRef,
|
||||
docxRef,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.docxWrap {
|
||||
padding-top: 0px;
|
||||
margin: 0 auto;
|
||||
overflow-x: auto;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
}
|
||||
</style>
|
||||
4
go.mod
4
go.mod
@@ -1,3 +1,7 @@
|
||||
module github.com/fumiama/paper-manager
|
||||
|
||||
go 1.20
|
||||
|
||||
require github.com/sirupsen/logrus v1.9.0
|
||||
|
||||
require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
|
||||
15
go.sum
Normal file
15
go.sum
Normal file
@@ -0,0 +1,15 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
36
main.go
36
main.go
@@ -1,5 +1,39 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
import (
|
||||
"flag"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/fumiama/paper-manager/backend/api"
|
||||
"github.com/fumiama/paper-manager/backend/file"
|
||||
)
|
||||
|
||||
func line() int {
|
||||
_, _, fileLine, ok := runtime.Caller(1)
|
||||
if ok {
|
||||
return fileLine
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func main() {
|
||||
addr := flag.String("l", "[::]:3000", "listen addr")
|
||||
flag.Parse()
|
||||
l, err := net.Listen("tcp", *addr)
|
||||
if err != nil {
|
||||
logrus.Errorln("[net.Listen]\t", err)
|
||||
os.Exit(line())
|
||||
}
|
||||
|
||||
http.HandleFunc("/api/", api.Handler)
|
||||
http.HandleFunc("/file/", file.Handler)
|
||||
|
||||
logrus.Infoln("[http.Serve]\t start at", l.Addr())
|
||||
logrus.Errorln("[http.Serve]\t", http.Serve(l, nil))
|
||||
os.Exit(line())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user