[add] UI 修改

This commit is contained in:
sdaduanbilei-d1581 2025-09-18 11:54:44 +08:00
parent ef34bdb2db
commit ba4b062cae
35 changed files with 1020 additions and 1204 deletions

View File

@ -7,7 +7,7 @@ export default {
* @returns {Promise | Promise<unknown>}
*/
menus() {
return fetch('/sys/menu/menu')
return fetch('/menu/route')
},
// ++++++++++++++++++++++++++++++++
@ -16,8 +16,8 @@ export default {
* @param params
* @returns {Promise | Promise<unknown>}
*/
clients(params) {
return fetch('/sys/client/list', params)
clientList(params) {
return fetch('/client/getClientList', params,'post','json')
},
/**
@ -25,18 +25,17 @@ export default {
* @param params
* @returns {Promise<unknown>}
*/
clientsSave(params) {
return fetch('/sys/client/submit', params,'post','json')
clientSave(params) {
return fetch('/client/save', params,'post','json')
},
clientUpdate(params) {
return fetch('/client/update', params,'post','json')
},
clientRemove(params) {
return fetch('/client/delete', params,'post','json')
},
/**
* 获取全部客户端列表
* @param params
* @returns {Promise | Promise<unknown>}
*/
clientList() {
return fetch('/sys/client/list')
},
/**
* 客户端详情
@ -54,10 +53,10 @@ export default {
* @returns {Promise | Promise<unknown>}
*/
menuTree(params) {
return fetch('/sys/menu/all/by/appid', params)
return fetch('/menu/getAllMenuTree', params,'post','json')
},
menuRemove(params) {
return fetch('/sys/menu/remove', params, 'post')
return fetch('/menu/delete', params, 'post')
},
/**
* 新鲜或者编辑
@ -65,7 +64,11 @@ export default {
* @returns {Promise | Promise<unknown>}
*/
menuSave(params) {
return fetch('/sys/menu/submit', params, 'post', 'json')
return fetch('/menu/save', params, 'post', 'json')
},
menuUpdate(params) {
return fetch('/menu/update', params, 'post', 'json')
},
// ++++++++++++++++++++++++++++++++
@ -76,10 +79,34 @@ export default {
* @returns {Promise | Promise<unknown>}
*/
roleList(params) {
return fetch('/sys/role/list', params)
return fetch('/role/getRoleList', params,'post','json')
},
roleChange(params) {
roleUpdate(params) {
return fetch('/role/update', params, 'post','json')
},
/**
* 新增 或者更新
* @param params
* @returns {Promise | Promise<unknown>}
*/
roleSave(params) {
return fetch('/role/save', params, 'post', 'json')
},
roleRemove(params) {
return fetch('/role/delete', params, 'post', 'json')
},
roleInfo(params) {
return fetch('/role/detail', params)
},
// ******************** 系统用户管理
staffList(params) {
return fetch('/user/getUserList', params,'post','json')
},
staffUpdate(params) {
return fetch('/user/update', params, 'post','json')
},
@ -88,31 +115,13 @@ export default {
* @param params
* @returns {Promise | Promise<unknown>}
*/
roleSubmit(params) {
return fetch('/sys/role/submit', params, 'post', 'json')
staffSave(params) {
return fetch('/user/save', params, 'post', 'json')
},
// ++++++++++++++++++++++++++++++++
/**
* 字典
* @param params
* @returns {Promise<unknown>}
*/
dictPage(params) {
return fetch('/sys/dict/page', params)
staffRemove(params) {
return fetch('/user/delete', params, 'post', 'json')
},
dictSave(params) {
return fetch('/sys/dict/submit', params, 'post', 'json')
staffInfo(params) {
return fetch('/user/detail', params)
},
dict(params) {
return fetch('/sys/dict/detail', params)
},
dictRemove(params) {
return fetch('/sys/dict/remove', params, 'post')
},
// +============================== log /
logPage(params) {
return fetch('/sys/log/page', params)
}
}

View File

@ -1,13 +1,17 @@
import fetch from '../fetch.js'
export default {
captcha(){
return fetch('/auth/captcha')
},
/**
* 用户登录
* @param params
* @returns {Promise | Promise<unknown>}
*/
login(params) {
return fetch('/user/login', params, 'post','json')
return fetch('/auth/login', params, 'post','json')
},
info(params) {

View File

@ -16,7 +16,7 @@
>
<template v-for="item in list" :key="item.id" @click="goPath(item.id)">
<!-- 有子菜单 -->
<a-sub-menu v-if="item.children && item.children.length" :key="item.id+'@'+item.value">
<a-sub-menu v-if="item.children && item.children.length" :key="item.id+'@'+item.permission">
<template #icon>
<icon-apps />
</template>
@ -24,7 +24,7 @@
<a-menu-item
v-for="sub in item.children"
:key="sub.id+'@'+sub.value"
:key="sub.id+'@'+sub.permission"
style="text-align: start;padding-left: 22px"
>
{{ sub.name }}
@ -32,7 +32,7 @@
</a-sub-menu>
<!-- 没有子菜单首页这种 -->
<a-menu-item v-else :key="item.id+'@'+item.value">
<a-menu-item v-else :key="item.id+'@'+item.permission">
<template #icon>
<icon-apps />
</template>
@ -47,24 +47,25 @@
<a-menu
mode="pop"
:style="{ width: '200px', height: '100%' }"
@menu-item-click="action"
>
<a-sub-menu key="2">
<template #icon>
<a-avatar :image-url="user.avatar" :size="24">
</a-avatar>
</template>
<template #title>{{user.name}}</template>
<a-menu-item key="2_0">
<template #title>{{user.user.account}}</template>
<a-menu-item key="message">
<template #icon>
<icon-message/>
</template>
消息中心</a-menu-item>
<a-menu-item key="2_1">
<a-menu-item key="user">
<template #icon>
<icon-user />
</template>
个人中心</a-menu-item>
<a-menu-item key="2_2">
<a-menu-item key="logout">
<template #icon>
<icon-poweroff />
</template>
@ -96,6 +97,7 @@ export default {
if (tmp) {
this.user = JSON.parse(tmp)
}
console.log(this.user)
},
fetchMenu() {
this.$api.sys.menus().then(res => {
@ -104,7 +106,7 @@ export default {
}
if (this.list.length > 0) {
const path = this.list[0]
this.keys[0] = path.id + "@" + path.value
this.keys[0] = path.id + "@" + path.permission
}
})
@ -113,7 +115,14 @@ export default {
const path = res.split("@")
this.keys[0] = res
this.$router.push(path[1])
}
},
action(type){
if (type === 'logout'){
localStorage.clear()
sessionStorage.clear()
this.$router.push('/login')
}
}
}
}
</script>

View File

@ -39,7 +39,7 @@
</template>
<script>
import addUser from '@/views/base/components/edit-user.vue'
import addUser from '@/views/base/components/edit.vue-user.vue'
import viewUser from '@/views/base/components/view-user.vue'
export default {

View File

@ -101,8 +101,8 @@
</template>
<script>
import editUser from '@/views/base/components/edit-user.vue'
import editRole from '@/views/base/components/edit-role.vue'
import editUser from '@/views/base/components/edit.vue-user.vue'
import editRole from '@/views/base/components/edit.vue-role.vue'
export default {
components: {

View File

@ -27,7 +27,7 @@
</template>
<script>
import editContacts from '@/views/base/customer/components/edit-contacts.vue'
import editContacts from '@/views/base/customer/components/edit.vue-contacts.vue'
export default {
components: {

View File

@ -73,7 +73,7 @@
<script>
import Contacts from '@/views/base/customer/components/contacts.vue'
import editContacts from '@/views/base/customer/components/edit-contacts.vue'
import editContacts from '@/views/base/customer/components/edit.vue-contacts.vue'
export default {
components: { Contacts, editContacts },

View File

@ -42,7 +42,7 @@
<script>
import navbar from '@/components/navbar/index.vue'
import editDept from '@/views/base/components/edit-dept.vue'
import editDept from '@/views/base/components/edit.vue-dept.vue'
import user from './components/user.vue'
export default {

View File

@ -46,7 +46,7 @@
</template>
<script>
import editContract from '@/views/contract/components/edit-contract.vue'
import editContract from '@/views/contract/components/edit.vue-contract.vue'
import projectPicker from '@/views/contract/components/project-picker.vue'
import moreInfo from '@/views/project/index/components/more-info.vue'

View File

@ -14,10 +14,31 @@
</template>
</a-input>
</a-form-item>
<a-form-item hide-label>
<a-input
v-model:visibility="visibility"
v-model="form.captcha"
placeholder="请输入验证码"
@press-enter="login"
allow-clear
>
<template #prefix>
<icon-code />
</template>
<template #append>
<img
:src="captcha"
style="width: 90px"
alt="captcha logo"
@click="fetch"
/>
</template>
</a-input>
</a-form-item>
<a-form-item hide-label>
<a-input-password
v-model:visibility="visibility"
v-model="form.pwd"
v-model="form.password"
placeholder="请输入登录密码"
@press-enter="login"
allow-clear
@ -40,7 +61,7 @@
<div class="bottom">
<div>
Copyright © 2024-{{ new Date().getFullYear() }}
植趣 版权所有
植趣 版权所有
</div>
<div class="mt-10">All Rights Reserved.</div>
</div>
@ -63,19 +84,31 @@ export default {
return {
form: {
account: '',
pwd: ''
password: '',
captcha: ''
},
visibility: true,
redirect: ''
redirect: '',
captcha: '',
captchaId: ''
}
},
mounted() {
this.fetch()
this.redirect = this.$route.query.redirect
},
methods: {
fetch() {
this.$api.user.captcha().then(res => {
if (res.code === 200) {
this.captcha = res.data.captcha
this.captchaId = res.data.captchaId
}
})
},
login() {
if (this.form.account && this.form.pwd) {
const data = { ...this.form }
if (this.form.account && this.form.password && this.form.captcha) {
const data = { ...this.form, captchaId: this.captchaId }
this.$api.user.login(data).then(res => {
if (res.code === 200) {
const user = res.data
@ -85,6 +118,7 @@ export default {
this.$message.success('登录成功')
} else {
this.$message.error(res.msg)
this.fetch()
}
})
} else {
@ -102,6 +136,10 @@ export default {
background-size: cover;
}
:deep(.arco-input-append) {
padding: 0;
}
.login-windows {
background-color: white;
border-radius: 8px;

View File

@ -38,7 +38,7 @@
</template>
<script>
import editContacts from '@/views/base/customer/components/edit-contacts.vue'
import editContacts from '@/views/base/customer/components/edit.vue-contacts.vue'
export default {
components: {

View File

@ -34,7 +34,7 @@
</template>
<script>
import EditContract from '@/views/project/contract/components/edit-contract.vue'
import EditContract from '@/views/project/contract/components/edit.vue-contract.vue'
import attach from "@/views/project/components/attach/index.vue";
export default {

View File

@ -89,7 +89,7 @@
<script>
import task from '@/views/project/index/components/task.vue'
import editTask from '@/views/project/index/components/edit-task.vue'
import editTask from '@/views/project/index/components/edit.vue-task.vue'
import baseInfo from '@/views/project/index/components/base-info.vue'

View File

@ -72,7 +72,7 @@ path: '/project/index',
import navbar from '@/components/navbar/index.vue'
import moreInfo from './components/more-info.vue'
import cityTree from '@/views/project/index/components/city-tree.vue'
import editProject from "@/views/project/index/components/edit-project.vue";
import editProject from "@/views/project/index/components/edit.vue-project.vue";
export default {
components: {

View File

@ -1,84 +0,0 @@
<template>
<div>
<navbar title="应用管理"></navbar>
<div class="container">
<div class="flex flex-center flex-justify-between">
<div>
<add-client @ok="fetchList"/>
</div>
<div>
<a-button type="outline" shape="circle" @click="fetchList">
<icon-refresh />
</a-button>
</div>
</div>
<a-table :columns="columns" :data="list" size="small" class="mt-20">
<template #menu="{ record }">
<div>
<edit-client :id="record.id" @ok="fetchList"/>
</div>
</template>
</a-table>
</div>
</div>
</template>
<script>
import navbar from '@/components/navbar/index.vue'
import editClient from "@/views/system/components/edit-client.vue";
import addClient from "@/views/system/components/add-client.vue";
export default {
components: {
navbar,
addClient,
editClient
},
data() {
return {
list: [],
page: {
page: 0,
size: 10
},
columns: [
{
title: '名称',
dataIndex: 'name'
},
{
title: 'appId',
dataIndex: 'appid'
},{
title: 'secret',
dataIndex: 'secret'
},
{
title: '创建时间',
dataIndex: 'createdAt'
},
{
title: '操作',
slotName: 'menu'
}
]
}
},
mounted() {
this.fetchList()
},
methods: {
fetchList() {
this.$api.sys.clients(this.page).then(res => {
console.log(res)
if (res.code === 200) {
this.list = res.data
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,99 @@
<template>
<div>
<a-button type="text" @click="show = true">{{ type === 'edit'? '编辑' : type === 'addSub' ? '新增子项':'新增'}}</a-button>
<a-modal
v-model:visible="show"
width="640px"
@before-ok="submit"
:mask-closable="false"
@close="this.$refs.form.resetFields()"
>
<a-form :model="form" ref="form" auto-label-width>
<a-form-item
label="名称"
field="name"
:required="[{ required: true, message: '请填写名称' }]"
>
<a-input
placeholder="请输入名称"
v-model="form.name"
></a-input>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
export default {
props: {
type: {
type: String,
default: 'new'
},
info:{
type: Object,
default:null
}
},
watch: {
info:{
handler(val){
if (this.type === 'addSub'){
this.form.parentId = val.id
} else if (this.type === 'edit'){
this.form = val
}
},
immediate: true
}
},
data() {
return {
show: false,
form: {
id:'',
name: '',
sort: 1,
}
}
},
methods: {
submit(done) {
this.$refs.form.validate(errors => {
if (errors === undefined) {
if (this.form.id !== ""){
this.$api.sys.clientUpdate(this.form).then(res => {
if (res.code === 200) {
this.$message.success(res.msg)
this.$emit('ok')
done()
} else {
this.$notification.error(res.msg)
done(false)
}
})
} else {
this.$api.sys.clientSave(this.form).then(res => {
if (res.code === 200) {
this.$message.success(res.msg)
this.$emit('ok')
done()
} else {
this.$message.error(res.msg)
done(false)
}
})
}
} else {
done(false)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,106 @@
<template>
<div>
<navbar title="客户端管理"></navbar>
<div class="container">
<div class="flex flex-center flex-justify-between">
<edit @ok="fetchList" />
</div>
<div class="mt-20">
<a-table :columns="columns" :data="list">
<template #type="{ record }">
<div>
<a-tag color="blue" v-if="record.type === 0"
>菜单
</a-tag>
<a-tag color="red" v-if="record.type === 1"
>按钮
</a-tag>
</div>
</template>
<template #menu="{ record }">
<div class="flex flex-center flex-justify-between">
<div class="flex flex-center flex-justify-start">
<edit type="edit" :info="record" @click="fetchList"></edit>
<div>
<a-popconfirm
content="确认删除?"
@ok="remove(record.id)"
>
<a-button type="text" size="small">
删除
</a-button>
</a-popconfirm>
</div>
</div>
</div>
</template>
</a-table>
</div>
</div>
</div>
</template>
<script>
import navbar from '@/components/navbar/index.vue'
import edit from './componets/edit.vue'
export default {
components: {
navbar,
edit
},
data() {
return {
client: '',
list: [],
options: [],
page:{
current:1,
pageSize:10,
},
index: 0,
columns: [
{ title: '名称', dataIndex: 'name' },
{ title: '路径', dataIndex: 'permission' },
{ title: '排序', dataIndex: 'sort' },
{ title: '操作', slotName: 'menu', width: 260 }
]
}
},
mounted() {
this.fetchList()
},
methods: {
change(e) {
this.client = e
this.index = this.options.findIndex(sub => sub.value === e)
this.fetchList()
},
fetchList() {
const data = {...this.page}
this.$api.sys
.clientList(data)
.then(res => {
if (res.code === 200) {
this.list = res.data.list
} else {
this.$message.error(res.msg)
}
})
},
remove(id) {
this.$api.sys.clientRemove({ ids: [id] }).then(res => {
if (res.code === 200) {
this.$notification.success(res.msg)
this.fetchList()
} else {
this.$notification.error(res.msg)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,51 +0,0 @@
<template>
<div>
<a-button type="text" size="small" @click="show = true">新增</a-button>
<a-modal v-model:visible="show" @before-close="submit">
<a-form ref="form" :model="form" auto-label-width>
<a-form-item label="名称" field="name" :rules="[{required:true,message:'请填写应用名称'}]">
<a-input v-model="form.name" placeholder="请填写应用名称"></a-input>
</a-form-item>
<a-form-item label="配置" field="name" >
<a-textarea :auto-size="{minRows: 5}" placeholder="请填写应用配置" v-model="form.config"></a-textarea>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
export default {
data() {
return {
show: false,
form: {
name:'',
config:''
}
}
},
methods: {
submit(done) {
this.$refs.form.validate(error => {
if (error === undefined){
this.$api.sys.clientsSave(this.form).then(res=>{
if (res.code === 200){
this.$emit('ok')
done()
} else {
this.$notification.error(res.msg)
}
})
} else {
done(false)
}
})
}
},
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -1,139 +0,0 @@
<template>
<div>
<div>
<a-button
v-if="type === 'add'"
type="primary"
size="small"
@click="show = true"
>
<template #icon>
<icon-plus />
</template>
<template #default> 新增</template>
</a-button>
<a-button
v-if="type === 'new'"
type="primary"
size="small"
@click="show = true"
>
<template #icon>
<icon-plus />
</template>
<template #default> 新增</template>
</a-button>
<a-button
v-if="type === 'edit'"
type="text"
size="small"
@click="show = true"
>编辑
</a-button>
<a-button
v-if="type === 'addSub'"
type="text"
size="small"
@click="show = true"
>新增子项
</a-button>
</div>
<a-modal
:mask-closable="false"
v-model:visible="show"
@close="this.$refs.form.resetFields()"
@before-ok="submit"
>
<a-form auto-label-width :model="form" ref="form">
<a-form-item label="名称" field="label" required>
<a-input v-model="form.label"></a-input>
</a-form-item>
<a-form-item label="code" field="code" required>
<a-input
v-model="form.code"
:disabled="form.pid !== '0'"
></a-input>
</a-form-item>
<a-form-item label="值" field="value">
<a-input v-model="form.value"></a-input>
</a-form-item>
<a-form-item label="排序" field="sort" required>
<a-input-number v-model="form.sort"></a-input-number>
</a-form-item>
<a-form-item label="备注" field="remark">
<a-input v-model="form.remark"></a-input>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
export default {
props: {
type: {
type: String,
default: 'add'
},
info: {
type: Object,
default: null
}
},
watch: {
show: {
handler(val) {
if (val) {
if (this.type === 'add' && this.info) {
delete this.form.id
this.form.pid = this.info.id
this.form.code = this.info.code
} else if (this.type === 'addSub') {
this.form.pid = this.info.id
this.form.code = this.info.code
} else if (this.type === 'edit') {
this.form = this.info
}
}
},
immediate: true
}
},
data() {
return {
show: false,
form: {
label: '',
code: '',
remark: '',
sort: '',
pid:'0'
}
}
},
methods: {
submit(done) {
this.$refs.form.validate(errors => {
if (errors === undefined) {
this.$api.sys.dictSave(this.form).then(res => {
if (res.code === 200) {
this.form = res.data
this.$emit('ok')
done()
} else {
this.$notification.error(res.msg)
done(false)
}
})
} else {
done(false)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,84 +0,0 @@
<template>
<div>
<a-button type="text" @click="show = true">新增</a-button>
<a-modal
v-model:visible="show"
:mask-closable="false"
@close="this.$refs.form.resetFields()"
@before-ok="submit"
>
<a-form auto-label-width :model="form" ref="form">
<a-form-item label="名称" field="name" required>
<a-input
placeholder="请输入名称"
v-model="form.name"
></a-input>
</a-form-item>
<a-form-item label="code" field="code" required>
<a-input
placeholder="请输入Code"
v-model="form.code"
></a-input>
</a-form-item>
<a-form-item label="备注" field="remark" required>
<a-input
placeholder="请输入备注"
v-model="form.remark"
></a-input>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
export default {
props: {
appId: {
type: String,
default: ''
}
},
watch: {
appId: {
handler(val) {
this.form.appId = val
},
immediate: true
}
},
data() {
return {
show: false,
form: {
appId: '',
name: '',
code: '',
remark: ''
}
}
},
methods: {
submit(done) {
this.$refs.form.validate(errors => {
if (errors === undefined) {
this.$api.sys.roleSubmit(this.form).then(res => {
if (res.code === 200) {
this.$notification.success(res.msg)
this.$emit('ok')
done()
} else {
this.$notification.error(res.msg)
done(false)
}
})
} else {
done(false)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,93 +0,0 @@
<template>
<div>
<a-button type="text" size="small" @click="show = true">查看</a-button>
<a-drawer v-model:visible="show" width="760px" :footer="false" @before-close="update">
<a-form v-if="form" :model="form" auto-label-width >
<a-form-item label="logo">
<upload @ok="uploadSucc"/>
</a-form-item>
<div>
<a-image :src="pic"></a-image>
</div>
<a-form-item field="name" label="客户端名称">
<a-input v-model="form.name" ></a-input>
</a-form-item>
<a-form-item field="config" label="配置">
<a-textarea v-model="form.config" :auto-size="{minRows: 5}" ></a-textarea>
</a-form-item>
</a-form>
</a-drawer>
</div>
</template>
<script>
import upload from "@/components/upload/index.vue";
export default {
components: {
upload,
},
props: {
id: {
type: Number,
default: 0
},
},
watch: {
show:{
handler(newVal, oldVal) {
if (newVal){
this.fetchInfo()
}
},
immediate: true
}
},
data() {
return {
show: false,
pic:'',
form:null
}
},
methods: {
uploadSucc(res){
this.pic = "/api/file/info?id=" + res.id
},
fetchInfo() {
this.$api.sys.clientInfo({id:this.id}).then(res => {
if (res.code === 200) {
this.form = res.data
}
})
},
update(done){
// config json
if (this.form.config.length > 0){
try {
const parsedConfig = JSON.parse(this.form.config);
if (typeof parsedConfig === 'object' && parsedConfig !== null) {
this.$api.sys.clientsSave(this.form).then(res => {
if (res.code === 200){
this.$emit('ok')
done()
} else {
this.$notification.error(res.msg)
done(false)
}
})
} else {
this.$notification.error("配置必须为JSON 结构")
}
} catch (error) {
this.$notification.error("配置必须为JSON 结构")
}
}
}
},
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -1,147 +0,0 @@
<template>
<div>
<a-button
v-if="type === 'edit'"
type="text"
size="small"
@click="show = true"
>
编辑
</a-button>
<a-button
v-else-if="type === 'addSub'"
type="text"
size="small"
@click="show = true"
>
新增子项
</a-button>
<a-button v-else type="text" size="small" @click="show = true">
新增
</a-button>
<a-modal
v-model:visible="show"
:mask-closable="false"
@close="this.$refs.form.resetFields()"
@before-ok="ok"
>
<div>
<a-form auto-label-width ref="form" :model="form">
<a-form-item
label="名称"
field="name"
:required="[{ required: true, message: '请填写名称' }]"
>
<a-input
v-model="form.name"
placeholder="请填写名称"
></a-input>
</a-form-item>
<a-form-item
label="路径/code"
field="value"
:required="[
{ message: '请输入路径/code', required: true }
]"
>
<a-input
v-model="form.value"
placeholder="请输入路径/code"
></a-input>
</a-form-item>
<a-form-item
label="类型"
:required="[{ message: '请选择类型', required: true }]"
>
<a-radio-group v-model="form.type">
<a-radio :value="0">菜单</a-radio>
<a-radio :value="1">按钮</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
label="排序"
field="sort"
:required="[{ message: '请输入排序', required: true }]"
>
<a-input-number
v-model="form.sort"
placeholder="请输入排序"
></a-input-number>
</a-form-item>
</a-form>
</div>
</a-modal>
</div>
</template>
<script>
export default {
props: {
type: {
required: true,
type: String,
default: 'add'
},
info: {
type: Object,
default: null
}
},
watch: {
info: {
handler(val) {
if (val) {
if (this.type === 'edit') {
this.form = val
} else if (this.type === 'addSub') {
this.form.appid = val.appid
this.form.pid = val.id
} else {
this.form.appid = val.appid
this.form.pid = val.pid
}
}
},
immediate: true
}
},
data() {
return {
show: false,
form: {
appid: '',
id: '',
name: '',
value: '',
sort: '',
type: 0,
pid: '',
remark:''
}
}
},
methods: {
ok(done) {
this.$refs.form.validate(errors => {
if (errors === undefined) {
this.$api.sys.menuSave(this.form).then(res => {
if (res.code === 200) {
this.$notification.success(res.msg)
this.$emit('ok')
done()
} else {
this.$notification.error(res.msg)
done(false)
}
})
} else {
done(false)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,81 +0,0 @@
<template>
<div>
<a-button type="text" @click="show = true">配置</a-button>
<a-modal mask-closable v-model:visible="show" @before-ok="submit">
<a-tree
:checkable="true"
:data="list"
v-model:checked-keys="defaultKeys"
:field-names="{ key: 'id', title: 'name' }"
/>
</a-modal>
</div>
</template>
<script>
export default {
props: {
id: {
required: true,
type: String,
default: ''
},
appId: {
required: true,
type: String,
default: ''
},
menuIds: {
type: String,
default: ''
}
},
watch: {
show: {
handler(val) {
if (val) {
this.fetchMenu()
if (this.menuIds) {
this.defaultKeys = this.menuIds.split(',')
}
}
}
}
},
data() {
return {
show: false,
defaultKeys: null,
list: []
}
},
methods: {
fetchMenu() {
this.$api.sys.menuTree({ appid: this.appId }).then(res => {
if (res.code === 200) {
this.list = res.data.map(e => {
e.key = e.id
return e
})
}
})
},
submit(done) {
const data = { id: this.id, menuIds: this.defaultKeys.join(',') }
console.log(data)
this.$api.sys.roleSubmit(data).then(res => {
if (res.code === 200) {
this.$notification.success(res.msg)
this.$emit('ok')
done()
} else {
this.$notification.error(res.msg)
done(false)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,124 +0,0 @@
<template>
<div>
<navbar title="业务字典" />
<div class="flex flex-align-start">
<div class="container mr-20" style="flex: 1">
<div class="flex flex-center flex-justify-start">
<add-dict @ok="fetchList" type="new" />
</div>
<a-table
class="mt-20"
:columns="columns"
:data="list"
:pagination="page"
@row-click="rowClick"
@page-change="pageChange"
>
<template #label="{ record }">
<div>{{ record.sort }} {{ record.label }}</div>
</template>
</a-table>
</div>
<div class="container" style="flex: 3" v-if="dict">
<div class="flex flex-center flex-justify-between">
<div class="flex flex-center">
<h2>{{ dict.label }}</h2>
<add-dict class="ml-20" :info="dict" type="edit" @click="fetchDetail" />
</div>
<add-dict :info="dict" type="add" @ok="fetchDetail" />
</div>
<a-table class="mt-20" :columns="columns2" :data="list2">
<template #menu="{ record }">
<div class="flex flex-center flex-justify-start">
<add-dict
:info="record"
type="edit"
@ok="fetchDetail"
/>
<add-dict :info="record" type="addSub" />
<a-popconfirm
content="确定删除该数据?"
@ok="remove(record.id)"
>
<a-button type="text" size="small"
>删除
</a-button>
</a-popconfirm>
</div>
</template>
</a-table>
</div>
</div>
</div>
</template>
<script>
import navbar from '@/components/navbar/index.vue'
import addDict from '@/views/system/components/add-dict.vue'
export default {
components: {
navbar,
addDict
},
data() {
return {
page: { page: 1, size: 10, total: 0 },
list: [],
list2: [],
columns: [{ title: '名称', slotName: 'label' }],
columns2: [
{ title: '名称', dataIndex: 'label' },
{ title: '值', dataIndex: 'value' },
{ title: 'code', dataIndex: 'code' },
{ title: '排序', dataIndex: 'sort' },
{ title: '操作', slotName: 'menu' }
],
dict: null,
index: 0
}
},
mounted() {
this.fetchList()
},
methods: {
pageChange(page) {
this.page.page = page
this.fetchList()
},
fetchList() {
this.$api.sys.dictPage(this.page).then(res => {
if (res.code === 200) {
this.list = res.data.records
this.dict = this.list[this.index]
this.fetchDetail()
this.page.total = res.data.total
}
})
},
fetchDetail() {
this.$api.sys.dict({ code: this.dict.code }).then(res => {
if (res.code === 200) {
this.list2 = res.data
}
})
},
rowClick(res) {
this.dict = res
this.index = this.list.findIndex(sub => sub.id === res.id)
this.fetchDetail()
},
remove(id) {
this.$api.sys.dictRemove({ id: id }).then(res => {
if (res.code === 200) {
this.fetchList()
this.$notification.success(res.msg)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,105 +0,0 @@
<template>
<div>
<navbar title="日志中心" />
<div class="container">
<div class="flex flex-center flex-justify-start">
<a-button type="text" size="small" @click="fetchList"
>刷新日志
</a-button>
</div>
<a-table
class="mt-10"
:columns="columns"
:pagination="page"
@page-change="pageChange"
size="small"
:data="list"
>
<template #errorMsg="{ record }">
<div>
<a-tag color="green" v-if="record.errorMsg === null"
>成功
</a-tag>
<a-tag color="red" v-else>失败</a-tag>
</div>
</template>
<template #jsonResult="{ record }">
<div>
<div v-if="record.errorMsg === null">
{{ record.jsonResult }}
</div>
<div v-else class="red">
{{ record.errorMsg }}
</div>
</div>
</template>
</a-table>
</div>
</div>
</template>
<script>
import navbar from '@/components/navbar/index.vue'
export default {
components: {
navbar
},
data() {
return {
page: { page: 0, size: 10 },
columns: [
{
title: '用户',
dataIndex: 'account'
},
{
title: '类型',
dataIndex: 'title'
},
{
title: '客户端',
dataIndex: 'browser'
},
{
title: '地区',
dataIndex: 'location'
},
{
title: '状态',
slotName: 'errorMsg'
},
{
title: '详情',
slotName: 'jsonResult',
width: 680
},
{
title: '时间',
dataIndex: 'createTime'
}
],
list: []
}
},
mounted() {
this.fetchList()
},
methods: {
pageChange(page) {
this.page.page = page - 1
this.fetchList()
},
fetchList() {
this.$api.sys.logPage(this.page).then(res => {
if (res.code === 200) {
this.list = res.data.records
this.page.total = res.data.total
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,136 +0,0 @@
<template>
<div>
<navbar title="菜单管理"></navbar>
<div class="container">
<div class="flex flex-center flex-justify-between">
<a-select
style="width: 360px"
:options="options"
:model-value="client"
@change="change"
></a-select>
<edit-menu
:info="{ appid: client, pid: '0' }"
type="add"
@ok="fetchClientList"
/>
</div>
<div class="mt-20">
<a-table :columns="columns" :data="list">
<template #type="{ record }">
<div>
<a-tag color="blue" v-if="record.type === 0"
>菜单
</a-tag>
<a-tag color="red" v-if="record.type === 1"
>按钮
</a-tag>
</div>
</template>
<template #menu="{ record }">
<div class="flex flex-center flex-justify-start">
<edit-menu
:info="record"
type="addSub"
@ok="fetchClientList"
/>
<edit-menu
:info="record"
type="edit"
@ok="fetchClientList"
/>
<div>
<a-popconfirm
content="确认删除?"
@ok="remove(record.id)"
>
<a-button
type="text"
size="small"
:disabled="
record.value === '/' ||
record.value.indexOf('/system') >
-1 ||
record.children
"
>
删除
</a-button>
</a-popconfirm>
</div>
</div>
</template>
</a-table>
</div>
</div>
</div>
</template>
<script>
import navbar from '@/components/navbar/index.vue'
import editMenu from './components/edit-menu.vue'
export default {
components: {
navbar,
editMenu
},
data() {
return {
client: '',
list: [],
options: [],
index:0,
columns: [
{ title: '名称', dataIndex: 'name' },
{ title: '路径', dataIndex: 'value' },
{ title: '排序', dataIndex: 'sort' },
{ title: '类型', slotName: 'type' },
{ title: '操作', slotName: 'menu', width: 260 }
]
}
},
mounted() {
this.fetchClientList()
},
methods: {
change(e){
this.client = e
this.index = this.options.findIndex(sub => sub.value === e)
this.fetchMenus()
},
fetchClientList() {
this.$api.sys.clientList().then(res => {
if (res.code === 200) {
this.options = res.data.map(e => {
e.label = e.name
e.value = e.appid
return e
})
this.client = this.options[this.index].appid
this.fetchMenus()
}
})
},
fetchMenus() {
this.$api.sys.menuTree({ appid: this.client }).then(res => {
if (res.code === 200) {
this.list = res.data
}
})
},
remove(id) {
this.$api.sys.menuRemove({ ids: id }).then(res => {
if (res.code === 200) {
this.$notification.success(res.msg)
this.fetchClientList()
} else {
this.$notification.error(res.msg)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,117 @@
<template>
<div>
<a-button type="text" @click="show = true">{{ type === 'edit'? '编辑' : type === 'addSub' ? '新增子菜单':'新增菜单'}}</a-button>
<a-modal
v-model:visible="show"
width="640px"
@before-ok="submit"
:mask-closable="false"
@close="this.$refs.form.resetFields()"
>
<a-form :model="form" ref="form" auto-label-width>
<a-form-item
label="名称"
field="name"
:required="[{ required: true, message: '请填写名称' }]"
>
<a-input
placeholder="请输入名称"
v-model="form.name"
></a-input>
</a-form-item>
<a-form-item
label="路径"
field="permission"
:required="[{ required: true, message: '请输入路径' }]"
>
<a-input
placeholder="请输入路径"
v-model="form.permission"
></a-input>
</a-form-item>
<a-form-item label="排序" field="sort" required>
<a-input-number
placeholder="请输入排序"
v-model="form.sort"
></a-input-number>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
export default {
props: {
type: {
type: String,
default: 'new'
},
info:{
type: Object,
default:null
}
},
watch: {
info:{
handler(val){
if (this.type === 'addSub'){
this.form.parentId = val.id
} else if (this.type === 'edit'){
this.form = val
}
},
immediate: true
}
},
data() {
return {
show: false,
form: {
id:'',
name: '',
permission: '',
sort: 1,
category: 0,
parentId: '0'
}
}
},
methods: {
submit(done) {
this.$refs.form.validate(errors => {
if (errors === undefined) {
if (this.form.id !== ""){
this.$api.sys.menuUpdate(this.form).then(res => {
if (res.code === 200) {
this.$notification.success(res.msg)
this.$emit('ok')
done()
} else {
this.$notification.error(res.msg)
done(false)
}
})
} else {
this.$api.sys.menuSave(this.form).then(res => {
if (res.code === 200) {
this.$notification.success(res.msg)
this.$emit('ok')
done()
} else {
this.$notification.error(res.msg)
done(false)
}
})
}
} else {
done(false)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,99 @@
<template>
<div>
<navbar title="菜单管理"></navbar>
<div class="container">
<div class="flex flex-center flex-justify-between">
<edit @ok="fetchMenus"/>
</div>
<div class="mt-20">
<a-table :columns="columns" :data="list">
<template #type="{ record }">
<div>
<a-tag color="blue" v-if="record.type === 0"
>菜单
</a-tag>
<a-tag color="red" v-if="record.type === 1"
>按钮
</a-tag>
</div>
</template>
<template #menu="{ record }">
<div class="flex flex-center flex-justify-between">
<div class="flex flex-center flex-justify-start">
<edit type="addSub" :info="record" @ok="fetchMenus"></edit>
<edit type="edit" :info="record" @ok="fetchMenus"></edit>
<div>
<a-popconfirm
content="确认删除?"
@ok="remove(record.id)"
>
<a-button
type="text"
size="small"
>
删除
</a-button>
</a-popconfirm>
</div>
</div>
</div>
</template>
</a-table>
</div>
</div>
</div>
</template>
<script>
import navbar from '@/components/navbar/index.vue'
import edit from './componets/edit.vue'
export default {
components: {
navbar,
edit
},
data() {
return {
list: [],
columns: [
{ title: '名称', dataIndex: 'name' },
{ title: '路径', dataIndex: 'permission' },
{ title: '排序', dataIndex: 'sort' },
{ title: '操作', slotName: 'menu', width: 260 }
]
}
},
mounted() {
this.fetchMenus()
},
methods: {
change(e){
this.client = e
this.index = this.options.findIndex(sub => sub.value === e)
this.fetchMenus()
},
fetchMenus() {
this.$api.sys.menuTree({ category: 0,parentId:'0' }).then(res => {
if (res.code === 200) {
this.list = res.data
}else {
this.$message.error(res.msg)
}
})
},
remove(id) {
this.$api.sys.menuRemove({ ids: [id] }).then(res => {
if (res.code === 200) {
this.$message.success(res.msg)
this.fetchMenus()
} else {
this.$message.error(res.msg)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,90 +0,0 @@
<template>
<div>
<navbar title="权限管理"></navbar>
<div class="container">
<div class="flex flex-center flex-justify-between">
<a-select
:options="options"
:model-value="client"
style="width: 360px"
@change="change"
></a-select>
<add-role :app-id="client" @ok="fetchClientList" />
</div>
<div class="mt-20">
<a-table :columns="columns" :data="list">
<template #menu="{ record }">
<div>
<edit-role
:app-id="client"
:id="record.id"
:menu-ids="record.menuIds"
@ok="fetchRole"
/>
</div>
</template>
</a-table>
</div>
</div>
</div>
</template>
<script>
import navbar from '@/components/navbar/index.vue'
import addRole from '@/views/system/components/add-role.vue'
import editRole from '@/views/system/components/edit-role.vue'
export default {
components: {
navbar,
addRole,
editRole
},
data() {
return {
client: '',
options: [],
index:0,
list: [],
columns: [
{ title: '名称', dataIndex: 'name' },
{ title: 'code', dataIndex: 'code' },
{ title: '备注', dataIndex: 'remark' },
{ title: '操作', slotName: 'menu' }
]
}
},
mounted() {
this.fetchClientList()
},
methods: {
change(e){
this.client = e
this.index = this.options.findIndex(sub => sub.value === e)
this.fetchClientList()
},
fetchClientList() {
this.$api.sys.clientList().then(res => {
if (res.code === 200) {
this.options = res.data.map(e => {
e.label = e.name
e.value = e.appid
return e
})
this.client = this.options[this.index].appid
this.fetchRole()
}
})
},
fetchRole() {
this.$api.sys.roleList({ appid: this.client }).then(res => {
if (res.code === 200) {
this.list = res.data
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,118 @@
<template>
<div>
<a-button type="text" @click="show = true">{{ type === 'edit'? '编辑' : type === 'addSub' ? '新增子项':'新增'}}</a-button>
<a-modal
v-model:visible="show"
width="640px"
@before-ok="submit"
:mask-closable="false"
@close="this.$refs.form.resetFields()"
>
<a-form :model="form" ref="form" auto-label-width>
<a-form-item
label="名称"
field="name"
:required="[{ required: true, message: '请填写名称' }]"
>
<a-input
placeholder="请输入名称"
v-model="form.name"
></a-input>
</a-form-item>
<a-form-item
label="code"
field="code"
:required="[{ required: true, message: '请输入路径' }]"
>
<a-input
placeholder="请输入路径"
v-model="form.code"
></a-input>
</a-form-item>
<a-form-item label="排序" field="sort" required>
<a-input-number
placeholder="请输入排序"
v-model="form.sort"
></a-input-number>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
export default {
props: {
type: {
type: String,
default: 'new'
},
info:{
type: Object,
default:null
}
},
watch: {
info:{
handler(val){
if (this.type === 'addSub'){
this.form.parentId = val.id
} else if (this.type === 'edit'){
this.form = val
}
},
immediate: true
}
},
data() {
return {
show: false,
form: {
id:'',
name: '',
permission: '',
sort: 1,
code:'',
category: 0,
parentId: '0'
}
}
},
methods: {
submit(done) {
this.$refs.form.validate(errors => {
if (errors === undefined) {
if (this.form.id !== ""){
this.$api.sys.roleUpdate(this.form).then(res => {
if (res.code === 200) {
this.$notification.success(res.msg)
this.$emit('ok')
done()
} else {
this.$notification.error(res.msg)
done(false)
}
})
} else {
this.$api.sys.roleSave(this.form).then(res => {
if (res.code === 200) {
this.$notification.success(res.msg)
this.$emit('ok')
done()
} else {
this.$notification.error(res.msg)
done(false)
}
})
}
} else {
done(false)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,50 @@
<template>
<div>
<a-button type="text" @click="show = true">授权</a-button>
<a-modal v-model:visible="show">
{{list}}
</a-modal>
</div>
</template>
<script>
export default {
props: {
id: {
type: String,
required:true,
default: ''
},
},
data() {
return {
show: false
}
},
watch: {
show:{
handler(val){
if(val){
this.fetchPermission()
}
},
immediate:true
}
},
methods: {
fetchPermission() {
this.$api.sys.roleInfo({id: this.id}).then(res => {
if (res.code === 200){
this.list = res.data
} else {
this.$message.error(res.msg)
}
})
}
},
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,85 @@
<template>
<div>
<navbar title="权限管理" />
<a-card>
<div class="flex flex-center flex-justify-between mb-15">
<edit @ok="fetchList" />
</div>
<a-table :data="list" :columns="columns">
<template #menu="{ record }">
<div class="flex flex-center flex-justify-between">
<div class="flex flex-center flex-justify-start">
<permission :id="record.id"/>
<edit type="edit" :info="record"></edit>
<div>
<a-popconfirm
content="确认删除?"
@ok="remove(record.id)"
>
<a-button type="text" size="small">
删除
</a-button>
</a-popconfirm>
</div>
</div>
</div>
</template>
</a-table>
</a-card>
</div>
</template>
<script>
import navbar from '@/components/navbar/index.vue'
import edit from './components/edit.vue'
import permission from './components/permission.vue'
export default {
components: {
navbar,
edit,
permission
},
data() {
return {
page: {
current: 1,
pageSize: 10
},
list: [],
columns: [
{ title: '名称', dataIndex: 'name' },
{ title: 'code', dataIndex: 'code' },
{ title: '排序', dataIndex: 'sort' },
{ title: '操作', slotName: 'menu', width: 260 }
]
}
},
mounted() {
this.fetchList()
},
methods: {
fetchList() {
const data = { ...this.page }
this.$api.sys.roleList(data).then(res => {
if (res.code === 200) {
this.list = res.data.list
} else {
this.$message.error(res.msg)
}
})
},
remove(id) {
this.$api.sys.roleRemove({ ids: [id] }).then(res => {
if (res.code === 200) {
this.$message.success(res.msg)
this.fetchList()
} else {
this.$message.error(res.msg)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,110 @@
<template>
<div>
<a-button type="text" @click="show = true">{{ type === 'edit'? '编辑' : type === 'addSub' ? '新增子项':'新增'}}</a-button>
<a-modal
v-model:visible="show"
width="640px"
@before-ok="submit"
:mask-closable="false"
@close="this.$refs.form.resetFields()"
>
<a-form :model="form" ref="form" auto-label-width>
<a-form-item
label="账户"
field="account"
:required="[{ required: true, message: '请填写账户' }]"
>
<a-input
placeholder="请输入账户"
v-model="form.account"
></a-input>
</a-form-item>
<a-form-item
label="账户"
field="phone"
:required="[{ required: true, message: '请填写电话' }]"
>
<a-input
placeholder="请输入电话"
v-model="form.phone"
></a-input>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
export default {
props: {
type: {
type: String,
default: 'new'
},
info:{
type: Object,
default:null
}
},
watch: {
info:{
handler(val){
if (this.type === 'addSub'){
this.form.parentId = val.id
} else if (this.type === 'edit'){
this.form = val
}
},
immediate: true
}
},
data() {
return {
show: false,
form: {
id:'',
name: '',
phone:''
}
}
},
methods: {
submit(done) {
this.$refs.form.validate(errors => {
if (errors === undefined) {
if (this.form.id !== ""){
this.$api.sys.staffUpdate(this.form).then(res => {
if (res.code === 200) {
this.$message.success(res.msg)
this.$emit('ok')
done()
} else {
this.$notification.error(res.msg)
done(false)
}
})
} else {
this.$api.sys.staffSave(this.form).then(res => {
if (res.code === 200) {
this.$message.success(res.msg)
this.$emit('ok')
done()
} else {
this.$message.error(res.msg)
done(false)
}
})
}
} else {
done(false)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,105 @@
<template>
<div>
<navbar title="客户端管理"></navbar>
<div class="container">
<div class="flex flex-center flex-justify-between">
<edit @ok="fetchList" />
</div>
<div class="mt-20">
<a-table :columns="columns" :data="list">
<template #type="{ record }">
<div>
<a-tag color="blue" v-if="record.type === 0"
>菜单
</a-tag>
<a-tag color="red" v-if="record.type === 1"
>按钮
</a-tag>
</div>
</template>
<template #menu="{ record }">
<div class="flex flex-center flex-justify-between">
<div class="flex flex-center flex-justify-start">
<edit type="edit" :info="record" @click="fetchList"></edit>
<div>
<a-popconfirm
content="确认删除?"
@ok="remove(record.id)"
>
<a-button type="text" size="small">
删除
</a-button>
</a-popconfirm>
</div>
</div>
</div>
</template>
</a-table>
</div>
</div>
</div>
</template>
<script>
import navbar from '@/components/navbar/index.vue'
import edit from './componets/edit.vue'
export default {
components: {
navbar,
edit
},
data() {
return {
client: '',
list: [],
options: [],
page:{
current:1,
pageSize:10,
},
index: 0,
columns: [
{ title: '账户', dataIndex: 'account' },
{ title: '电话', dataIndex: 'phone' },
{ title: '角色', dataIndex: 'roles' },
{ title: '操作', slotName: 'menu', width: 260 }
]
}
},
mounted() {
this.fetchList()
},
methods: {
change(e) {
this.client = e
this.index = this.options.findIndex(sub => sub.value === e)
this.fetchList()
},
fetchList() {
const data = {...this.page}
this.$api.sys
.staffList(data)
.then(res => {
if (res.code === 200) {
this.list = res.data.list
} else {
this.$message.error(res.msg)
}
})
},
remove(id) {
this.$api.sys.staffRemove({ ids: [id] }).then(res => {
if (res.code === 200) {
this.$notification.success(res.msg)
this.fetchList()
} else {
this.$notification.error(res.msg)
}
})
}
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -46,7 +46,8 @@ export default defineConfig({
proxy: {
'/api': {
// 正式环境地址
target: 'https://prod.wutongshucloud.com/api',
// target: 'https://prod.wutongshucloud.com/api',
target: 'http://129.28.103.17:3411/api',
// target: 'http://127.0.0.1:3000',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')