提交 12f46b74 authored 作者: pengxiaohui's avatar pengxiaohui

init

上级
VITE_LOGIN_URL=https://login.ezijing.com/auth/login/index
VITE_BASE_URL=https://learn-api.ezijing.com
VITE_LOGIN_URL=https://login.ezijing.com/auth/login/index
VITE_BASE_URL=https://learn-api.ezijing.com
VITE_LOGIN_URL=https://login2.ezijing.com/auth/login/index
VITE_BASE_URL=https://learn-api2.ezijing.com
module.exports = {
env: {
node: true
},
extends: ['plugin:vue/essential', 'standard'],
rules: {
'vue/comment-directive': 'off',
'space-before-function-paren': 'off'
}
}
node_modules
.DS_Store
dist
dist-ssr
*.local
\ No newline at end of file
const fs = require('fs')
const path = require('path')
const chalk = require('chalk')
const OSS = require('ali-oss')
const log = console.log
const client = new OSS({
region: 'oss-cn-beijing',
accessKeyId: 'LTAIOTuuLTaWoGJj',
accessKeySecret: 'dE5tTGm2lh35eItct2krW2DeH2lf2I',
bucket: 'webapp-pub'
})
async function uploadTarget(src, dist) {
try {
const result = await client.put(dist, path.join(__dirname, src))
log(chalk.green('上传成功', result.url))
} catch (e) {
log(chalk.red('上传失败', src))
log(e)
}
}
function generateUploadTarget(src, dist) {
fs.readdir(path.join(__dirname, src), function (err, files) {
if (err) {
log(err)
return
}
files.forEach(function (file) {
const _src = src + '/' + file
const _dist = dist + '/' + file
const stats = fs.statSync(path.join(__dirname, _src))
// 判断是否为文件
stats.isFile() && uploadTarget(_src, _dist)
// 判断是否为文件夹
stats.isDirectory() && generateUploadTarget(_src, _dist)
})
})
}
generateUploadTarget('./dist', '/website/prod/x-admin')
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA9MjUExxOj6tfMUaU+t/k0+VokVkh1GVZACLPdxR2VcjjOkaI
qloY4TK4/uybncLhoqu9ggyiJFNmXVLxe2TxReGXhR8tAc0RTCuaFv0oHJ7HXI48
CZ/zc8sgjyd7XsuyRurKwpHWXfy9FMHE7r/9R+IUHAkIPmZ17qlwqphlePK8I1fn
DU/LOvglXPIlR55TUe6NKVoCPZXQEHHPZClH0qdnXFiUq5I1f6GMUoGCbV5DLECb
OAndCP/snqakw8oeLmoBGSig/FGrc/41l2DJxyIxm/CfaEhfnSN4hvOTAFXcro9c
gyy88H1BfT/bEhM7OO+RRaKUrV5CieeIOEmvGQIDAQABAoIBAGmkcsJ8qPsgPskJ
aSqMjjlU/Lgd+5eq1apVW6xMzHVhaY+w+TJsB+jI90Yt30tK3A5UiEkkIqYCyF7m
eQmEGwzJu5bcSZRJaHmzJ6FcSH9xlyC+0fJlcbA7riWaKIhU6O/qTO+D+Tw+42ud
5NwVR75KN9uRmlkz5xnFTraRZtm3MJmA7dwXK3hrN+dFJR2vLO3KBAtpgtpPdkK1
ObpJQ1Q7jsnEmODVRZ7n1CKZEDmXd8GBPA/jCVqgiEbVVCdkhHkyxyIMQenBReyy
tJIPf7CdL3O3PPsThhMa1P2CP/xehS4bcQSLw9wtNTJcvVPHTvffHKOKUfhUxkHu
0cpl+zECgYEA/jzySW/br7W+xS2e4VBHzY+UZJwxd/3mY3d/kasMV1zuipr6WOhQ
FVsd5uJXPRr+rHBypwyOIlP205V2K5oQEK0yT+tF+IBvKGdJv89wskCgrXcD3Kfb
dCFbt014pHw89A8jb8LBbGOPH6jhZhGkxP33CJdVPtncUc4m0hj4HHcCgYEA9nsc
KcCZOIYRlZmJ93DoukhjxaouGFDTOZoujaqasrfXUaWRnpZYekDZWa1NneOzLBEz
h1RwPcmeYLCVRmXtpRzLOKXfJY0gGSJr979I0AVkzj8A9NZcU/HxUP0GqpwBbzAp
EEShQVhjYppQ62KAwZ1tbsVWX2V1SBsa3McExO8CgYEA6kVy5aTDhOgugDeHnguB
/rN9hDBBjVZTQ/jLfolld+NUlDg21FJN6T/rD+Qli1MitfdwTupM1ukUGugw2gC/
KP7Py8D62wBObaav2KXoLPlMlkuDLYMnv501jHVA5CDvcd25Q7Ts01nyerP97zX2
5Oc5CZuZm67ZTDBwqU0E5AUCgYBIC2wL+DPRBb8WDy74mJQt/wLKwBeBG/7hk2OQ
HRHis0HIp7CMvj1WXqYpRDKvt+KjOtPo9pFoPgqBEJxRW3G/FU+BW1qCS2HadulA
HTVXOHxinJ/W8OFD2DBFD/Bm5fq1WUpnaugHhaJnK9wDMWOZND7MZfn9IFbLoMCV
T8bhGQKBgG7qsZhI9ldAqooZQ1xSua/2SBc8GI8d03g7y8kZkkx/XclbEz6X6wUu
U3PVL+neY8Qw3JxC1cHS++KIdHR2ZSoTpF00A4QvDJL0+eo1KgI88vRV8QaWLxPB
ahvXwmkKW2+jgvCAqFtepZx/KsKpQW+x3GOJyhl2tIT8sZwRmE6u
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIFhzCCBG+gAwIBAgIQCzEi4VmynSzbyBV1UEXGojANBgkqhkiG9w0BAQsFADBu
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
RFYgVExTIENBIC0gRzEwHhcNMjAxMTA5MDAwMDAwWhcNMjExMTA5MjM1OTU5WjAa
MRgwFgYDVQQDEw9kZXYuZXppamluZy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQD0yNQTHE6Pq18xRpT63+TT5WiRWSHUZVkAIs93FHZVyOM6Roiq
WhjhMrj+7JudwuGiq72CDKIkU2ZdUvF7ZPFF4ZeFHy0BzRFMK5oW/SgcnsdcjjwJ
n/NzyyCPJ3tey7JG6srCkdZd/L0UwcTuv/1H4hQcCQg+ZnXuqXCqmGV48rwjV+cN
T8s6+CVc8iVHnlNR7o0pWgI9ldAQcc9kKUfSp2dcWJSrkjV/oYxSgYJtXkMsQJs4
Cd0I/+yepqTDyh4uagEZKKD8Uatz/jWXYMnHIjGb8J9oSF+dI3iG85MAVdyuj1yD
LLzwfUF9P9sSEzs475FFopStXkKJ54g4Sa8ZAgMBAAGjggJzMIICbzAfBgNVHSME
GDAWgBRVdE+yck/1YLpQ0dfmUVyaAYca1zAdBgNVHQ4EFgQUkRHkmubxZAvEWtCY
IBT9sw/3Yb8wGgYDVR0RBBMwEYIPZGV2LmV6aWppbmcuY29tMA4GA1UdDwEB/wQE
AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwTAYDVR0gBEUwQzA3
BglghkgBhv1sAQIwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu
Y29tL0NQUzAIBgZngQwBAgEwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY
aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEoGCCsGAQUFBzAChj5odHRwOi8vY2Fj
ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcx
LmNydDAJBgNVHRMEAjAAMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYA9lyUL9F3
MCIUVBgIMJRWjuNNExkzv98MLyALzE7xZOMAAAF1qpa6jQAABAMARzBFAiAwHOcp
Ua1H0WK4OZUHiQ1rndqnYxPHhP9XWunwpRMoagIhAOB2MPSW9M4qj6Yih7eQkydl
lgawpoBZzRzhisU+TN67AHYAXNxDkv7mq0VEsV6a1FbmEDf71fpH3KFzlLJe5vbH
DsoAAAF1qpa63gAABAMARzBFAiEA92ZeW0PgyWW3j+3wypLS0O/wI63C+x0WTvMZ
Vngp6AMCIBoThjaKif+XY11YbaV89ndqs1nDlzbEfBrFftoB9fchMA0GCSqGSIb3
DQEBCwUAA4IBAQA2geo9wQAd+vx+lwAafVRxCBQyBiS0qT413ewYpZYDnSkLX0l1
5kRdxDGWQhPzOio0ckj/jOtOlbbSsiovBBVTyYPB8WfkNjMd0psMNx2e6Wy/WKkQ
X3DqEOB4XGg0RwpebiAmz6lWxyFwIAbCrwCntkkaIF4LnIvczn6pvPFBtK2nXJJC
HL0Igbxo+xJLt3Hql7TcwkFDXz/LIB8AwhhkkhhwW45r3Eyjw8eOyzvflDPwSNH+
ByadQ+AH4H4vYYVo0ILNIPCdaokLQ+u4FttB9VQ+iGmpJ56Yg2muxWh8Qckca+vH
40RbC5aK1RSy2RIRpC5fwvq2JuV/CksP5G5Q
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0xNzExMjcxMjQ2MTBaFw0yNzExMjcxMjQ2MTBaMG4xCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH
MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPeP6wkab41dyQh6mKc
oHqt3jRIxW5MDvf9QyiOR7VfFwK656es0UFiIb74N9pRntzF1UgYzDGu3ppZVMdo
lbxhm6dWS9OK/lFehKNT0OYI9aqk6F+U7cA6jxSC+iDBPXwdF4rs3KRyp3aQn6pj
pp1yr7IB6Y4zv72Ee/PlZ/6rK6InC6WpK0nPVOYR7n9iDuPe1E4IxUMBH/T33+3h
yuH3dvfgiWUOUkjdpMbyxX+XNle5uEIiyBsi4IvbcTCh8ruifCIi5mDXkZrnMT8n
wfYCV6v6kDdXkbgGRLKsR4pucbJtbKqIkUGxuZI2t7pfewKRc5nWecvDBZf3+p1M
pA8CAwEAAaOCAU8wggFLMB0GA1UdDgQWBBRVdE+yck/1YLpQ0dfmUVyaAYca1zAf
BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYw
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C
AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu
Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAK3Gp6/aGq7aBZsxf/oQ+TD/B
SwW3AU4ETK+GQf2kFzYZkby5SFrHdPomunx2HBzViUchGoofGgg7gHW0W3MlQAXW
M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV
4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ
sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy
rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg==
-----END CERTIFICATE-----
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="https://zws-imgs-pub.ezijing.com/pc/base/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>金融数据合规管理系统-紫荆教育</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"version": "0.0.0",
"scripts": {
"dev": "vite --mode dev",
"build": "cross-env BUILD_ENV=prod vite build && cross-env BUILD_ENV=prod npm run deploy",
"build:pre": "vite build",
"build:test": "vite build --mode test",
"preview": "vite preview",
"deploy": "node ./deploy.js",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src"
},
"dependencies": {
"axios": "^0.22.0",
"element-ui": "^2.15.6",
"query-string": "^7.0.1",
"vue": "^2.6.14",
"vue-router": "^3.5.2",
"vuex": "^3.6.2"
},
"devDependencies": {
"@rollup/plugin-eslint": "^8.0.1",
"ali-oss": "^6.16.0",
"chalk": "^4.1.2",
"cross-env": "^7.0.3",
"eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^7.19.1",
"sass": "1.42.1",
"vite": "^2.6.5",
"vite-plugin-vue2": "^1.8.2",
"vue-template-compiler": "^2.6.14"
}
}
<template>
<div id="app">
<router-view />
</div>
</template>
import httpRequest from '@/utils/axios'
// 获取用户信息
export function getUser() {
return httpRequest.get('/api/passport/account/get-user-info')
}
// 退出登录
export function logout() {
return httpRequest.get('/api/passport/rest/logout')
}
// 获取oss token
export function getToken() {
return httpRequest.get('/api/usercenter/aliyun/assume-role')
}
// 获取oss signature
export function getSignature() {
return httpRequest.get('/api/usercenter/aliyun/get-signature')
}
// 图片上传
export function uploadFile(data) {
return httpRequest.post('https://webapp-pub.oss-cn-beijing.aliyuncs.com', data, {
withCredentials: false,
headers: { 'Content-Type': 'multipart/form-data' }
})
}
// 获取所有权限
export function getPermissions() {
return httpRequest.get('/api/lms-financial/school/permission/get-permissions')
}
/**
* 获取课程列表
*/
export function getCourseList(params) {
return httpRequest.get('/api/lms-financial/school/course/list', { params })
}
/**
* 获取班级列表
*/
export function getClassList() {
return httpRequest.get('/api/lms-financial/school/class/list', { params: { page: 'false' } })
}
html * {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
outline: none;
-webkit-text-size-adjust: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
body,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
p,
blockquote,
dl,
dt,
dd,
ul,
ol,
li,
pre,
form,
fieldset,
legend,
button,
input,
textarea,
th,
td {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: 100%;
}
ul,
ol,
li {
list-style: none;
}
em,
i {
font-style: normal;
}
strong,
b {
font-weight: normal;
}
img {
border: none;
}
input,
img {
vertical-align: middle;
}
a {
color: inherit;
text-decoration: none;
}
input,
button,
select,
textarea {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-appearance: none;
border: 0;
border-radius: 0;
font: inherit;
}
textarea:focus {
outline: 0;
}
$--color-primary: #3276fc;
$--color-info: #3c4043;
// border
$--border-radius-small: 8px !default;
// dialog
$--message-close-size: 20px !default;
/* 改变 icon 字体路径变量,必需 */
$--font-path: 'element-ui/lib/theme-chalk/fonts';
@import 'element-ui/packages/theme-chalk/src/index';
<template>
<div class="app-card">
<div class="app-card-hd">
<slot name="header">
<h2 class="app-card-hd__title" v-if="title">{{ title }}</h2>
<slot name="header-aside"></slot>
</slot>
</div>
<div class="app-card-bd">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'AppCard',
props: { title: String }
}
</script>
<style>
.app-card {
background: #ffffff;
box-shadow: 0 1px 6px 0 rgb(228 232 235 / 20%);
border-radius: 8px;
margin-bottom: 20px;
padding: 32px;
}
.app-card-hd {
display: flex;
}
.app-card-hd__title {
flex: 1;
font-size: 18px;
font-weight: 700;
margin-bottom: 16px;
}
</style>
<template>
<el-select v-bind="$attrs" v-on="$listeners" :placeholder="placeholder">
<el-option
v-for="item in options"
:key="item.id"
:label="item.class_name"
:value="item.id">
</el-option>
</el-select>
</template>
<script>
import { getClassList } from '@/api/base'
export default {
props: {
placeholder: {
type: String,
default: '请选择'
}
},
data() {
return {
options: []
}
},
created() {
this.fetchCourseList()
},
methods: {
fetchCourseList() {
const params = {}
getClassList(params).then(res => {
if (res.code === 0 && res.data) {
this.options = res.data.list
}
})
}
}
}
</script>
<template>
<el-select v-bind="$attrs" v-on="$listeners" style="width:220px;" :placeholder="placeholder">
<el-option
v-for="item in options"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</template>
<script>
import { getCourseList } from '@/api/base'
export default {
props: {
placeholder: {
type: String,
default: '请选择'
}
},
data() {
return {
options: [
{ id: '11', name: '黄金糕' },
{ id: '22', name: '双皮奶' },
{ id: '33', name: '蚵仔煎' },
{ id: '44', name: '龙须面' },
{ id: '55', name: '北京烤鸭' }
]
}
},
created() {
},
methods: {
fetchCourseList() {
const params = {}
getCourseList(params).then(res => {})
}
}
}
</script>
<template>
<div class="table-list">
<div class="table-list-hd">
<!-- 筛选 -->
<div class="table-list-filter" v-if="filters.length">
<el-form :inline="true" :model="params" ref="filterForm" label-width="110px">
<template v-for="item in filters">
<el-form-item :label="item.label" :prop="item.prop" :key="item.prop">
<template v-if="item.slots">
<slot :name="item.slots" v-bind="{ params }"></slot>
</template>
<template v-else>
<!-- input -->
<el-input v-model="params[item.prop]" v-bind="item" clearable v-if="item.type === 'input'" />
<!-- select -->
<el-select
v-model="params[item.prop]"
clearable
v-bind="item"
v-if="item.type === 'select'"
@change="search"
>
<template v-for="(option, index) in item.options">
<el-option
:label="option[item.labelKey] || option.label"
:value="option[item.valueKey] || option.value"
:key="index"
></el-option>
</template>
</el-select>
</template>
</el-form-item>
</template>
<el-form-item class="filter-buttons">
<el-button type="primary" icon="el-icon-search" @click="search">搜索</el-button>
<el-button icon="el-icon-refresh-left" @click="reset">重置</el-button>
<el-button @click="showMoreFilter" v-if="hasMoreFilter">更多筛选</el-button>
</el-form-item>
</el-form>
</div>
<div class="table-list-hd-aside"><slot name="header-aside" /></div>
</div>
<slot></slot>
<!-- 主体 -->
<div class="table-list-bd">
<slot name="body" v-bind="{ data: dataList }">
<el-table
:data="dataList"
v-loading="loading"
v-bind="$attrs"
v-on="$listeners"
style="height: 100%"
ref="table"
>
<template v-for="item in columns">
<el-table-column v-bind="item" :key="item.prop" v-if="visible(item)">
<template v-slot:default="scope" v-if="item.slots || item.computed">
<slot :name="item.slots" v-bind="scope" v-if="item.slots"></slot>
<div v-html="item.computed(scope)" v-if="item.computed"></div>
</template>
</el-table-column>
</template>
</el-table>
</slot>
</div>
<!-- 底部 -->
<div class="table-list-ft">
<div style="padding: 10px 0">
<slot name="footer"></slot>
</div>
<el-pagination
class="table-list-pagination"
layout="total, prev, pager, next, sizes, jumper"
:page-sizes="[10, 20, 30, 50, 100]"
:page-size="page.size"
:total="page.total"
:current-page.sync="page.currentPage"
@size-change="pageSizeChange"
@current-change="fetchList()"
:hide-on-single-page="true"
v-if="hasPagination"
>
</el-pagination>
</div>
<!-- 更多筛选 -->
<el-drawer title="更多筛选" :visible.sync="moreFilterVisible" ref="drawer">
<div class="more-filter-drawer">
<div class="more-filter">
<el-form :model="params" ref="moreFilterForm">
<template v-for="item in moreFilters">
<el-form-item :label="item.label" :prop="item.prop" :key="item.prop">
<template v-if="item.slots">
<slot :name="item.slots" v-bind="{ params }"></slot>
</template>
<template v-else>
<!-- input -->
<el-input v-model="params[item.prop]" v-bind="item" clearable v-if="item.type === 'input'" />
<!-- select -->
<el-select
v-model="params[item.prop]"
clearable
v-bind="item"
v-if="item.type === 'select'"
style="width: 100%"
>
<template v-for="(option, index) in item.options">
<el-option
:label="option[item.labelKey] || option.label"
:value="option[item.valueKey] || option.value"
:key="index"
></el-option>
</template>
</el-select>
</template>
</el-form-item>
</template>
</el-form>
</div>
<div class="more-filter-buttons">
<el-button @click="cancelMoreFilter">取 消</el-button>
<el-button type="primary" @click="primaryMoreFilter">确定</el-button>
</div>
</div>
</el-drawer>
</div>
</template>
<script>
export default {
name: 'AppList',
props: {
// 接口请求
remote: { type: Object, default: () => ({}) },
// 筛选
filters: { type: Array, default: () => [] },
// 更多筛选
moreFilters: { type: Array, default: () => [] },
// 列表项
columns: { type: Array, default: () => [] },
// 列表数据
data: { type: Array, default: () => [] },
// 是否含有翻页
hasPagination: { type: Boolean, default: true },
// 每页多少条数据
limit: { type: Number, default: 20 }
},
data() {
return {
loading: false,
params: this.remote.params || {},
dataList: this.data,
page: { total: 0, size: this.limit, currentPage: 1 },
moreFilterVisible: false
}
},
watch: {
'remote.params': {
immediate: true,
handler(data) {
this.params = data || {}
}
},
data: {
immediate: true,
handler(data) {
this.dataList = data
}
}
},
computed: {
table() {
return this.$refs.table
},
hasMoreFilter() {
return !!this.moreFilters.length
}
},
methods: {
fetchList(isReset) {
/**
* @param function httpRequest api接口
* @param function beforeRequest 接口请求之前
* @param function callback 接口请求成功回调
*/
const { httpRequest, beforeRequest, callback } = this.remote
if (!httpRequest) {
return
}
// 参数设置
let params = this.params
// 翻页参数设置
if (this.hasPagination) {
params.page = this.page.currentPage.toString()
params['per-page'] = this.page.size.toString()
}
// 接口请求之前
if (beforeRequest) {
params = beforeRequest(params, isReset)
}
for (const key in params) {
if (params[key] === '' || params[key] === undefined || params[key] === null) {
delete params[key]
}
}
this.loading = true
httpRequest(params)
.then(res => {
const { data = {} } = res || {}
this.page.total = parseInt(data.total || 0)
if (Array.isArray(data)) {
this.dataList = callback ? callback(data) : data
} else {
this.dataList = callback ? callback(data) : data.list
}
})
.catch(() => {
this.page.total = 0
this.dataList = []
})
.finally(() => {
this.loading = false
})
},
// 搜索
search() {
this.page.currentPage = 1
this.fetchList()
},
// 重置
reset() {
// 清空筛选条件
this.$refs.filterForm && this.$refs.filterForm.resetFields()
// 清空更多筛选条件
this.hasMoreFilter && this.$refs.moreFilterForm && this.$refs.moreFilterForm.resetFields()
// 初始化页码
this.page.currentPage = 1
// 刷新列表
this.fetchList(true)
},
// 刷新
refetch(isForce) {
isForce ? this.reset() : this.fetchList()
},
// 页数改变
pageSizeChange(value) {
this.page.currentPage = 1
this.page.size = value
this.fetchList()
},
visible(item) {
return Object.prototype.hasOwnProperty.call(item, 'visible') ? item.visible : true
},
// 显示更多筛选
showMoreFilter() {
this.moreFilterVisible = true
},
// 取消更多筛选
cancelMoreFilter() {
this.moreFilterVisible = false
// 清空筛选条件
this.$refs.moreFilterForm && this.$refs.moreFilterForm.resetFields()
},
// 确定更多筛选
primaryMoreFilter() {
this.moreFilterVisible = false
this.search()
}
},
beforeMount() {
this.fetchList()
}
}
</script>
<style lang="scss">
.table-list {
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.table-list-hd {
display: flex;
}
.table-list-filter {
flex: 1;
}
.table-list-bd {
flex: 1;
}
.table-list-ft {
display: flex;
align-items: center;
justify-content: space-between;
}
.table-list-pagination {
padding: 10px 0;
text-align: right;
}
.el-table-column--selection .cell {
padding: 0 14px !important;
}
.more-filter-drawer {
height: 100%;
display: flex;
flex-direction: column;
padding: 0 20px 20px;
box-sizing: border-box;
}
.more-filter {
flex: 1;
overflow-y: auto;
}
.more-filter-buttons {
display: flex;
.el-button {
flex: 1;
}
}
</style>
<template>
<el-select v-bind="$attrs" v-on="$listeners" :placeholder="placeholder">
<el-option
v-for="item in options"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</template>
<script>
import { getCourseList } from '@/api/base'
export default {
props: {
placeholder: {
type: String,
default: '请选择'
}
},
data() {
return {
options: [
{ id: '11', name: '张三' },
{ id: '22', name: '李四' },
{ id: '33', name: '王五' },
{ id: '44', name: '赵六' },
{ id: '55', name: '孙七' }
]
}
},
created() {
},
methods: {
fetchCourseList() {
const params = {}
getCourseList(params).then(res => {})
}
}
}
</script>
<template>
<aside class="app-aside">
<nav class="nav">
<el-menu :default-active="defaultActive" :router="true">
<template v-for="item in menuList">
<el-submenu :index="item.path" :key="item.path" v-if="item.children">
<template #title><i :class="item.icon"></i>{{ item.name }}</template>
<el-menu-item
:index="subitem.path"
v-for="subitem in item.children"
:key="subitem.path"
v-show="menuVisible(subitem.tag)"
>
{{ subitem.name }}
</el-menu-item>
</el-submenu>
<el-menu-item :index="item.path" :key="item.path" v-else v-show="menuVisible(item.tag)">
<i :class="item.icon"></i>{{ item.name }}
</el-menu-item>
</template>
</el-menu>
</nav>
</aside>
</template>
<script>
export default {
name: 'AppAside',
data() {
return {
menuList: [
{
name: '仪表台',
path: '/dashboard',
icon: 'el-icon-data-board',
children: [
{ name: '数据总览', path: '/dashboard', tag: '' }
]
},
{
tag: 'menu_school',
name: '教务管理',
path: '/school/class',
icon: 'el-icon-user',
children: [
{ name: '班级管理', path: '/school/class', tag: '' },
{ name: '课程管理', path: '/school/course', tag: '' },
{ name: '考试管理', path: '/school/exam', tag: '' },
{ name: '证书管理', path: '/school/cert', tag: '' },
// { name: '教师管理', path: '/school/teacher', tag: '' },
// { name: '学员管理', path: '/school/student', tag: '' }
]
},
{
name: '人员管理',
path: '/personnel/teacher',
icon: 'el-icon-s-custom',
children: [
{ name: '教工管理', path: '/personnel/teacher', tag: '' },
{ name: '人员管理', path: '/personnel/student', tag: '' }
]
},
// {
// name: '系统管理',
// path: '/system/safe',
// icon: 'el-icon-setting',
// tag: 'menu_system',
// children: [{ name: '安全设置', path: '/system/safe', tag: '' }]
// },
{
name: '审核管理',
path: '/audit/my',
icon: 'el-icon-s-check',
children: [
// { name: '事务类型', path: '/audit/type', tag: '' },
{ name: '我的审核', path: '/audit/my', tag: '' }
]
}
]
}
},
computed: {
defaultActive() {
// 扁平菜单
const flatMenuList = this.menuList.reduce((result, item) => {
result.push(item)
if (item.children) {
result = result.concat(item.children)
}
return result
}, [])
const found = flatMenuList.reverse().find(item => {
return this.$route.path.includes(item.path)
})
return found ? found.path : ''
},
// 菜单权限
menuPermissions() {
return this.$store.state.permissions
}
},
methods: {
menuVisible(tag) {
if (!tag) {
return true
}
return !!this.menuPermissions.find(item => item.tag === tag)
}
}
}
</script>
<style lang="scss">
.app-aside {
position: sticky;
top: 0;
width: 200px;
z-index: 100;
background: #fff;
border-right: 1px solid rgba(0, 0, 0, 0.12);
overflow-x: hidden;
overflow-y: auto;
flex: 0 0 200px;
}
.nav {
margin: 20px 0;
.el-menu {
border-right: 0;
i {
margin-right: 14px;
font-size: 24px;
}
.el-icon-arrow-down {
margin-right: 0;
font-size: 16px;
}
}
.el-menu-item {
display: flex;
align-items: center;
margin: 0 16px;
font-size: 16px;
border-radius: 8px;
}
.el-submenu .el-menu-item {
min-width: auto;
padding-left: 58px !important;
}
.el-submenu__title {
display: flex;
align-items: center;
margin: 0 16px;
font-size: 16px;
border-radius: 8px;
&:hover {
background-color: rgba(86, 100, 210, 0.04);
}
}
}
</style>
<template>
<div class="app-breadcrumb" v-if="routes.length">
<el-breadcrumb>
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item v-for="route in routes" :key="route.path">
<router-link :to="route.path">{{ route.meta.title }}</router-link>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script>
export default {
name: 'AppBreadcrumb',
computed: {
routes() {
return this.$route.matched.filter(route => route.meta.title)
}
}
}
</script>
<style lang="scss">
.app-breadcrumb {
padding: 18px 0 32px;
.el-breadcrumb {
font-size: 20px;
font-weight: 400;
line-height: 1;
}
.el-breadcrumb__inner a {
font-weight: normal;
color: #5b91fd;
}
.router-link-active {
color: #1a1b1c;
}
}
</style>
<template>
<header class="app-header">
<div class="logo">
<router-link to="/"><img src="https://zws-imgs-pub.ezijing.com/pc/base/ezijing-logo-white.svg" /></router-link>
</div>
<div class="app-header-right">
<el-dropdown>
<div class="avatar">
<img :src="user.avatar || 'https://zws-imgs-pub.ezijing.com/pc/base/logo.png'" />
</div>
<el-dropdown-menu slot="dropdown">
<div class="user">
<div class="user-name">{{ user.realname || user.nickname }}</div>
<el-button round size="medium" class="btn-logout" @click="logout">退出登录</el-button>
</div>
</el-dropdown-menu>
</el-dropdown>
</div>
</header>
</template>
<script>
export default {
name: 'AppHeader',
computed: {
user() {
return this.$store.state.user
}
},
methods: {
logout() {
this.$store.dispatch('logout').then(() => {
window.location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(window.location.href)}`
})
}
}
}
</script>
<style lang="scss">
.app-header {
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
background-color: #3276fc;
color: #fff;
.logo {
width: 120px;
}
}
.app-header-right {
display: flex;
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
overflow: hidden;
}
}
}
.user {
padding: 16px;
}
.user-name {
margin-bottom: 20px;
}
.btn-logout {
width: 200px;
}
</style>
<template>
<div class="app-layout">
<app-header></app-header>
<div class="app-layout-container">
<app-aside></app-aside>
<app-main></app-main>
</div>
</div>
</template>
<script>
import AppHeader from './Header.vue'
import AppAside from './Aside.vue'
import AppMain from './Main.vue'
export default {
name: 'AppLayout',
components: { AppHeader, AppAside, AppMain }
}
</script>
<style>
.app-layout {
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: #ededed;
}
.app-layout-container {
flex: 1;
display: flex;
}
</style>
<template>
<section class="app-main">
<div class="app-main-inner">
<div class="app-main-header">
<app-breadcrumb v-if="hasBreadcrumb"></app-breadcrumb>
</div>
<div class="app-main-container">
<router-view></router-view>
</div>
</div>
</section>
</template>
<script>
import AppBreadcrumb from './Breadcrumb.vue'
export default {
name: 'AppMain',
props: { hasBreadcrumb: { type: Boolean, default: true } },
components: { AppBreadcrumb }
}
</script>
<style>
.app-main {
position: relative;
flex: 1;
padding: 20px;
overflow: hidden;
}
.app-main-inner {
margin: 0 auto;
}
.app-main-container::after {
content: '';
display: table;
clear: both;
}
.el-form--label-top .el-form-item__label {
padding-bottom: 0;
}
</style>
<template>
<editor :init="init" v-bind="$attrs" v-on="$listeners" @onChange="onChange" @onBlur="onBlur" />
</template>
<script>
import Editor from '@tinymce/tinymce-vue'
import ImageUpload from './imageUpload'
export default {
components: {
editor: Editor
},
data() {
return {
init: {
min_height: 600,
max_height: 600,
menubar: false,
statusbar: false,
plugins: 'table autoresize charmap fullscreen hr lists link code preview quickbars',
toolbar:
'undo redo | fontsizeselect lineheight bold italic underline strikethrough forecolor backcolor | link quickimage image media table | align hangingindent indent outdent numlist bullist | charmap blockquote hr fullscreen | code preview',
// font_formats:
// '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Times New Roman',
fontsize_formats: '8px 10px 12px 14px 15px 16px 17px 18px 20px 24px',
lineheight_formats: '0.5 1 1.2 1.5 2',
images_upload_handler: ImageUpload,
automatic_uploads: true,
quickbars_insert_toolbar: false,
// style_formats: [{ title: '悬挂缩进', block: 'p', styles: { textIndent: '-2em', paddingLeft: '2em' } }],
content_style: 'img {max-width:100%;}'
}
}
},
methods: {
onChange(event, editor) {
this.dispatch('ElFormItem', 'el.form.change', editor.getContent())
},
onBlur(event, editor) {
this.dispatch('ElFormItem', 'el.form.blur', editor.getContent())
},
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root
var name = parent.$options.componentName
while (parent && (!name || name !== componentName)) {
parent = parent.$parent
if (parent) {
name = parent.$options.componentName
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params))
}
}
}
}
</script>
<style>
.tox .tox-tbtn--bespoke .tox-tbtn__select-label {
width: 4em !important;
}
</style>
import { getSignature, uploadFile } from '@/api/base'
import md5 from 'blueimp-md5'
export default function (blobInfo, succFun, failFun) {
const file = blobInfo.blob()
getSignature()
.then(response => {
const prefix = 'upload/admin/'
const fileName = file.name
const key = prefix + md5(fileName + new Date().getTime()) + fileName.substr(fileName.lastIndexOf('.'))
const { accessid, policy, signature, host } = response
const data = { key, OSSAccessKeyId: accessid, policy, signature, success_action_status: '200', file }
const fileUrl = `${host}/${key}`
uploadFile(data)
.then(() => {
succFun(fileUrl)
})
.catch(() => {
failFun('上传失败')
})
})
.catch(response => {
failFun('获取Signature失败')
})
}
<template>
<div class="app-upload">
<draggable
v-model="fileList"
group="people"
class="file-list el-upload-list--picture-card"
@end="onEnd"
v-if="showFileList"
>
<div class="file-item" v-for="(file, index) in fileList" :key="index">
<!-- <el-image style="width: 100%; height: 100%" :src="file.url" :preview-src-list="fileList.map(item => item.url)"> -->
<img :src="file.url" />
<span class="el-upload-list__item-actions">
<span class="el-upload-list__item-preview" @click="handlePreview(file)">
<i class="el-icon-zoom-in"></i>
</span>
<span v-if="!$attrs.disabled" class="el-upload-list__item-delete" @click="handleRemove(index)">
<i class="el-icon-delete"></i>
</span>
</span>
</div>
</draggable>
<el-upload
action="https://webapp-pub.oss-cn-beijing.aliyuncs.com"
type="drag"
:before-upload="beforeUpload"
:on-success="handleSuccess"
:show-file-list="false"
:on-exceed="handleExceed"
:data="data"
v-bind="$attrs"
v-on="$listeners"
style="display: inline"
>
<slot>
<i class="el-icon-plus"></i>
</slot>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="previewFile.url" />
</el-dialog>
</div>
</template>
<script>
import { getSignature } from '@/api/base'
import md5 from 'blueimp-md5'
import draggable from 'vuedraggable'
export default {
name: 'AppUpload',
components: { draggable },
props: {
value: { type: [String, Array] },
prefix: { type: String, default: 'upload/shop-admin/' },
showFileList: { type: Boolean, default: true }
},
data() {
return {
data: {}, // 请求参数
fileList: [], // 文件列表
dialogVisible: false,
previewFile: {},
message: null
}
},
watch: {
value: {
immediate: true,
handler(value) {
if (!value) {
return
}
let fileList = []
if (this.isMultiple) {
fileList = value.map(item => {
return { name: item.name || item, url: item.url || item }
})
} else {
fileList.push({ name: '附件', url: value })
}
this.fileList = [...fileList]
}
}
},
computed: {
isMultiple() {
return Array.isArray(this.value)
}
},
methods: {
beforeUpload(file) {
const limit = this.$attrs.limit
if (limit && this.fileList.length >= limit) {
this.message && this.message.close()
this.message = this.$message({ type: 'error', message: '文件超出个数限制' })
return false
}
const fileName = file.name
const key = this.prefix + md5(fileName + new Date().getTime()) + fileName.substr(fileName.lastIndexOf('.'))
return new Promise((resolve, reject) => {
getSignature()
.then(response => {
const { accessid, policy, signature, host } = response
this.data = { key, OSSAccessKeyId: accessid, policy, signature, success_action_status: '200' }
file.url = `${host}/${key}`
resolve(true)
})
.catch(err => {
// console.log(err)
reject(err)
})
})
},
handleSuccess(response, file) {
let value = null
if (this.isMultiple) {
const limit = this.$attrs.limit
if (limit && this.fileList.length >= limit) {
return
}
this.fileList.push({ name: file.name, url: file.raw.url })
value = this.fileList
} else {
this.fileList = [file.raw.url]
value = file.raw.url
}
this.$emit('input', value)
this.dispatch('ElFormItem', 'el.form.change', value)
},
// 删除
handleRemove(index) {
this.fileList.splice(index, 1)
this.$emit('input', this.fileList)
this.dispatch('ElFormItem', 'el.form.change', this.fileList)
},
// 预览
handlePreview(file) {
this.previewFile = file
this.dialogVisible = true
},
handleExceed() {
this.$message({ type: 'error', message: '文件超出个数限制' })
},
onEnd() {
this.$emit('input', this.fileList)
},
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root
var name = parent.$options.componentName
while (parent && (!name || name !== componentName)) {
parent = parent.$parent
if (parent) {
name = parent.$options.componentName
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params))
}
}
}
}
</script>
<style lang="scss">
.el-upload-list--picture-card .el-upload-list__item-thumbnail {
object-fit: cover;
}
.el-upload-list__item {
transition: none !important;
}
.file-list {
margin: 0;
display: inline;
vertical-align: top;
}
.file-item {
position: relative;
overflow: hidden;
background-color: #fff;
border: 1px solid #c0ccda;
border-radius: 6px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 148px;
height: 148px;
margin: 0 8px 8px 0;
display: inline-block;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
</style>
<template>
<app-upload class="avatar-uploader" accept="image/*" :show-file-list="false" v-bind="$attrs" v-on="$listeners">
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</app-upload>
</template>
<script>
import AppUpload from './Upload.vue'
export default {
name: 'AppUploadImage',
components: { AppUpload },
data() {
return {}
},
computed: {
imageUrl() {
return this.$attrs.value
}
}
}
</script>
<style lang="scss">
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 148px;
height: 148px;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
line-height: 148px;
text-align: center;
}
.avatar {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
</style>
<template>
<app-upload class="video-uploader" accept="video/*" :show-file-list="false" v-bind="$attrs" v-on="$listeners">
<img v-if="videoUrl" :src="`${videoUrl}?x-oss-process=video/snapshot,t_10,f_jpg,w_0,h_0,m_fast`" class="avatar" />
<i v-else class="el-icon-plus video-uploader-icon"></i>
</app-upload>
</template>
<script>
import AppUpload from './Upload.vue'
export default {
name: 'AppUploadVideo',
components: { AppUpload },
data() {
return {}
},
computed: {
videoUrl() {
return this.$attrs.value
}
}
}
</script>
<style lang="scss">
.video-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 148px;
height: 148px;
}
.video-uploader .el-upload:hover {
border-color: #409eff;
}
.video-uploader-icon {
font-size: 28px;
color: #8c939d;
line-height: 148px;
text-align: center;
}
.avatar {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
</style>
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import modules from './modules'
// 公共css
import './assets/css/base.css'
// Element-UI
import ElementUI from 'element-ui'
import './assets/theme/style.scss'
// 公共组件
import AppCard from './components/base/AppCard.vue'
import AppList from './components/base/AppList.vue'
import beforeEnter from './utils/beforeEnter'
// 注册element-ui组件
Vue.use(ElementUI, { size: 'small' })
// 注册公共组件
Vue.component('AppCard', AppCard)
Vue.component('AppList', AppList)
// 注册模块
modules({ router, store })
router.beforeEach(beforeEnter)
new Vue({
store,
router,
render: h => h(App)
}).$mount('#app')
import httpRequest from '@/utils/axios'
/**
* 获取审批列表
*/
export function getAuditList(params) {
return httpRequest.get('/api/lms-financial/school/lobby/list', { params })
}
/**
* 审批
*/
export function auditApply(data) {
return httpRequest.post('/api/lms-financial/school/lobby/update', data)
}
/**
* 一键审批通过
*/
export function multiPass(data) {
return httpRequest.post('/api/lms-financial/school/lobby/multi-pass', data)
}
/**
* 获取审批详情
*/
export function getAuditDetails(params) {
return httpRequest.get('/api/lms-financial/school/lobby/view', { params })
}
\ No newline at end of file
<template>
<el-dialog title="基础信息" v-bind="$attrs" v-on="$listeners" width="400px" top="20vh">
<el-form ref="formRef" :model="form" label-width="80px">
<el-form-item label="姓名" prop="personal_name">
<el-input v-model="form.personal_name" disabled></el-input>
</el-form-item>
<el-form-item label="班级" prop="class_name">
<el-input v-model="form.class_name" disabled></el-input>
</el-form-item>
<template v-if="info.status === 0">
<el-form-item label="流转状态" prop="status">
<el-switch v-model="form.status" active-text="通过" inactive-text="驳回" active-value="1" inactive-value="2"></el-switch>
</el-form-item>
<el-form-item label="审批备注" prop="comment">
<el-input v-model="form.comment" type="textarea" size="small" rows="4" placeholder="请输入审批备注"/>
</el-form-item>
</template>
<template v-else>
<el-form-item label="审批状态" prop="status">
<p :class="{ 'audit_status': true, 'is_pass': info.status === 1}">{{info.status === 1 ? '审批通过' : '驳回'}}</p>
</el-form-item>
<el-form-item label="审批备注" prop="comment">
<p class="audit_remark">{{form.comment}}</p>
</el-form-item>
</template>
<el-form-item v-if="info.status === 0">
<el-button @click="$emit('update:visible', false)" style="margin-left: 50px">取消</el-button>
<el-button type="primary" style="margin-left: 20px" @click="onSubmit">提交</el-button>
</el-form-item>
</el-form>
</el-dialog>
</template>
<script>
import { auditApply, getAuditDetails } from '../api'
export default {
props: {
info: {
type: Object,
default() {
return {}
}
}
},
data() {
return {
form: {
personal_name: '',
class_name: '',
status: '1',
comment: ''
}
}
},
created() {
if (this.info.status !== 0) {
console.log(this.info)
this.fetchDetails()
}
this.form.personal_name = this.info.personal_name
this.form.class_name = this.info.class_name
},
methods: {
onSubmit() {
console.log(this.form)
const params = {
id: this.info.id,
status: this.form.status,
comment: this.form.comment
}
auditApply(params).then(res => {
if (res.code === 0) {
this.$message.success('审批成功')
this.$emit('success')
}
})
},
fetchDetails() {
getAuditDetails({ id: this.info.id }).then(res => {
if (res.code === 0) {
this.form.comment = res.data.comment
}
})
}
}
}
</script>
<style scoped>
video{
width:100%;
}
</style>
\ No newline at end of file
const routes = [
{
path: '/audit',
component: () => import('@/components/layout/Index.vue'),
children: [
{
path: 'my',
component: () => import('./views/List.vue'),
meta: { title: '我的审核' }
}
]
}
]
export { routes }
<template>
<app-card>
<el-tabs type="card" v-model="tabActive" @tab-click="tabClick">
<el-tab-pane name="0" style="padding-right:40px;">
<span slot="label">
<el-badge :value="total" :max="999" class="item" style="padding-right:10px;">
待办审批
</el-badge>
</span>
</el-tab-pane>
<el-tab-pane name="1">
<span slot="label">已完成审批</span>
</el-tab-pane>
</el-tabs>
<app-list v-bind="tableOptions" ref="list" @selection-change="handleSelectionChange">
<!-- <template v-slot:filter-student="{ params }">
<app-student-select v-model="params.student_id" placeholder="请选择学员" />
</template> -->
<template v-slot:filter-class="{ params }">
<app-class-select v-model="params.class_id" placeholder="请选择班级" />
</template>
<template v-slot:table-img="{ row }">
<img :src="row.url" width="120" height="70">
</template>
<template v-slot:table-x="{ row }">
<el-button v-if="tabActive !== '0'" type="primary" size="mini" plain @click="onDetails(row)">查看</el-button>
<el-button v-else type="primary" size="mini" plain @click="onAudit(row)">办理</el-button>
</template>
<template #footer>
<div v-if="tabActive === '0'">
已选中 {{multipleSelection.length}}
<el-button style="margin-left:15px;" size="mini" :disabled="!multipleSelection.length" @click="fetchMultiPass">一键通过</el-button>
</div>
</template>
</app-list>
<dialog-audit v-if="isShowDialog" :info="info" :title="info ? '更新课程':'添加课程'" :visible.sync="isShowDialog" @success="handleSuccess" />
</app-card>
</template>
<script>
import AppStudentSelect from '@/components/base/AppStudentSelect.vue'
import AppClassSelect from '@/components/base/AppClassSelect.vue'
import DialogAudit from '../components/DialogAudit.vue'
import { getAuditList, multiPass } from '../api'
const statusMap = { 0: '未审批', 1: '审核通过', 2: '驳回' }
export default {
data() {
return {
tabActive: 0,
multipleSelection: [],
isShowDialog: false,
total: 0
}
},
components: {
AppStudentSelect,
AppClassSelect,
DialogAudit
},
computed: {
// 列表配置
tableOptions() {
return {
remote: {
httpRequest: getAuditList,
params: { status: 0, student_id: '', class_id: '' },
beforeRequest: this.beforeRequest,
callback: this.tableCallback
},
filters: [
{ type: 'input', placeholder: '姓名', prop: 'personal_name' },
{ placeholder: '所属班级', slots: 'filter-class' }
],
columns: [
{ type: 'selection', minWidth: '50px', fixed: 'left', visible: this.tabActive === '0'},
{ label: '事项', align: 'center', prop: 'type_label' },
{ label: '申请人', align: 'center', prop: 'personal_name' },
{ label: '所属班级', align: 'center', prop: 'class_name' },
{ label: '申请时间', align: 'center', prop: 'created_time' },
// { label: '流转状态', align: 'center', prop: 'status' },
{
label: '流转状态',
align: 'center',
computed({ row }) {
return statusMap[row.status]
}
},
{ label: '操作', slots: 'table-x', align: 'center', width: '220', fixed: 'right' }
]
}
}
},
methods: {
beforeRequest(params) {
params.status = this.tabActive
return params
},
tableCallback(data) {
if (this.tabActive === '0') {
this.total = data.total
}
return data.list
},
handleSelectionChange(val) {
this.multipleSelection = val
},
tabClick() {
this.$refs.list.refetch()
},
onDetails(row) {
this.isShowDialog = true
this.info = row
},
onAudit(row) {
this.isShowDialog = true
this.info = row
},
handleSuccess() {
this.isShowDialog = false
this.$refs.list.refetch()
},
fetchMultiPass() {
const params = {
id: this.multipleSelection.map(item => item.id).join()
}
multiPass(params).then(res => {
if (res.code === 0) {
this.$message.success('审批成功')
this.$refs.list.refetch()
}
})
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .el-tabs__item{
padding-right:40px;
}
::v-deep .el-badge__content{
top:50%;
// transform:translate(0, -50%)
}
</style>
import httpRequest from '@/utils/axios'
/**
* 获取课程列表
*/
export function getCourseList(params) {
return httpRequest.get('/api/zy/v3-school/class/list', { params })
}
\ No newline at end of file
<template>
<el-dialog :title="title" v-bind="$attrs" v-on="$listeners" width="400px" top="20vh">
<el-form ref="formRef" :model="form" label-width="130px" :disabled="disabled">
<el-form-item label="事务名称" prop="name">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="表单名称" prop="table_name">
<el-input v-model="form.table_name"></el-input>
</el-form-item>
<el-form-item label="限制申请次数" prop="count">
<el-input v-model="form.count"></el-input>
</el-form-item>
<el-form-item label="限制申请学员状态" prop="count">
<el-input v-model="form.status"></el-input>
</el-form-item>
<el-form-item label="审批层级" prop="level">
<el-input v-model="form.level"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="$emit('update:visible', false)" style="margin-left: 50px">取消</el-button>
<el-button type="primary" style="margin-left: 20px" @click="onSubmit">提交</el-button>
</el-form-item>
</el-form>
</el-dialog>
</template>
<script>
export default {
props: {
info: {
type: Object,
default() {
return {}
}
},
type: {
type: String,
default: 'create'
}
},
computed: {
title() {
const map = { create: '创建', update: '更新', details: '查看' }
return map[this.type] + '事务类型'
}
},
data() {
return {
form: {
name: '',
table_name: '',
count: '',
status: '',
level: ''
},
disabled: false
}
},
created() {
if (this.type !== 'create') {
Object.keys(this.form).forEach(key => {
this.form[key] = this.info[key]
})
}
if (this.type === 'details') this.disabled = true
},
methods: {
onSubmit() {}
}
}
</script>
<style scoped>
video{
width:100%;
}
</style>
const routes = [
{
path: '/audit',
component: () => import('@/components/layout/Index.vue'),
children: [
{
path: 'type',
component: () => import('./views/List.vue'),
meta: { title: '事务类型' }
}
]
}
]
export { routes }
<template>
<app-card>
<app-list v-bind="tableOptions" ref="list">
<template>
<el-row style="margin-bottom: 20px">
<el-button type="primary" icon="el-icon-plus" size="mini" @click="onCreate">创建事务类型</el-button>
</el-row>
</template>
<template v-slot:table-time="{ row }">
{{row.start_time + ' - ' + row.end_time}}
</template>
<template v-slot:table-x="{ row }">
<el-button type="primary" size="mini" plain @click="onDetails(row)">查看</el-button>
<el-button type="primary" size="mini" plain @click="onUpdate(row)">更新</el-button>
<el-button type="primary" size="mini" plain @click="onRemove(row)">删除</el-button>
</template>
</app-list>
<dialog-type v-if="isShowDialog" :info="info" :type="dialogType" :visible.sync="isShowDialog" @success="handleSuccess" />
</app-card>
</template>
<script>
import DialogType from '../components/DialogType.vue'
export default {
components: { DialogType },
data() {
return {
isShowDialog: false,
info: null,
dialogType: ''
}
},
computed: {
// 列表配置
tableOptions() {
return {
remote: {
// httpRequest: getCourseList,
params: { course_id: '' }
},
// data: this.detail.staff.list,
data: [
{ id: '111', name: '重考', table_name: 'retake', count: 1, status: 0, level: 1 },
{ id: '222', name: '乐分享', table_name: 'sharing', count: 1, status: 0, level: 1 },
{ id: '333', name: '纸质成绩单', table_name: 'pareport', count: 1, status: 0, level: 1 },
{ id: '444', name: '学术活动', table_name: 'symposium', count: 1, status: 0, level: 1 }
],
columns: [
{ label: 'ID', align: 'center', prop: 'id' },
{ label: '事务名称', align: 'center', prop: 'name' },
{ label: '表单名称', align: 'center', prop: 'table_name' },
{ label: '限制申请次数', align: 'center', prop: 'count' },
{ label: '限制申请学员状态', align: 'center', prop: 'status' },
{ label: '审批层级', align: 'center', prop: 'level' },
{ label: '操作', slots: 'table-x', align: 'center', width: '220', fixed: 'right' }
]
}
}
},
methods: {
onCreate() {
this.isShowDialog = true
this.dialogType = 'create'
},
onDetails(row) {
this.isShowDialog = true
this.dialogType = 'details'
this.info = row
},
onUpdate(row) {
this.isShowDialog = true
this.dialogType = 'update'
this.info = row
},
// 删除
onRemove(row) {
this.$confirm('事务类型删除请谨慎操作,确定删除?', '提示', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.fetchRemove(row)
})
},
handleSuccess() {},
fetchRemove(row) {}
}
}
</script>
import httpRequest from '@/utils/axios'
/**
* 获取控制台数据
*/
export function getDashboardList(params) {
return httpRequest.get('/api/lms-financial/school/console/index', { params })
}
/**
* 获取控制台数据
*/
export function getStudentList(params) {
return httpRequest.get('/api/lms-financial/school/student/list', { params })
}
<template>
<el-dialog :title="info.title" v-bind="$attrs" v-on="$listeners" width="800px" top="20vh">
<video :src="info.url" controls="controls" autoplay="autoplay"></video>
</el-dialog>
</template>
<script>
export default {
props: {
info: {
type: Object,
default() {
return {}
}
}
},
data() {
return {}
}
}
</script>
<style scoped>
video{
width:100%;
}
</style>
\ No newline at end of file
const routes = [
{
path: '/dashboard',
component: () => import('@/components/layout/Index.vue'),
children: [
{
path: '',
component: () => import('./views/List.vue'),
meta: { title: '工作台' }
},
{
path: 'details',
component: () => import('./views/Details.vue'),
meta: { title: '证书信息' }
}
]
}
]
export { routes }
<template>
<div>
<app-card class="base-info" title="证书信息">
<p>还未取得证书</p>
<div style="margin:20px;">
<img src="https://d1icd6shlvmxi6.cloudfront.net/gsc/VZBEW1/80/94/0b/80940b325e974c2bbb1947333cef5a62/images/%E6%88%91%E7%9A%84%E8%AF%81%E4%B9%A6/u195.png?token=a74c08777a71a87475bc07e15ce324e9ba84b5f1ae0b3ee46443d323e17180df" />
</div>
<div style="margin:20px;">
<img src="https://d1icd6shlvmxi6.cloudfront.net/gsc/VZBEW1/80/94/0b/80940b325e974c2bbb1947333cef5a62/images/%E6%88%91%E7%9A%84%E8%AF%81%E4%B9%A6/u193.png?token=292d1b529d8cd3149295c1fd7226df2c2404d5ee5aec9a56467c9a13586806af" />
</div>
</app-card>
</div>
</template>
<script>
import AppStudentSelect from '@/components/base/AppStudentSelect.vue'
import AppClassSelect from '@/components/base/AppClassSelect.vue'
export default {
components: { AppStudentSelect, AppClassSelect },
data() {
return {
}
},
methods: {
}
}
</script>
<style scoped lang="scss">
.app-card{
padding:15px 0 20px;
::v-deep .app-card-hd{
padding:0 20px;
border-bottom:1px solid #ececec;
}
::v-deep .app-card-bd{
padding:20px;
}
}
.base-info{
::v-deep .app-card-bd{
.title{
margin-bottom:20px;
padding-bottom:14px;
border-bottom:1px solid #f0f0f0;
i{
font-size:24px;
color: #3276fc;
}
}
}
}
</style>
\ No newline at end of file
<template>
<div>
<app-card class="class-cont" title="我的班级">
<div class="item" v-for="item in classList" :key="item.id">
<div class="status">
<el-tag v-if="item.status === 1" type="success">Active</el-tag>
<el-tag v-else type="info">Inactive</el-tag>
</div>
<div class="cont">
<h5>{{item.class_name}}</h5>
<p>{{item.created_time}}</p>
</div>
<div class="count">
<el-progress :percentage="item.progress" color="#67c23a"></el-progress>
<span><i class="el-icon-user-solid"></i>{{item.student_count}}</span>
</div>
</div>
</app-card>
<app-card title="数据汇总">
<app-list v-bind="tableOptions" ref="list">
<template v-slot:filter-progress_min="{ params }">
<el-input v-model="params.progress_min" style="width:69px;" onkeyup="this.value = this.value.replace(/[^\d.]/g,'');" />%<p style="display:inline-block;width:10px;"></p>-
</template>
<template v-slot:filter-progress_max="{ params }">
<el-input v-model="params.progress_max" style="width:69px;" onkeyup="this.value = this.value.replace(/[^\d.]/g,'');" />%
</template>
<template v-slot:filter-score="{ params }">
<el-input v-model="params.score_min" style="width:83px;margin-right:10px;" onkeyup="this.value = this.value.replace(/[^\d.]/g,'');" />-
</template>
<template v-slot:filter-score_max="{ params }">
<el-input v-model="params.score_max" style="width:83px;" onkeyup="this.value = this.value.replace(/[^\d.]/g,'');" />
</template>
<template v-slot:filter-cert="{ params }">
<el-select v-model="params.has_certificate" placeholder="请选择">
<el-option label="已获得" value="1" />
<el-option label="未获得" value="0" />
</el-select>
</template>
<template v-slot:table-img="{ row }">
<img :src="row.url" width="120" height="70">
</template>
</app-list>
</app-card>
</div>
</template>
<script>
import AppStudentSelect from '@/components/base/AppStudentSelect.vue'
import AppClassSelect from '@/components/base/AppClassSelect.vue'
import { getDashboardList, getStudentList } from '../api'
export default {
data() {
return {
classList: []
}
},
components: {
AppStudentSelect,
AppClassSelect
},
computed: {
// 列表配置
tableOptions() {
return {
remote: {
httpRequest: getStudentList,
params: { personal_name: '', telephone: '', email: '', class_name: '', study_times: '', progress_min: '', progress_max: '', score_min: '', score_max: '', has_certificate: '' }
},
filters: [
{ type: 'input', label: '学员姓名', placeholder: '学员姓名', prop: 'personal_name' },
{ type: 'input', label: '手机号', placeholder: '手机号', prop: 'telephone' },
{ type: 'input', label: '邮箱', placeholder: '邮箱', prop: 'email' },
{ type: 'input', label: '所属班级', placeholder: '所属班级', prop: 'class_name' },
{ type: 'input', label: '重修次数', placeholder: '重修次数', prop: 'study_times' },
{ label: '当前学习进度', slots: 'filter-progress_min', prop: 'progress_min' },
{ slots: 'filter-progress_max', prop: 'progress_max' },
{ label: '分数', slots: 'filter-score', prop: 'score_min' },
{ slots: 'filter-score_max', prop: 'score_max' },
{ label: '是否取证', slots: 'filter-cert', prop: 'has_certificate' }
],
columns: [
{ label: 'ID', align: 'center', prop: 'id' },
{ label: '姓名', align: 'center', prop: 'personal_name' },
{ label: '所属班级', align: 'center', prop: 'class_name' },
{ label: '电话', align: 'center', prop: 'telephone' },
{ label: '邮箱', align: 'center', prop: 'email' },
{ label: '重修次数', align: 'center', prop: 'study_times' },
{ label: '当前学习进度', align: 'center', prop: 'progress' },
{ label: '考试成绩', align: 'center', prop: 'score' },
{
label: '证书',
align: 'center',
computed({ row }) {
return row.has_certificate ? '已获得' : '未获得'
}
}
]
}
}
},
created() {
this.fetchList()
},
methods: {
keyupNumber() {
},
fetchList() {
getDashboardList().then(res => {
if (res.code === 0) {
this.classList = res.data
}
})
}
}
}
</script>
<style lang="scss" scoped>
.app-card{
padding:15px 0 20px;
::v-deep .app-card-hd{
padding:0 20px;
border-bottom:1px solid #ececec;
}
::v-deep .app-card-bd{
padding:20px;
}
}
::v-deep .table-list-filter{
.filter-buttons{
margin-left:110px;
}
}
.class-cont{
.item{
display: flex;
align-items:center;
padding:8px 0;
.status{
width:20%;
}
.cont{
width:50%;
color:#363636;
p{
color:#666;
font-size:12px;
}
}
.count{
width:30%;
font-size:14px;
color:#777;
display: flex;
align-items:center;
::v-deep .el-progress{
width:80%;
}
}
}
}
</style>
const routes = [
{
path: '/401',
component: () => import('./views/401.vue')
},
{
name: 'browser',
path: '/browser',
component: () => import('./views/browser.vue')
}
]
export { routes }
<template>
<div class="container">
<svg width="251" height="294">
<g fill="none" fill-rule="evenodd">
<path
d="M0 129.023v-2.084C0 58.364 55.591 2.774 124.165 2.774h2.085c68.574 0 124.165 55.59 124.165 124.165v2.084c0 68.575-55.59 124.166-124.165 124.166h-2.085C55.591 253.189 0 197.598 0 129.023"
fill="#E4EBF7"
></path>
<path d="M41.417 132.92a8.231 8.231 0 1 1-16.38-1.65 8.231 8.231 0 0 1 16.38 1.65" fill="#FFF"></path>
<path d="M38.652 136.36l10.425 5.91M49.989 148.505l-12.58 10.73" stroke="#FFF" stroke-width="2"></path>
<path
d="M41.536 161.28a5.636 5.636 0 1 1-11.216-1.13 5.636 5.636 0 0 1 11.216 1.13M59.154 145.261a5.677 5.677 0 1 1-11.297-1.138 5.677 5.677 0 0 1 11.297 1.138M100.36 29.516l29.66-.013a4.562 4.562 0 1 0-.004-9.126l-29.66.013a4.563 4.563 0 0 0 .005 9.126M111.705 47.754l29.659-.013a4.563 4.563 0 1 0-.004-9.126l-29.66.013a4.563 4.563 0 1 0 .005 9.126"
fill="#FFF"
></path>
<path
d="M114.066 29.503V29.5l15.698-.007a4.563 4.563 0 1 0 .004 9.126l-15.698.007v-.002a4.562 4.562 0 0 0-.004-9.122M185.405 137.723c-.55 5.455-5.418 9.432-10.873 8.882-5.456-.55-9.432-5.418-8.882-10.873.55-5.455 5.418-9.432 10.873-8.882 5.455.55 9.432 5.418 8.882 10.873"
fill="#FFF"
></path>
<path d="M180.17 143.772l12.572 7.129M193.841 158.42L178.67 171.36" stroke="#FFF" stroke-width="2"></path>
<path
d="M185.55 171.926a6.798 6.798 0 1 1-13.528-1.363 6.798 6.798 0 0 1 13.527 1.363M204.12 155.285a6.848 6.848 0 1 1-13.627-1.375 6.848 6.848 0 0 1 13.626 1.375"
fill="#FFF"
></path>
<path
d="M152.988 194.074a2.21 2.21 0 1 1-4.42 0 2.21 2.21 0 0 1 4.42 0zM225.931 118.217a2.21 2.21 0 1 1-4.421 0 2.21 2.21 0 0 1 4.421 0zM217.09 153.051a2.21 2.21 0 1 1-4.421 0 2.21 2.21 0 0 1 4.42 0zM177.84 109.842a2.21 2.21 0 1 1-4.422 0 2.21 2.21 0 0 1 4.421 0zM196.114 94.454a2.21 2.21 0 1 1-4.421 0 2.21 2.21 0 0 1 4.421 0zM202.844 182.523a2.21 2.21 0 1 1-4.42 0 2.21 2.21 0 0 1 4.42 0z"
stroke="#FFF"
stroke-width="2"
></path>
<path
stroke="#FFF"
stroke-width="2"
d="M215.125 155.262l-1.902 20.075-10.87 5.958M174.601 176.636l-6.322 9.761H156.98l-4.484 6.449M175.874 127.28V111.56M221.51 119.404l-12.77 7.859-15.228-7.86V96.668"
></path>
<path
d="M180.68 29.32C180.68 13.128 193.806 0 210 0c16.193 0 29.32 13.127 29.32 29.32 0 16.194-13.127 29.322-29.32 29.322-16.193 0-29.32-13.128-29.32-29.321"
fill="#A26EF4"
></path>
<path
d="M221.45 41.706l-21.563-.125a1.744 1.744 0 0 1-1.734-1.754l.071-12.23a1.744 1.744 0 0 1 1.754-1.734l21.562.125c.964.006 1.74.791 1.735 1.755l-.071 12.229a1.744 1.744 0 0 1-1.754 1.734"
fill="#FFF"
></path>
<path
d="M215.106 29.192c-.015 2.577-2.049 4.654-4.543 4.64-2.494-.014-4.504-2.115-4.489-4.693l.04-6.925c.016-2.577 2.05-4.654 4.543-4.64 2.494.015 4.504 2.116 4.49 4.693l-.04 6.925zm-4.53-14.074a6.877 6.877 0 0 0-6.916 6.837l-.043 7.368a6.877 6.877 0 0 0 13.754.08l.042-7.368a6.878 6.878 0 0 0-6.837-6.917zM167.566 68.367h-3.93a4.73 4.73 0 0 1-4.717-4.717 4.73 4.73 0 0 1 4.717-4.717h3.93a4.73 4.73 0 0 1 4.717 4.717 4.73 4.73 0 0 1-4.717 4.717"
fill="#FFF"
></path>
<path
d="M168.214 248.838a6.611 6.611 0 0 1-6.61-6.611v-66.108a6.611 6.611 0 0 1 13.221 0v66.108a6.611 6.611 0 0 1-6.61 6.61"
fill="#5BA02E"
></path>
<path
d="M176.147 248.176a6.611 6.611 0 0 1-6.61-6.61v-33.054a6.611 6.611 0 1 1 13.221 0v33.053a6.611 6.611 0 0 1-6.61 6.611"
fill="#92C110"
></path>
<path
d="M185.994 293.89h-27.376a3.17 3.17 0 0 1-3.17-3.17v-45.887a3.17 3.17 0 0 1 3.17-3.17h27.376a3.17 3.17 0 0 1 3.17 3.17v45.886a3.17 3.17 0 0 1-3.17 3.17"
fill="#F2D7AD"
></path>
<path
d="M81.972 147.673s6.377-.927 17.566-1.28c11.729-.371 17.57 1.086 17.57 1.086s3.697-3.855.968-8.424c1.278-12.077 5.982-32.827.335-48.273-1.116-1.339-3.743-1.512-7.536-.62-1.337.315-7.147-.149-7.983-.1l-15.311-.347s-3.487-.17-8.035-.508c-1.512-.113-4.227-1.683-5.458-.338-.406.443-2.425 5.669-1.97 16.077l8.635 35.642s-3.141 3.61 1.219 7.085"
fill="#FFF"
></path>
<path
d="M75.768 73.325l-.9-6.397 11.982-6.52s7.302-.118 8.038 1.205c.737 1.324-5.616.993-5.616.993s-1.836 1.388-2.615 2.5c-1.654 2.363-.986 6.471-8.318 5.986-1.708.284-2.57 2.233-2.57 2.233"
fill="#FFC6A0"
></path>
<path
d="M52.44 77.672s14.217 9.406 24.973 14.444c1.061.497-2.094 16.183-11.892 11.811-7.436-3.318-20.162-8.44-21.482-14.496-.71-3.258 2.543-7.643 8.401-11.76M141.862 80.113s-6.693 2.999-13.844 6.876c-3.894 2.11-10.137 4.704-12.33 7.988-6.224 9.314 3.536 11.22 12.947 7.503 6.71-2.651 28.999-12.127 13.227-22.367"
fill="#FFB594"
></path>
<path
d="M76.166 66.36l3.06 3.881s-2.783 2.67-6.31 5.747c-7.103 6.195-12.803 14.296-15.995 16.44-3.966 2.662-9.754 3.314-12.177-.118-3.553-5.032.464-14.628 31.422-25.95"
fill="#FFC6A0"
></path>
<path
d="M64.674 85.116s-2.34 8.413-8.912 14.447c.652.548 18.586 10.51 22.144 10.056 5.238-.669 6.417-18.968 1.145-20.531-.702-.208-5.901-1.286-8.853-2.167-.87-.26-1.611-1.71-3.545-.936l-1.98-.869zM128.362 85.826s5.318 1.956 7.325 13.734c-.546.274-17.55 12.35-21.829 7.805-6.534-6.94-.766-17.393 4.275-18.61 4.646-1.121 5.03-1.37 10.23-2.929"
fill="#FFF"
></path>
<path
d="M78.18 94.656s.911 7.41-4.914 13.078"
stroke="#E4EBF7"
stroke-width="1.051"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M87.397 94.68s3.124 2.572 10.263 2.572c7.14 0 9.074-3.437 9.074-3.437"
stroke="#E4EBF7"
stroke-width=".932"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M117.184 68.639l-6.781-6.177s-5.355-4.314-9.223-.893c-3.867 3.422 4.463 2.083 5.653 4.165 1.19 2.082.848 1.143-2.083.446-5.603-1.331-2.082.893 2.975 5.355 2.091 1.845 6.992.955 6.992.955l2.467-3.851z"
fill="#FFC6A0"
></path>
<path
d="M105.282 91.315l-.297-10.937-15.918-.027-.53 10.45c-.026.403.17.788.515.999 2.049 1.251 9.387 5.093 15.799.424.287-.21.443-.554.431-.91"
fill="#FFB594"
></path>
<path
d="M107.573 74.24c.817-1.147.982-9.118 1.015-11.928a1.046 1.046 0 0 0-.965-1.055l-4.62-.365c-7.71-1.044-17.071.624-18.253 6.346-5.482 5.813-.421 13.244-.421 13.244s1.963 3.566 4.305 6.791c.756 1.041.398-3.731 3.04-5.929 5.524-4.594 15.899-7.103 15.899-7.103"
fill="#5C2552"
></path>
<path
d="M88.426 83.206s2.685 6.202 11.602 6.522c7.82.28 8.973-7.008 7.434-17.505l-.909-5.483c-6.118-2.897-15.478.54-15.478.54s-.576 2.044-.19 5.504c-2.276 2.066-1.824 5.618-1.824 5.618s-.905-1.922-1.98-2.321c-.86-.32-1.897.089-2.322 1.98-1.04 4.632 3.667 5.145 3.667 5.145"
fill="#FFC6A0"
></path>
<path
stroke="#DB836E"
stroke-width="1.145"
stroke-linecap="round"
stroke-linejoin="round"
d="M100.843 77.099l1.701-.928-1.015-4.324.674-1.406"
></path>
<path
d="M105.546 74.092c-.022.713-.452 1.279-.96 1.263-.51-.016-.904-.607-.882-1.32.021-.713.452-1.278.96-1.263.51.016.904.607.882 1.32M97.592 74.349c-.022.713-.452 1.278-.961 1.263-.509-.016-.904-.607-.882-1.32.022-.713.452-1.279.961-1.263.51.016.904.606.882 1.32"
fill="#552950"
></path>
<path
d="M91.132 86.786s5.269 4.957 12.679 2.327"
stroke="#DB836E"
stroke-width="1.145"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M99.776 81.903s-3.592.232-1.44-2.79c1.59-1.496 4.897-.46 4.897-.46s1.156 3.906-3.457 3.25"
fill="#DB836E"
></path>
<path
d="M102.88 70.6s2.483.84 3.402.715M93.883 71.975s2.492-1.144 4.778-1.073"
stroke="#5C2552"
stroke-width="1.526"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M86.32 77.374s.961.879 1.458 2.106c-.377.48-1.033 1.152-.236 1.809M99.337 83.719s1.911.151 2.509-.254"
stroke="#DB836E"
stroke-width="1.145"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M87.782 115.821l15.73-3.012M100.165 115.821l10.04-2.008"
stroke="#E4EBF7"
stroke-width="1.051"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M66.508 86.763s-1.598 8.83-6.697 14.078"
stroke="#E4EBF7"
stroke-width="1.114"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M128.31 87.934s3.013 4.121 4.06 11.785"
stroke="#E4EBF7"
stroke-width="1.051"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M64.09 84.816s-6.03 9.912-13.607 9.903"
stroke="#DB836E"
stroke-width=".795"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M112.366 65.909l-.142 5.32s5.993 4.472 11.945 9.202c4.482 3.562 8.888 7.455 10.985 8.662 4.804 2.766 8.9 3.355 11.076 1.808 4.071-2.894 4.373-9.878-8.136-15.263-4.271-1.838-16.144-6.36-25.728-9.73"
fill="#FFC6A0"
></path>
<path
d="M130.532 85.488s4.588 5.757 11.619 6.214"
stroke="#DB836E"
stroke-width=".75"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M121.708 105.73s-.393 8.564-1.34 13.612"
stroke="#E4EBF7"
stroke-width="1.051"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M115.784 161.512s-3.57-1.488-2.678-7.14"
stroke="#648BD8"
stroke-width="1.051"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M101.52 290.246s4.326 2.057 7.408 1.03c2.842-.948 4.564.673 7.132 1.186 2.57.514 6.925 1.108 11.772-1.269-.104-5.551-6.939-4.01-12.048-6.763-2.582-1.39-3.812-4.757-3.625-8.863h-9.471s-1.402 10.596-1.169 14.68"
fill="#CBD1D1"
></path>
<path
d="M101.496 290.073s2.447 1.281 6.809.658c3.081-.44 3.74.485 7.479 1.039 3.739.554 10.802-.07 11.91-.9.415 1.108-.347 2.077-.347 2.077s-1.523.608-4.847.831c-2.045.137-5.843.293-7.663-.507-1.8-1.385-5.286-1.917-5.77-.243-3.947.958-7.41-.288-7.41-.288l-.16-2.667z"
fill="#2B0849"
></path>
<path d="M108.824 276.19h3.116s-.103 6.751 4.57 8.62c-4.673.624-8.62-2.32-7.686-8.62" fill="#A4AABA"></path>
<path
d="M57.65 272.52s-2.122 7.47-4.518 12.396c-1.811 3.724-4.255 7.548 5.505 7.548 6.698 0 9.02-.483 7.479-6.648-1.541-6.164.268-13.296.268-13.296H57.65z"
fill="#CBD1D1"
></path>
<path
d="M51.54 290.04s2.111 1.178 6.682 1.178c6.128 0 8.31-1.662 8.31-1.662s.605 1.122-.624 2.18c-1 .862-3.624 1.603-7.444 1.559-4.177-.049-5.876-.57-6.786-1.177-.831-.554-.692-1.593-.138-2.078"
fill="#2B0849"
></path>
<path
d="M58.533 274.438s.034 1.529-.315 2.95c-.352 1.431-1.087 3.127-1.139 4.17-.058 1.16 4.57 1.592 5.194.035.623-1.559 1.303-6.475 1.927-7.306.622-.831-4.94-2.135-5.667.15"
fill="#A4AABA"
></path>
<path
d="M100.885 277.015l13.306.092s1.291-54.228 1.843-64.056c.552-9.828 3.756-43.13.997-62.788l-12.48-.64-22.725.776s-.433 3.944-1.19 9.921c-.062.493-.677.838-.744 1.358-.075.582.42 1.347.318 1.956-2.35 14.003-6.343 32.926-8.697 46.425-.116.663-1.227 1.004-1.45 2.677-.04.3.21 1.516.112 1.785-6.836 18.643-10.89 47.584-14.2 61.551l14.528-.014s2.185-8.524 4.008-16.878c2.796-12.817 22.987-84.553 22.987-84.553l3-.517 1.037 46.1s-.223 1.228.334 2.008c.558.782-.556 1.117-.39 2.233l.39 1.784s-.446 7.14-.892 11.826c-.446 4.685-.092 38.954-.092 38.954"
fill="#7BB2F9"
></path>
<path
d="M77.438 220.434c1.146.094 4.016-2.008 6.916-4.91M107.55 223.931s2.758-1.103 6.069-3.862"
stroke="#648BD8"
stroke-width="1.051"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M108.459 220.905s2.759-1.104 6.07-3.863"
stroke="#648BD8"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M76.099 223.557s2.608-.587 6.47-3.346M87.33 150.82c-.27 3.088.297 8.478-4.315 9.073M104.829 149.075s.11 13.936-1.286 14.983c-2.207 1.655-2.975 1.934-2.975 1.934M101.014 149.63s.035 12.81-1.19 24.245M94.93 174.965s7.174-1.655 9.38-1.655M75.671 204.754c-.316 1.55-.64 3.067-.973 4.535 0 0-1.45 1.822-1.003 3.756.446 1.934-.943 2.034-4.96 15.273-1.686 5.559-4.464 18.49-6.313 27.447-.078.38-4.018 18.06-4.093 18.423M77.043 196.743a313.269 313.269 0 0 1-.877 4.729M83.908 151.414l-1.19 10.413s-1.091.148-.496 2.23c.111 1.34-2.66 15.692-5.153 30.267M57.58 272.94h13.238"
stroke="#648BD8"
stroke-width="1.051"
stroke-linecap="round"
stroke-linejoin="round"
></path>
<path
d="M117.377 147.423s-16.955-3.087-35.7.199c.157 2.501-.002 4.128-.002 4.128s14.607-2.802 35.476-.31c.251-2.342.226-4.017.226-4.017"
fill="#192064"
></path>
<path
d="M107.511 150.353l.004-4.885a.807.807 0 0 0-.774-.81c-2.428-.092-5.04-.108-7.795-.014a.814.814 0 0 0-.784.81l-.003 4.88c0 .456.371.82.827.808a140.76 140.76 0 0 1 7.688.017.81.81 0 0 0 .837-.806"
fill="#FFF"
></path>
<path
d="M106.402 149.426l.002-3.06a.64.64 0 0 0-.616-.643 94.135 94.135 0 0 0-5.834-.009.647.647 0 0 0-.626.643l-.001 3.056c0 .36.291.648.651.64 1.78-.04 3.708-.041 5.762.012.36.009.662-.279.662-.64"
fill="#192064"
></path>
<path
d="M101.485 273.933h12.272M102.652 269.075c.006 3.368.04 5.759.11 6.47M102.667 263.125c-.009 1.53-.015 2.98-.016 4.313M102.204 174.024l.893 44.402s.669 1.561-.224 2.677c-.892 1.116 2.455.67.893 2.231-1.562 1.562.893 1.116 0 3.347-.592 1.48-.988 20.987-1.09 34.956"
stroke="#648BD8"
stroke-width="1.051"
stroke-linecap="round"
stroke-linejoin="round"
></path>
</g>
</svg>
<p class="tips">很抱歉,你暂时无权限访问...</p>
<a :href="loginUrl"><el-button round type="primary">重新登录</el-button></a>
</div>
</template>
<script>
export default {
computed: {
loginUrl() {
return `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(window.location.origin)}`
}
}
}
</script>
<style lang="scss" scoped>
.container {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.tips {
margin: 30px;
font-size: 24px;
color: #313131;
}
</style>
<template>
<div class="container">
<div class="box">
<h1 class="tips">你的浏览器版本较低,请升级你的浏览器。</h1>
<div class="list">
<ul>
<li v-for="item in browserList" :key="item.name">
<a :href="item.href" target="_blank">
<img :src="item.logoUrl" />
<p class="t1">{{ item.name }}</p>
<p class="t2">{{ item.company }}</p>
</a>
</li>
</ul>
</div>
</div>
<p class="ua">{{ UA }}</p>
</div>
</template>
<script>
export default {
data() {
const UA = window.navigator.userAgent
const isIe = window.ActiveXObject || 'ActiveXObject' in window
return {
UA,
isIe,
browserList: [
{
name: 'Chrome',
company: 'Google',
href: 'https://www.google.cn/chrome',
logoUrl: 'https://webapp-pub.ezijing.com/website/base/images/chrome.png'
},
{
name: 'Edge',
company: 'Microsoft',
href: 'https://www.microsoft.com/edge',
logoUrl: 'https://webapp-pub.ezijing.com/website/base/images/edge.png'
},
{
name: 'Firefox',
company: 'Mozilla Foundation',
href: 'https://download.mozilla.org',
logoUrl: 'https://webapp-pub.ezijing.com/website/base/images/firefox.png'
}
]
}
},
beforeMount() {
!this.isIe && this.$router.replace('/')
}
}
</script>
<style lang="scss" scoped>
.container {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.box {
width: 700px;
background-color: #f4f5f7;
border-radius: 10px;
}
.tips {
margin: 30px;
font-size: 24px;
color: #172b4d;
text-align: center;
}
ul {
margin: 0;
padding: 0;
display: flex;
list-style: none;
li {
flex: 1;
padding: 40px;
text-align: center;
}
img {
width: 100px;
}
p {
margin: 0;
text-align: center;
}
.t1 {
color: #e25600;
}
.t2 {
color: #aaa;
text-decoration: none;
}
a {
text-decoration: none;
}
}
.ua {
font-size: 12px;
margin: 20px;
}
</style>
const modules = Object.values(import.meta.globEager('./**/index.js'))
export default function ({ router }) {
modules.forEach(({ routes }) => {
// 注册路由
routes.forEach(route => {
router.addRoute(route)
})
})
}
import httpRequest from '@/utils/axios'
/**
* 获取课程列表
*/
export function getCertList(params) {
return httpRequest.get('/api/lms-financial/school/certificate/list', { params })
}
/**
* 获取课程列表
*/
export function getCertDetails(params) {
return httpRequest.get('/api/lms-financial/school/certificate/view', { params })
}
\ No newline at end of file
<template>
<el-dialog :title="info.title" v-bind="$attrs" v-on="$listeners" width="800px" top="20vh">
<video :src="info.url" controls="controls" autoplay="autoplay"></video>
</el-dialog>
</template>
<script>
export default {
props: {
info: {
type: Object,
default() {
return {}
}
}
},
data() {
return {}
}
}
</script>
<style scoped>
video{
width:100%;
}
</style>
\ No newline at end of file
const routes = [
{
path: '/school',
component: () => import('@/components/layout/Index.vue'),
children: [
{
path: 'cert',
component: () => import('./views/List.vue'),
meta: { title: '证书管理' }
},
{
path: 'cert/details',
component: () => import('./views/Details.vue'),
meta: { title: '证书信息' }
}
]
}
]
export { routes }
<template>
<div>
<app-card class="base-info" title="证书信息">
<p v-if="list.length === 0">还未取得证书</p>
<template v-else>
<div style="margin:20px;" v-for="item in list" :key="item.id">
<img :src="item.url" />
</div>
</template>
</app-card>
</div>
</template>
<script>
import AppStudentSelect from '@/components/base/AppStudentSelect.vue'
import AppClassSelect from '@/components/base/AppClassSelect.vue'
import { getCertDetails } from '../api'
export default {
components: { AppStudentSelect, AppClassSelect },
data() {
return {
list: []
}
},
created() {
this.fetchDetails()
},
methods: {
fetchDetails() {
getCertDetails({student_id: this.$route.query.id }).then(res => {
if (res.code === 0) {
this.list = res.data
}
})
}
}
}
</script>
<style scoped lang="scss">
.app-card{
padding:15px 0 20px;
::v-deep .app-card-hd{
padding:0 20px;
border-bottom:1px solid #ececec;
}
::v-deep .app-card-bd{
padding:20px;
}
}
.base-info{
::v-deep .app-card-bd{
.title{
margin-bottom:20px;
padding-bottom:14px;
border-bottom:1px solid #f0f0f0;
i{
font-size:24px;
color: #3276fc;
}
}
}
}
</style>
\ No newline at end of file
<template>
<app-card>
<app-list v-bind="tableOptions" ref="list">
<!-- <template v-slot:filter-student="{ params }">
<app-student-select v-model="params.personal_name" placeholder="请选择学员" />
</template> -->
<template v-slot:filter-class="{ params }">
<app-class-select v-model="params.class_id" placeholder="所属班级" />
</template>
<template v-slot:filter-cert="{ params }">
<el-select v-model="params.has_certificate" placeholder="是否取证">
<el-option label="已获得" :value="true" />
<el-option label="未获得" :value="false" />
</el-select>
</template>
<template v-slot:table-img="{ row }">
<img :src="row.url" width="120" height="70">
</template>
<template v-slot:table-x="{ row }">
<router-link :to="{ path: '/school/cert/details', query: { id: row.student_id } }">
<el-button type="primary" size="mini" plain>查看</el-button>
</router-link>
</template>
</app-list>
</app-card>
</template>
<script>
import AppStudentSelect from '@/components/base/AppStudentSelect.vue'
import AppClassSelect from '@/components/base/AppClassSelect.vue'
import { getCertList } from '../api'
export default {
data() {
return {
isShowDialog: false,
info: {}
}
},
components: {
AppStudentSelect,
AppClassSelect
},
computed: {
// 列表配置
tableOptions() {
return {
remote: {
httpRequest: getCertList,
params: { personal_name: '', class_id: '', has_certificate: '' }
},
filters: [
{ type: 'input', placeholder: '姓名', prop: 'personal_name' },
{ slots: 'filter-class' },
{ slots: 'filter-cert' }
],
// data: this.detail.staff.list,
// data: [
// { name: '张三', class: '广发银行金融班', has_cert: '1', time: '2022-01-03 10:30:00' },
// { name: '李四', class: '工商银行金融1班', has_cert: '0', time: '2022-01-03 10:30:00' },
// { name: '王五', class: '北京银行金融班', has_cert: '0', time: '2022-01-03 10:30:00' },
// { name: '赵六', class: '平安银行金融班', has_cert: '1', time: '2022-01-03 10:30:00' }
// ],
columns: [
{ label: '姓名', align: 'center', prop: 'personal_name' },
{ label: '所属班级', align: 'center', prop: 'class_name' },
{
label: '是否取证',
align: 'center',
computed({ row }) {
return row.has_certificate ? '已获得' : '未获得'
}
},
{ label: '取证时间', align: 'center', prop: 'time' },
{ label: '操作', slots: 'table-x', align: 'center', width: '220', fixed: 'right' }
]
}
}
},
methods: {
handleAdd() {
this.isShowDialog = true
this.info = null
},
onDetails(row) {
console.log(row)
},
onEdit(row) {
this.isShowDialog = true
this.info = row
},
// 删除课程
onRemove(row) {
this.$confirm('你确定要删除此项吗', '确认提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 调用删除接口
this.delTeach(row)
})
},
delTeach(row) {
}
}
}
</script>
import httpRequest from '@/utils/axios'
/**
* 获取班级列表
*/
export function getClassList(params) {
return httpRequest.get('/api/lms-financial/school/class/list', { params })
}
/**
* 获取班级详情
*/
export function getClassDetail(params) {
return httpRequest.get('/api/lms-financial/school/class/view', { params })
}
/**
* 创建班级
*/
export function createClass(data) {
return httpRequest.post('/api/lms-financial/school/class/add', data)
}
/**
* 班级更新
*/
export function updateClass(data) {
return httpRequest.post('/api/lms-financial/school/class/update', data)
}
/**
* 删除班级
*/
export function deleteClass(data) {
return httpRequest.post('/api/lms-financial/school/class/delete', data)
}
/**
* 获取添加课程列表
*/
export function getAddCourseList(params) {
return httpRequest.get('/api/lms-financial/school/class/add-course', { params })
}
/**
* 添加(删除)课程
*/
export function addCourse(data) {
return httpRequest.post('/api/lms-financial/school/class/add-course', data)
}
/**
* 添加(删除)课程
*/
export function updateCourse(data) {
return httpRequest.post('/api/lms-financial/school/class/update-course', data)
}
/**
* 获取添加教师列表
*/
export function addTeacher(params) {
return httpRequest.get('/api/lms-financial/school/class/add-teacher', { params })
}
/**
* 添加教师
*/
export function addTeachers(data) {
return httpRequest.post('/api/lms-financial/school/class/add-teacher', data)
}
/**
* 获取添加学员列表
*/
export function addStudent(params) {
return httpRequest.get('/api/lms-financial/school/class/add-student', { params })
}
/**
* 添加学员
*/
export function addStudents(data) {
return httpRequest.post('/api/lms-financial/school/class/add-student', data)
}
/**
* 学员导入
*/
export function importStudents(data) {
return httpRequest({
url: '/api/lms-financial/school/class/import-student',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 900000,
data,
withCredentials: true
})
}
<template>
<el-dialog v-bind="$attrs" v-on="$listeners" width="400px" top="30vh">
<el-form ref="formRef" :model="form" :rules="formRules" label-width="100px">
<el-form-item v-if="!info" label="课程" prop="courses_id">
<el-select v-model="form.courses_id" multiple placeholder="请选择课程" style="width:220px;">
<el-option v-for="item in list" :key="item.index" :value="item.id" :label="item.course_name"></el-option>
</el-select>
</el-form-item>
<el-form-item label="开课时间" prop="start_time">
<el-date-picker
v-model="form.start_time"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择开课时间">
</el-date-picker>
</el-form-item>
<el-form-item label="结课时间" prop="end_time">
<el-date-picker
v-model="form.end_time"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择结课时间">
</el-date-picker>
</el-form-item>
<el-form-item label="权重" prop="weight">
<el-input-number v-model="form.weight" placeholder="请输入内容" size="small" :step-strictly="true" />
</el-form-item>
<el-form-item>
<el-button @click="$emit('update:visible', false)" style="margin-left: 50px">取消</el-button>
<el-button type="primary" style="margin-left: 20px" @click="submit">提交</el-button>
</el-form-item>
</el-form>
</el-dialog>
</template>
<script>
import AppCourseSelect from '@/components/base/AppCourseSelect.vue'
import { getAddCourseList, addCourse, updateCourse } from '../api'
export default {
components: { AppCourseSelect },
props: {
info: {
type: Object,
default() {
return {}
}
}
},
data() {
const startTimeChecked = (rule, value, callback) => {
if (value && this.form.end_time) {
const start = new Date(value).getTime()
const end = new Date(this.form.end_time)
if (end <= start) {
callback(new Error('开始时间不能晚于结束时间'))
} else {
this.$refs.formRef.clearValidate(['end_time'])
callback()
}
}
}
const endTimeChecked = (rule, value, callback) => {
if (value && this.form.start_time) {
const end = new Date(value).getTime()
const start = new Date(this.form.start_time)
if (end <= start) {
callback(new Error('结束时间不能早于开始时间'))
} else {
this.$refs.formRef.clearValidate(['start_time'])
callback()
}
}
}
return {
list: [],
form: {
courses_id: [],
start_time: '',
end_time: '',
weight: 0
},
formRules: {
courses_id: { required: true, message: '请选择课程', trigger: 'change' },
start_time: [
{ required: true, message: '请选择开始时间', trigger: 'change' },
{ validator: startTimeChecked, trigger: 'change' }
],
end_time: [
{ required: true, message: '请选择结束时间', trigger: 'change' },
{ validator: endTimeChecked, trigger: 'change' }
],
weight: { required: true, message: '请输入权重', trigger: 'change' }
}
}
},
mounted() {
if (this.info) {
this.form.course_id = this.info.id
this.form.start_time = this.info.start_time
this.form.end_time = this.info.end_time
this.form.weight = this.info.weight
} else {
this.fetchCourseList()
}
},
methods: {
handleChange(val) {
console.log(val)
},
// 提交
submit() {
this.$refs.formRef.validate((valid) => {
if (valid) {
if (!this.info) this.fetchAddCourse()
else this.fetchUpdateCourse()
}
})
},
fetchAddCourse() {
const params = Object.assign({ id: this.$route.query.id, type: 'add' }, this.form)
params.courses_id = this.form.courses_id.join()
addCourse(params).then(res => {
if (res.code === 0) {
this.$message.success('添加成功')
this.$emit('success')
}
})
},
fetchUpdateCourse() {
const params = Object.assign({ class_id: this.$route.query.id }, this.form)
delete params.courses_id
updateCourse(params).then(res => {
if (res.code === 0) {
this.$message.success('更新成功')
this.$emit('success')
}
})
},
// 获取课程列表
fetchCourseList() {
// 调用接口
const params = { id: this.$route.query.id, personal_name: this.form.personal_name }
getAddCourseList(params).then(res => {
this.list = res.data
})
}
}
}
</script>
<template>
<el-dialog v-bind="$attrs" v-on="$listeners" width="25%" top="40vh">
<el-form ref="formRef" :model="form">
<el-form-item label="学员" prop="personal_name">
<el-select v-model="form.students_id" multiple placeholder="请选择学员">
<el-option v-for="item in detail" :key="item.index" :value="item.id" :label="item.personal_name"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="$emit('update:visible', false)" style="margin-left: 50px">取消</el-button>
<el-button type="primary" style="margin-left: 20px" @click="submit">提交</el-button>
</el-form-item>
</el-form>
</el-dialog>
</template>
<script>
import { addStudent, addStudents } from '../api'
export default {
data() {
return {
detail: [],
form: {
students_id: '',
personal_name: ''
}
}
},
mounted() {
this.getStudent()
},
methods: {
// 获取添加学生列表
getStudent() {
// 调用接口
addStudent({ id: this.$route.query.id, personal_name: this.form.personal_name }).then(res => {
this.detail = res.data
})
},
// 提交
submit() {
const params = {
id: this.$route.query.id,
students_id: this.form.students_id.join(','),
type: 'add'
}
addStudents(params).then(res => {
this.$emit('update:visible', false)
this.$emit('success')
})
}
}
}
</script>
<template>
<el-dialog v-bind="$attrs" v-on="$listeners" width="25%" top="40vh">
<el-form ref="formRef" :model="form">
<el-form-item label="教师" prop="personal_name">
<el-select v-model="form.staffs_id" multiple placeholder="请选择教师">
<el-option v-for="item in detail" :key="item.index" :value="item.id" :label="item.personal_name"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="$emit('update:visible', false)" style="margin-left: 50px">取消</el-button>
<el-button type="primary" style="margin-left: 20px" @click="submit">提交</el-button>
</el-form-item>
</el-form>
</el-dialog>
</template>
<script>
import { addTeacher, addTeachers } from '../api'
export default {
data() {
return {
detail: [],
form: {
staffs_id: '',
personal_name: ''
}
}
},
created() {
this.getTeacher()
},
methods: {
// 获取添加教师列表
getTeacher() {
// 调用接口
addTeacher({ id: this.$route.query.id, personal_name: this.form.personal_name }).then(res => {
this.detail = res.data
})
},
// 提交
submit() {
const params = {
id: this.$route.query.id,
staffs_id: this.form.staffs_id.join(','),
type: 'add'
}
addTeachers(params).then(res => {
this.$emit('update:visible', false)
this.$emit('success')
})
}
}
}
</script>
<template>
<el-form :model="form" label-width="120px">
<el-form-item label="班级名称" prop="class_name">
<el-input v-model="form.class_name" disabled />
</el-form-item>
<el-form-item label="班级编号" prop="class_number">
<el-input v-model="form.class_number" disabled />
</el-form-item>
<el-form-item label="班级描述" prop="class_describe">
<el-input type="textarea" v-model="form.class_describe" disabled />
</el-form-item>
<el-form-item label="创建日期" prop="created_time">
<el-input v-model="form.created_time" disabled />
</el-form-item>
<el-form-item label="更新日期" prop="updeted_time">
<el-input v-model="form.updated_time" disabled />
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
form: {
class_name: '',
class_number: '',
class_describe: '',
created_time: '',
updated_time: ''
}
}
},
props: {
detail: {
type: Object
}
},
watch: {
detail(val) {
this.form.class_name = val.class_name
this.form.class_number = val.class_number
this.form.class_describe = val.class_describe
this.form.created_time = val.created_time
this.form.updated_time = val.updated_time
}
}
}
</script>
<style>
.my-label {
width: 200px;
}
</style>
<template>
<app-list v-bind="tableOptions" ref="list">
<template #header-aside>
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">添加</el-button>
</template>
<template v-slot:table-img="{ row }">
<img v-if="row.curriculum" :src="row.curriculum.course_picture" width="120" height="70">
</template>
<template v-slot:table-x="{ row }">
<router-link :to="{ path: '/school/course/details', query: { id: row.id } }">
<el-button type="primary" size="mini" plain style="margin-right: 10px">查看</el-button>
</router-link>
<el-button @click="onEdit(row)" type="primary" size="mini" plain>更新</el-button>
<el-button @click="onRemove(row)" type="danger" size="mini" plain>删除</el-button>
</template>
<AddCourse v-if="isShowDialog" :info="info" :title="info ? '更新课程':'添加课程'" :visible.sync="isShowDialog" @success="success" />
</app-list>
</template>
<script>
import { addCourse } from '../api'
import AddCourse from './AddCourse.vue'
export default {
data() {
return {
isShowDialog: false,
info: {}
}
},
components: {
AddCourse
},
props: {
detail: {
type: Object
},
id: {
type: String
}
},
computed: {
// 列表配置
tableOptions() {
return {
data: this.detail.course.list,
// data: [
// { id: '111', url: 'https://webapp-pub.ezijing.com/www/h5/images/college-i1.png', name: '资产配置原理', start_time: '2022-01-01', end_time: '2022-12-31' },
// { id: '222', url: 'https://webapp-pub.ezijing.com/www/h5/images/college-i2.png', name: '金融市场与实践', start_time: '2022-01-01', end_time: '2022-12-31' },
// { id: '333', url: 'https://webapp-pub.ezijing.com/www/h5/images/college-i3.png', name: '家族财富传承', start_time: '2022-01-01', end_time: '2022-12-31' }
// ],
columns: [
{ label: '课程图片', align: 'center', slots: 'table-img' },
{ label: '课程名称', align: 'center', prop: 'course_name' },
{ label: '开课时间', align: 'center', prop: 'start_time' },
{ label: '结课时间', align: 'center', prop: 'end_time' },
{ label: '操作', slots: 'table-x', align: 'center', width: '220', fixed: 'right' }
]
}
}
},
created() {
},
methods: {
success() {
this.$parent.$parent.getDetail()
this.isShowDialog = false
},
handleAdd() {
this.isShowDialog = true
this.info = null
},
onDetails(row) {
console.log(row)
},
onEdit(row) {
this.isShowDialog = true
this.info = row
},
// 删除课程
onRemove(row) {
this.$confirm('课程删除请谨慎操作,确定删除?', '删除课程', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 调用删除接口
this.delTeach(row)
})
},
delTeach(row) {
const params = {
id: this.$route.query.id,
courses_id: row.id,
type: 'delete'
}
addCourse(params).then(res => {
if (res.code === 0) {
this.$parent.$parent.getDetail()
this.$message.success('删除成功')
}
})
}
}
}
</script>
<template>
<!-- 学员 -->
<app-list v-bind="tableOptions" ref="list">
<template #header-aside>
<el-button type="primary" icon="el-icon-plus" @click="addStudent">添加</el-button>
<el-button type="primary" icon="el-icon-upload2" style="margin-left: 20px" @click="importStudent">导入</el-button>
</template>
<template v-slot:table-x="{ row }">
<el-button @click="onRemove(row)" type="danger" size="mini" plain>删除</el-button>
</template>
<AddStudent :visible.sync="isShowDialog" @success="success" />
</app-list>
</template>
<script>
import { addStudents } from '../api'
import AddStudent from './AddStudent.vue'
export default {
data() {
return {
isShowDialog: false
}
},
components: {
AddStudent
},
props: {
id: {
type: String
},
detail: {
type: Object
}
},
computed: {
// 列表配置
tableOptions() {
return {
data: this.detail.student.list,
columns: [
{ label: '姓名', align: 'center', prop: 'personal_name' },
{ label: '学号', align: 'center', prop: 'sno' },
{ label: '手机号', align: 'center', prop: 'telephone' },
{ label: '邮箱', align: 'center', prop: 'email' },
{ label: '操作', slots: 'table-x', align: 'center', width: '120', fixed: 'right' }
]
}
}
},
methods: {
success() {
this.$parent.$parent.getDetail()
},
addStudent() {
this.isShowDialog = true
},
importStudent() {
this.$router.push({
name: 'ImportStudent',
query: {
id: this.id
}
})
},
// 删除
onRemove(row) {
this.$confirm('班级删除请谨慎操作,确定删除?', '删除班级', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.handleRemove(row)
})
},
// 删除
handleRemove(row) {
const params = {
id: this.id,
students_id: row.id,
type: 'delete'
}
// 删除学员
addStudents(params).then(res => {
if (res.code === 0) {
this.$parent.$parent.getDetail()
this.$message({ type: 'success', message: '删除成功' })
}
})
}
}
}
</script>
<template>
<app-list v-bind="tableOptions" ref="list">
<template #header-aside>
<el-button type="primary" icon="el-icon-plus" @click="addTeacher">添加</el-button>
</template>
<template v-slot:table-x="{ row }">
<el-button @click="onRemove(row)" type="danger" size="mini" plain>删除</el-button>
</template>
<AddTeacher :visible.sync="isShowDialog" @success="success" />
</app-list>
</template>
<script>
import { addTeachers } from '../api'
import AddTeacher from './AddTeacher.vue'
export default {
data() {
return {
isShowDialog: false
}
},
components: {
AddTeacher
},
props: {
detail: {
type: Object
},
id: {
type: String
}
},
computed: {
// 列表配置
tableOptions() {
return {
data: this.detail.staff.list,
columns: [
{ label: '姓名', align: 'center', prop: 'personal_name' },
{ label: '手机号', align: 'center', prop: 'telephone' },
{ label: '操作', slots: 'table-x', align: 'center', width: '120', fixed: 'right' }
]
}
}
},
methods: {
success() {
this.$parent.$parent.getDetail()
},
addTeacher() {
this.isShowDialog = true
},
// 删除教务
onRemove(row) {
this.$confirm('你确定要删除此项吗', '确认提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 调用删除接口
this.delTeach(row)
})
},
delTeach(row) {
const params = {
id: this.id,
staffs_id: row.id,
type: 'delete'
}
addTeachers(params).then(res => {
if (res.code === 0) {
this.$parent.$parent.getDetail()
this.$message.success('删除成功')
}
})
}
}
}
</script>
const routes = [
{
path: '/school',
component: () => import('@/components/layout/Index.vue'),
children: [
{
path: 'class',
component: () => import('./views/List.vue'),
meta: { title: '班级管理' }
},
{
name: 'update',
path: 'class/update',
component: () => import('./views/Update.vue'),
props: { isEdit: true },
meta: { title: '班级管理' }
},
{
name: 'classDetail',
path: 'class/view',
component: () => import('./views/Detail.vue'),
props: true,
meta: { title: '班级管理' }
},
{
name: 'ImportStudent',
path: 'class/ImportStudent',
component: () => import('../student/components/ImportStudent.vue'),
meta: { title: '导入学员' }
}
]
}
]
export { routes }
<template>
<div>
<!-- 班级 -->
<app-card title="班级详情">
<ClassForm :detail="detail.info" />
</app-card>
<!-- 课程 -->
<app-card title="课程安排">
<CourseList :detail="detail" :id="id" />
</app-card>
<!-- 教务 -->
<app-card title="教师">
<TeachList :detail="detail" :id="id" />
</app-card>
<!-- 学员 -->
<app-card title="学员">
<StudentList :detail="detail" :id="id" />
</app-card>
</div>
</template>
<script>
import StudentList from '../components/StudentList.vue'
import TeachList from '../components/TeachList.vue'
import CourseList from '../components/CourseList.vue'
import ClassForm from '../components/ClassForm.vue'
import { getClassDetail } from '../api'
export default {
components: { CourseList, StudentList, TeachList, ClassForm },
data() {
return {
id: '',
detail: {
info: {},
staff: {},
student: {},
course: {}
}
}
},
mounted() {
this.id = this.$route.query.id
this.getDetail() // 获取班级详情
},
methods: {
getDetail() {
const params = { id: this.id }
getClassDetail(params).then(res => {
this.detail = res.data
})
}
}
}
</script>
<template>
<app-card>
<app-list v-bind="tableOptions" ref="list">
<template>
<router-link :to="{ path: 'class/update' }">
<el-button type="primary" icon="el-icon-plus">新建</el-button>
</router-link>
</template>
<template v-slot:table-x="{ row }">
<router-link :to="{ path: 'class/view', query: { id: row.id } }">
<el-button type="primary" style="margin-left: 10px" size="mini" plain>管理</el-button>
</router-link>
<router-link :to="{ path: 'class/update', query: { id: row.id } }">
<el-button type="success" style="margin-left: 10px" size="mini" plain>更新</el-button>
</router-link>
<el-button type="danger" @click="onRemove(row)" style="margin-left: 10px" size="mini" plain>删除</el-button>
</template>
</app-list>
</app-card>
</template>
<script>
// 接口
import { getClassList, deleteClass } from '../api'
export default {
data() {
return {
type: 'create'
}
},
computed: {
// 列表配置
tableOptions() {
return {
remote: {
httpRequest: getClassList, // 获取班级列表
params: {
class_name: '',
class_describe: '',
class_logo: ''
}
},
filters: [
{
type: 'input',
prop: 'class_number',
placeholder: '班级编号'
},
{
type: 'input',
prop: 'class_name',
placeholder: '班级名称'
}
],
columns: [
{ label: '班级编号', prop: 'class_number', align: 'center' },
{ label: '班级名称', prop: 'class_name', align: 'center' },
{ label: '班级描述', prop: 'class_describe', align: 'center' },
{ label: '创建日期', prop: 'created_time', align: 'center' },
{ label: '操作', slots: 'table-x', align: 'center', width: '300', fixed: 'right' }
]
}
}
},
methods: {
// 删除
onRemove(row) {
this.$confirm('班级删除请谨慎操作,确定删除?', '删除班级', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.handleRemove(row)
})
},
// 删除
handleRemove(row) {
deleteClass({ id: row.id }).then(res => {
this.$message({ type: 'success', message: '删除成功' })
this.$refs.list.refetch()
})
}
}
}
</script>
<template>
<app-card :title="title">
<el-form ref="form" :model="form" :rules="rules" label-position="top">
<el-form-item label="班级名称" prop="class_name">
<el-input v-model="form.class_name" maxlength="40" />
</el-form-item>
<el-form-item label="班级编号" prop="class_number">
<el-input v-model="form.class_number" maxlength="40" />
</el-form-item>
<el-form-item label="班级描述" prop="class_describe">
<el-input type="textarea" v-model="form.class_describe" maxlength="50"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="onPrimary">提交</el-button>
</el-form-item>
</el-form>
</app-card>
</template>
<script>
import { getClassDetail, createClass, updateClass } from '../api.js'
export default {
props: {
isEdit: { type: Boolean, default: false }
},
data() {
return {
id: '',
form: {
class_name: '',
class_number: '',
class_describe: ''
},
rules: {
class_name: [
{ required: true, message: '请输入班级名称', trigger: 'blur' },
{ min: 0, max: 40, message: '您最多可输入40 个字符', trigger: 'blur' }
],
class_number: [{ min: 0, max: 40, message: '您最多可输入40 个字符', trigger: 'blur' }],
class_describe: [{ min: 0, max: 100, message: '您最多可输入50个字符', trigger: 'blur' }]
}
}
},
watch: {
data: {
immediate: true,
handler(data) {
this.form = Object.assign({}, this.form, data)
}
}
},
computed: {
title() {
if (this.id) {
return '编辑班级'
} else {
return '创建班级'
}
}
},
mounted() {
this.id = this.$route.query.id || ''
if (this.id) {
this.getDetail() // 获取班级详情信息
}
},
methods: {
cancel() {
this.$router.push('/class')
},
// 获取班级详情信息
getDetail() {
const params = { id: this.id }
getClassDetail(params).then(res => {
this.form = res.data.info
})
},
// 确定
onPrimary() {
this.$refs.form.validate().then(() => {
if (this.id) {
this.edit()
} else {
this.create()
}
})
},
// 创建班级
create() {
createClass(this.form).then(res => {
this.$message.success('创建成功')
this.$router.push('/class')
})
},
// 编辑班级
edit() {
updateClass(Object.assign({ id: this.id }, this.form)).then(res => {
this.$message.success('更新成功')
this.$router.push('/class')
})
}
}
}
</script>
import httpRequest from '@/utils/axios'
/**
* 获取课程列表
*/
export function getCourseList(params) {
return httpRequest.get('/api/lms-financial/school/course/list', { params })
}
/**
* 获取课程详情
*/
export function getCourseDetails(params) {
return httpRequest.get('/api/lms-financial/school/course/view', { params })
}
\ No newline at end of file
<template>
<el-dialog :title="info.title" v-bind="$attrs" v-on="$listeners" width="800px" top="20vh">
<video :src="info.url" controls="controls" autoplay="autoplay"></video>
</el-dialog>
</template>
<script>
export default {
props: {
info: {
type: Object,
default() {
return {}
}
}
},
data() {
return {}
}
}
</script>
<style scoped>
video{
width:100%;
}
</style>
const routes = [
{
path: '/school',
component: () => import('@/components/layout/Index.vue'),
children: [
{
path: 'course',
component: () => import('./views/List.vue'),
meta: { title: '课程管理' }
},
{
path: 'course/details',
component: () => import('./views/Details.vue'),
meta: { title: '课程基本信息' }
}
]
}
]
export { routes }
<template>
<div>
<app-card class="base-info" :title="info.course_name">
<div class="left">
<img :src="info.course_picture">
</div>
<div class="right">
<h1>{{info.course_name}}</h1>
<div v-html="info.course_introducation"></div>
</div>
</app-card>
<app-card class="content" title="课程内容">
<el-collapse v-model="active" accordion>
<el-collapse-item v-for="(item, index) in list" :key="index" :name="index">
<template slot="title">
<div class="course-title">{{item.name}}</div>
</template>
<div class="item" v-for="(it, idx) in item.children" :key="idx">
<i v-if="it.type === 2" class="el-icon-video-play" @click="onPlay(it)"></i>
<p>{{it.name}}</p>
<div class="time" v-if="it.type === 2">
<span class="text">时长</span>
<el-tag size="mini">{{it.video.video_length | formatSeconds}}</el-tag>
</div>
</div>
</el-collapse-item>
</el-collapse>
</app-card>
<video-player v-if="isShowDialog" :info="videoInfo" :visible.sync="isShowDialog" />
</div>
</template>
<script>
import VideoPlayer from '../components/VideoPlayer.vue'
import { getCourseDetails } from '../api'
export default {
components: { VideoPlayer },
data() {
return {
info: {},
list: [
{
title: '第一章 导论',
courseList: [
{ title: '1.张伟:了解金融市场', type: 'video', time: '07:14', url: 'https://webapp-pub.ezijing.com/upload/cms-admin/012236a48c7852c89ef436bc8c046186.mp4' },
{ title: '2.张伟:了解金融市场(下)', type: 'video', time: '07:46' },
{ title: '3.金融市场和金融体系的定义', type: 'video', time: '04:57' },
{ title: '4.金融市场参与者(上)', type: 'video', time: '11:14' }
]
},
{
title: '第二章 金融市场与工具',
courseList: [
{ title: '1.张伟:了解金融市场', type: 'video', time: '07:14' },
{ title: '2.张伟:了解金融市场(下)', type: 'video', time: '07:46' },
{ title: '3.金融市场和金融体系的定义', type: 'video', time: '04:57' },
{ title: '4.金融市场参与者(上)', type: 'video', time: '11:14' }
]
}
],
active: 0,
videoInfo: null,
isShowDialog: false
}
},
filters: {
formatSeconds(value) {
let result = parseInt(value)
let h = Math.floor(result / 3600) < 10 ? '0' + Math.floor(result / 3600) : Math.floor(result / 3600);
let m = Math.floor((result / 60 % 60)) < 10 ? '0' + Math.floor((result / 60 % 60)) : Math.floor((result / 60 % 60));
let s = Math.floor((result % 60)) < 10 ? '0' + Math.floor((result % 60)) : Math.floor((result % 60));
let res = '';
if(h !== '00') res += `${h}h:`;
if(m !== '00') res += `${m}:`;
res += `${s}`;
return res;
}
},
created() {
this.fetchCourseList()
},
methods: {
onPlay(val) {
this.isShowDialog = true
this.videoInfo = val
},
fetchCourseList() {
const params = {
id: this.$route.query.id || ''
}
getCourseDetails(params).then(res => {
console.log(res)
this.info = res.data.info
this.list = res.data.chapters
})
}
}
}
</script>
<style scoped lang="scss">
.app-card{
padding:15px 0 20px;
::v-deep .app-card-hd{
padding:0 20px;
border-bottom:1px solid #ececec;
}
::v-deep .app-card-bd{
padding:20px;
}
}
.base-info{
::v-deep .app-card-bd{
display:flex;
.left{
width:40%;
img{
width:100%;
border: 1px solid #ddd;
padding:4px;
border-radius: 4px;
}
}
.right{
flex:1;
padding:30px;
h1{
font-size:30px;
line-height:50px;
color:#333;
padding-bottom:20px;
}
p{
font-size:14px;
line-height:24px;
color:#666;
text-indent:2em;
}
}
}
}
.content{
.el-collapse{
border:none;
.el-collapse-item{
border:1px solid #ececec;
border-radius:6px;
margin-bottom:15px;
overflow:hidden;
::v-deep .el-collapse-item__header, ::v-deep .el-collapse-item__wrap{
border:none;
padding:0 20px;
font-size:14px;
}
::v-deep .el-collapse-item__header{
background-color: #f5f5f5;
}
::v-deep .el-collapse-item__header.is-active{
border-bottom:1px solid #ececec;
}
::v-deep .el-collapse-item__wrap .item{
display:flex;
align-items:center;
padding:6px 0;
border-bottom:1px solid #f3f3f3;
cursor:pointer;
i{
width:30px;
font-size:20px;
}
i:hover{
color:#3276fc
}
p{
flex:1;
font-size:14px;
line-height:44px;
}
.time{
width:60px;
text-align:center;
.text{
line-height:20px;
height:20px;
}
}
}
}
}
}
</style>
<template>
<app-card>
<app-list v-bind="tableOptions" ref="list">
<template v-slot:filter-course="{ params }">
<app-course-select v-model="params.course_id" placeholder="请选择课程" />
</template>
<template v-slot:table-img="{ row }">
<img v-if="row.curriculum" :src="row.curriculum.course_picture" width="120" height="70">
</template>
<template v-slot:table-desc="{ row }">
<div v-html="row.course_introducation"></div>
</template>
<template v-slot:table-x="{ row }">
<router-link :to="{ path: '/school/course/details', query: { id: row.id } }">
<el-button type="primary" size="mini" plain>查看</el-button>
</router-link>
</template>
</app-list>
</app-card>
</template>
<script>
import AppCourseSelect from '@/components/base/AppCourseSelect.vue'
import { getCourseList } from '../api'
export default {
data() {
return {
isShowDialog: false,
info: {}
}
},
components: {
AppCourseSelect
},
computed: {
// 列表配置
tableOptions() {
return {
remote: {
httpRequest: getCourseList,
params: { course_name: '' }
},
filters: [
{ type: 'input', placeholder: '课程名称', prop: 'course_name' }
],
columns: [
{ label: '课程图片', align: 'center', slots: 'table-img' },
{ label: '课程名称', align: 'center', prop: 'course_name' },
{ label: '课程描述', align: 'center', slots: 'table-desc' },
{ label: '操作', slots: 'table-x', align: 'center', width: '220', fixed: 'right' }
]
}
}
},
methods: {
handleAdd() {
this.isShowDialog = true
this.info = null
},
onDetails(row) {
console.log(row)
},
onEdit(row) {
this.isShowDialog = true
this.info = row
},
// 删除课程
onRemove(row) {
this.$confirm('你确定要删除此项吗', '确认提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 调用删除接口
this.delTeach(row)
})
},
delTeach(row) {
}
}
}
</script>
<style lang="scss" scoped>
</style>
import httpRequest from '@/utils/axios'
/**
* 获取课程列表
*/
export function getExamList(params) {
return httpRequest.get('/api/lms-financial/school/examination/list', { params })
}
/**
* 获取课程列表
*/
export function getExamineeList(params) {
return httpRequest.get('/api/lms-financial/school/examination/sheet-list', { params })
}
\ No newline at end of file
<template>
<el-dialog :title="info.title" v-bind="$attrs" v-on="$listeners" width="800px" top="20vh">
<video :src="info.url" controls="controls" autoplay="autoplay"></video>
</el-dialog>
</template>
<script>
export default {
props: {
info: {
type: Object,
default() {
return {}
}
}
},
data() {
return {}
}
}
</script>
<style scoped>
video{
width:100%;
}
</style>
\ No newline at end of file
const routes = [
{
path: '/school',
component: () => import('@/components/layout/Index.vue'),
children: [
{
path: 'exam',
component: () => import('./views/List.vue'),
meta: { title: '考试管理' }
},
{
path: 'exam/details',
component: () => import('./views/Details.vue'),
meta: { title: '考试管理信息' }
}
]
}
]
export { routes }
<template>
<div>
<app-card class="base-info" :title="$route.query.title">
<div class="title">
<i class="el-icon-document"></i>{{$route.query.title}}
</div>
<el-descriptions class="margin-top" :column="4" direction="vertical">
<el-descriptions-item label="考试名称">{{$route.query.title}}</el-descriptions-item>
<el-descriptions-item label="考试有效期">-</el-descriptions-item>
<el-descriptions-item label="考试时间" :span="2">--</el-descriptions-item>
</el-descriptions>
</app-card>
<app-card class="content" title="考生信息">
<app-list v-bind="tableOptions" ref="list">
<template v-slot:filter-student="{ params }">
<app-student-select v-model="params.personal_name" placeholder="请选择学生" @change="$refs.list.refetch()" />
</template>
<template v-slot:filter-class="{ params }">
<app-class-select v-model="params.class_id" placeholder="请选择班级" @change="$refs.list.refetch()" />
</template>
<template v-slot:table-img="{ row }">
<img :src="row.url" width="120" height="70">
</template>
<template v-slot:table-x="{ row }">
<router-link :to="{ path: '/course/details', query: { id: row.id } }">
<el-button type="primary" size="mini" plain>查看</el-button>
</router-link>
</template>
</app-list>
</app-card>
</div>
</template>
<script>
import AppStudentSelect from '@/components/base/AppStudentSelect.vue'
import AppClassSelect from '@/components/base/AppClassSelect.vue'
import { getExamineeList } from '../api'
export default {
components: { AppStudentSelect, AppClassSelect },
data() {
return {
}
},
computed: {
// 列表配置
tableOptions() {
return {
remote: {
httpRequest: getExamineeList,
params: { examination_id: this.$route.query.id, personal_name: '', class_id: '' }
},
filters: [
{ type: 'input', label: '学员姓名', slots: 'filter-student' },
{ type: 'input', label: '所属班级', slots: 'filter-class' },
],
// data: this.detail.staff.list,
// data: [
// { name: '张三', score: 80, time: '2022-01-01 10:43:32' },
// { name: '李四', score: 92, time: '2022-01-02 11:21:32' },
// { name: '王五', score: 69, time: '2022-01-05 10:43:32' },
// { name: '赵六', score: 73, time: '2022-01-03 10:43:32' },
// ],
columns: [
{ label: '姓名', align: 'center', prop: 'personal_name' },
{ label: '分数', align: 'center', prop: 'score' },
{ label: '考试时间', align: 'center', prop: 'created_time' }
]
}
}
},
methods: {
research() {
this.$refs.list.refetch()
}
}
}
</script>
<style scoped lang="scss">
.app-card{
padding:15px 0 20px;
::v-deep .app-card-hd{
padding:0 20px;
border-bottom:1px solid #ececec;
}
::v-deep .app-card-bd{
padding:20px;
}
}
.base-info{
::v-deep .app-card-bd{
.title{
margin-bottom:20px;
padding-bottom:14px;
border-bottom:1px solid #f0f0f0;
i{
font-size:24px;
color: #3276fc;
}
}
}
}
</style>
\ No newline at end of file
<template>
<app-card>
<app-list v-bind="tableOptions" ref="list">
<template v-slot:table-time="{ row }">
{{row.start_time}} - {{row.end_time}}
</template>
<template v-slot:table-x="{ row }">
<router-link :to="{ path: '/exam/details', query: { id: row.id, title: row.paper_title } }">
<el-button type="primary" size="mini" plain>查看</el-button>
</router-link>
</template>
</app-list>
</app-card>
</template>
<script>
import { getExamList } from '../api'
const statusMap = { 0: '未设置', 1: '进行中', 2: '已完成'}
export default {
data() {
return {
isShowDialog: false
}
},
computed: {
// 列表配置
tableOptions() {
return {
remote: {
httpRequest: getExamList,
params: {}
},
columns: [
{ label: '考试名称', align: 'center', prop: 'paper_title' },
{
prop: 'status',
label: '考试有效期',
align: 'center',
computed({ row }) {
return statusMap[row.status]
}
},
{ label: '考试日期', align: 'center', slots: 'table-time' },
{ label: '操作', slots: 'table-x', align: 'center', width: '220', fixed: 'right' }
]
}
}
},
methods: {
}
}
</script>
import httpRequest from '@/utils/axios'
/**
* 获取学员列表
*/
export function getStudentList(params) {
return httpRequest.get('/api/lms-financial/school/student/list', { params })
}
/**
* 获取学员详情
*/
export function getStudentDetail(params) {
return httpRequest.get('/api/lms-financial/school/student/view', { params })
}
/**
* 学员添加
*/
export function createStudent(data) {
return httpRequest.post('/api/lms-financial/school/student/add', data)
}
/**
* 学员更新
*/
export function updateStudent(data) {
return httpRequest.post('/api/lms-financial/school/student/update', data)
}
/**
* 学员导入
*/
export function importStudent(data) {
return httpRequest({
url: '/api/lms-financial/school/student/import',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 900000,
data,
withCredentials: true
})
}
/**
* 学员列表下载
*/
export function downStuList(params) {
return httpRequest.get('/api/lms-financial/school/student/download', { params })
}
/**
* 学员删除
*/
export function deleteStudent(data) {
return httpRequest.post('/api/lms-financial/school/student/delete', data)
}
<template>
<el-card>
<el-upload
style="text-align: center"
class="file-import"
ref="upload"
action="#"
:auto-upload="false"
:file-list="fileList"
:limit="1"
:before-upload="beforeUpload"
:http-request="fetchFileUpload"
accept=".xls,.xlsx"
drag
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</el-upload>
<div style="margin-bottom: 10px; text-align: center">
导入模板下载:<a
href="https://webapp-pub.ezijing.com/x-training-new/%E5%AD%A6%E5%91%98%E6%A8%A1%E6%9D%BF.xlsx"
download="学员模板"
><el-button type="text">student_import.xlsx</el-button></a
>
</div>
<div style="text-align: center">
<el-button size="mini" @click="cancel">取消</el-button>
<el-button type="primary" size="mini" @click="submitUpload"><i style="margin-right: 5px"></i>确认提交</el-button>
</div>
</el-card>
</template>
<script>
import { splitStrLast } from '@/utils/util'
import { importStudent } from '../api'
import { importStudents } from '../../class/api'
export default {
data() {
return {
id: '',
fileList: []
}
},
mounted() {
this.id = this.$route.query.id
},
methods: {
beforeUpload(file) {
const suffix = splitStrLast(file.name, '.')
if (!['xlsx', 'xls'].includes(suffix)) {
this.$message.error('只能上传excel文件')
return false
} else {
return true
}
},
fetchFileUpload(data) {
if (this.id) {
// 班级管理导入学员
importStudents({ id: this.id, file: data.file }).then(res => {
if (res.code === 0) {
this.$message.success('导入数据成功')
history.go(-1)
}
})
} else {
// 学员管理导入学员
importStudent({ file: data.file }).then(res => {
if (res.code === 0) {
this.$message.success('导入数据成功')
history.go(-1)
}
})
}
},
submitUpload() {
this.$refs.upload.submit()
},
cancel() {
this.$router.go(-1)
}
}
}
</script>
<template>
<app-card>
<app-list v-bind="tableOptions" ref="list">
<template v-slot:progress="{ row }">
<el-progress :percentage="row.progress"></el-progress>
</template>
</app-list>
</app-card>
</template>
<script>
export default {
computed: {
// 列表配置
tableOptions() {
return {
data: this.$route.query.row,
columns: [
{ label: '章节名称', align: 'center', prop: 'name' },
{ label: '章节类型', align: 'center', prop: 'type' },
{ label: '学习进度', align: 'center', prop: 'progress', slots: 'progress' },
{ label: '是否标记完成', align: 'center', prop: 'sign' },
{ label: '最大学习时点', align: 'center', prop: 'mpt' },
{ label: '最后学习时点', align: 'center', prop: 'cpt' },
{ label: '累计学习时长', align: 'center', prop: 'pt' }
]
}
}
}
}
</script>
const routes = [
{
path: '/personnel',
component: () => import('@/components/layout/Index.vue'),
children: [
{
name: 'student',
path: 'student',
component: () => import('./views/List.vue'),
meta: { title: '人员管理' }
},
{
path: 'student/view',
component: () => import('./views/Detail.vue'),
meta: { title: '人员管理' }
},
{
path: 'student/process',
component: () => import('./views/Process.vue'),
meta: { title: '人员管理' }
},
{
name: 'processDetail',
path: 'student/process/detail',
component: () => import('../student/components/processDetail.vue'),
meta: { title: '人员管理' }
},
{
name: 'addStudents',
path: 'student/update',
component: () => import('./views/Update.vue'),
meta: { title: '人员管理' }
},
{
name: 'importStudent',
path: 'student/importStudent',
component: () => import('../student/components/ImportStudent.vue'),
meta: { title: '人员管理' }
}
]
}
]
export { routes }
<template>
<app-card>
<el-form :model="form" label-width="120px">
<!-- 姓名 -->
<el-form-item label="姓名" prop="personal_name">
<el-input v-model="form.personal_name" disabled></el-input>
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-input v-model="form.gender" disabled></el-input>
</el-form-item>
<!-- 手机号 -->
<el-form-item label="手机号" prop="telephone">
<el-input type="number" v-model="form.telephone" disabled></el-input>
</el-form-item>
<!--学号 -->
<el-form-item label="学号" prop="sno">
<el-input v-model="form.sno" disabled></el-input>
</el-form-item>
<!-- 邮箱 -->
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" disabled></el-input>
</el-form-item>
</el-form>
</app-card>
</template>
<script>
import AppCard from '../../../../components/base/AppCard.vue'
import { getStudentDetail } from '../api'
export default {
components: { AppCard },
data() {
return {
form: {
gender: ''
}
}
},
mounted() {
this.getDetail()
},
methods: {
getDetail() {
getStudentDetail({ id: this.$route.query.id }).then(res => {
this.form = res.data.info
if (this.form.gender === 0) {
this.form.gender = '女士'
} else if (this.form.gender === 1) {
this.form.gender = '男士'
} else {
this.form.gender = '未知'
}
})
}
}
}
</script>
<style>
.my-label {
width: 200px;
}
</style>
<template>
<app-card>
<app-list v-bind="tableOptions" ref="list">
<template>
<el-row style="margin-bottom: 20px">
<el-button type="primary" icon="el-icon-plus" size="mini" @click="add">新建</el-button>
<el-button type="primary" icon="el-icon-upload2" style="margin-left: 20px" @click="imports" size="mini"
>导入</el-button
>
<!-- <el-button type="primary" icon="el-icon-download" style="margin-left: 20px" @click="downLoad" size="mini">下载</el-button> -->
</el-row>
</template>
<template v-slot:table-x="{ row }">
<!-- <router-link :to="{ path: 'student/process', query: { id: row.id } }">
<el-button type="warning" size="mini" plain>学习进度</el-button>
</router-link> -->
<router-link :to="{ path: 'student/view', query: { id: row.id } }">
<el-button type="primary" style="margin-left: 10px" size="mini" plain>查看</el-button>
</router-link>
<router-link :to="{ path: 'student/update', query: { id: row.id } }">
<el-button type="success" style="margin-left: 10px" size="mini" plain>更新</el-button>
</router-link>
<el-button type="danger" @click="onRemove(row)" style="margin-left: 10px" size="mini" plain>删除</el-button>
</template>
</app-list>
</app-card>
</template>
<script>
// 接口
import { getStudentList, deleteStudent } from '../api'
import queryString from 'query-string'
export default {
data() {
return {}
},
computed: {
// 列表配置
tableOptions() {
return {
remote: {
httpRequest: getStudentList,
params: {
personal_name: '',
telephone: '',
id: '',
sno: ''
}
},
filters: [
{
type: 'input',
width: '300',
prop: 'personal_name',
placeholder: '姓名'
},
{
type: 'input',
width: '300',
prop: 'telephone',
placeholder: '手机号'
},
{
type: 'input',
width: '300',
prop: 'email',
placeholder: '邮箱'
}
],
columns: [
{ label: '姓名', prop: 'personal_name', align: 'center' },
{ label: '所属班级', prop: 'class_name', align: 'center' },
{ label: '手机号', prop: 'telephone', align: 'center' },
{ label: '邮箱', prop: 'email', align: 'center' },
{ label: '操作', slots: 'table-x', align: 'center', width: '300', fixed: 'right' }
]
}
}
},
methods: {
add() {
this.$router.push({
name: 'addStudents'
})
},
imports() {
this.$router.push({
name: 'importStudent'
})
},
// 删除
onRemove(row) {
this.$confirm('学员删除请谨慎操作,确定删除?', '删除学员', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.handleRemove(row)
})
},
// 删除
handleRemove(row) {
deleteStudent({ id: row.id }).then(res => {
this.$message({ type: 'success', message: '删除成功' })
this.$refs.list.refetch()
})
},
// 下载学生
downLoad() {
Object.keys(this.tableOptions.remote.params).forEach(key => {
if (this.tableOptions.remote.params[key] === '') delete this.tableOptions.remote.params[key]
})
const params = queryString.stringify(this.tableOptions.remote.params)
window.open(`/api/zy/v3-school/student/download?${params}`)
}
}
}
</script>
<template>
<app-card>
<app-list v-bind="tableOptions" ref="list">
<template v-slot:course_picture="{ row }">
<el-image :src="row.course_picture"></el-image>
</template>
<template v-slot:progress="{ row }">
<el-progress :percentage="row.progress"></el-progress>
</template>
<template v-slot:table-x="{ row }">
<el-button type="primary" size="mini" plain @click="toDetail(row)">查看详情</el-button>
</template>
</app-list>
</app-card>
</template>
<script>
// 接口
import { getStudentDetail, deleteStudent } from '../api'
export default {
data() {
return {
id: '',
list: [],
data: []
}
},
computed: {
// 列表配置
tableOptions() {
return {
data: this.list,
columns: [
{ label: '课程ID', align: 'center', prop: 'id' },
{ label: '课程名称', align: 'center', prop: 'course_name' },
{ label: '课程图片', align: 'center', prop: 'course_picture', slots: 'course_picture' },
{ label: '总进度', align: 'center', prop: 'progress', slots: 'progress' },
{ label: '总时长', align: 'center', prop: 'max_length' },
{ label: '操作', slots: 'table-x', align: 'center', fixed: 'right' }
]
}
}
},
mounted() {
this.id = this.$route.query.id
this.getDetail()
},
methods: {
getDetail() {
getStudentDetail({ id: this.id }).then(res => {
this.list = res.data.courses
// console.log(this.list)
})
},
toDetail(row) {
// console.log(row)
this.$router.push({
name: 'processDetail',
query: {
row: row.data
}
})
},
// 删除
onRemove(row) {
this.$confirm('班级删除请谨慎操作,确定删除?', '删除班级', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.handleRemove(row)
})
},
// 删除
handleRemove(row) {
deleteStudent({ id: row.id }).then(res => {
this.$message({ type: 'success', message: '删除成功' })
this.$refs.list.refetch()
})
}
}
}
</script>
<template>
<el-card>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<!-- 姓名 -->
<el-form-item label="姓名" prop="personal_name">
<el-input v-model="form.personal_name" maxlength="40"></el-input>
</el-form-item>
<!-- 性别 -->
<el-form-item label="性别" prop="gender">
<el-select placeholder="请选择性别" v-model="form.gender">
<el-option label="女士" :value="0"></el-option>
<el-option label="先生" :value="1"></el-option>
<el-option label="未知" :value="2"></el-option>
</el-select>
</el-form-item>
<!-- 学号 -->
<el-form-item label="学号" prop="sno">
<el-input v-model="form.sno" maxlength="40"></el-input>
</el-form-item>
<!-- 手机号 -->
<el-form-item label="电话" prop="telephone">
<el-input v-model="form.telephone" maxlength="11" type="number"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password" v-if="!this.id">
<el-input v-model="form.password" show-password></el-input>
</el-form-item>
<!-- 邮箱 -->
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="submit">提交</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
import { getStudentDetail, createStudent, updateStudent } from '../api'
export default {
data() {
return {
id: '',
form: {
personal_name: '',
gender: '',
telephone: '',
sno: '',
password: '',
email: ''
},
rules: {
personal_name: [
{ required: true, message: '请输入姓名', trigger: 'blur' },
{ min: 0, max: 40, message: '您最多可输入 40 个字符', trigger: 'blur' }
],
telephone: [{ required: true, message: '请输入电话', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
sno: [
{ required: true, message: '请输入学号', trigger: 'blur' },
{ min: 0, max: 40, message: '您最多可输入40 个字符', trigger: 'blur' }
]
}
}
},
created() {
this.id = this.$route.query.id || ''
if (this.id) {
this.getDetail()
}
},
methods: {
cancel() {
this.$router.push({
name: 'student'
})
},
getDetail() {
getStudentDetail({ id: this.id }).then(res => {
console.log(res.data.info)
Object.keys(this.form).forEach(key => {
this.form[key] = res.data.info[key]
})
})
},
submit() {
this.$refs.form.validate().then(() => {
if (this.id) {
this.edit()
} else {
this.create()
}
})
},
// 创建学生
create() {
createStudent(this.form).then(res => {
this.$message.success('创建成功')
this.$router.go(-1)
})
},
// 编辑学生
edit() {
const params = Object.assign({ id: this.id }, this.form)
updateStudent(params).then(res => {
this.$message.success('更新成功')
this.$router.go(-1)
})
}
}
}
</script>
import httpRequest from '@/utils/axios'
/**
* 获取班级列表
*/
export function getTeacherList(params) {
return httpRequest.get('/api/lms-financial/school/teacher/list', { params })
}
/**
* 获取教师详情
*/
export function getTeacherDetail(params) {
return httpRequest.get('/api/lms-financial/school/teacher/view', { params })
}
/**
* 教师添加/创建
*/
export function createTeacher(data) {
return httpRequest.post('/api/lms-financial/school/teacher/add', data)
}
/**
* 修改教师
*/
export function updateTeacher(data) {
return httpRequest.post('/api/lms-financial/school/teacher/update', data)
}
/**
* 删除教师
*/
export function deleteTeacher(data) {
return httpRequest.post('/api/lms-financial/school/teacher/delete', data)
}
/**
* 导入教师
*/
export function importTeacher(data) {
return httpRequest({
url: '/api/lms-financial/school/teacher/import',
method: 'post',
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 900000,
data,
withCredentials: true
})
}
<template>
<el-card>
<el-upload
style="text-align: center"
class="file-import"
ref="upload"
action="#"
:auto-upload="false"
:file-list="fileList"
:limit="1"
:before-upload="beforeUpload"
:http-request="fetchFileUpload"
accept=".xls,.xlsx"
drag
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
</el-upload>
<div style="margin-bottom: 10px; text-align: center">
导入模板下载:<a
href="https://webapp-pub.ezijing.com/x-training-new/%E8%80%81%E5%B8%88%E6%A8%A1%E6%9D%BF.xlsx"
download="教师模板"
><el-button type="text">teacher_import.xlsx</el-button></a
>
</div>
<div style="text-align: center">
<el-button size="mini" @click="cancel">取消</el-button>
<el-button type="primary" size="mini" @click="submitUpload" style="margin-right: 5px"> 确认提交</el-button>
</div>
</el-card>
</template>
<script>
import { splitStrLast } from '@/utils/util'
import { importTeacher } from '../api'
export default {
data() {
return {
fileList: []
}
},
methods: {
beforeUpload(file) {
const suffix = splitStrLast(file.name, '.')
if (!['xlsx', 'xls'].includes(suffix)) {
this.$message.error('只能上传excel文件')
return false
} else {
return true
}
},
fetchFileUpload(data) {
importTeacher({ file: data.file }).then(res => {
if (res.code === 0) {
this.$message.success('导入数据成功')
history.go(-1)
}
})
},
submitUpload() {
this.$refs.upload.submit()
},
cancel() {
this.$router.push({
name: 'teacher'
})
}
}
}
</script>
const routes = [
{
path: '/personnel',
component: () => import('@/components/layout/Index.vue'),
children: [
{
name: 'teacher',
path: 'teacher',
component: () => import('./views/List.vue'),
meta: { title: '教师管理' }
},
{
path: 'teacher/view',
component: () => import('./views/Detail.vue'),
props: true,
meta: { title: '教师管理' }
},
{
name: 'addTeachers',
path: 'teacher/update',
component: () => import('./views/Update.vue'),
props: true,
meta: { title: '教师管理' }
},
{
name: 'importTeacher',
path: 'teacher/ImportTeachert',
component: () => import('./components/ImportTeacher.vue'),
meta: { title: '教师管理' }
}
]
}
]
export { routes }
<template>
<app-card>
<el-form :model="form" label-width="120px">
<!-- 姓名 -->
<el-form-item label="姓名" prop="personal_name">
<el-input v-model="form.personal_name" disabled></el-input>
</el-form-item>
<!-- 手机号 -->
<el-form-item label="手机号" prop="telephone">
<el-input type="number" v-model="form.telephone" disabled></el-input>
</el-form-item>
<!-- 邮箱 -->
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" disabled></el-input>
</el-form-item>
<!--密码 -->
<el-form-item label="创建日期" prop="created_time">
<el-input v-model="form.created_time" disabled></el-input>
</el-form-item>
<!-- 工号 -->
<el-form-item label="更新日期" prop="updated_time">
<el-input v-model="form.updated_time" disabled></el-input>
</el-form-item>
</el-form>
</app-card>
</template>
<script>
import AppCard from '../../../../components/base/AppCard.vue'
import { getTeacherDetail } from '../api'
export default {
components: { AppCard },
data() {
return {
form: {}
}
},
mounted() {
this.getDetail()
},
methods: {
getDetail() {
getTeacherDetail({ id: this.$route.query.id }).then(res => {
this.form = res.data
})
}
}
}
</script>
<style>
.my-label {
width: 200px;
}
</style>
<template>
<app-card>
<app-list v-bind="tableOptions" ref="list">
<template>
<el-row style="margin-bottom: 20px">
<el-button type="primary" icon="el-icon-plus" @click="add" size="mini">新建</el-button>
<el-button type="primary" icon="el-icon-upload2" style="margin-left: 20px" @click="imports" size="mini"
>导入</el-button
>
</el-row>
</template>
<template v-slot:table-x="{ row }">
<router-link :to="{ path: 'teacher/view', query: { id: row.id } }">
<el-button type="primary" size="mini" plain>查看</el-button>
</router-link>
<router-link :to="{ path: 'teacher/update', query: { id: row.id } }">
<el-button type="success" style="margin-left: 10px" size="mini" plain>更新</el-button>
</router-link>
<el-button type="danger" style="margin-left: 10px" size="mini" plain @click="onRemove(row)">删除</el-button>
</template>
</app-list>
</app-card>
</template>
<script>
// 接口
import { getTeacherList, deleteTeacher } from '../api'
export default {
data() {
return {}
},
computed: {
// 列表配置
tableOptions() {
return {
remote: {
httpRequest: getTeacherList,
params: {
personal_name: '',
telephone: '',
id: '',
job_number: ''
}
},
filters: [
{
type: 'input',
prop: 'personal_name',
placeholder: '姓名'
},
{
type: 'input',
prop: 'telephone',
placeholder: '手机号'
},
{
type: 'input',
prop: 'email',
placeholder: '邮箱'
}
],
columns: [
{ label: '姓名', align: 'center', prop: 'personal_name' },
{ label: '手机号', align: 'center', prop: 'telephone' },
{ label: '邮箱', align: 'center', prop: 'email' },
{ label: '创建日期', align: 'center', prop: 'created_time' },
{ label: '操作', slots: 'table-x', align: 'center', width: '300', fixed: 'right' }
]
}
}
},
methods: {
add() {
this.$router.push({ path: 'teacher/update' })
},
imports() {
this.$router.push({
name: 'importTeacher'
})
},
// 删除
onRemove(row) {
this.$confirm('教师删除请谨慎操作,确定删除?', '删除教师', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.handleRemove(row)
})
},
// 删除
handleRemove(row) {
deleteTeacher({ id: row.id }).then(res => {
this.$message({ type: 'success', message: '删除成功' })
this.$refs.list.refetch()
})
}
}
}
</script>
<template>
<el-card>
<el-form :model="form" label-width="120px" ref="form" :rules="rules">
<!-- 姓名 -->
<el-form-item label="姓名" prop="personal_name">
<el-input v-model="form.personal_name" maxlength="40"></el-input>
</el-form-item>
<!-- 手机号 -->
<el-form-item label="手机号" prop="telephone">
<el-input type="number" v-model="form.telephone" maxlength="11"></el-input>
</el-form-item>
<!--密码 -->
<el-form-item label="密码" prop="password" v-if="!this.id">
<el-input v-model="form.password" type="password" show-password></el-input>
</el-form-item>
<!-- 工号 -->
<el-form-item label="工号" prop="job_number">
<el-input v-model="form.job_number" maxlength="40"></el-input>
</el-form-item>
<!-- 邮箱 -->
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="submit">提交</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
import { getTeacherDetail, createTeacher, updateTeacher } from '../api'
export default {
data() {
return {
form: {
personal_name: '',
telephone: '',
job_number: '',
password: '',
email: ''
},
rules: {
personal_name: [
{ required: true, message: '请输入姓名', trigger: 'blur' },
{ min: 0, max: 40, message: '您最多可输入40 个字符', trigger: 'blur' }
],
telephone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
job_number: [
{ required: true, message: '请输入工号', trigger: 'blur' },
{ min: 0, max: 40, message: '您最多可输入40 个字符', trigger: 'blur' }
]
}
}
},
mounted() {
this.id = this.$route.query.id || ''
if (this.id) {
this.getDetail() // 获取班级详情信息
}
},
methods: {
getDetail() {
getTeacherDetail({ id: this.id }).then(res => {
this.form = res.data
})
},
cancel() {
this.$router.go(-1)
},
submit() {
this.$refs.form.validate().then(() => {
if (this.id) {
this.edit()
} else {
this.create()
}
})
},
// 创建教师
create() {
createTeacher(this.form).then(res => {
this.$message.success('创建成功')
this.$router.push({
name: 'teacher'
})
})
},
// 编辑教师
edit() {
updateTeacher(Object.assign({ id: this.id }, this.form)).then(res => {
this.$message.success('更新成功')
this.$router.push({
name: 'teacher'
})
})
}
}
}
</script>
import httpRequest from '@/utils/axios'
// 获取用户信息
export function updateUser(data) {
return httpRequest.post('/api/usercenter/user/update-user', data)
}
// 获取用户信息
export function updatePassword(data) {
return httpRequest.post('/api/usercenter/user/change-pwd-by-cookie', data)
}
<template>
<app-card title="密码">
<ul class="app-card-list">
<li class="app-card-item" @click="showUpdate">
<div class="app-card-item-content">••••••••</div>
<div class="app-card-item-aside"><i class="el-icon-arrow-right"></i></div>
</li>
</ul>
<!-- 密码修改 -->
<el-dialog
title="密码"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
@close="resetFormData"
width="600px"
>
<el-form ref="form" :model="form" :rules="rules" :hide-required-asterisk="true" label-position="top">
<el-form-item label="旧密码" prop="old_password">
<el-input type="password" v-model="form.old_password"></el-input>
</el-form-item>
<el-form-item label="新密码" prop="password">
<el-input type="password" v-model="form.password"></el-input>
</el-form-item>
<el-form-item label="确认新密码" prop="passwordR">
<el-input type="password" v-model="form.passwordR"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="handleCancelUpdate">取消</el-button>
<el-button type="primary" size="medium" @click="handlePrimaryUpdate">保存</el-button>
</div>
</el-dialog>
</app-card>
</template>
<script>
import { updatePassword } from '../api.js'
export default {
data() {
const validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入新密码'))
} else if (value !== this.form.password) {
callback(new Error('密码不一致'))
} else {
callback()
}
}
return {
form: {},
rules: {
old_password: [{ required: true, message: '请输入旧密码', trigger: 'blur' }],
password: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 20, message: '长度为6-20个字符', trigger: 'blur' }
],
passwordR: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ validator: validatePass, trigger: 'blur' }
]
},
dialogVisible: false
}
},
computed: {
user() {
return this.$store.state.user
}
},
methods: {
showUpdate() {
this.dialogVisible = true
},
handleCancelUpdate() {
this.dialogVisible = false
},
handlePrimaryUpdate() {
this.$refs.form.validate().then(() => {
this.updateHandler(this.form, () => {
this.dialogVisible = false
this.$message.success('密码修改成功')
})
})
},
updateHandler(params, callback) {
return updatePassword(params).then(resp => {
this.$store.dispatch('getUser')
callback && callback(resp)
})
},
resetFormData() {
this.form = {}
this.$refs.form && this.$refs.form.resetFields()
}
}
}
</script>
<style lang="scss" scoped>
.app-card-item {
display: flex;
align-items: center;
cursor: pointer;
padding: 10px 0;
&:hover {
background-color: rgba(86, 100, 210, 0.04);
}
}
.app-card-item-label {
flex-basis: 156px;
margin-right: 24px;
font-weight: 500;
color: #5f6368;
}
.app-card-item-content {
flex: 1;
}
</style>
const routes = [
{
path: '/system',
component: () => import('@/components/layout/Index.vue'),
children: [
{
path: 'safe',
component: () => import('./views/Index.vue'),
meta: { title: '安全设置' }
}
]
}
]
export { routes }
<template>
<div>
<app-password></app-password>
</div>
</template>
<script>
export default {
components: { appPassword: () => import('../components/Password.vue') }
}
</script>
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [{ path: '*', redirect: '/school/class' }]
const router = new VueRouter({
mode: 'history',
routes
})
export default router
import Vue from 'vue'
import Vuex from 'vuex'
import { getUser, logout, getPermissions } from '@/api/base'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
user: {},
permissions: []
},
mutations: {
setUser(state, user) {
state.user = user
},
setPermissions(state, permissions) {
state.permissions = permissions
}
},
actions: {
// 获取用户信息
getUser({ commit }) {
getUser().then(response => {
commit('setUser', response)
})
},
// 退出登录
logout({ commit }) {
return logout().then(response => {
commit('setUser', {})
return response
})
},
async checkLogin({ commit }) {
const isLogin = await getUser()
.then(response => {
if (response.code === 0) {
commit('setUser', response.data)
return true
} else {
commit('setUser', {})
return false
}
})
.catch(() => {
commit('setUser', {})
return false
})
return isLogin
},
// 获取所有权限列表
getPermissions({ commit }) {
getPermissions({ type: 1 }).then(res => {
if (res.data && res.data.permissions) {
commit('setPermissions', res.data.permissions)
}
})
}
}
})
store.dispatch('getPermissions')
export default store
import axios from 'axios'
import queryString from 'query-string'
import { Message } from 'element-ui'
import router from '@/router'
const httpRequest = axios.create({
baseURL: import.meta.env.VITE_BASE_URL,
timeout: 60000,
withCredentials: true,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
// 请求拦截
httpRequest.interceptors.request.use(
function (config) {
if (config.headers['Content-Type'] === 'application/x-www-form-urlencoded') {
config.data = queryString.stringify(config.data)
}
if (config.headers['Content-Type'] === 'multipart/form-data') {
const formData = new window.FormData()
for (const key in config.data) {
formData.append(key, config.data[key])
}
config.data = formData
}
return config
},
function (error) {
return Promise.reject(error)
}
)
// 响应拦截
httpRequest.interceptors.response.use(
function (response) {
const { data } = response
// console.log(data)
// 正常返回
if (data.code === 0) {
return data
}
Message({ message: data.message || data.msg, type: 'error' })
return Promise.reject(data)
},
function (error) {
if (error.response) {
const { status, message } = error.response.data
if (status === 401) {
router.push('/401')
}
// 未登录
if (status === 403) {
window.location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(window.location.href)}`
} else {
Message.error(message || error.response.data)
}
return Promise.reject(error.response)
} else {
console.log(error)
}
return Promise.reject(error)
}
)
export default httpRequest
import store from '@/store'
const UA = navigator.userAgent
const isMobile = /iphone/i.test(UA) || (/android/i.test(UA) && /mobile/i.test(UA))
const isIe = window.ActiveXObject || 'ActiveXObject' in window
// const isChrome = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime)
// let chromeVersion = 0
// if (isChrome) {
// const temp = UA.match(/Chrome\/([\d.]+)/)
// chromeVersion = temp ? parseFloat(temp[1]) : 0
// }
// const notSupport = !isMobile && (isIe || (isChrome && chromeVersion < 70))
const notSupport = !isMobile && isIe
// https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
export default async function (to, from, next) {
// 浏览器不支持
if (notSupport && to.name !== 'browser') {
next({ path: '/browser' })
return
}
// 登录白名单
const whiteList = []
if (whiteList.includes(to.path)) {
next()
return
}
const isLogin = store.state.user.id || (await store.dispatch('checkLogin'))
if (!isLogin) {
// window.location.href = `${import.meta.env.VITE_LOGIN_URL}?rd=${encodeURIComponent(window.location.href)}`
// return
}
next()
}
/**
* 文件下载
* @param {string} fileUrl 文件下载地址
* @param {string} fileName 文件名
* @returns {null}
*/
export function funDownload(fileUrl, fileName) {
// console.log(fileUrl)
const elink = document.createElement('a') // 创建一个a标签
elink.download = fileName // 设置a标签的下载属性
elink.style.display = 'none' // 将a标签设置为隐藏
elink.href = fileUrl // 把之前处理好的地址赋给a标签的href
document.body.appendChild(elink) // 将a标签添加到body中
elink.click() // 执行a标签的点击方法
// URL.revokeObjectURL(elink.href) // 下载完成释放URL 对象
document.body.removeChild(elink) // 移除a标签
}
/**
* 分割字符串,取得尾部
* @param {string} str 字符串
* @param {string} split 分割符
* @returns {string}
*/
export function splitStrLast(str, split) {
const fileNameArr = str.split(split)
const last = fileNameArr[fileNameArr.length - 1]
return last
}
import fs from 'fs'
import path from 'path'
import { defineConfig } from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'
import eslint from '@rollup/plugin-eslint'
export default defineConfig({
base: process.env.BUILD_ENV === 'prod' ? 'https://webapp-pub.ezijing.com/website/prod/x-admin/' : '/',
plugins: [eslint({ include: '**/*.+(vue|js|jsx|ts|tsx)' }), createVuePlugin()],
server: {
open: true,
host: 'dev.ezijing.com',
https: {
key: fs.readFileSync(path.join(__dirname, './https/dev.ezijing.com.key')),
cert: fs.readFileSync(path.join(__dirname, './https/dev.ezijing.com.pem'))
},
proxy: {
// '/api': 'http://localhost-financial-api.ezijing.com'
// '/api': 'https://learn-api.ezijing.com'
'/api/lms-financial': {
target: 'http://localhost-financial-api.ezijing.com',
changeOrigin: true,
rewrite: path => path.replace(/^\/api\/fd/, '')
}
}
},
resolve: {
alias: [
{
find: '@',
replacement: path.resolve(__dirname, 'src')
}
]
},
css: {
// 禁用SASS警告提醒
preprocessorOptions: { scss: { quietDeps: true } }
}
})
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论