master
xy 1 year ago
parent f91294e666
commit 17c3fd5afe

@ -2,6 +2,6 @@
ENV='development'
# base api
VUE_APP_BASE_API=zhiyuan-test.ali251.langye.net
VUE_APP_UPLOAD_API=zhiyuan-test.ali251.langye.net/api/admin/upload-file
VUE_APP_BASE_API=http://zhiyuan-test.ali251.langye.net
VUE_APP_UPLOAD_API=http://zhiyuan-test.ali251.langye.net/api/admin/upload-file
VUE_APP_PREVIEW_API=http://view.ali251.langye.net:8012/onlinePreview

@ -2,6 +2,6 @@
ENV = 'production'
# base api
VUE_APP_BASE_API = http://starter.ali251.langye.net
VUE_APP_UPLOAD_API =http://starter.ali251.langye.net/api/admin/upload-file
VUE_APP_BASE_API = ''
VUE_APP_UPLOAD_API =/api/admin/upload-file
VUE_APP_PREVIEW_API=http://view.ali251.langye.net:8012/onlinePreview

@ -189,7 +189,7 @@ module.exports = {
'yield-star-spacing': [2, 'both'],
'yoda': [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'no-debugger': 0,
'object-curly-spacing': [2, 'always', {
objectsInObjects: false
}],

1
.gitignore vendored

@ -14,3 +14,4 @@ tests/**/coverage/
*.ntvs*
*.njsproj
*.sln
/script/

@ -37,9 +37,14 @@
"vuedraggable": "^2.24.3",
"vuex": "3.1.0",
"webpack-md5-hash": "^0.0.6",
"xlsx": "^0.18.5"
"xlsx": "^0.18.5",
"vxe-table-plugin-export-xlsx": "^3.3.4",
"vxe-pc-ui": "^3.3.11",
"vxe-table": "^3.11.12",
"exceljs": "^4.4.0"
},
"devDependencies": {
"ejs": "^3.1.10",
"@vue/cli-plugin-babel": "4.4.4",
"@vue/cli-plugin-eslint": "4.4.4",
"@vue/cli-plugin-unit-jest": "4.4.4",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,37 @@
import request from '@/utils/request'
export function index(params,isLoading = true) {
return request({
method: 'get',
url: '/api/admin/area/index',
params,
isLoading
})
}
export function show(params,isLoading = true) {
return request({
method: 'get',
url: '/api/admin/area/show',
params,
isLoading
})
}
export function save(data, isLoading = true) {
return request({
method: 'post',
url: '/api/admin/area/save',
data,
isLoading
})
}
export function destroy(params, isLoading = true) {
return request({
method: 'get',
url: '/api/admin/area/destroy',
params,
isLoading
})
}

@ -0,0 +1,37 @@
import request from '@/utils/request'
export function index(params,isLoading = true) {
return request({
method: 'get',
url: '/api/admin/school/index',
params,
isLoading
})
}
export function show(params,isLoading = true) {
return request({
method: 'get',
url: '/api/admin/school/show',
params,
isLoading
})
}
export function save(data, isLoading = true) {
return request({
method: 'post',
url: '/api/admin/school/save',
data,
isLoading
})
}
export function destroy(params, isLoading = true) {
return request({
method: 'get',
url: '/api/admin/school/destroy',
params,
isLoading
})
}

@ -0,0 +1,37 @@
import request from '@/utils/request'
export function index(params,isLoading = true) {
return request({
method: 'get',
url: '/api/admin/score/index',
params,
isLoading
})
}
export function show(params,isLoading = true) {
return request({
method: 'get',
url: '/api/admin/score/show',
params,
isLoading
})
}
export function save(data, isLoading = true) {
return request({
method: 'post',
url: '/api/admin/score/save',
data,
isLoading
})
}
export function destroy(params, isLoading = true) {
return request({
method: 'get',
url: '/api/admin/score/destroy',
params,
isLoading
})
}

@ -0,0 +1,37 @@
import request from '@/utils/request'
export function index(params,isLoading = true) {
return request({
method: 'get',
url: '/api/admin/user/index',
params,
isLoading
})
}
export function show(params,isLoading = true) {
return request({
method: 'get',
url: '/api/admin/user/show',
params,
isLoading
})
}
export function save(data, isLoading = true) {
return request({
method: 'post',
url: '/api/admin/user/save',
data,
isLoading
})
}
export function destroy(params, isLoading = true) {
return request({
method: 'get',
url: '/api/admin/user/destroy',
params,
isLoading
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 126 KiB

@ -0,0 +1,75 @@
<template>
<div>
<vxe-modal
:width="defaultModalSize.width"
v-model="showModal"
:height="defaultModalSize.height"
show-zoom
resize
transfer
esc-closable
show-footer
show-confirm-button
show-cancel-button
:fullscreen="$store.getters.device === 'mobile'"
:z-index="zIndex"
title="文本"
@confirm="confirm"
>
<template>
<my-tinymce v-model="myText" style="height: 100%;" :disabled="readonly" />
</template>
</vxe-modal>
</div>
</template>
<script>
import { PopupManager } from 'element-ui/lib/utils/popup'
import { defaultModalSize } from '@/settings'
export default {
name: 'RichTextModal',
data() {
return {
defaultModalSize,
zIndex: PopupManager.nextZIndex(),
showModal: false,
myText: '',
readonly: false,
fieldName: '',
row: {}
}
},
computed: {},
created() {
this.$bus.$on('rich-text', ({ text, readonly }) => this.open(text, readonly))
},
beforeDestroy() {
this.$bus.$off('rich-text')
},
methods: {
open({ text, readonly, row, fieldName }) {
this.showModal = true
this.$nextTick(() => {
this.myText = text
this.readonly = !!readonly
this.row = row
this.fieldName = fieldName
})
},
confirm() {
if (!this.readonly) {
this.$emit('confirm', {
row: this.row,
fieldName: this.fieldName,
text: this.myText
})
console.log(this.myText)
}
}
}
}
</script>
<style scoped lang="scss">
</style>

@ -36,8 +36,8 @@
transition: opacity 1.5s;
}
.sidebar-name-logo {
width: 120px;
height: 22px;
object-fit: contain;
height: 80%;
}
.sidebarLogoFade-enter,

@ -52,6 +52,21 @@ Vue.use(avue)
import AvueMap from 'avue-plugin-map'
Vue.use(AvueMap);
import VxeUI from 'vxe-pc-ui'
import 'vxe-pc-ui/lib/style.css'
import VxeUITable from 'vxe-table'
import 'vxe-table/lib/style.css'
import domZIndex from 'dom-zindex'
domZIndex.setCurrent(2000)
import VxeTable from 'vxe-table'
Vue.use(VxeUI)
import VXETablePluginExportXLSX from 'vxe-table-plugin-export-xlsx'
import ExcelJS from 'exceljs'
VxeTable.use(VXETablePluginExportXLSX, {
ExcelJS
})
Vue.use(VxeUITable)
Vue.directive('loadMore', {
bind(el, binding) {
const selectWrap = el.querySelector('.el-scrollbar__wrap')
@ -94,5 +109,8 @@ new Vue({
el: '#app',
router,
store,
beforeCreate() {
Vue.prototype.$bus = this
},
render: h => h(App)
})

@ -1,7 +1,7 @@
module.exports = {
title: '朗业基础平台',
TOKEN_KEY: 'starter_token',
title: '新飞跃',
TOKEN_KEY: 'xinfeiyue_token',
/**
* @type {boolean} true | false
* @description Whether fix the header
@ -12,5 +12,12 @@ module.exports = {
* @type {boolean} true | false
* @description Whether show the logo in sidebar
*/
sidebarLogo: true
sidebarLogo: true,
uploadSize: 20 * 1024 * 1024,
defaultModalSize: {
width: '76vw',
height: '80vh',
sWidth: '64vw',
sHeight: '72vh',
}
}

@ -228,6 +228,11 @@ const actions = {
} else {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
asyncRoutes.push({
path: '*',
redirect: '/404',
hidden: true
})
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4052909 */
src: url('iconfont.woff2?t=1683361610626') format('woff2'),
url('iconfont.woff?t=1683361610626') format('woff'),
url('iconfont.ttf?t=1683361610626') format('truetype');
font-family: "iconfont"; /* Project id 4840280 */
src: url('iconfont.woff2?t=1740625338753') format('woff2'),
url('iconfont.woff?t=1740625338753') format('woff'),
url('iconfont.ttf?t=1740625338753') format('truetype');
}
.iconfont {
@ -13,107 +13,151 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-jurassic_process-list:before {
content: "\e6c4";
.icon-xiaoliang:before {
content: "\e600";
}
.icon-biaodan:before {
content: "\e663";
.icon-customer:before {
content: "\e618";
}
.icon-biaozhunhuaguizeguanli:before {
content: "\e60a";
.icon-report:before {
content: "\e622";
}
.icon-fenshuxian-:before {
content: "\e613";
}
.icon-fenshuxian-1:before {
content: "\e614";
}
.icon-kehuqunzu:before {
content: "\e673";
}
.icon-zhibiaomoban:before {
content: "\e631";
}
.icon-zhibiaotongji:before {
content: "\e632";
}
.icon-zhibiaojihua:before {
content: "\e633";
}
.icon-zhuanyemingcheng:before {
content: "\e627";
}
.icon-renshixuexiao:before {
content: "\e604";
}
.icon-tongji:before {
content: "\e605";
}
.icon-zhuanyeyukecheng:before {
content: "\e660";
}
.icon-a-zhidu6:before {
content: "\eb07";
.icon-xueshengchengchangdangan_xueshengchengchangdangan:before {
content: "\e601";
}
.icon-dat:before {
content: "\e691";
.icon-xuexiao:before {
content: "\e643";
}
.icon-audio:before {
content: "\e692";
.icon-fenshu:before {
content: "\e60b";
}
.icon-video:before {
content: "\e693";
.icon-xuexiao_xuexiaoxinxi:before {
content: "\e661";
}
.icon-zip:before {
content: "\e694";
.icon-zhuanyexitong:before {
content: "\e60a";
}
.icon-image:before {
content: "\e695";
.icon-xuexiao_kemu:before {
content: "\e615";
}
.icon-pdf:before {
content: "\e696";
.icon-jiankangzhibiao:before {
content: "\e67c";
}
.icon-ppt:before {
content: "\e697";
.icon-xuesheng:before {
content: "\e6e7";
}
.icon-21excel:before {
content: "\e698";
.icon-kehu:before {
content: "\e623";
}
.icon-21word:before {
content: "\e699";
.icon-tuijian:before {
content: "\e602";
}
.icon-21move:before {
content: "\e69a";
.icon-tongji1:before {
content: "\e6b7";
}
.icon-21setting:before {
content: "\e69b";
.icon-yunwei:before {
content: "\e6b8";
}
.icon-21upload:before {
content: "\e69c";
.icon-statistics-full:before {
content: "\e86b";
}
.icon-21download:before {
content: "\e69d";
.icon-zhuanyeguanli:before {
content: "\e70b";
}
.icon-21cancel:before {
.icon-yunweifenxipingtai:before {
content: "\e69e";
}
.icon-21ok:before {
content: "\e69f";
.icon-tuijian1:before {
content: "\e759";
}
.icon-xueshengtejia:before {
content: "\e6ed";
}
.icon-21copy:before {
content: "\e6a0";
.icon-fenxiangtuijian-m:before {
content: "\e7dc";
}
.icon-21delete:before {
content: "\e6a1";
.icon-zhibiaoyingyong:before {
content: "\e66f";
}
.icon-21edit:before {
content: "\e6a2";
.icon-icon_xueshengdanganshengcheng:before {
content: "\e61e";
}
.icon-21new:before {
content: "\e6a3";
.icon-xitongyunweilei:before {
content: "\e634";
}
.icon-21folder:before {
content: "\e6a4";
.icon-zhiyuantianbao:before {
content: "\e60f";
}
.icon-21mutil:before {
content: "\e6a5";
.icon-gaokao_zhiyuantianbao:before {
content: "\e61a";
}
.icon-21file:before {
content: "\e6a6";
.icon-zhiyuantianbao1:before {
content: "\e6dc";
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -52,7 +52,7 @@ export async function download(url, method = "get", info, filename) {
if (!filename) {
filename =
response.headers["content-disposition"]?.match(/filename=(.*)/)[1] ||
"";
`${new Date().valueOf()}.xlsx`;
}
// 将二进制流转为blob

@ -171,3 +171,15 @@ export function debounce(func, delay) {
}
}
export function formatFileSize(size) {
if (size < 1024 * 1024) {
const temp = size / 1024;
return temp.toFixed(2) + "KB";
} else if (size < 1024 * 1024 * 1024) {
const temp = size / (1024 * 1024);
return temp.toFixed(2) + "MB";
} else {
const temp = size / (1024 * 1024 * 1024);
return temp.toFixed(2) + "GB";
}
}

@ -8,13 +8,13 @@
<img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
</div>
<div class="bullshit">
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__info">All rights reserved
<a style="color:#20a0ff" href="https://wallstreetcn.com" target="_blank">wallstreetcn</a>
</div>
<div class="bullshit__oops">404</div>
<!-- <div class="bullshit__info">-->
<!-- <a style="color:#20a0ff" href="https://wallstreetcn.com" target="_blank">wallstreetcn</a>-->
<!-- </div>-->
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div>
<a href="" class="bullshit__return-home">Back to home</a>
<!-- <div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div>-->
<a href="" class="bullshit__return-home">返回首页</a>
</div>
</div>
</div>
@ -26,7 +26,7 @@ export default {
name: 'Page404',
computed: {
message() {
return 'The webmaster said that you can not enter this page...'
return '访问页面不存在...'
}
}
}

@ -0,0 +1,406 @@
<template>
<div>
<el-card shadow="never" style="margin-top: 20px">
<template #header>
<slot name="header">
<vxe-toolbar
:export="isHasAuth('export')"
:print="isHasAuth('print')"
custom
ref="toolbar"
>
<template #buttons>
<el-button
v-if="isHasAuth('create')"
icon="el-icon-plus"
type="primary"
size="small"
@click="isShowAdd = true"
>新增</el-button
>
<el-button
v-if="isHasAuth('search')"
icon="el-icon-search"
type="primary"
plain
size="small"
@click="getList"
>搜索</el-button
>
</template>
</vxe-toolbar>
</slot>
</template>
<vxe-table
ref="table"
stripe
style="margin-top: 10px"
:loading="loading"
:height="tableHeight"
keep-source
show-overflow
:menu-config="{
className: 'my-menus',
body: {
options: [
[
{
code: 'copy',
name: '复制',
prefixConfig: { icon: 'vxe-icon-copy' },
suffixConfig: { content: 'Ctrl+C' },
},
{
code: 'remove',
name: '删除',
prefixConfig: {
icon: 'vxe-icon-delete-fill',
className: 'color-red',
},
},
],
],
},
}"
@menu-click="contextMenuClickEvent"
:row-config="{ isCurrent: true, isHover: true }"
:column-config="{ resizable: true }"
:export-config="{}"
:edit-rules="validRules"
:edit-config="{
trigger: 'manual',
mode: 'row',
showStatus: true,
isHover: true,
autoClear: false,
}"
:align="allAlign"
:data="tableData"
>
<vxe-column type="seq" width="58" align="center" />
<vxe-column
header-align="center"
field="name"
width="160"
title="名称"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
field="operate"
header-align="center"
title="操作"
min-width="220"
fixed="right"
>
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"
>保存</el-button
>
<el-button
size="small"
type="primary"
plain
@click="cancelRowEvent(row)"
>取消</el-button
>
</template>
<template v-else>
<el-button
v-if="isHasAuth('detail')"
size="small"
type="primary"
plain
@click="detail(row)"
>查看</el-button
>
<el-button
v-if="isHasAuth('edit')"
size="small"
type="warning"
@click="editRowEvent(row)"
>编辑</el-button
>
<el-button
v-if="isHasAuth('delete')"
size="small"
type="danger"
@click="destroyRowEvent(row)"
>删除</el-button
>
</template>
</template>
</vxe-column>
</vxe-table>
<el-pagination
style="margin-top: 10px; display: flex; justify-content: flex-end"
:current-page.sync="select.page"
:page-sizes="[20, 30, 40, 50]"
:page-size.sync="select.page_size"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="
(e) => {
select.page_size = e;
select.page = 1;
getList();
}
"
@current-change="
(e) => {
select.page = e;
getList();
}
"
/>
</el-card>
<AddArea ref="AddArea" :is-show.sync="isShowAdd" @refresh="getList" />
<ShowArea ref="ShowArea" :is-show.sync="isShowDetail" />
</div>
</template>
<script>
import VxeUI from "vxe-pc-ui";
import { authMixin } from "@/mixin/authMixin";
import { uploadSize } from "@/settings";
import { deepCopy } from "@/utils";
import { destroy, index, save } from "@/api/area/area";
import AddArea from "./components/AddArea.vue";
import ShowArea from "./components/ShowArea.vue";
import axios from "axios";
import { getToken } from "@/utils/auth";
export default {
name: "Area",
mixins: [authMixin],
components: {
AddArea,
ShowArea,
},
data() {
return {
uploadSize,
examineKey: 0,
isShowAdd: false,
isShowDetail: false,
loading: false,
tableHeight: 400,
select: {
page: 1,
page_size: 20,
keyword: "",
show_relation: [],
},
total: 0,
allAlign: null,
tableData: [],
form: {
id: "",
name: "",
},
validRules: {},
};
},
computed: {
isActiveStatus() {
return function (row) {
if (this.$refs["table"]) {
return this.$refs["table"].isEditByRow(row);
}
};
},
isHasAuth() {
return function (auth) {
return this.auths_auth_mixin.indexOf(auth) !== -1;
};
},
},
created() {
this.getList();
},
mounted() {
this.bindToolbar();
this.calcTableHeight();
},
methods: {
calcTableHeight() {
let clientHeight = document.documentElement.clientHeight;
let cardTitle = document
.querySelector(".el-card__header")
?.getBoundingClientRect()?.height;
let search = document
.querySelector(".vxe-toolbar")
?.getBoundingClientRect()?.height;
let paginationHeight =
(document.querySelector(".el-pagination")?.getBoundingClientRect()
?.height ?? 0) + 10; //
let topHeight = 50; //
let padding = 80;
let margin = 20;
this.tableHeight =
clientHeight -
cardTitle -
search -
paginationHeight -
topHeight -
padding -
margin;
},
contextMenuClickEvent({ menu, row, column }) {
switch (menu.code) {
case "copy":
//
if (row && column) {
if (VxeUI.clipboard.copy(row[column.field])) {
this.$message.success("已复制到剪贴板!");
}
}
break;
case "remove":
if (row && column) {
this.destroyRowEvent(row);
}
default:
}
},
async detail(row) {
await this.$refs["ShowArea"].getDetail(row.id);
this.isShowDetail = true;
},
uploadMethod(file) {
const formData = new FormData();
formData.append("file", file);
window.$_uploading = true;
return axios
.post(process.env.VUE_APP_UPLOAD_API, formData, {
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
.then((response) => {
window.$_uploading = false;
if (response.status === 200 && !response.data.code) {
return {
response: response.data,
name: response.data.original_name,
url: response.data.url,
};
} else {
this.$message.error("上传失败");
}
})
.catch((_) => {
window.$_uploading = false;
});
},
bindToolbar() {
this.$nextTick(() => {
if (this.$refs["table"] && this.$refs["toolbar"]) {
this.$refs["table"].connect(this.$refs["toolbar"]);
}
});
},
editRowEvent(row) {
if (this.$refs["table"]) {
this.$refs["table"].setEditRow(row);
}
},
cancelRowEvent(row) {
if (this.$refs["table"]) {
this.$refs["table"].clearEdit().then(() => {
//
this.$refs["table"].revertData(row);
});
}
},
async getList() {
this.loading = true;
try {
const res = await index(this.select, false);
this.tableData = res.data;
this.total = res.total;
this.loading = false;
} catch (err) {
console.error(err);
this.loading = false;
}
},
async saveRowEvent(row) {
if (window.$_uploading) {
this.$message.warning("文件正在上传中");
return;
}
try {
const errMap = await this.$refs["table"].validate();
if (errMap) {
throw new Error(errMap);
}
await this.$confirm("确认保存?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
});
await this.$refs["table"].clearEdit();
let form = deepCopy(this.form);
for (const key in form) {
form[key] = row[key];
}
this.loading = true;
await save(form, false);
await this.getList();
this.loading = false;
} catch (err) {
this.loading = false;
}
},
async destroyRowEvent(row) {
try {
await this.$confirm("确认删除?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
});
this.loading = true;
if (row.id) {
await destroy(
{
id: row.id,
},
false
);
await this.getList();
} else {
console.log(row);
this.tableData.splice(
this.tableData.findIndex((i) => i._X_ROW_KEY === row._X_ROW_KEY),
1
);
}
this.loading = false;
} catch (err) {
this.loading = false;
}
},
},
};
</script>
<style scoped lang="scss">
::v-deep .el-card__header {
padding: 6px 20px;
}
::v-deep .el-tag + .el-tag {
margin-left: 4px;
}
</style>

@ -0,0 +1,181 @@
<template>
<div>
<el-drawer
:title="$route.meta.title"
direction="rtl"
size="68%"
:visible.sync="visible"
append-to-body
:before-close="handleClose"
@close="$emit('update:isShow', false)"
>
<section class="drawer-container">
<el-form
class="drawer-container__form"
ref="elForm"
:model="form"
:rules="rules"
label-position="top"
label-width="120px"
size="small"
>
<div class="form-layout">
<el-form-item label="名称" prop="name">
<el-input
v-model="form['name']"
clearable
placeholder="请填写名称"
style="width: 100%"
></el-input>
</el-form-item>
</div>
</el-form>
<div class="drawer-container__footer">
<el-button @click="reset"> </el-button>
<el-button type="primary" @click="submit" :loading="loading">{{
loading ? "提交中 ..." : "确 定"
}}</el-button>
</div>
</section>
</el-drawer>
</div>
</template>
<script>
import { save, show } from "@/api/area/area";
import axios from "axios";
import { getToken } from "@/utils/auth";
import { uploadSize } from "@/settings";
import { formatFileSize } from "@/utils";
export default {
name: "AreaDrawer",
props: {
isShow: {
type: Boolean,
default: false,
required: true,
},
},
data() {
return {
uploadSize,
action: process.env.VUE_APP_UPLOAD_API,
loading: false,
visible: false,
form: {
name: "",
},
rules: {},
};
},
watch: {
isShow(newVal) {
this.visible = newVal;
},
visible(newVal) {
this.$emit("update:isShow", newVal);
},
},
methods: {
uploadBefore(file) {
if (file.size > uploadSize) {
this.$message({
type: "warning",
message: `上传图片大小超过${formatFileSize(uploadSize)}`,
});
return false;
}
window.$_uploading = true;
},
uploadSuccess(response, file, fileList, fieldName) {
window.$_uploading = false;
fileList.forEach((file) => {
if (file.response?.data && !file.response?.code) {
file.response = file.response.data;
}
});
this.form[fieldName] = fileList;
},
uploadRemove(file, fileList, fieldName) {
this.form[fieldName] = fileList;
},
uploadError(err, file, fileList, fieldName) {
window.$_uploading = false;
this.form[fieldName] = fileList;
this.$message({
type: "warning",
message: err,
});
},
formatFileSize,
getToken,
handleClose(done) {
this.$confirm("确定关闭窗口?")
.then((_) => {
done();
})
.catch((_) => {});
},
reset() {
this.form = {
name: "",
};
this.$refs["elForm"].resetFields();
},
submit() {
if (window.$_uploading) {
this.$message.warning("文件正在上传中");
return;
}
this.$refs["elForm"].validate(async (valid) => {
if (valid) {
this.loading = true;
try {
await save(this.form);
this.$message.success("新增成功");
this.$emit("refresh");
this.$emit("update:isShow", false);
this.loading = false;
this.reset();
} catch (err) {
this.loading = false;
}
}
});
},
},
};
</script>
<style scoped lang="scss">
.span2 {
grid-column: span 2;
}
::v-deep .el-form-item > * {
max-width: 100%;
}
.form-layout {
display: grid;
grid-gap: 2%;
grid-template-columns: repeat(2, 1fr);
}
.drawer-container {
height: 100%;
padding: 20px;
display: flex;
flex-direction: column;
&__form {
flex: 1;
overflow-y: scroll;
}
&__footer {
margin-top: 20px;
display: flex;
}
}
</style>

@ -0,0 +1,95 @@
<template>
<div>
<el-drawer
:title="$route.meta.title"
direction="rtl"
size="68%"
:visible.sync="visible"
append-to-body
@close="$emit('update:isShow', false)"
>
<section class="drawer-container">
<el-descriptions
class="drawer-container__desc"
size="small"
border
ref="elDesc"
:column="2"
direction="vertical"
:labelStyle="{ 'font-weight': '500', 'font-size': '15px' }"
>
<el-descriptions-item label="名称">
{{ form["name"] }}
</el-descriptions-item>
</el-descriptions>
</section>
</el-drawer>
</div>
</template>
<script>
import { show } from "@/api/area/area";
export default {
name: "AreaShow",
props: {
isShow: {
type: Boolean,
default: false,
required: true,
},
},
data() {
return {
loading: false,
visible: false,
form: {
name: "",
},
};
},
watch: {
isShow(newVal) {
this.visible = newVal;
},
visible(newVal) {
this.$emit("update:isShow", newVal);
},
},
methods: {
async getDetail(id) {
try {
const detail = await show({
id,
});
for (let key in this.form) {
if (detail.hasOwnProperty(key)) {
this.form[key] = detail[key];
}
}
} catch (err) {
console.error(err);
}
},
},
};
</script>
<style scoped lang="scss">
.span2 {
grid-column: span 2;
}
::v-deep .el-form-item > * {
max-width: 100%;
}
.drawer-container {
height: 100%;
padding: 20px;
display: flex;
flex-direction: column;
& > * {
flex: 1;
}
}
</style>

@ -0,0 +1,685 @@
<template>
<div>
<el-card shadow="never" style="margin-top: 20px">
<template #header>
<slot name="header">
<vxe-toolbar
custom
:print="isHasAuth('print')"
ref="toolbar"
>
<template #toolSuffix>
<vxe-button style="margin-right: 10px;" v-if="isHasAuth('export')" icon="vxe-icon-download" circle @click="exportMethod"></vxe-button>
</template>
<template #buttons>
<el-button
v-if="isHasAuth('create')"
icon="el-icon-plus"
type="primary"
size="small"
@click="isShowAdd = true"
>新增</el-button
>
<template v-if="isHasAuth('search')">
<el-input style="width: 120px;" v-model="select['filter[1][value]']" placeholder="学校名称.." size="small"></el-input>
<el-select style="width: 100px;" v-model="select['filter[0][value]']" placeholder="区域.." size="small">
<el-option v-for="item in area" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-input style="width: 120px;" v-model="select['filter[2][value]']" placeholder="学校星级.." size="small"></el-input>
<el-button
icon="el-icon-search"
type="primary"
plain
size="small"
@click="getList"
>搜索</el-button
>
</template>
</template>
</vxe-toolbar>
</slot>
</template>
<vxe-table
ref="table"
stripe
style="margin-top: 10px"
:loading="loading"
:height="tableHeight"
keep-source
show-overflow
:menu-config="{
className: 'my-menus',
body: {
options: [
[
{
code: 'copy',
name: '复制',
prefixConfig: { icon: 'vxe-icon-copy' },
suffixConfig: { content: 'Ctrl+C' },
},
{
code: 'remove',
name: '删除',
prefixConfig: {
icon: 'vxe-icon-delete-fill',
className: 'color-red',
},
},
],
],
},
}"
@menu-click="contextMenuClickEvent"
:row-config="{ isCurrent: true, isHover: true }"
:column-config="{ resizable: true }"
:export-config="{}"
:edit-rules="validRules"
:edit-config="{
trigger: 'manual',
mode: 'row',
showStatus: true,
isHover: true,
autoClear: false,
}"
:align="allAlign"
:data="tableData"
>
<vxe-column type="checkbox" width="50" align="center" />
<vxe-column type="seq" width="58" align="center" />
<vxe-column
header-align="center"
field="name"
width="160"
title="学校名称"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
align="center"
field="area_id"
width="180"
title="区域"
:edit-render="{
name: 'VxeSelect',
options: area,
props: { multiple: false },
optionProps: { value: 'id', label: 'name' },
}"
/>
<vxe-column
header-align="center"
field="code"
width="160"
title="学校代码"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
header-align="center"
field="star"
width="160"
title="星级"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
header-align="center"
field="address"
width="160"
title="地址"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
align="center"
field="nature"
width="180"
title="性质"
:edit-render="{
name: 'VxeSelect',
options: [
{ value: 1, label: '公办' },
{ value: 2, label: '民办' },
],
props: { multiple: false },
optionProps: { value: 'value', label: 'label' },
}"
/>
<vxe-column
align="center"
field="type"
width="180"
title="学校类型"
:edit-render="{
name: 'VxeSelect',
options: [
{ label: '初中', value: '初中' },
{ label: '中专', value: '中专' },
{ label: '3+3', value: '3+3' },
{ label: '五年制大专', value: '五年制大专' },
{ label: '中本贯通5+2', value: '中本贯通5+2' },
{ label: '中本贯通3+4', value: '中本贯通3+4' },
{ label: '综合高中', value: '综合高中' },
{ label: '高中', value: '高中' },
],
props: { multiple: true },
optionProps: { value: 'value', label: 'label' },
}"
/>
<vxe-column
header-align="center"
field="build_year"
width="160"
title="建校年份"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
align="center"
header-align="center"
field="introduce"
width="120"
title="学校介绍"
:edit-render="{}"
>
<template #default="{ row }">
<el-button
slot="reference"
size="small"
type="primary"
icon="el-icon-search"
@click="
$refs['RichTextModal'].open({
text: row['introduce'],
readonly: true,
fieldName: 'introduce',
row,
})
"
>查看</el-button
>
</template>
<template #edit="{ row }">
<el-button
slot="reference"
size="small"
type="primary"
icon="el-icon-edit"
@click="
$refs['RichTextModal'].open({
text: row['introduce'],
readonly: false,
fieldName: 'introduce',
row,
})
"
>编辑</el-button
>
</template>
</vxe-column>
<vxe-column
align="center"
header-align="center"
field="teacher"
width="120"
title="教师信息"
:edit-render="{}"
>
<template #default="{ row }">
<el-button
slot="reference"
size="small"
type="primary"
icon="el-icon-search"
@click="
$refs['RichTextModal'].open({
text: row['teacher'],
readonly: true,
fieldName: 'teacher',
row,
})
"
>查看</el-button
>
</template>
<template #edit="{ row }">
<el-button
slot="reference"
size="small"
type="primary"
icon="el-icon-edit"
@click="
$refs['RichTextModal'].open({
text: row['teacher'],
readonly: false,
fieldName: 'teacher',
row,
})
"
>编辑</el-button
>
</template>
</vxe-column>
<vxe-column
field="operate"
header-align="center"
title="操作"
min-width="220"
fixed="right"
>
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"
>保存</el-button
>
<el-button
size="small"
type="primary"
plain
@click="cancelRowEvent(row)"
>取消</el-button
>
</template>
<template v-else>
<el-button
v-if="isHasAuth('detail')"
size="small"
type="primary"
plain
@click="detail(row)"
>查看</el-button
>
<el-button
v-if="isHasAuth('edit')"
size="small"
type="warning"
@click="editRowEvent(row)"
>编辑</el-button
>
<el-button
v-if="isHasAuth('delete')"
size="small"
type="danger"
@click="destroyRowEvent(row)"
>删除</el-button
>
</template>
</template>
</vxe-column>
</vxe-table>
<el-pagination
style="margin-top: 10px; display: flex; justify-content: flex-end"
:current-page.sync="select.page"
:page-sizes="[20, 30, 40, 50]"
:page-size.sync="select.page_size"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="
(e) => {
select.page_size = e;
select.page = 1;
getList();
}
"
@current-change="
(e) => {
select.page = e;
getList();
}
"
/>
</el-card>
<AddSchool
ref="AddSchool"
:area="area"
:is-show.sync="isShowAdd"
@refresh="getList"
/>
<ShowSchool ref="ShowSchool" :area="area" :is-show.sync="isShowDetail" />
<rich-text-modal
ref="RichTextModal"
@confirm="({ row, fieldName, text }) => (row[fieldName] = text)"
></rich-text-modal>
</div>
</template>
<script>
import VxeUI from "vxe-pc-ui";
import RichTextModal from "@/components/RichTextModal/index.vue";
import { authMixin } from "@/mixin/authMixin";
import { uploadSize } from "@/settings";
import { deepCopy } from "@/utils";
import { destroy, index, save } from "@/api/school/school";
import AddSchool from "./components/AddSchool.vue";
import ShowSchool from "./components/ShowSchool.vue";
import axios from "axios";
import { getToken } from "@/utils/auth";
import { download } from "@/utils/downloadRequest"
import { index as areaIndex } from "@/api/area/area";
export default {
name: "School",
mixins: [authMixin],
components: {
RichTextModal,
AddSchool,
ShowSchool,
},
data() {
return {
uploadSize,
examineKey: 0,
isShowAdd: false,
isShowDetail: false,
loading: false,
tableHeight: 400,
select: {
page: 1,
page_size: 20,
keyword: "",
show_relation: [],
'filter[0][key]': 'area_id',
'filter[0][op]': 'eq',
'filter[0][value]': '',
'filter[1][key]': 'name',
'filter[1][op]': 'like',
'filter[1][value]': '',
'filter[2][key]': 'star',
'filter[2][op]': 'like',
'filter[2][value]': '',
},
total: 0,
allAlign: null,
tableData: [],
form: {
id: "",
name: "",
area_id: "",
code: "",
star: "",
address: "",
nature: "",
type: "",
build_year: "",
introduce: "",
teacher: "",
},
validRules: {
name: [
{
required: true,
message: "学校名称" + "必填",
},
],
area_id: [
{
required: true,
message: "区域" + "必填",
},
],
},
area: [],
};
},
computed: {
isActiveStatus() {
return function (row) {
if (this.$refs["table"]) {
return this.$refs["table"].isEditByRow(row);
}
};
},
isHasAuth() {
return function (auth) {
return this.auths_auth_mixin.indexOf(auth) !== -1;
};
},
},
created() {
this.getArea();
this.getList();
},
mounted() {
this.bindToolbar();
this.calcTableHeight();
},
methods: {
exportMethod() {
this.$confirm("请选择导出方式", "提示", {
confirmButtonText: "全量导出",
cancelButtonText: "部分导出",
distinguishCancelAndClose: true
}).then(_ => {
download('/api/admin/school/index', 'get', {
...this.select,
page: 1,
page_size: 9999,
is_export: 1,
export_fields: Object.keys(this.form)
})
}).catch(action => {
if (action === 'cancel' && this.$refs['table']) {
this.$refs['table'].openExport()
}
});
},
calcTableHeight() {
let clientHeight = document.documentElement.clientHeight;
let cardTitle = document
.querySelector(".el-card__header")
?.getBoundingClientRect()?.height;
let search = document
.querySelector(".vxe-toolbar")
?.getBoundingClientRect()?.height;
let paginationHeight =
(document.querySelector(".el-pagination")?.getBoundingClientRect()
?.height ?? 0) + 10; //
let topHeight = 50; //
let padding = 80;
let margin = 20;
this.tableHeight =
clientHeight -
cardTitle -
search -
paginationHeight -
topHeight -
padding -
margin;
},
contextMenuClickEvent({ menu, row, column }) {
switch (menu.code) {
case "copy":
//
if (row && column) {
if (VxeUI.clipboard.copy(row[column.field])) {
this.$message.success("已复制到剪贴板!");
}
}
break;
case "remove":
if (row && column) {
this.destroyRowEvent(row);
}
default:
}
},
async detail(row) {
await this.$refs["ShowSchool"].getDetail(row.id);
this.isShowDetail = true;
},
async getArea() {
try {
const res = await areaIndex(
{
page: 1,
page_size: 999,
},
false
);
this.area = res.data;
} catch (err) {
console.error(err);
}
},
uploadMethod(file) {
const formData = new FormData();
formData.append("file", file);
window.$_uploading = true;
return axios
.post(process.env.VUE_APP_UPLOAD_API, formData, {
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
.then((response) => {
window.$_uploading = false;
if (response.status === 200 && !response.data.code) {
return {
response: response.data,
name: response.data.original_name,
url: response.data.url,
};
} else {
this.$message.error("上传失败");
}
})
.catch((_) => {
window.$_uploading = false;
});
},
bindToolbar() {
this.$nextTick(() => {
if (this.$refs["table"] && this.$refs["toolbar"]) {
this.$refs["table"].connect(this.$refs["toolbar"]);
}
});
},
editRowEvent(row) {
if (this.$refs["table"]) {
this.$refs["table"].setEditRow(row);
}
},
cancelRowEvent(row) {
if (this.$refs["table"]) {
this.$refs["table"].clearEdit().then(() => {
//
this.$refs["table"].revertData(row);
});
}
},
async getList() {
this.loading = true;
try {
const res = await index(this.select, false);
this.tableData = res.data;
this.total = res.total;
this.loading = false;
} catch (err) {
console.error(err);
this.loading = false;
}
},
async saveRowEvent(row) {
if (window.$_uploading) {
this.$message.warning("文件正在上传中");
return;
}
try {
const errMap = await this.$refs["table"].validate();
if (errMap) {
throw new Error(errMap);
}
await this.$confirm("确认保存?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
});
await this.$refs["table"].clearEdit();
let form = deepCopy(this.form);
for (const key in form) {
form[key] = row[key];
}
this.loading = true;
await save(form, false);
await this.getList();
this.loading = false;
} catch (err) {
this.loading = false;
}
},
async destroyRowEvent(row) {
try {
await this.$confirm("确认删除?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
});
this.loading = true;
if (row.id) {
await destroy(
{
id: row.id,
},
false
);
await this.getList();
} else {
console.log(row);
this.tableData.splice(
this.tableData.findIndex((i) => i._X_ROW_KEY === row._X_ROW_KEY),
1
);
}
this.loading = false;
} catch (err) {
this.loading = false;
}
},
},
};
</script>
<style scoped lang="scss">
::v-deep .vxe-buttons--wrapper > * + * {
margin-left: 10px;
}
::v-deep .el-card__header {
padding: 6px 20px;
}
::v-deep .el-tag + .el-tag {
margin-left: 4px;
}
</style>

@ -0,0 +1,351 @@
<template>
<div>
<el-drawer
:title="$route.meta.title"
direction="rtl"
size="68%"
:visible.sync="visible"
append-to-body
:before-close="handleClose"
@close="$emit('update:isShow', false)"
>
<section class="drawer-container">
<el-form
class="drawer-container__form"
ref="elForm"
:model="form"
:rules="rules"
label-position="top"
label-width="120px"
size="small"
>
<div class="form-layout">
<el-form-item label="学校名称" prop="name">
<el-input
v-model="form['name']"
clearable
placeholder="请填写学校名称"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="区域" prop="area_id" clearable>
<el-select
v-model="form['area_id']"
clearable
placeholder="请填写区域"
style="width: 100%"
>
<el-option
v-for="(option, optionIndex) in area"
:key="optionIndex"
:label="option['name']"
:value="option['id']"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="学校代码" prop="code">
<el-input
v-model="form['code']"
clearable
placeholder="请填写学校代码"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="星级" prop="star">
<el-input
v-model="form['star']"
clearable
placeholder="请填写星级"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input
v-model="form['address']"
clearable
placeholder="请填写地址"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="性质" prop="nature" clearable>
<el-select
v-model="form['nature']"
clearable
placeholder="请填写性质"
style="width: 100%"
>
<el-option
v-for="(option, optionIndex) in [
{ value: 1, label: '公办' },
{ value: 2, label: '民办' },
]"
:key="optionIndex"
:label="option['label']"
:value="option['value']"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="学校类型" prop="type" clearable>
<el-select
v-model="form['type']"
clearable
placeholder="请填写学校类型"
multiple
style="width: 100%"
>
<el-option
v-for="(option, optionIndex) in [
{ label: '初中', value: '初中' },
{ label: '中专', value: '中专' },
{ label: '3+3', value: '3+3' },
{ label: '五年制大专', value: '五年制大专' },
{ label: '中本贯通5+2', value: '中本贯通5+2' },
{ label: '中本贯通3+4', value: '中本贯通3+4' },
{ label: '综合高中', value: '综合高中' },
{ label: '高中', value: '高中' },
]"
:key="optionIndex"
:label="option['label']"
:value="option['value']"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="建校年份" prop="build_year">
<el-input
v-model="form['build_year']"
clearable
placeholder="请填写建校年份"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="学校介绍" prop="introduce">
<my-tinymce
v-model="form['introduce']"
clearable
placeholder="请填写学校介绍"
style="width: 100%"
></my-tinymce>
</el-form-item>
<el-form-item label="教师信息" prop="teacher">
<my-tinymce
v-model="form['teacher']"
clearable
placeholder="请填写教师信息"
style="width: 100%"
></my-tinymce>
</el-form-item>
</div>
</el-form>
<div class="drawer-container__footer">
<el-button @click="reset"> </el-button>
<el-button type="primary" @click="submit" :loading="loading">{{
loading ? "提交中 ..." : "确 定"
}}</el-button>
</div>
</section>
</el-drawer>
</div>
</template>
<script>
import { save, show } from "@/api/school/school";
import axios from "axios";
import { getToken } from "@/utils/auth";
import { uploadSize } from "@/settings";
import { formatFileSize } from "@/utils";
export default {
name: "SchoolDrawer",
props: {
isShow: {
type: Boolean,
default: false,
required: true,
},
area: {
type: Array,
default: () => [],
},
},
data() {
return {
uploadSize,
action: process.env.VUE_APP_UPLOAD_API,
loading: false,
visible: false,
form: {
name: "",
area_id: "",
code: "",
star: "",
address: "",
nature: "",
type: "",
build_year: "",
introduce: "",
teacher: "",
},
rules: {
name: [
{
required: true,
message: "学校名称" + "必填",
},
],
area_id: [
{
required: true,
message: "区域" + "必填",
},
],
},
};
},
watch: {
isShow(newVal) {
this.visible = newVal;
},
visible(newVal) {
this.$emit("update:isShow", newVal);
},
},
methods: {
uploadBefore(file) {
if (file.size > uploadSize) {
this.$message({
type: "warning",
message: `上传图片大小超过${formatFileSize(uploadSize)}`,
});
return false;
}
window.$_uploading = true;
},
uploadSuccess(response, file, fileList, fieldName) {
window.$_uploading = false;
fileList.forEach((file) => {
if (file.response?.data && !file.response?.code) {
file.response = file.response.data;
}
});
this.form[fieldName] = fileList;
},
uploadRemove(file, fileList, fieldName) {
this.form[fieldName] = fileList;
},
uploadError(err, file, fileList, fieldName) {
window.$_uploading = false;
this.form[fieldName] = fileList;
this.$message({
type: "warning",
message: err,
});
},
formatFileSize,
getToken,
handleClose(done) {
this.$confirm("确定关闭窗口?")
.then((_) => {
done();
})
.catch((_) => {});
},
reset() {
this.form = {
name: "",
area_id: "",
code: "",
star: "",
address: "",
nature: "",
type: "",
build_year: "",
introduce: "",
teacher: "",
};
this.$refs["elForm"].resetFields();
},
submit() {
if (window.$_uploading) {
this.$message.warning("文件正在上传中");
return;
}
this.$refs["elForm"].validate(async (valid) => {
if (valid) {
this.loading = true;
try {
await save(this.form);
this.$message.success("新增成功");
this.$emit("refresh");
this.$emit("update:isShow", false);
this.loading = false;
this.reset();
} catch (err) {
this.loading = false;
}
}
});
},
},
};
</script>
<style scoped lang="scss">
.span2 {
grid-column: span 2;
}
::v-deep .el-form-item > * {
max-width: 100%;
}
.form-layout {
display: grid;
grid-gap: 2%;
grid-template-columns: repeat(2, 1fr);
}
.drawer-container {
height: 100%;
padding: 20px;
display: flex;
flex-direction: column;
&__form {
flex: 1;
overflow-y: scroll;
}
&__footer {
margin-top: 20px;
display: flex;
}
}
</style>

@ -0,0 +1,170 @@
<template>
<div>
<el-drawer
:title="$route.meta.title"
direction="rtl"
size="68%"
:visible.sync="visible"
append-to-body
@close="$emit('update:isShow', false)"
>
<section class="drawer-container">
<el-descriptions
class="drawer-container__desc"
size="small"
border
ref="elDesc"
:column="2"
direction="vertical"
:labelStyle="{ 'font-weight': '500', 'font-size': '15px' }"
>
<el-descriptions-item label="学校名称">
{{ form["name"] }}
</el-descriptions-item>
<el-descriptions-item label="区域">
{{
area.find((i) => i["id"] === form["area_id"])
? area.find((i) => i["id"] === form["area_id"])["name"]
: ""
}}
</el-descriptions-item>
<el-descriptions-item label="学校代码">
{{ form["code"] }}
</el-descriptions-item>
<el-descriptions-item label="星级">
{{ form["star"] }}
</el-descriptions-item>
<el-descriptions-item label="地址">
{{ form["address"] }}
</el-descriptions-item>
<el-descriptions-item label="性质">
{{
[
{ value: 1, label: "公办" },
{ value: 2, label: "民办" },
].find((i) => i["value"] === form["nature"])
? [
{ value: 1, label: "公办" },
{ value: 2, label: "民办" },
].find((i) => i["value"] === form["nature"])["label"]
: ""
}}
</el-descriptions-item>
<el-descriptions-item label="学校类型">
{{
form["type"].toString()
}}
</el-descriptions-item>
<el-descriptions-item label="建校年份">
{{ form["build_year"] }}
</el-descriptions-item>
<el-descriptions-item label="学校介绍" span="2">
{{ form["introduce"] }}
</el-descriptions-item>
<el-descriptions-item label="教师信息" span="2">
{{ form["teacher"] }}
</el-descriptions-item>
</el-descriptions>
</section>
</el-drawer>
</div>
</template>
<script>
import { show } from "@/api/school/school";
export default {
name: "SchoolShow",
props: {
isShow: {
type: Boolean,
default: false,
required: true,
},
area: {
type: Array,
default: () => [],
},
},
data() {
return {
loading: false,
visible: false,
form: {
name: "",
area_id: "",
code: "",
star: "",
address: "",
nature: "",
type: "",
build_year: "",
introduce: "",
teacher: "",
},
};
},
watch: {
isShow(newVal) {
this.visible = newVal;
},
visible(newVal) {
this.$emit("update:isShow", newVal);
},
},
methods: {
async getDetail(id) {
try {
const detail = await show({
id,
});
for (let key in this.form) {
if (detail.hasOwnProperty(key)) {
this.form[key] = detail[key];
}
}
} catch (err) {
console.error(err);
}
},
},
};
</script>
<style scoped lang="scss">
.span2 {
grid-column: span 2;
}
::v-deep .el-form-item > * {
max-width: 100%;
}
.drawer-container {
height: 100%;
padding: 20px;
display: flex;
flex-direction: column;
& > * {
flex: 1;
}
}
</style>

@ -0,0 +1,539 @@
<template>
<div>
<el-card shadow="never" style="margin-top: 20px">
<template #header>
<slot name="header">
<vxe-toolbar
:print="isHasAuth('print')"
custom
ref="toolbar"
>
<template #toolSuffix>
<vxe-button style="margin-right: 10px;" v-if="isHasAuth('export')" icon="vxe-icon-download" circle @click="exportMethod"></vxe-button>
</template>
<template #buttons>
<el-button
v-if="isHasAuth('create')"
icon="el-icon-plus"
type="primary"
size="small"
@click="isShowAdd = true"
>新增</el-button
>
<template v-if="isHasAuth('search')">
<el-select style="width: 120px;" v-model="select['filter[0][value]']" placeholder="学校.." size="small">
<el-option v-for="item in school" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-date-picker style="width: 100px;" type="year" value-format="yyyy" v-model="select['filter[1][value]']" placeholder="年份.." size="small"></el-date-picker>
<el-button
icon="el-icon-search"
type="primary"
plain
size="small"
@click="getList"
>搜索</el-button
>
</template>
</template>
</vxe-toolbar>
</slot>
</template>
<vxe-table
ref="table"
stripe
style="margin-top: 10px"
:loading="loading"
:height="tableHeight"
keep-source
show-overflow
:menu-config="{
className: 'my-menus',
body: {
options: [
[
{
code: 'copy',
name: '复制',
prefixConfig: { icon: 'vxe-icon-copy' },
suffixConfig: { content: 'Ctrl+C' },
},
{
code: 'remove',
name: '删除',
prefixConfig: {
icon: 'vxe-icon-delete-fill',
className: 'color-red',
},
},
],
],
},
}"
@menu-click="contextMenuClickEvent"
:row-config="{ isCurrent: true, isHover: true }"
:column-config="{ resizable: true }"
:export-config="{}"
:edit-rules="validRules"
:edit-config="{
trigger: 'manual',
mode: 'row',
showStatus: true,
isHover: true,
autoClear: false,
}"
:align="allAlign"
:data="tableData"
>
<vxe-column type="checkbox" width="50" align="center" />
<vxe-column type="seq" width="58" align="center" />
<vxe-column
header-align="center"
field="code"
width="160"
title="代码"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
align="center"
field="school_id"
width="180"
title="所属学校"
:edit-render="{
name: 'VxeSelect',
options: school,
props: { multiple: false },
optionProps: { value: 'id', label: 'name' },
}"
/>
<vxe-column
align="center"
field="year"
width="180"
title="年份"
:edit-render="{ name: 'VxeDatePicker', props: { type: 'year' } }"
/>
<vxe-column
header-align="center"
align="right"
field="total_score"
width="160"
title="统招总分"
:edit-render="{ name: 'input', attrs: { type: 'number' } }"
/>
<vxe-column
header-align="center"
align="right"
field="main_score"
width="160"
title="统招语数外"
:edit-render="{ name: 'input', attrs: { type: 'number' } }"
/>
<vxe-column
header-align="center"
align="right"
field="area_total_score"
width="160"
title="跨区总分"
:edit-render="{ name: 'input', attrs: { type: 'number' } }"
/>
<vxe-column
header-align="center"
align="right"
field="area_main_score"
width="160"
title="跨区语数外"
:edit-render="{ name: 'input', attrs: { type: 'number' } }"
/>
<vxe-column
field="operate"
header-align="center"
title="操作"
min-width="220"
fixed="right"
>
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"
>保存</el-button
>
<el-button
size="small"
type="primary"
plain
@click="cancelRowEvent(row)"
>取消</el-button
>
</template>
<template v-else>
<el-button
v-if="isHasAuth('detail')"
size="small"
type="primary"
plain
@click="detail(row)"
>查看</el-button
>
<el-button
v-if="isHasAuth('edit')"
size="small"
type="warning"
@click="editRowEvent(row)"
>编辑</el-button
>
<el-button
v-if="isHasAuth('delete')"
size="small"
type="danger"
@click="destroyRowEvent(row)"
>删除</el-button
>
</template>
</template>
</vxe-column>
</vxe-table>
<el-pagination
style="margin-top: 10px; display: flex; justify-content: flex-end"
:current-page.sync="select.page"
:page-sizes="[20, 30, 40, 50]"
:page-size.sync="select.page_size"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="
(e) => {
select.page_size = e;
select.page = 1;
getList();
}
"
@current-change="
(e) => {
select.page = e;
getList();
}
"
/>
</el-card>
<AddScore
ref="AddScore"
:school="school"
:is-show.sync="isShowAdd"
@refresh="getList"
/>
<ShowScore ref="ShowScore" :school="school" :is-show.sync="isShowDetail" />
</div>
</template>
<script>
import VxeUI from "vxe-pc-ui";
import { authMixin } from "@/mixin/authMixin";
import { uploadSize } from "@/settings";
import { deepCopy } from "@/utils";
import { destroy, index, save } from "@/api/score/score";
import AddScore from "./components/AddScore.vue";
import ShowScore from "./components/ShowScore.vue";
import axios from "axios";
import { getToken } from "@/utils/auth";
import { index as schoolIndex } from "@/api/school/school";
import {download} from "@/utils/downloadRequest";
export default {
name: "Score",
mixins: [authMixin],
components: {
AddScore,
ShowScore,
},
data() {
return {
uploadSize,
examineKey: 0,
isShowAdd: false,
isShowDetail: false,
loading: false,
tableHeight: 400,
select: {
page: 1,
page_size: 20,
keyword: "",
show_relation: [],
"filter[0][key]": "school_id",
"filter[0][op]": "eq",
"filter[0][value]": "",
"filter[1][key]": "year",
"filter[1][op]": "eq",
"filter[1][value]": "",
},
total: 0,
allAlign: null,
tableData: [],
form: {
id: "",
code: "",
school_id: "",
year: "",
total_score: "",
main_score: "",
area_total_score: "",
area_main_score: "",
},
validRules: {},
school: [],
};
},
computed: {
isActiveStatus() {
return function (row) {
if (this.$refs["table"]) {
return this.$refs["table"].isEditByRow(row);
}
};
},
isHasAuth() {
return function (auth) {
return this.auths_auth_mixin.indexOf(auth) !== -1;
};
},
},
created() {
this.getSchool();
this.getList();
},
mounted() {
this.bindToolbar();
this.calcTableHeight();
},
methods: {
exportMethod() {
this.$confirm("请选择导出方式", "提示", {
confirmButtonText: "全量导出",
cancelButtonText: "部分导出",
distinguishCancelAndClose: true
}).then(_ => {
download('/api/admin/school/index', 'get', {
...this.select,
page: 1,
page_size: 9999,
is_export: 1,
export_fields: Object.keys(this.form)
})
}).catch(action => {
if (action === 'cancel' && this.$refs['table']) {
this.$refs['table'].openExport()
}
});
},
calcTableHeight() {
let clientHeight = document.documentElement.clientHeight;
let cardTitle = document
.querySelector(".el-card__header")
?.getBoundingClientRect()?.height;
let search = document
.querySelector(".vxe-toolbar")
?.getBoundingClientRect()?.height;
let paginationHeight =
(document.querySelector(".el-pagination")?.getBoundingClientRect()
?.height ?? 0) + 10; //
let topHeight = 50; //
let padding = 80;
let margin = 20;
this.tableHeight =
clientHeight -
cardTitle -
search -
paginationHeight -
topHeight -
padding -
margin;
},
contextMenuClickEvent({ menu, row, column }) {
switch (menu.code) {
case "copy":
//
if (row && column) {
if (VxeUI.clipboard.copy(row[column.field])) {
this.$message.success("已复制到剪贴板!");
}
}
break;
case "remove":
if (row && column) {
this.destroyRowEvent(row);
}
default:
}
},
async detail(row) {
await this.$refs["ShowScore"].getDetail(row.id);
this.isShowDetail = true;
},
async getSchool() {
try {
const res = await schoolIndex(
{
page: 1,
page_size: 999,
},
false
);
this.school = res.data;
} catch (err) {
console.error(err);
}
},
uploadMethod(file) {
const formData = new FormData();
formData.append("file", file);
window.$_uploading = true;
return axios
.post(process.env.VUE_APP_UPLOAD_API, formData, {
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
.then((response) => {
window.$_uploading = false;
if (response.status === 200 && !response.data.code) {
return {
response: response.data,
name: response.data.original_name,
url: response.data.url,
};
} else {
this.$message.error("上传失败");
}
})
.catch((_) => {
window.$_uploading = false;
});
},
bindToolbar() {
this.$nextTick(() => {
if (this.$refs["table"] && this.$refs["toolbar"]) {
this.$refs["table"].connect(this.$refs["toolbar"]);
}
});
},
editRowEvent(row) {
if (this.$refs["table"]) {
this.$refs["table"].setEditRow(row);
}
},
cancelRowEvent(row) {
if (this.$refs["table"]) {
this.$refs["table"].clearEdit().then(() => {
//
this.$refs["table"].revertData(row);
});
}
},
async getList() {
this.loading = true;
try {
const res = await index(this.select, false);
this.tableData = res.data;
this.total = res.total;
this.loading = false;
} catch (err) {
console.error(err);
this.loading = false;
}
},
async saveRowEvent(row) {
if (window.$_uploading) {
this.$message.warning("文件正在上传中");
return;
}
try {
const errMap = await this.$refs["table"].validate();
if (errMap) {
throw new Error(errMap);
}
await this.$confirm("确认保存?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
});
await this.$refs["table"].clearEdit();
let form = deepCopy(this.form);
for (const key in form) {
form[key] = row[key];
}
this.loading = true;
await save(form, false);
await this.getList();
this.loading = false;
} catch (err) {
this.loading = false;
}
},
async destroyRowEvent(row) {
try {
await this.$confirm("确认删除?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
});
this.loading = true;
if (row.id) {
await destroy(
{
id: row.id,
},
false
);
await this.getList();
} else {
console.log(row);
this.tableData.splice(
this.tableData.findIndex((i) => i._X_ROW_KEY === row._X_ROW_KEY),
1
);
}
this.loading = false;
} catch (err) {
this.loading = false;
}
},
},
};
</script>
<style scoped lang="scss">
::v-deep .vxe-buttons--wrapper > * + * {
margin-left: 10px;
}
::v-deep .el-card__header {
padding: 6px 20px;
}
::v-deep .el-tag + .el-tag {
margin-left: 4px;
}
</style>

@ -0,0 +1,281 @@
<template>
<div>
<el-drawer
:title="$route.meta.title"
direction="rtl"
size="68%"
:visible.sync="visible"
append-to-body
:before-close="handleClose"
@close="$emit('update:isShow', false)"
>
<section class="drawer-container">
<el-form
class="drawer-container__form"
ref="elForm"
:model="form"
:rules="rules"
label-position="top"
label-width="120px"
size="small"
>
<div class="form-layout">
<el-form-item label="代码" prop="code">
<el-input
v-model="form['code']"
clearable
placeholder="请填写代码"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="所属学校" prop="school_id" clearable>
<el-select
v-model="form['school_id']"
clearable
placeholder="请填写所属学校"
style="width: 100%"
>
<el-option
v-for="(option, optionIndex) in school"
:key="optionIndex"
:label="option['name']"
:value="option['id']"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="年份" prop="year" clearable>
<el-date-picker
v-model="form['year']"
clearable
placeholder="请填写年份"
type="year"
value-format="yyyy-MM-dd"
style="width: 100%"
></el-date-picker>
</el-form-item>
<el-form-item label="统招总分" prop="total_score">
<el-input-number
v-model="form['total_score']"
clearable
placeholder="请填写统招总分"
:precision="2"
controls-position="right"
style="width: 100%"
></el-input-number>
</el-form-item>
<el-form-item label="统招语数外" prop="main_score">
<el-input-number
v-model="form['main_score']"
clearable
placeholder="请填写统招语数外"
:precision="2"
controls-position="right"
style="width: 100%"
></el-input-number>
</el-form-item>
<el-form-item label="跨区总分" prop="area_total_score">
<el-input-number
v-model="form['area_total_score']"
clearable
placeholder="请填写跨区总分"
:precision="2"
controls-position="right"
style="width: 100%"
></el-input-number>
</el-form-item>
<el-form-item label="跨区语数外" prop="area_main_score">
<el-input-number
v-model="form['area_main_score']"
clearable
placeholder="请填写跨区语数外"
:precision="2"
controls-position="right"
style="width: 100%"
></el-input-number>
</el-form-item>
</div>
</el-form>
<div class="drawer-container__footer">
<el-button @click="reset"> </el-button>
<el-button type="primary" @click="submit" :loading="loading">{{
loading ? "提交中 ..." : "确 定"
}}</el-button>
</div>
</section>
</el-drawer>
</div>
</template>
<script>
import { save, show } from "@/api/score/score";
import axios from "axios";
import { getToken } from "@/utils/auth";
import { uploadSize } from "@/settings";
import { formatFileSize } from "@/utils";
export default {
name: "ScoreDrawer",
props: {
isShow: {
type: Boolean,
default: false,
required: true,
},
school: {
type: Array,
default: () => [],
},
},
data() {
return {
uploadSize,
action: process.env.VUE_APP_UPLOAD_API,
loading: false,
visible: false,
form: {
code: "",
school_id: "",
year: "",
total_score: "",
main_score: "",
area_total_score: "",
area_main_score: "",
},
rules: {},
};
},
watch: {
isShow(newVal) {
this.visible = newVal;
},
visible(newVal) {
this.$emit("update:isShow", newVal);
},
},
methods: {
uploadBefore(file) {
if (file.size > uploadSize) {
this.$message({
type: "warning",
message: `上传图片大小超过${formatFileSize(uploadSize)}`,
});
return false;
}
window.$_uploading = true;
},
uploadSuccess(response, file, fileList, fieldName) {
window.$_uploading = false;
fileList.forEach((file) => {
if (file.response?.data && !file.response?.code) {
file.response = file.response.data;
}
});
this.form[fieldName] = fileList;
},
uploadRemove(file, fileList, fieldName) {
this.form[fieldName] = fileList;
},
uploadError(err, file, fileList, fieldName) {
window.$_uploading = false;
this.form[fieldName] = fileList;
this.$message({
type: "warning",
message: err,
});
},
formatFileSize,
getToken,
handleClose(done) {
this.$confirm("确定关闭窗口?")
.then((_) => {
done();
})
.catch((_) => {});
},
reset() {
this.form = {
code: "",
school_id: "",
year: "",
total_score: "",
main_score: "",
area_total_score: "",
area_main_score: "",
};
this.$refs["elForm"].resetFields();
},
submit() {
if (window.$_uploading) {
this.$message.warning("文件正在上传中");
return;
}
this.$refs["elForm"].validate(async (valid) => {
if (valid) {
this.loading = true;
try {
await save(this.form);
this.$message.success("新增成功");
this.$emit("refresh");
this.$emit("update:isShow", false);
this.loading = false;
this.reset();
} catch (err) {
this.loading = false;
}
}
});
},
},
};
</script>
<style scoped lang="scss">
.span2 {
grid-column: span 2;
}
::v-deep .el-form-item > * {
max-width: 100%;
}
.form-layout {
display: grid;
grid-gap: 2%;
grid-template-columns: repeat(2, 1fr);
}
.drawer-container {
height: 100%;
padding: 20px;
display: flex;
flex-direction: column;
&__form {
flex: 1;
overflow-y: scroll;
}
&__footer {
margin-top: 20px;
display: flex;
}
}
</style>

@ -0,0 +1,156 @@
<template>
<div>
<el-drawer
:title="$route.meta.title"
direction="rtl"
size="68%"
:visible.sync="visible"
append-to-body
@close="$emit('update:isShow', false)"
>
<section class="drawer-container">
<el-descriptions
class="drawer-container__desc"
size="small"
border
ref="elDesc"
:column="2"
direction="vertical"
:labelStyle="{ 'font-weight': '500', 'font-size': '15px' }"
>
<el-descriptions-item label="代码">
{{ form["code"] }}
</el-descriptions-item>
<el-descriptions-item label="所属学校">
{{
school.find((i) => i["id"] === form["school_id"])
? school.find((i) => i["id"] === form["school_id"])["name"]
: ""
}}
</el-descriptions-item>
<el-descriptions-item label="年份">
{{ form["year"] }}
</el-descriptions-item>
<el-descriptions-item label="统招总分">
{{
typeof form["total_score"] === "number"
? form["total_score"].toFixed(2)
: form["total_score"]
}}
</el-descriptions-item>
<el-descriptions-item label="统招语数外">
{{
typeof form["main_score"] === "number"
? form["main_score"].toFixed(2)
: form["main_score"]
}}
</el-descriptions-item>
<el-descriptions-item label="跨区总分">
{{
typeof form["area_total_score"] === "number"
? form["area_total_score"].toFixed(2)
: form["area_total_score"]
}}
</el-descriptions-item>
<el-descriptions-item label="跨区语数外">
{{
typeof form["area_main_score"] === "number"
? form["area_main_score"].toFixed(2)
: form["area_main_score"]
}}
</el-descriptions-item>
</el-descriptions>
</section>
</el-drawer>
</div>
</template>
<script>
import { show } from "@/api/score/score";
export default {
name: "ScoreShow",
props: {
isShow: {
type: Boolean,
default: false,
required: true,
},
school: {
type: Array,
default: () => [],
},
},
data() {
return {
loading: false,
visible: false,
form: {
code: "",
school_id: "",
year: "",
total_score: "",
main_score: "",
area_total_score: "",
area_main_score: "",
},
};
},
watch: {
isShow(newVal) {
this.visible = newVal;
},
visible(newVal) {
this.$emit("update:isShow", newVal);
},
},
methods: {
async getDetail(id) {
try {
const detail = await show({
id,
});
for (let key in this.form) {
if (detail.hasOwnProperty(key)) {
this.form[key] = detail[key];
}
}
} catch (err) {
console.error(err);
}
},
},
};
</script>
<style scoped lang="scss">
.span2 {
grid-column: span 2;
}
::v-deep .el-form-item > * {
max-width: 100%;
}
.drawer-container {
height: 100%;
padding: 20px;
display: flex;
flex-direction: column;
& > * {
flex: 1;
}
}
</style>

@ -0,0 +1,547 @@
<template>
<div>
<el-card shadow="never" style="margin-top: 20px">
<template #header>
<slot name="header">
<vxe-toolbar
:export="isHasAuth('export')"
:print="isHasAuth('print')"
custom
ref="toolbar"
>
<template #buttons>
<el-button
v-if="isHasAuth('create')"
icon="el-icon-plus"
type="primary"
size="small"
@click="isShowAdd = true"
>新增</el-button
>
<el-button
v-if="isHasAuth('search')"
icon="el-icon-search"
type="primary"
plain
size="small"
@click="getList"
>搜索</el-button
>
</template>
</vxe-toolbar>
</slot>
</template>
<vxe-table
ref="table"
stripe
style="margin-top: 10px"
:loading="loading"
:height="tableHeight"
keep-source
show-overflow
:menu-config="{
className: 'my-menus',
body: {
options: [
[
{
code: 'copy',
name: '复制',
prefixConfig: { icon: 'vxe-icon-copy' },
suffixConfig: { content: 'Ctrl+C' },
},
{
code: 'remove',
name: '删除',
prefixConfig: {
icon: 'vxe-icon-delete-fill',
className: 'color-red',
},
},
],
],
},
}"
@menu-click="contextMenuClickEvent"
:row-config="{ isCurrent: true, isHover: true }"
:column-config="{ resizable: true }"
:export-config="{}"
:edit-rules="validRules"
:edit-config="{
trigger: 'manual',
mode: 'row',
showStatus: true,
isHover: true,
autoClear: false,
}"
:align="allAlign"
:data="tableData"
>
<vxe-column type="seq" width="58" align="center" />
<vxe-column
header-align="center"
field="nickname"
width="160"
title="昵称"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
header-align="center"
field="openid"
width="160"
title="openid"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
header-align="center"
field="headimgurl"
width="160"
title="头像地址"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
header-align="center"
field="mobile"
width="160"
title="手机号"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
align="center"
field="sex"
width="180"
title="性别"
:edit-render="{
name: 'VxeSelect',
options: [
{ value: '男', label: '男' },
{ value: '女', label: '女' },
{ value: '保密', label: '保密' },
],
props: { multiple: false },
optionProps: { value: 'value', label: 'label' },
}"
/>
<vxe-column
header-align="center"
field="area"
width="160"
title="区县"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
header-align="center"
field="province"
width="160"
title="省份"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
header-align="center"
field="city"
width="160"
title="城市"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
header-align="center"
field="name"
width="160"
title="学生姓名"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
header-align="center"
field="grade"
width="160"
title="年级"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
align="center"
field="middle_exam_year"
width="180"
title="中考年份"
:edit-render="{ name: 'VxeDatePicker', props: { type: 'year' } }"
/>
<vxe-column
header-align="center"
field="nationality"
width="160"
title="民族"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
header-align="center"
field="school"
width="160"
title="学校"
:edit-render="{ name: 'input', attrs: { type: 'text' } }"
/>
<vxe-column
field="operate"
header-align="center"
title="操作"
min-width="220"
fixed="right"
>
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"
>保存</el-button
>
<el-button
size="small"
type="primary"
plain
@click="cancelRowEvent(row)"
>取消</el-button
>
</template>
<template v-else>
<el-button
v-if="isHasAuth('detail')"
size="small"
type="primary"
plain
@click="detail(row)"
>查看</el-button
>
<el-button
v-if="isHasAuth('edit')"
size="small"
type="warning"
@click="editRowEvent(row)"
>编辑</el-button
>
<el-button
v-if="isHasAuth('delete')"
size="small"
type="danger"
@click="destroyRowEvent(row)"
>删除</el-button
>
</template>
</template>
</vxe-column>
</vxe-table>
<el-pagination
style="margin-top: 10px; display: flex; justify-content: flex-end"
:current-page.sync="select.page"
:page-sizes="[20, 30, 40, 50]"
:page-size.sync="select.page_size"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="
(e) => {
select.page_size = e;
select.page = 1;
getList();
}
"
@current-change="
(e) => {
select.page = e;
getList();
}
"
/>
</el-card>
<AddUser ref="AddUser" :is-show.sync="isShowAdd" @refresh="getList" />
<ShowUser ref="ShowUser" :is-show.sync="isShowDetail" />
</div>
</template>
<script>
import VxeUI from "vxe-pc-ui";
import { authMixin } from "@/mixin/authMixin";
import { uploadSize } from "@/settings";
import { deepCopy } from "@/utils";
import { destroy, index, save } from "@/api/user/user";
import AddUser from "./components/AddUser.vue";
import ShowUser from "./components/ShowUser.vue";
import axios from "axios";
import { getToken } from "@/utils/auth";
export default {
name: "User",
mixins: [authMixin],
components: {
AddUser,
ShowUser,
},
data() {
return {
uploadSize,
examineKey: 0,
isShowAdd: false,
isShowDetail: false,
loading: false,
tableHeight: 400,
select: {
page: 1,
page_size: 20,
keyword: "",
show_relation: [],
},
total: 0,
allAlign: null,
tableData: [],
form: {
id: "",
nickname: "",
openid: "",
headimgurl: "",
mobile: "",
sex: "",
area: "",
province: "",
city: "",
name: "",
grade: "",
middle_exam_year: "",
nationality: "",
school: "",
},
validRules: {
mobile: [
{
required: true,
message: "手机号" + "必填",
},
{
pattern: /(^$)|(^1[3456789]\d{9})|(^(0\d{2,3}(-)*)?\d{7})$/,
message: "手机号" + "验证失败",
},
],
},
};
},
computed: {
isActiveStatus() {
return function (row) {
if (this.$refs["table"]) {
return this.$refs["table"].isEditByRow(row);
}
};
},
isHasAuth() {
return function (auth) {
return this.auths_auth_mixin.indexOf(auth) !== -1;
};
},
},
created() {
this.getList();
},
mounted() {
this.bindToolbar();
this.calcTableHeight();
},
methods: {
calcTableHeight() {
let clientHeight = document.documentElement.clientHeight;
let cardTitle = document
.querySelector(".el-card__header")
?.getBoundingClientRect()?.height;
let search = document
.querySelector(".vxe-toolbar")
?.getBoundingClientRect()?.height;
let paginationHeight =
(document.querySelector(".el-pagination")?.getBoundingClientRect()
?.height ?? 0) + 10; //
let topHeight = 50; //
let padding = 80;
let margin = 20;
this.tableHeight =
clientHeight -
cardTitle -
search -
paginationHeight -
topHeight -
padding -
margin;
},
contextMenuClickEvent({ menu, row, column }) {
switch (menu.code) {
case "copy":
//
if (row && column) {
if (VxeUI.clipboard.copy(row[column.field])) {
this.$message.success("已复制到剪贴板!");
}
}
break;
case "remove":
if (row && column) {
this.destroyRowEvent(row);
}
default:
}
},
async detail(row) {
await this.$refs["ShowUser"].getDetail(row.id);
this.isShowDetail = true;
},
uploadMethod(file) {
const formData = new FormData();
formData.append("file", file);
window.$_uploading = true;
return axios
.post(process.env.VUE_APP_UPLOAD_API, formData, {
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
.then((response) => {
window.$_uploading = false;
if (response.status === 200 && !response.data.code) {
return {
response: response.data,
name: response.data.original_name,
url: response.data.url,
};
} else {
this.$message.error("上传失败");
}
})
.catch((_) => {
window.$_uploading = false;
});
},
bindToolbar() {
this.$nextTick(() => {
if (this.$refs["table"] && this.$refs["toolbar"]) {
this.$refs["table"].connect(this.$refs["toolbar"]);
}
});
},
editRowEvent(row) {
if (this.$refs["table"]) {
this.$refs["table"].setEditRow(row);
}
},
cancelRowEvent(row) {
if (this.$refs["table"]) {
this.$refs["table"].clearEdit().then(() => {
//
this.$refs["table"].revertData(row);
});
}
},
async getList() {
this.loading = true;
try {
const res = await index(this.select, false);
this.tableData = res.data;
this.total = res.total;
this.loading = false;
} catch (err) {
console.error(err);
this.loading = false;
}
},
async saveRowEvent(row) {
if (window.$_uploading) {
this.$message.warning("文件正在上传中");
return;
}
try {
const errMap = await this.$refs["table"].validate();
if (errMap) {
throw new Error(errMap);
}
await this.$confirm("确认保存?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
});
await this.$refs["table"].clearEdit();
let form = deepCopy(this.form);
for (const key in form) {
form[key] = row[key];
}
this.loading = true;
await save(form, false);
await this.getList();
this.loading = false;
} catch (err) {
this.loading = false;
}
},
async destroyRowEvent(row) {
try {
await this.$confirm("确认删除?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
});
this.loading = true;
if (row.id) {
await destroy(
{
id: row.id,
},
false
);
await this.getList();
} else {
console.log(row);
this.tableData.splice(
this.tableData.findIndex((i) => i._X_ROW_KEY === row._X_ROW_KEY),
1
);
}
this.loading = false;
} catch (err) {
this.loading = false;
}
},
},
};
</script>
<style scoped lang="scss">
::v-deep .el-card__header {
padding: 6px 20px;
}
::v-deep .el-tag + .el-tag {
margin-left: 4px;
}
</style>

@ -0,0 +1,370 @@
<template>
<div>
<el-drawer
:title="$route.meta.title"
direction="rtl"
size="68%"
:visible.sync="visible"
append-to-body
:before-close="handleClose"
@close="$emit('update:isShow', false)"
>
<section class="drawer-container">
<el-form
class="drawer-container__form"
ref="elForm"
:model="form"
:rules="rules"
label-position="top"
label-width="120px"
size="small"
>
<div class="form-layout">
<el-form-item label="昵称" prop="nickname">
<el-input
v-model="form['nickname']"
clearable
placeholder="请填写昵称"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="openid" prop="openid">
<el-input
v-model="form['openid']"
clearable
placeholder="请填写openid"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="头像地址" prop="headimgurl">
<el-input
v-model="form['headimgurl']"
clearable
placeholder="请填写头像地址"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input
v-model="form['mobile']"
clearable
placeholder="请填写手机号"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="性别" prop="sex" clearable>
<el-select
v-model="form['sex']"
clearable
placeholder="请填写性别"
style="width: 100%"
>
<el-option
v-for="(option, optionIndex) in [
{ value: '男', label: '男' },
{ value: '女', label: '女' },
{ value: '保密', label: '保密' },
]"
:key="optionIndex"
:label="option['label']"
:value="option['value']"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="区县" prop="area">
<el-input
v-model="form['area']"
clearable
placeholder="请填写区县"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="省份" prop="province">
<el-input
v-model="form['province']"
clearable
placeholder="请填写省份"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="城市" prop="city">
<el-input
v-model="form['city']"
clearable
placeholder="请填写城市"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="学生姓名" prop="name">
<el-input
v-model="form['name']"
clearable
placeholder="请填写学生姓名"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="年级" prop="grade">
<el-input
v-model="form['grade']"
clearable
placeholder="请填写年级"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="中考年份" prop="middle_exam_year" clearable>
<el-date-picker
v-model="form['middle_exam_year']"
clearable
placeholder="请填写中考年份"
type="year"
value-format="yyyy-MM-dd"
style="width: 100%"
></el-date-picker>
</el-form-item>
<el-form-item label="民族" prop="nationality">
<el-input
v-model="form['nationality']"
clearable
placeholder="请填写民族"
style="width: 100%"
></el-input>
</el-form-item>
<el-form-item label="学校" prop="school">
<el-input
v-model="form['school']"
clearable
placeholder="请填写学校"
style="width: 100%"
></el-input>
</el-form-item>
</div>
</el-form>
<div class="drawer-container__footer">
<el-button @click="reset"> </el-button>
<el-button type="primary" @click="submit" :loading="loading">{{
loading ? "提交中 ..." : "确 定"
}}</el-button>
</div>
</section>
</el-drawer>
</div>
</template>
<script>
import { save, show } from "@/api/user/user";
import axios from "axios";
import { getToken } from "@/utils/auth";
import { uploadSize } from "@/settings";
import { formatFileSize } from "@/utils";
export default {
name: "UserDrawer",
props: {
isShow: {
type: Boolean,
default: false,
required: true,
},
},
data() {
return {
uploadSize,
action: process.env.VUE_APP_UPLOAD_API,
loading: false,
visible: false,
form: {
nickname: "",
openid: "",
headimgurl: "",
mobile: "",
sex: "",
area: "",
province: "",
city: "",
name: "",
grade: "",
middle_exam_year: "",
nationality: "",
school: "",
},
rules: {
mobile: [
{
required: true,
message: "手机号" + "必填",
},
{
validator: (rule, value, callback) => {
if (
/(^$)|(^1[3456789]\d{9})|(^(0\d{2,3}(-)*)?\d{7})$/.test(value)
) {
callback();
} else {
callback(new Error("手机号" + "验证失败"));
}
},
message: "手机号" + "验证失败",
},
],
},
};
},
watch: {
isShow(newVal) {
this.visible = newVal;
},
visible(newVal) {
this.$emit("update:isShow", newVal);
},
},
methods: {
uploadBefore(file) {
if (file.size > uploadSize) {
this.$message({
type: "warning",
message: `上传图片大小超过${formatFileSize(uploadSize)}`,
});
return false;
}
window.$_uploading = true;
},
uploadSuccess(response, file, fileList, fieldName) {
window.$_uploading = false;
fileList.forEach((file) => {
if (file.response?.data && !file.response?.code) {
file.response = file.response.data;
}
});
this.form[fieldName] = fileList;
},
uploadRemove(file, fileList, fieldName) {
this.form[fieldName] = fileList;
},
uploadError(err, file, fileList, fieldName) {
window.$_uploading = false;
this.form[fieldName] = fileList;
this.$message({
type: "warning",
message: err,
});
},
formatFileSize,
getToken,
handleClose(done) {
this.$confirm("确定关闭窗口?")
.then((_) => {
done();
})
.catch((_) => {});
},
reset() {
this.form = {
nickname: "",
openid: "",
headimgurl: "",
mobile: "",
sex: "",
area: "",
province: "",
city: "",
name: "",
grade: "",
middle_exam_year: "",
nationality: "",
school: "",
};
this.$refs["elForm"].resetFields();
},
submit() {
if (window.$_uploading) {
this.$message.warning("文件正在上传中");
return;
}
this.$refs["elForm"].validate(async (valid) => {
if (valid) {
this.loading = true;
try {
await save(this.form);
this.$message.success("新增成功");
this.$emit("refresh");
this.$emit("update:isShow", false);
this.loading = false;
this.reset();
} catch (err) {
this.loading = false;
}
}
});
},
},
};
</script>
<style scoped lang="scss">
.span2 {
grid-column: span 2;
}
::v-deep .el-form-item > * {
max-width: 100%;
}
.form-layout {
display: grid;
grid-gap: 2%;
grid-template-columns: repeat(2, 1fr);
}
.drawer-container {
height: 100%;
padding: 20px;
display: flex;
flex-direction: column;
&__form {
flex: 1;
overflow-y: scroll;
}
&__footer {
margin-top: 20px;
display: flex;
}
}
</style>

@ -0,0 +1,179 @@
<template>
<div>
<el-drawer
:title="$route.meta.title"
direction="rtl"
size="68%"
:visible.sync="visible"
append-to-body
@close="$emit('update:isShow', false)"
>
<section class="drawer-container">
<el-descriptions
class="drawer-container__desc"
size="small"
border
ref="elDesc"
:column="2"
direction="vertical"
:labelStyle="{ 'font-weight': '500', 'font-size': '15px' }"
>
<el-descriptions-item label="昵称">
{{ form["nickname"] }}
</el-descriptions-item>
<el-descriptions-item label="openid">
{{ form["openid"] }}
</el-descriptions-item>
<el-descriptions-item label="头像地址">
{{ form["headimgurl"] }}
</el-descriptions-item>
<el-descriptions-item label="手机号">
{{ form["mobile"] }}
</el-descriptions-item>
<el-descriptions-item label="性别">
{{
[
{ value: "男", label: "男" },
{ value: "女", label: "女" },
{ value: "保密", label: "保密" },
].find((i) => i["value"] === form["sex"])
? [
{ value: "男", label: "男" },
{ value: "女", label: "女" },
{ value: "保密", label: "保密" },
].find((i) => i["value"] === form["sex"])["label"]
: ""
}}
</el-descriptions-item>
<el-descriptions-item label="区县">
{{ form["area"] }}
</el-descriptions-item>
<el-descriptions-item label="省份">
{{ form["province"] }}
</el-descriptions-item>
<el-descriptions-item label="城市">
{{ form["city"] }}
</el-descriptions-item>
<el-descriptions-item label="学生姓名">
{{ form["name"] }}
</el-descriptions-item>
<el-descriptions-item label="年级">
{{ form["grade"] }}
</el-descriptions-item>
<el-descriptions-item label="中考年份">
{{ form["middle_exam_year"] }}
</el-descriptions-item>
<el-descriptions-item label="民族">
{{ form["nationality"] }}
</el-descriptions-item>
<el-descriptions-item label="学校">
{{ form["school"] }}
</el-descriptions-item>
</el-descriptions>
</section>
</el-drawer>
</div>
</template>
<script>
import { show } from "@/api/user/user";
export default {
name: "UserShow",
props: {
isShow: {
type: Boolean,
default: false,
required: true,
},
},
data() {
return {
loading: false,
visible: false,
form: {
nickname: "",
openid: "",
headimgurl: "",
mobile: "",
sex: "",
area: "",
province: "",
city: "",
name: "",
grade: "",
middle_exam_year: "",
nationality: "",
school: "",
},
};
},
watch: {
isShow(newVal) {
this.visible = newVal;
},
visible(newVal) {
this.$emit("update:isShow", newVal);
},
},
methods: {
async getDetail(id) {
try {
const detail = await show({
id,
});
for (let key in this.form) {
if (detail.hasOwnProperty(key)) {
this.form[key] = detail[key];
}
}
} catch (err) {
console.error(err);
}
},
},
};
</script>
<style scoped lang="scss">
.span2 {
grid-column: span 2;
}
::v-deep .el-form-item > * {
max-width: 100%;
}
.drawer-container {
height: 100%;
padding: 20px;
display: flex;
flex-direction: column;
& > * {
flex: 1;
}
}
</style>

@ -25,7 +25,7 @@ module.exports = {
* Detail: https://cli.vuejs.org/config/#publicpath
*/
publicPath: process.env.ENV === 'staging' ? '/admin_test' : '/admin',
outputDir: './dist',
outputDir: `../git-store/zhiyuan/public/${process.env.ENV === 'staging' ? '/admin_test' : '/admin'}`,
assetsDir: 'static',
css: {
loaderOptions: { // 向 CSS 相关的 loader 传递选项
@ -37,7 +37,7 @@ module.exports = {
}
}
},
lintOnSave: process.env.NODE_ENV === 'development',
lintOnSave: false,
productionSourceMap: false,
devServer: {
port: port,

Loading…
Cancel
Save