提交 c8431db1 authored 作者: 龙菲's avatar 龙菲

布展管理、上传组件封装

上级 212abb71
......@@ -2,4 +2,4 @@
ENV = 'development'
# base api
VUE_APP_BASE_API = 'http://127.0.0.1:4523/m1/1105343-0-default/'
VUE_APP_BASE_API = 'http://172.24.100.189:8080'
import request from '@/utils/request'
/**
*
* @param {*} data 查询参数 data{limit:100,page:1,dictId:xxx}
*
*/
export function getDictCode(data) {
return request({
url: '/sysDictDetail/getDictDetailsByTypes',
method: 'post',
data
})
}
import request from '@/utils/request'
export function getList(params) {
export function getList(data) {
return request({
url: '/display/getList',
method: 'get',
params
url: '/bizExhibition/listByPage',
method: 'post',
data
})
}
import request from '@/utils/request'
export function getLiteratureList(data) {
return request({
url: 'sysLiterature/listByPage',
method: 'post',
data
})
}
import request from '@/utils/request'
export function getList(params) {
return request({
url: '/vue-admin-template/table/list',
method: 'get',
params
})
}
import request from '@/utils/request'
export function getVerify() {
return request({
url: '/sys/getVerify',
method: 'get',
responseType: 'blob'
})
}
export function login(data) {
return request({
url: '/user/login',
url: '/sys/user/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/user/info',
url: '/sys/user',
method: 'get',
params: { token }
params: {
token
}
})
}
export function logout() {
return request({
url: '/user/logout',
url: '/sys/user/logout',
method: 'post'
})
}
import {
getDictCode
} from "@/api/dict";
import store from "@/store";
import Vue from "vue";
class Dict {
constructor(dict) {
this.dict = dict;
}
async init(names) {
// const ps = [];
await getDictCode(names).then((data) => {
names.forEach(name => {
// 如果vuex中已经存在当前字典名,则不进行设置
if (store.getters.dicts[name]) {
Vue.set(this.dict, name, store.getters.dicts[name]);
} else {
Vue.set(this.dict, name, []);
// this.dict[name] = Object.freeze(data);
// 把dictType都为name的全部传入this.dict[name]中
var arr = []
data.data.map(item => {
if (item.dictType === name) {
arr.push(item)
}
})
this.dict[name] = Object.freeze(arr)
store.commit("dict/SET_DICTS", {
label: name,
value: Object.freeze(arr),
});
}
});
})
}
}
const install = function (Vue) {
Vue.mixin({
data() {
if (
this.$options.dicts instanceof Array &&
this.$options.dicts.length > 0
) {
return {
dict: {}
};
} else {
return {};
}
},
created() {
if (
this.$options.dicts instanceof Array &&
this.$options.dicts.length > 0
) {
new Dict(this.dict).init(this.$options.dicts);
}
},
});
};
export default {
install
};
<template>
<el-form :inline="true" :model="form">
<el-form-item
item.label="展览名称"
v-for="(item, index) in config"
:key="index"
>
<el-input
v-if="item.type == 'input'"
v-model="form[item.prop]"
:placeholder="item.placeholder || item.label"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="search" icon="el-icon-search">
查询</el-button
>
<el-button type="primary" @click="reset" icon="el-icon-refresh">
重置</el-button
>
</el-form-item>
</el-form>
</template>
<script>
export default {
name: "SearchBar",
props: {
form: {
type: Object,
default: () => ({}),
},
config: {
type: Array,
default: () => [{}],
},
/**
*
* form:{
name:'',
status:''
}
config: [
{
prop: "name",
type: "input",
label: "展览名称",
},
{
prop: "status",
type: "select",
label: "状态",
selectOptions: [
{
label: "在线",
value: "true",
},
{
label: "离线",
value: "false",
},
],
},
],
*
*
*/
},
data() {
return {};
},
methods: {
search() {
this.$emit("search");
},
reset() {
this.$emit("reset");
},
},
};
</script>
<style>
</style>
\ No newline at end of file
<template>
<!-- <el-button-group>
<el-button
v-for="(op, index) in operations"
:key="index"
size="mini"
:icon="icons[op.type]"
@click="clickOperation(op)"
:type="op.type == 'delete' ? 'danger' : 'primary'"
>
<span v-if="op.type == 'delete'">
<el-popconfirm title="确定删除当前文物吗?">
<span slot="reference"> {{ op.title }}</span>
</el-popconfirm>
</span>
<span v-else>{{ op.title }}</span>
</el-button>
</el-button-group> -->
<span>
<span v-for="(op, index) in operations" :key="index">
<span v-if="op.type == 'delete'">
<el-popconfirm title="确定删除吗?">
<el-button
type="danger"
size="mini"
slot="reference"
:icon="icons[op.type]"
@click="clickOperation(op)"
style="margin-right: 4px"
>
{{ op.title }}</el-button
>
</el-popconfirm>
</span>
<el-button
size="mini"
:icon="icons[op.type]"
@click="clickOperation(op)"
type="primary"
style="margin-right: 4px"
v-else
>{{ op.title }}
</el-button>
</span>
</span>
</template>
<script>
export default {
name: "TableComponent",
props: {
operations: {
type: Array,
},
rawData: {
type: Object,
},
},
data() {
return {
icons: {
edit: "el-icon-edit",
view: "el-icon-view",
delete: "el-icon-delete",
},
};
},
methods: {
clickOperation(operation) {
this.$emit("handleOperation", operation, this.rawData);
},
},
};
</script>
<!-- -->
<template>
<el-table
:data="data"
class="mt-10"
fit
stripe
border
empty-text="暂无数据"
:highlight-current-row="true"
@selection-change="handleSelectionChange"
>
<el-table-column v-if="hasSelection" type="selection" width="55" />
<el-table-column
v-for="(item, index) in tableTitle"
:key="index"
:prop="item.prop"
:label="item.label"
:width="item.width ? item.width : null"
:min-width="item.minwidth ? item.minwidth : null"
:sortable="item.sortable ? item.sortable : false"
:align="item.columnAlign"
:header-align="item.titleAlign"
>
<template slot-scope="scope">
<template v-if="item.tag">
<slot name="tags" :scope="scope.row"></slot>
</template>
<template v-if="item.isStatus">
<slot name="status" :scope="scope.row"></slot>
</template>
<span v-else>{{ scope.row[item.prop] }}</span>
</template>
</el-table-column>
<!-- 如果需要自定义最后一栏则需传入operates对象,并提供模板 -->
<el-table-column
v-if="operates.operate"
:label="operates.label"
:width="operates.width"
:align="operates.columnAlign"
:header-align="operates.titleAlign"
>
<template slot-scope="scope">
<slot name="operates" :scope="scope"></slot>
</template>
</el-table-column>
</el-table>
</template>
<script>
export default {
props: {
data: {
type: Array,
default: () => [],
},
tableTitle: {
type: Array,
default: () => [],
},
operates: {
type: Object,
default: () => ({}),
},
hasSelection: {
type: Boolean,
default: false,
},
},
methods: {
handleSelectionChange(val) {
if (this.hasSelection) {
this.$emit("handleSelectionChange", val);
}
},
},
};
</script>
<style lang="scss" scoped>
</style>
<template>
<div class="images-list">
<el-upload
:action="uploadUrl"
:before-upload="handleBeforeUpload"
:on-success="handleSuccess"
:on-error="handleUploadError"
:on-remove="handleRemove"
:on-exceed="handleExceed"
:on-change="handleChange"
:file-list="fileList"
:multiple="fileLimit > 1"
:headers="headers"
:limit="fileLimit"
:list-type="listType"
name="files"
:class="{ disabled: uploadDisabled }"
>
<i v-if="listType === 'picture-card'" class="el-icon-plus"></i>
<el-button v-else size="small" type="primary">点击上传</el-button>
<div v-if="showTip" slot="tip" class="el-upload__tip">
只能上传{{ fileTypeName || "jpg/png" }}文件,且不超过 {{ fileSize }}MB
</div>
</el-upload>
</div>
</template>
<script>
import { getToken } from "@/utils/auth";
export default {
name: "Uploader",
props: {
// 值
value: [String, Object, Array],
// 大小限制(MB)
fileSize: {
type: Number,
default: 5,
},
// 文件类型, 例如["doc", "xls", "ppt", "txt", "pdf"]
fileType: {
type: Array,
default: () => ["png", "jpg", "jpeg"],
},
// 文件列表类型 text/picture/picture-card
listType: {
type: String,
default: "picture",
},
// 是否显示提示
isShowTip: {
type: Boolean,
default: true,
},
// 最大允许上传个数
fileLimit: {
type: Number,
default: 99,
},
},
data() {
return {
uploadUrl:
process.env.NODE_ENV === "test" ||
process.env.NODE_ENV === "development"
? "/api/sysFiles/upload"
: process.env.NODE_ENV + "/sysFiles/upload", // 上传的图片服务器地址
headers: {
authorization: getToken(),
},
fileList: [],
tempFileList: [],
uploadDisabled: false,
};
},
watch: {
value: {
handler: function (newVal, oldVa) {
this.tempFileList = newVal;
},
immediate: true,
deep: true,
},
},
computed: {
// 是否显示提示
showTip() {
return this.isShowTip && (this.fileType || this.fileSize);
},
fileTypeName() {
let typeName = "";
this.fileType.forEach((item) => {
typeName += `${item},`;
});
return typeName;
},
fileAccept() {
let fileAccept = "";
this.fileType.forEach((element) => {
fileAccept += `.${element},`;
});
return fileAccept;
},
},
created() {
this.fileList = JSON.parse(JSON.stringify(this.value));
},
methods: {
// 上传前校检格式和大小
handleBeforeUpload(file) {
// 校检文件类型
if (this.fileType && file) {
let fileExtension = "";
if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
}
const isTypeOk = this.fileType.some((type) => {
if (file.type.indexOf(type) > -1) return true;
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
return false;
});
if (!isTypeOk & file) {
this.$message.error(
`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`
);
return false;
}
}
// 校检文件大小
if (this.fileSize && file) {
const isLt = file.size / 1024 / 1024 < this.fileSize;
if (!isLt) {
this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`);
return false;
}
}
return true;
},
handleUploadError(err) {
this.$message.error("上传失败, 请重试");
},
// 文件个数超出
handleExceed() {
this.$message.error(`超出上传文件个数,请删除以后再上传!`);
},
// 文件上传成功的钩子
handleSuccess(res, file, fileList) {
console.log("res", res);
this.$message.success("上传成功");
this.changeFileList(fileList);
},
// 文件列表移除文件时的钩子
handleRemove(file, fileList) {
this.changeFileList(fileList);
},
handleChange(file, fileList) {
if (fileList.length == this.fileLimit) {
this.uploadDisabled = true;
}
},
// 文件列表改变的时候,更新组件的v-model的文的数据
changeFileList(fileList) {
console.log("fileList", fileList);
const tempFileList = fileList.map((item) => {
let tempItem = {
name: item.name,
url: item.response ? item.response.data[0].url : item.url,
};
return tempItem;
});
// this.$emit("handleFileReady", tempFileList);
},
},
};
</script>
<style lang="scss" >
// .images-list{
// border: 1px dashed #d5d5d5;
// padding: 10px;
// border-radius: 4px;
// background: #fff;
// }
.disabled .el-upload--picture-card {
display: none !important;
}
</style>
\ No newline at end of file
<template>
<div class="navbar">
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<hamburger
:is-active="sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar"
/>
<breadcrumb class="breadcrumb-container" />
<div class="right-menu">
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
<img v-if="avatar" :src="avatar" class="user-avatar" />
<img v-else src="@/assets/imgs/avatar.png" class="user-avatar" />
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
<router-link to="/">
<el-dropdown-item>
Home
</el-dropdown-item>
<el-dropdown-item>主页</el-dropdown-item>
</router-link>
<a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
<el-dropdown-item>Github</el-dropdown-item>
</a>
<a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
<el-dropdown-item>Docs</el-dropdown-item>
</a>
<el-dropdown-item divided @click.native="logout">
<span style="display:block;">Log Out</span>
<span style="display: block">退出</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
......@@ -32,31 +29,28 @@
</template>
<script>
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger'
import { mapGetters } from "vuex";
import Breadcrumb from "@/components/Breadcrumb";
import Hamburger from "@/components/Hamburger";
export default {
components: {
Breadcrumb,
Hamburger
Hamburger,
},
computed: {
...mapGetters([
'sidebar',
'avatar'
])
...mapGetters(["sidebar", "avatar"]),
},
methods: {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
this.$store.dispatch("app/toggleSideBar");
},
async logout() {
await this.$store.dispatch('user/logout')
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
}
}
}
await this.$store.dispatch("user/logout");
this.$router.push(`/login?redirect=${this.$route.fullPath}`);
},
},
};
</script>
<style lang="scss" scoped>
......@@ -65,18 +59,18 @@ export default {
overflow: hidden;
position: relative;
background: #fff;
box-shadow: 0 1px 4px rgba(0,21,41,.08);
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background .3s;
-webkit-tap-highlight-color:transparent;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, .025)
background: rgba(0, 0, 0, 0.025);
}
}
......@@ -103,10 +97,10 @@ export default {
&.hover-effect {
cursor: pointer;
transition: background .3s;
transition: background 0.3s;
&:hover {
background: rgba(0, 0, 0, .025)
background: rgba(0, 0, 0, 0.025);
}
}
}
......
......@@ -4,7 +4,6 @@ import 'normalize.css/normalize.css' // A modern alternative to CSS resets
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import locale from 'element-ui/lib/locale/lang/en' // lang i18n
import '@/styles/index.scss' // global css
......@@ -14,23 +13,9 @@ import router from './router'
import '@/icons' // icon
import '@/permission' // permission control
import dict from '@/components/Dict'
Vue.use(dict);
/**
* If you don't want to use mock-server
* you want to use MockJs for mock api
* you can execute: mockXHR()
*
* Currently MockJs will be used in the production environment,
* please remove it before going online ! ! !
*/
if (process.env.NODE_ENV === 'production') {
const { mockXHR } = require('../mock')
mockXHR()
}
// set ElementUI lang to EN
// Vue.use(ElementUI, { locale })
// 如果想要中文版 element-ui,按如下方式声明
Vue.use(ElementUI)
Vue.config.productionTip = false
......
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import {
Message
} from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import {
getToken
} from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
NProgress.configure({
showSpinner: false
}) // NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
router.beforeEach(async (to, from, next) => {
// start progress bar
NProgress.start()
......@@ -23,26 +29,30 @@ router.beforeEach(async(to, from, next) => {
if (hasToken) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({ path: '/' })
next({
path: '/'
})
NProgress.done()
} else {
const hasGetUserInfo = store.getters.name
if (hasGetUserInfo) {
next()
} else {
try {
// get user info
await store.dispatch('user/getInfo')
next()
NProgress.done()
// const hasGetUserInfo = store.getters.name
// if (hasGetUserInfo) {
// next()
// } else {
// try {
// // get user info
// await store.dispatch('user/getInfo')
next()
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
// next()
// } catch (error) {
// // remove token and go to login page to re-login
// await store.dispatch('user/resetToken')
// Message.error(error || 'Has Error')
// next(`/login?redirect=${to.path}`)
// NProgress.done()
// }
// }
}
} else {
/* has no token*/
......
module.exports = {
title: 'Vue Admin Template',
title: '贵州省精品展览展示系统',
/**
* @type {boolean} true | false
......
......@@ -3,6 +3,7 @@ const getters = {
device: state => state.app.device,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name
name: state => state.user.name,
dicts: state => state.dict.dicts
}
export default getters
......@@ -4,6 +4,7 @@ import getters from './getters'
import app from './modules/app'
import settings from './modules/settings'
import user from './modules/user'
import dict from './modules/dict'
Vue.use(Vuex)
......@@ -11,7 +12,8 @@ const store = new Vuex.Store({
modules: {
app,
settings,
user
user,
dict
},
getters
})
......
const state = {
dicts: {},
};
const mutations = {
SET_DICTS(state, obj) {
state.dicts[obj.label] = obj.value;
},
};
export default {
namespaced: true,
state,
mutations,
};
\ No newline at end of file
import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
import {
login,
logout,
getInfo
} from '@/api/user'
import {
getToken,
setToken,
removeToken
} from '@/utils/auth'
import {
resetRouter
} from '@/router'
const getDefaultState = () => {
return {
......@@ -29,13 +39,26 @@ const mutations = {
const actions = {
// user login
login({ commit }, userInfo) {
const { username, password } = userInfo
login({
commit
}, userInfo) {
const {
username,
password,
captcha
} = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
login({
username: username.trim(),
password: password,
captcha: captcha
}).then(response => {
const {
data
} = response
commit('SET_TOKEN', data.accessToken)
commit('SET_NAME', data.username)
setToken(data.accessToken)
resolve()
}).catch(error => {
reject(error)
......@@ -44,16 +67,24 @@ const actions = {
},
// get user info
getInfo({ commit, state }) {
getInfo({
commit,
state
}) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
const {
data
} = response
if (!data) {
return reject('Verification failed, please Login again.')
}
const { name, avatar } = data
const {
name,
avatar
} = data
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
......@@ -65,7 +96,10 @@ const actions = {
},
// user logout
logout({ commit, state }) {
logout({
commit,
state
}) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
removeToken() // must remove token first
......@@ -79,7 +113,9 @@ const actions = {
},
// remove token
resetToken({ commit }) {
resetToken({
commit
}) {
return new Promise(resolve => {
removeToken() // must remove token first
commit('RESET_STATE')
......@@ -94,4 +130,3 @@ export default {
mutations,
actions
}
import Cookies from 'js-cookie'
const TokenKey = 'vue_admin_template_token'
const TokenKey = 'exhibition_token'
export function getToken() {
return Cookies.get(TokenKey)
// return Cookies.get(TokenKey)
return localStorage.getItem(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
return localStorage.setItem(TokenKey,token)
// return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
// return Cookies.remove(TokenKey)
localStorage.removeItem(TokenKey)
}
......@@ -115,3 +115,53 @@ export function param2Obj(url) {
})
return obj
}
/**
* 函数防抖
* @param fn
* @param delay
* @returns {Function}
* @constructor
*/
export const Debounce = (fn, t) => {
let delay = t || 500;
let timer;
return function() {
let args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
timer = null;
fn.apply(this, args);
}, delay);
}
};
/**
1. 函数节流
2. @param fn
3. @param interval
4. @returns {Function}
5. @constructor
*/
export const Throttle = (fn, t) => {
let last;
let timer;
let interval = t || 500;
return function() {
let args = arguments;
let now = +new Date();
if (last && now - last < interval) {
clearTimeout(timer);
timer = setTimeout(() => {
last = now;
fn.apply(this, args);
}, interval);
} else {
last = now;
fn.apply(this, args);
}
}
}
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import {
MessageBox,
Message
} from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import {
getToken
} from '@/utils/auth'
// create an axios instance
const service = axios.create({
// baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
baseURL:'/api',
timeout: 5000 // request timeout
baseURL: '/api',
timeout: 5000,
// headers: {
// 'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;',
// 'Content-Type': 'image/gif'
// }
})
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
if (getToken()) {
// let each request carry token
// ['X-Token'] is a custom headers key
// please modify it according to the actual situation
config.headers['X-Token'] = getToken()
config.headers['authorization'] = getToken()
}
return config
},
......@@ -27,6 +36,7 @@ service.interceptors.request.use(
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
......@@ -35,7 +45,7 @@ service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
*/
/**
* Determine the request status by custom code
......@@ -44,21 +54,26 @@ service.interceptors.response.use(
*/
response => {
const res = response.data
// debugger
console.log(res.code);
// console.log('响应拦截res--------',response);
if (response.config.url === '/api/sys/getVerify') {
// debugger
return res
}
// debugger
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
if (res.code !== 0) {
Message({
message: res.message || 'Error',
message: res.msg || 'Error',
type: 'error',
duration: 5 * 1000
})
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
// 401001l令牌过期;500001
if (res.code === 401001 || res.code === 500001) {
MessageBox.confirm('登录令牌已过期,请重新登录', '确认退出', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
......@@ -66,7 +81,20 @@ service.interceptors.response.use(
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
// if (res.code === 50008 || res.code === 50012 || res.code === 50014 || res.code ===401001) {
// // to re-login
// MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
// confirmButtonText: 'Re-Login',
// cancelButtonText: 'Cancel',
// type: 'warning'
// }).then(() => {
// store.dispatch('user/resetToken').then(() => {
// location.reload()
// })
// })
// }
return Promise.reject(new Error(res.msg || 'Error'))
} else {
return res
}
......@@ -74,7 +102,7 @@ service.interceptors.response.use(
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
message: error.msg,
type: 'error',
duration: 5 * 1000
})
......
import axios from 'axios'
import { getToken } from '@/utils/auth'
import {
getToken
} from '@/utils/auth'
/**
* 封装上传文件的post方法
* @param url
......@@ -9,9 +11,9 @@ import { getToken } from '@/utils/auth'
export function upload(url, data) {
return new Promise((resolve, reject) => {
axios.post(url, data, {
headers: {
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': 'Bearer ' + getToken(),
'authorization': getToken(),
}
}).then(response => {
resolve(response)
......@@ -30,10 +32,10 @@ export function upload(url, data) {
export function uploadPut(url, data) {
return new Promise((resolve, reject) => {
axios.put(url, data, {
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': 'Bearer ' + getToken(),
}
headers: {
'Content-Type': 'multipart/form-data',
'authorization': getToken(),
}
}).then(response => {
resolve(response)
}).catch(error => {
......@@ -41,4 +43,3 @@ export function uploadPut(url, data) {
})
})
}
export const list = [{
name: "精品展1",
status: true,
},
{
name: "精品展2",
status: true,
},
{
name: "精品展3",
status: false,
},
]
export const title = [{
prop: "name",
label: "展览名称",
width: 100,
columnAlign: 'center',
},
{
prop: "status",
label: "状态",
width: 100,
columnAlign: 'center',
isStatus: true
},
]
export const operates = {
operate: true,
label: "操作",
minwidth: "120px",
titleAlign: "center",
columnAlign: "center",
}
export const operations = [{
type: 'edit',
title: '编辑'
},
{
type: 'delete',
title: '删除'
},
]
<template>
<div class="app-container">
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
<TablePage
:data="tableData"
:tableTitle="tableTitle"
:operates="tableOperates"
>
<el-table-column align="center" label="ID" width="95">
<template slot-scope="scope">
{{ scope.$index }}
</template>
</el-table-column>
<el-table-column label="Title">
<template slot-scope="scope">
{{ scope.row.title }}
</template>
</el-table-column>
<el-table-column label="Author" width="110" align="center">
<template slot-scope="scope">
<span>{{ scope.row.author }}</span>
</template>
</el-table-column>
<el-table-column label="Pageviews" width="110" align="center">
<template slot-scope="scope">
{{ scope.row.pageviews }}
</template>
</el-table-column>
<el-table-column class-name="status-col" label="Status" width="110" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.status | statusFilter">{{ scope.row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="created_at" label="Display_time" width="200">
<template slot-scope="scope">
<i class="el-icon-time" />
<span>{{ scope.row.display_time }}</span>
</template>
</el-table-column>
</el-table>
<template v-slot:status="data">
<el-popconfirm :title="getStatusTitle(data.scope.status)" @confirm="handleChangeStatus(data.scope.status)">
<el-switch
slot="reference"
:value="data.scope.status"
></el-switch>
</el-popconfirm>
</template>
<template v-slot:operates="scope">
<TableOperation
:operations="tableOperations"
:rawData="scope.scope.row"
@handleOperation="handleOperation"
></TableOperation>
</template>
</TablePage>
</div>
</template>
<script>
import { getList } from '@/api/table'
import TablePage from "@/components/Table/TablePage.vue";
import TableOperation from "@/components/Table/TableOperation.vue";
import { list, title, operates, operations } from "./config";
export default {
filters: {
statusFilter(status) {
const statusMap = {
published: 'success',
draft: 'gray',
deleted: 'danger'
}
return statusMap[status]
}
components: {
TablePage,
TableOperation,
},
data() {
return {
list: null,
listLoading: true
}
return {};
},
created() {
this.fetchData()
computed: {
tableData() {
return list;
},
tableTitle() {
return title;
},
tableOperates() {
return operates;
},
tableOperations() {
return operations;
},
getStatusTitle(status){
return (status)=>{
if (status) {
return '是否确定要下架?'
}else{
return '是否确定要上架?'
}
}
}
},
methods: {
fetchData() {
this.listLoading = true
getList().then(response => {
this.list = response.data.items
this.listLoading = false
})
}
}
}
handleOperation(type, row) {
console.log("handleOperation", type, row);
},
handleChangeStatus(status) {
console.log('status',status);
},
},
};
</script>
<style>
</style>
\ No newline at end of file
......@@ -66,13 +66,13 @@
</el-table-column>
<el-table-column label="操作" width="300" fixed="right">
<template slot-scope="scope">
<el-button
<!-- <el-button
size="mini"
@click="handlePreview(scope.row)"
type="primary"
><i class="el-icon-edit" style="margin-right: 4px"></i
>预览</el-button
>
> -->
<el-button
size="mini"
@click="handleOpenDialog('edit', scope.row)"
......
export const title = [{
prop: "title",
label: "标题",
columnAlign: 'center',
},
{
prop: "keyword",
label: "关键词",
columnAlign: 'center',
},
{
prop: "type",
label: "展览类型",
columnAlign: 'center',
},
{
prop: "dep_id",
label: "展览单位id",
columnAlign: 'center',
},
{
prop: "faceImage",
label: "封面",
columnAlign: 'center',
},
{
prop: "images",
label: "展览图片",
columnAlign: 'center',
},
{
prop: "intro",
label: "展览介绍",
columnAlign: 'center',
},
{
prop: "literature",
label: "关联文献",
columnAlign: 'center',
},
{
prop: "collectCount",
label: "收藏量",
columnAlign: 'center',
},
{
prop: "loveCount",
label: "点赞量",
width: 100,
columnAlign: 'center',
},
{
prop: "region",
label: "所在地区",
width: 100,
columnAlign: 'center',
},
{
prop: "remark",
label: "备注",
width: 100,
columnAlign: 'center',
},
{
prop: "status",
label: "状态",
width: 100,
columnAlign: 'center',
isStatus: true
},
{
prop: "themeType",
label: "模板主题",
width: 100,
columnAlign: 'center',
isStatus: true
},
{
prop: "videos",
label: "展览视频",
width: 100,
columnAlign: 'center',
},
]
export const operates = {
operate: true,
label: "操作",
width: "300px",
minwidth: "220px",
titleAlign: "center",
columnAlign: "center",
}
export const operations = [{
type: 'view',
title: '预览'
},
{
type: 'edit',
title: '编辑'
},
{
type: 'delete',
title: '删除'
},
]
export const literatureTableTitle=[
{
prop: "name",
label: "文献名称",
columnAlign: 'center',
},
{
prop: "authors",
label: "作者",
columnAlign: 'center',
},
{
prop: "remark",
label: "备注",
columnAlign: 'center',
},
]
\ No newline at end of file
<!-- -->
<template>
<div class="display app-container">
<div class="top-bar">
<el-form :inline="true" :model="search" class="demo-form-inline">
<el-form-item label="展览名称">
<el-input v-model="search.name" placeholder="展览名称"></el-input>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="search.status" placeholder="状态">
<el-option label="已上线" :value="1"></el-option>
<el-option label="已下线" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmitSearch">
<i class="el-icon-search"></i>
查询</el-button
>
</el-form-item>
</el-form>
<el-button type="primary" @click.native="handleOpenDialog('add')">
<i class="el-icon-s-promotion"></i>
发布</el-button
>
</div>
<template>
<el-table
ref="multipleTable"
:data="list.record"
tooltip-effect="dark"
style="width: 100%"
border
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55"> </el-table-column>
<el-table-column
prop="theme_type"
label="展览主题"
show-overflow-tooltip
>
</el-table-column>
<el-table-column prop="title" label="展览标题" width="120">
</el-table-column>
<el-table-column prop="type" label="展览类型" show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="character"
label="展览性质"
show-overflow-tooltip
>
</el-table-column>
<el-table-column prop="ragion" label="所在地域" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="dep_id" label="展览单位" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="intro" label="展览介绍" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="status" label="状态" show-overflow-tooltip>
<template slot-scope="scope">
<el-popconfirm
:title="'是否确定' + getStatusLabel(scope.row.status)"
>
<el-switch
v-model="scope.row.status"
@change="handleChangeStatus(scope.row)"
slot="reference"
>
</el-switch>
</el-popconfirm>
</template>
</el-table-column>
<el-table-column label="操作" width="300" fixed="right">
<template slot-scope="scope">
<!-- <el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)"
type="primary"
><i class="el-icon-upload" style="margin-right: 4px"></i
></el-button
> -->
<!-- <el-button
size="mini"
@click="handleEdit(scope.row)"
type="primary"
>
<i class="el-icon-open" style="margin-right: 4px"></i
>上下架</el-button
> -->
<el-button
size="mini"
@click="handlePreview(scope.row)"
type="primary"
><i class="el-icon-edit" style="margin-right: 4px"></i
>预览</el-button
>
<el-button
size="mini"
@click="handleOpenDialog('edit', scope.row)"
type="primary"
><i class="el-icon-edit" style="margin-right: 4px"></i
>编辑</el-button
>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)"
>
<i class="el-icon-delete" style="margin-right: 4px"></i>
删除</el-button
>
</template>
</el-table-column>
</el-table>
</template>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="list.current"
:page-sizes="[100, 200, 300, 400]"
:page-size="list.size"
layout="total, sizes, prev, pager, next, jumper"
:total="list.total"
class="pagination"
>
</el-pagination>
<InfoEditDialog
:visible="drawerVisible"
:form="form"
:cascaderOptions="cascaderOptions"
@handleClose="handleClose"
/>
</div>
</template>
<script>
import { getList } from "@/api/display";
import InfoEditDialog from "./components/InfoEditDialog";
export default {
components: {
InfoEditDialog,
},
data() {
return {
multipleSelection: [],
pickerOptions: {
shortcuts: [
{
text: "最近一周",
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit("pick", [start, end]);
},
},
{
text: "最近一个月",
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
picker.$emit("pick", [start, end]);
},
},
{
text: "最近三个月",
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
picker.$emit("pick", [start, end]);
},
},
],
},
list: {
record: [],
size: 10,
current: 1,
total: 0,
},
search: {
searchTime: [new Date(), new Date()],
name: "",
status: "",
},
drawerVisible: false,
isAdd: true,
form: {
name: "",
startTime: "",
endTime: "",
imgs: [],
audios: [],
videos: [],
},
loading: false,
cascaderOptions: [
{
value: "zhinan",
label: "指南",
children: [
{
value: "shejiyuanze",
label: "设计原则",
children: [
{
value: "yizhi",
label: "一致",
},
{
value: "fankui",
label: "反馈",
},
{
value: "xiaolv",
label: "效率",
},
{
value: "kekong",
label: "可控",
},
],
},
{
value: "daohang",
label: "导航",
children: [
{
value: "cexiangdaohang",
label: "侧向导航",
},
{
value: "dingbudaohang",
label: "顶部导航",
},
],
},
],
},
],
};
},
computed: {
getStatusLabel(status) {
return (status) => {
return status ? "上线" : "下线";
};
},
},
mounted() {
this.loadData();
},
methods: {
// 加载表格数据
async loadData() {
var params = {
current: this.list.current,
size: this.list.size,
};
let res = await getList(params);
this.list = res.data;
},
// 选中、反选
toggleSelection(rows) {
if (rows) {
rows.forEach((row) => {
this.$refs.multipleTable.toggleRowSelection(row);
});
} else {
this.$refs.multipleTable.clearSelection();
}
},
// 多选
handleSelectionChange(val) {
this.multipleSelection = val;
},
// 改变页容量
handleSizeChange() {},
// 改变当前显示页
handleCurrentChange() {},
// 搜索
onSubmitSearch() {
console.log("submit!");
},
// 点击状态
handleChangeStatus(status) {
console.log("handleChangeStatus", status);
},
// 打开drawer
handleOpenDialog(type, value) {
this.drawerVisible = true;
switch (type) {
case "add":
console.log("新增");
break;
case "edit":
console.log("编辑", value);
break;
}
},
// 关闭Dialog
handleClose() {
this.drawerVisible = false;
},
// 预览展览
handlePreview(data) {},
},
};
</script>
<style lang="scss" scoped>
.display {
.top-bar {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.pagination {
margin-top: 10px;
}
}
</style>
<template>
<div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
<el-form
ref="loginForm"
:model="loginForm"
:rules="loginRules"
class="login-form"
auto-complete="on"
label-position="left"
>
<div class="title-container">
<h3 class="title">欢迎登录贵州省精品展览展示管理系统</h3>
</div>
......@@ -34,98 +40,160 @@
name="password"
tabindex="2"
auto-complete="on"
@keyup.enter.native="handleLogin"
/>
<span class="show-pwd" @click="showPwd">
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
<svg-icon
:icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
/>
</span>
</el-form-item>
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">Login</el-button>
<el-row :gutter="10">
<el-col :span="16">
<el-form-item prop="captcha">
<el-input
ref="captcha"
v-model="loginForm.captcha"
placeholder="请输入验证码"
name="captcha"
tabindex="3"
auto-complete="on"
@keyup.enter.native="handleLogin"
/>
</el-form-item>
</el-col>
<el-col :span="8" class="captcha">
<img
v-if="requestCodeSuccess"
style="margin-top: 2px"
:src="captchaImgSrc"
@click="handleGetCaptcha"
/>
<img
v-else
style="margin-top: 2px"
src="@/assets/404_images/checkcode.png"
@click="handleGetCaptcha"
/>
</el-col>
</el-row>
<el-button
:loading="loading"
type="primary"
style="width: 100%; margin-bottom: 30px"
@click.native.prevent="handleLogin"
>登录</el-button
>
</el-form>
</div>
</template>
<script>
import { validUsername } from '@/utils/validate'
import { validUsername } from "@/utils/validate";
import { getVerify } from "@/api/user";
export default {
name: 'Login',
name: "Login",
data() {
const validateUsername = (rule, value, callback) => {
if (!validUsername(value)) {
callback(new Error('Please enter the correct user name'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('The password can not be less than 6 digits'))
} else {
callback()
}
}
// const validateUsername = (rule, value, callback) => {
// if (!validUsername(value)) {
// callback(new Error("请输入用户名"));
// } else {
// callback();
// }
// };
// const validatePassword = (rule, value, callback) => {
// if (value.length < 6) {
// callback(new Error("The password can not be less than 6 digits"));
// } else {
// callback();
// }
// };
return {
loginForm: {
username: 'admin',
password: '111111'
username: "",
password: "",
captcha:''
},
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
username: [
{ required: true, trigger: "blur", message: "请输入用户名" },
],
password: [{ required: true, trigger: "blur", message: "请输入密码" }],
captcha: [{ required: true, trigger: "blur", message: "请输入验证码" } ],
},
loading: false,
passwordType: 'password',
redirect: undefined
}
passwordType: "password",
redirect: undefined,
requestCodeSuccess: false,
captchaImgSrc: "",
};
},
watch: {
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect
handler: function (route) {
this.redirect = route.query && route.query.redirect;
},
immediate: true
}
immediate: true,
},
},
created() {
this.handleGetCaptcha();
},
methods: {
showPwd() {
if (this.passwordType === 'password') {
this.passwordType = ''
if (this.passwordType === "password") {
this.passwordType = "";
} else {
this.passwordType = 'password'
this.passwordType = "password";
}
this.$nextTick(() => {
this.$refs.password.focus()
})
this.$refs.password.focus();
});
},
// 获取验证码
handleGetCaptcha() {
this.currdatetime = new Date().getTime();
getVerify()
.then((res) => {
this.requestCodeSuccess = true;
const imgSrc = window.URL.createObjectURL(res);
this.captchaImgSrc = imgSrc;
})
.catch(() => {
this.requestCodeSuccess = false;
});
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.loading = true
this.$store.dispatch('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
})
this.loading = true;
this.$store
.dispatch("user/login", this.loginForm)
.then(() => {
this.$router.push({ path: this.redirect || "/" });
this.loading = false;
})
.catch(() => {
this.loading = false;
});
} else {
console.log('error submit!!')
return false
console.log("error submit!!");
return false;
}
})
}
}
}
});
},
},
};
</script>
<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
$bg:#283443;
$light_gray:#fff;
$bg: #283443;
$light_gray: #fff;
$cursor: #fff;
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
......@@ -168,9 +236,9 @@ $cursor: #fff;
</style>
<style lang="scss" scoped>
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;
.login-container {
min-height: 100%;
......@@ -228,5 +296,15 @@ $light_gray:#eee;
cursor: pointer;
user-select: none;
}
.captcha {
// width: 100%;
height: 100%;
img {
display: block;
width: 100%;
height: 100%;
}
}
}
</style>
......@@ -44,7 +44,7 @@
</template>
<script>
import { getList } from '@/api/table'
import { getList } from '@/api/dict'
export default {
filters: {
......
......@@ -36,11 +36,9 @@ module.exports = {
warnings: false,
errors: true
},
// before: require('./mock/mock-server.js'),
proxy: {
'/api': {
target: 'http://127.0.0.1:4523/m1/1105343-0-default/',
target: 'http://172.24.100.189:8080',
changeOrigin: true,
pathRewrite: {
'^/api': ''
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论