- 恢复 permission.js 路由守卫,未登录访问自动跳转登录页 - 登录、用户信息、注销接口由 mock 路径改为后端 /auth 接口 - 请求拦截器恢复 X-Token 请求头注入,并增加 40101 登录失效处理 - 登录页移除模板自带的演示账号提示与默认填充值,标题改为千帆公众号管理后台 - 用户名校验由白名单 admin/editor 改为非空校验 - 移除生产环境构建时挂载的 mockXHR,避免劫持真实请求 - 新增 api/template.js 封装模板配置的查询、保存、删除、刷新缓存与微信模板拉取接口 - 新增模板管理页面,支持关键字筛选、新增编辑、删除、刷新缓存及从微信拉取模板自动解析关键字 - 路由新增模板管理菜单项qianfan/wechat/gzh/fwh/hq
parent
89eaec3795
commit
4b56e05536
10 changed files with 415 additions and 107 deletions
@ -0,0 +1,62 @@ |
||||
import request from '@/utils/request' |
||||
|
||||
/** |
||||
* 模板列表 |
||||
* @param data { onlyEnabled, keyword } |
||||
*/ |
||||
export function page(data) { |
||||
return request({ |
||||
url: '/template/list', |
||||
data |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* 模板详情 |
||||
*/ |
||||
export function detail(id) { |
||||
return request({ |
||||
url: `/template/detail/${id}`, |
||||
method: 'get' |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* 新增或更新模板 |
||||
*/ |
||||
export function save(data) { |
||||
return request({ |
||||
url: '/template/save', |
||||
data |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* 删除模板 |
||||
*/ |
||||
export function remove(id) { |
||||
return request({ |
||||
url: `/template/delete/${id}`, |
||||
data: {} |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* 刷新发送缓存 |
||||
*/ |
||||
export function refresh() { |
||||
return request({ |
||||
url: '/template/refresh', |
||||
data: {} |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* 从微信拉取当前公众号的模板列表 |
||||
*/ |
||||
export function wechatList() { |
||||
return request({ |
||||
url: '/template/wechat/list', |
||||
data: {} |
||||
}) |
||||
} |
||||
@ -1,64 +1,64 @@ |
||||
// import router from './router'
|
||||
// import store from './store'
|
||||
// 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 getPageTitle from '@/utils/get-page-title'
|
||||
//
|
||||
// NProgress.configure({ showSpinner: false }) // NProgress Configuration
|
||||
//
|
||||
// const whiteList = ['/login'] // no redirect whitelist
|
||||
//
|
||||
// router.beforeEach(async(to, from, next) => {
|
||||
// // start progress bar
|
||||
// NProgress.start()
|
||||
//
|
||||
// // set page title
|
||||
// document.title = getPageTitle(to.meta.title)
|
||||
//
|
||||
// // determine whether the user has logged in
|
||||
// const hasToken = getToken()
|
||||
//
|
||||
// if (hasToken) {
|
||||
// if (to.path === '/login') {
|
||||
// // if is logged in, redirect to the home page
|
||||
// next({ path: '/' })
|
||||
// NProgress.done()
|
||||
// } else {
|
||||
// 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()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// /* has no token*/
|
||||
//
|
||||
// if (whiteList.indexOf(to.path) !== -1) {
|
||||
// // in the free login whitelist, go directly
|
||||
// next()
|
||||
// } else {
|
||||
// // other pages that do not have permission to access are redirected to the login page.
|
||||
// next(`/login?redirect=${to.path}`)
|
||||
// NProgress.done()
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// router.afterEach(() => {
|
||||
// // finish progress bar
|
||||
// NProgress.done()
|
||||
// })
|
||||
import router from './router' |
||||
import store from './store' |
||||
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 getPageTitle from '@/utils/get-page-title' |
||||
|
||||
NProgress.configure({ showSpinner: false }) // NProgress Configuration
|
||||
|
||||
const whiteList = ['/login'] // no redirect whitelist
|
||||
|
||||
router.beforeEach(async(to, from, next) => { |
||||
// start progress bar
|
||||
NProgress.start() |
||||
|
||||
// set page title
|
||||
document.title = getPageTitle(to.meta.title) |
||||
|
||||
// determine whether the user has logged in
|
||||
const hasToken = getToken() |
||||
|
||||
if (hasToken) { |
||||
if (to.path === '/login') { |
||||
// if is logged in, redirect to the home page
|
||||
next({ path: '/' }) |
||||
NProgress.done() |
||||
} else { |
||||
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() |
||||
} |
||||
} |
||||
} |
||||
} else { |
||||
/* has no token*/ |
||||
|
||||
if (whiteList.indexOf(to.path) !== -1) { |
||||
// in the free login whitelist, go directly
|
||||
next() |
||||
} else { |
||||
// other pages that do not have permission to access are redirected to the login page.
|
||||
next(`/login?redirect=${to.path}`) |
||||
NProgress.done() |
||||
} |
||||
} |
||||
}) |
||||
|
||||
router.afterEach(() => { |
||||
// finish progress bar
|
||||
NProgress.done() |
||||
}) |
||||
|
||||
@ -0,0 +1,256 @@ |
||||
<template> |
||||
<div class="app-container full-height"> |
||||
<!-- 查询条件 --> |
||||
<el-card class="filter-container" shadow="never"> |
||||
<el-form |
||||
label-width="110px" |
||||
:inline="true" |
||||
:model="condition" |
||||
@submit.native.prevent |
||||
@keyup.enter.native="conditionQuery" |
||||
> |
||||
<el-form-item label="关键字" prop="keyword"> |
||||
<el-input v-model="condition.keyword" placeholder="业务键 / 名称 / 模板ID" clearable /> |
||||
</el-form-item> |
||||
<el-form-item label="只看启用"> |
||||
<el-switch v-model="condition.onlyEnabled" /> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button type="primary" @click="conditionQuery">查询</el-button> |
||||
</el-form-item> |
||||
<el-form-item> |
||||
<el-button @click="handleResetCondition">重置</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
</el-card> |
||||
|
||||
<!-- 表格操作 --> |
||||
<el-card class="table-operate-container" shadow="never"> |
||||
<el-button type="primary" size="mini" icon="el-icon-plus" @click="handleAdd">新增模板</el-button> |
||||
<el-button size="mini" icon="el-icon-refresh" @click="handleRefreshCache">刷新缓存</el-button> |
||||
<el-button size="mini" icon="el-icon-download" @click="handlePullFromWechat">从微信拉取</el-button> |
||||
</el-card> |
||||
|
||||
<!-- 表格 --> |
||||
<div class="table-container"> |
||||
<el-table v-loading="loading" :data="tableData" border style="width: 100%"> |
||||
<el-table-column type="index" label="序号" width="60" align="center" /> |
||||
<el-table-column property="biz_key" label="业务键" width="140" align="center"> |
||||
<template slot-scope="scope"> |
||||
<span>{{ scope.row.biz_key || '-' }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column property="title" label="名称" width="170" align="center"> |
||||
<template slot-scope="scope"> |
||||
<span>{{ scope.row.title || '-' }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column property="template_id" label="模板ID" min-width="30%" align="center" /> |
||||
<el-table-column property="keywords" label="关键字" min-width="22%" align="center" /> |
||||
<el-table-column label="状态" width="90" align="center"> |
||||
<template slot-scope="scope"> |
||||
<el-tag :type="scope.row.enabled === 1 ? 'success' : 'info'" size="mini"> |
||||
{{ scope.row.enabled === 1 ? '启用' : '停用' }} |
||||
</el-tag> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column property="updated_time" label="更新时间" width="170" align="center" /> |
||||
<el-table-column label="操作" width="150" align="center"> |
||||
<template slot-scope="scope"> |
||||
<el-button size="mini" @click.stop="handleEdit(scope.row)">编辑</el-button> |
||||
<el-button type="danger" plain size="mini" @click.stop="handleRemove(scope.row)">删除</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
</div> |
||||
|
||||
<!-- 编辑弹窗 --> |
||||
<el-dialog :title="dialogTitle" :visible.sync="showDialog" width="600px" @closed="handleDialogClosed"> |
||||
<el-form ref="editForm" :model="editForm" :rules="editRules" label-width="100px"> |
||||
<el-form-item label="业务键" prop="biz_key"> |
||||
<el-input v-model="editForm.biz_key" placeholder="业务代码引用的别名,如 order_paid" /> |
||||
</el-form-item> |
||||
<el-form-item label="名称" prop="title"> |
||||
<el-input v-model="editForm.title" placeholder="如 订单下单成功通知" /> |
||||
</el-form-item> |
||||
<el-form-item label="模板ID" prop="template_id"> |
||||
<el-input v-model="editForm.template_id" placeholder="微信侧模板ID" /> |
||||
</el-form-item> |
||||
<el-form-item label="关键字" prop="keywords"> |
||||
<el-input |
||||
v-model="editForm.keywords" |
||||
type="textarea" |
||||
:rows="2" |
||||
placeholder="英文逗号分隔,顺序即组装顺序,如 character_string1,time2,thing5,amount6" |
||||
/> |
||||
</el-form-item> |
||||
<el-form-item label="备注" prop="remark"> |
||||
<el-input v-model="editForm.remark" type="textarea" :rows="2" /> |
||||
</el-form-item> |
||||
<el-form-item label="启用"> |
||||
<el-switch v-model="editForm.enabled" :active-value="1" :inactive-value="0" /> |
||||
</el-form-item> |
||||
</el-form> |
||||
<div slot="footer"> |
||||
<el-button @click="showDialog = false">取消</el-button> |
||||
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button> |
||||
</div> |
||||
</el-dialog> |
||||
|
||||
<!-- 微信模板选择弹窗 --> |
||||
<el-dialog title="从微信拉取模板" :visible.sync="showWechatDialog" width="820px"> |
||||
<el-table v-loading="wechatLoading" :data="wechatTemplates" border max-height="420"> |
||||
<el-table-column property="title" label="名称" width="180" align="center" /> |
||||
<el-table-column property="template_id" label="模板ID" min-width="35%" align="center" /> |
||||
<el-table-column property="keywords" label="解析出的关键字" min-width="25%" align="center" /> |
||||
<el-table-column label="操作" width="100" align="center"> |
||||
<template slot-scope="scope"> |
||||
<el-button size="mini" type="primary" @click="handleUseWechatTemplate(scope.row)">填入</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
</el-dialog> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import { page, save, remove, refresh, wechatList } from '@/api/template' |
||||
|
||||
export default { |
||||
name: 'TemplateManage', |
||||
data() { |
||||
return { |
||||
loading: false, |
||||
saving: false, |
||||
condition: { |
||||
keyword: '', |
||||
onlyEnabled: false |
||||
}, |
||||
tableData: [], |
||||
total: 0, |
||||
showDialog: false, |
||||
dialogTitle: '新增模板', |
||||
editForm: this.getEmptyForm(), |
||||
editRules: { |
||||
template_id: [{ required: true, message: '请填写模板ID', trigger: 'blur' }], |
||||
keywords: [{ required: true, message: '请填写关键字', trigger: 'blur' }] |
||||
}, |
||||
showWechatDialog: false, |
||||
wechatLoading: false, |
||||
wechatTemplates: [] |
||||
} |
||||
}, |
||||
created() { |
||||
this.conditionQuery() |
||||
}, |
||||
methods: { |
||||
getEmptyForm() { |
||||
return { |
||||
id: null, |
||||
biz_key: '', |
||||
title: '', |
||||
template_id: '', |
||||
keywords: '', |
||||
remark: '', |
||||
enabled: 1 |
||||
} |
||||
}, |
||||
conditionQuery() { |
||||
this.loading = true |
||||
page({ |
||||
keyword: this.condition.keyword, |
||||
onlyEnabled: this.condition.onlyEnabled |
||||
}).then(res => { |
||||
const data = res.data || {} |
||||
this.tableData = data.rows || [] |
||||
this.total = data.total || 0 |
||||
}).finally(() => { |
||||
this.loading = false |
||||
}) |
||||
}, |
||||
handleResetCondition() { |
||||
this.condition.keyword = '' |
||||
this.condition.onlyEnabled = false |
||||
this.conditionQuery() |
||||
}, |
||||
handleAdd() { |
||||
this.dialogTitle = '新增模板' |
||||
this.editForm = this.getEmptyForm() |
||||
this.showDialog = true |
||||
}, |
||||
handleEdit(row) { |
||||
this.dialogTitle = '编辑模板' |
||||
this.editForm = Object.assign(this.getEmptyForm(), row) |
||||
this.showDialog = true |
||||
}, |
||||
handleDialogClosed() { |
||||
if (this.$refs.editForm) { |
||||
this.$refs.editForm.clearValidate() |
||||
} |
||||
}, |
||||
handleSave() { |
||||
this.$refs.editForm.validate(valid => { |
||||
if (!valid) return |
||||
this.saving = true |
||||
save(this.editForm).then(() => { |
||||
this.$message.success('保存成功') |
||||
this.showDialog = false |
||||
this.conditionQuery() |
||||
}).finally(() => { |
||||
this.saving = false |
||||
}) |
||||
}) |
||||
}, |
||||
handleRemove(row) { |
||||
this.$confirm(`确认删除模板「${row.title || row.template_id}」?`, '提示', { |
||||
confirmButtonText: '确定', |
||||
cancelButtonText: '取消', |
||||
type: 'warning' |
||||
}).then(() => { |
||||
remove(row.id).then(() => { |
||||
this.$message.success('删除成功') |
||||
this.conditionQuery() |
||||
}) |
||||
}).catch(() => {}) |
||||
}, |
||||
handleRefreshCache() { |
||||
refresh().then(() => { |
||||
this.$message.success('缓存已刷新') |
||||
}) |
||||
}, |
||||
handlePullFromWechat() { |
||||
this.showWechatDialog = true |
||||
this.wechatLoading = true |
||||
wechatList().then(res => { |
||||
this.wechatTemplates = res.data || [] |
||||
}).finally(() => { |
||||
this.wechatLoading = false |
||||
}) |
||||
}, |
||||
handleUseWechatTemplate(row) { |
||||
this.showWechatDialog = false |
||||
this.dialogTitle = '新增模板' |
||||
this.editForm = Object.assign(this.getEmptyForm(), { |
||||
title: row.title, |
||||
template_id: row.template_id, |
||||
keywords: row.keywords |
||||
}) |
||||
this.showDialog = true |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
.app-container { |
||||
padding: 16px; |
||||
} |
||||
|
||||
.filter-container { |
||||
margin-bottom: 12px; |
||||
} |
||||
|
||||
.table-operate-container { |
||||
margin-bottom: 12px; |
||||
} |
||||
</style> |
||||
Loading…
Reference in new issue