构建版本修改

This commit is contained in:
aixianling
2022-08-12 11:20:51 +08:00
parent c3f6fbbffe
commit 98946c1e28
167 changed files with 845 additions and 3473 deletions

View File

@@ -0,0 +1,288 @@
<template>
<section class="AppAccount">
<ai-list>
<ai-title slot="title" title="账号管理" isShowBottomBorder>
<template #rightBtn>
<el-button size="small" type="primary" icon="iconfont iconUpdate_Files" @click="syncDept">同步部门</el-button>
</template>
</ai-title>
<template #left>
<ai-address-book-menu :instance="instance" @select="handleSelect"/>
</template>
<template #content>
<ai-title :title="tableTitle"/>
<ai-search-bar>
<template #left>
<el-button type="primary" :disabled="!ids.toString()" @click="batchAllot">功能分配</el-button>
<el-button size="small" icon="iconfont iconUpdate_Files" @click="syncMembers">同步成员</el-button>
</template>
<template #right>
<el-input size="small" placeholder="搜索姓名、手机号" v-model="search.name" clearable
v-throttle="() => {page.current = 1, getTableData()}"/>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :total="page.total" :current.sync="page.current" :size.sync="page.size"
@getList="getTableData" :col-configs="colConfigs" :dict="dict"
@selection-change="v=>ids=v.filter(e=>e.sysUserId).map(e=>e.sysUserId)">
<el-table-column slot="name" label="姓名" width="180px">
<el-row type="flex" align="middle" slot-scope="{row}">
<el-image class="avatar" :src="row.avatar" :preview-src-list="[row.avatar]">
<el-image slot="error" src="https://cdn.cunwuyun.cn/dvcp/h5/defaultAvatar.png" alt=""/>
</el-image>
<div>{{ row.name }}</div>
</el-row>
</el-table-column>
<el-table-column slot="options" align="center" label="操作" fixed="right" width="160px">
<el-row type="flex" justify="center" align="middle" slot-scope="{row}">
<el-button v-if="$permissions('admin_sysuser_distribute')&&!!row.sysUserId"
type="text" @click="appAllot(row)">功能分配
</el-button>
</el-row>
</el-table-column>
</ai-table>
</template>
</ai-list>
<!--功能分配-->
<ai-dialog title="功能分配" :visible.sync="dialog" width="800px" @open="initDialogData" @onConfirm="updateAccount">
<el-form ref="updateAccountForm" :model="dialogForm" :rules="rules" size="small"
label-width="120px">
<el-form-item required label="角色" prop="roleId">
<el-select size="small" placeholder="请选择角色" :value="dialogForm.roleId" filterable
v-model="dialogForm.roleId" clearable>
<el-option v-for="(op,i) in accountRoles" :key="i" :label="op.name" :value="op.id"/>
</el-select>
</el-form-item>
<el-form-item label="行政地区" prop="areaId">
<ai-area-select v-model="dialogForm.areaId" always-show :instance="instance"
clearable @fullname="v=>dialogForm.areaFullName=v"
@name="v=>dialogForm.areaName=v"
:disabledLevel="disabledLevel"/>
</el-form-item>
<el-form-item label="党组织" prop="organizationId" v-if="user.info.organizationId">
<el-cascader :options="partyOrgOps" v-model="dialogForm.organizationId"
:props="cascaderProps" :show-all-levels="false" clearable/>
</el-form-item>
<!-- <el-form-item label="职务" prop="position">-->
<!-- <el-input placeholder="请输入职务" v-model="dialogForm.position" clearable/>-->
<!-- </el-form-item>-->
</el-form>
</ai-dialog>
</section>
</template>
<script>
import {mapState} from "vuex";
import AiAddressBookMenu from "../../../components/AiAddressBookMenu";
export default {
name: "AppAccount",
components: {AiAddressBookMenu},
label: "账号管理(村微版)",
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
...mapState(['user']),
cascaderProps() {
return {
value: 'id',
checkStrictly: true,
emitPath: false
}
},
partyOrgOps() {
let initData = JSON.parse(JSON.stringify(this.optionsParty)),
ops = initData.filter(e => !e.parentId)
ops.map(e => this.addChild(e, initData))
return ops
},
colConfigs() {
return [
{type: 'selection', align: 'center'},
{label: "姓名", slot: "name"},
{label: "职务", prop: "position", align: 'center', width: "120px"},
{label: "部门", prop: "departmentNames", align: 'center', width: "120px"},
{label: "联系方式", prop: "mobile", align: 'center', width: "120px"},
{label: "账号状态", prop: "status", dict: "wxUserStatus", width: "120px"},
{label: "账号角色", prop: "roleName", width: "120px", align: '120px'},
{label: "地区", prop: "areaName", width: "120px"},
{label: "党组织", prop: "organizationName", align: 'center', width: "120px"},
{slot: "options"}
]
},
tableTitle() {
return this.condition ? this.condition + `(${this.page.total})` : '请选择组织或标签'
},
rules() {
return {
name: [{required: true, message: "请填写姓名"}],
// organizationId: [{required: true, message: "请选择党组织"}],
// unitId: [{required: true, message: "请选择单位"}],
areaId: [{required: true, message: '请选择地区', trigger: 'change'}],
roleId: [{required: true, message: "请选择角色"}],
phone: [{required: true, message: "请输入手机号码"}]
}
},
disabledLevel() {
return this.user.info.areaList?.length || 0
}
},
data() {
return {
condition: "",
accountRoles: [],
page: {current: 1, size: 10, total: 0},
dialog: false,
dialogForm: {},
optionsParty: [],
tableData: [],
search: {name: ""},
ids: [],
lock: false
}
},
methods: {
getTableData() {
this.instance.post("/app/wxcp/wxuser/list", null, {
params: {...this.page, ...this.search}
}).then(res => {
if (res?.data) {
this.tableData = res.data?.records
this.page.total = res.data.total
}
})
},
handleSelect(v) {
if (v.type == 0) {
//选择部门
let {id: departmentId, name} = v
this.condition = name
this.search = {departmentId}
} else if (v.type == 1) {
//选择标签
let {id: tagIds, tagname: name} = v
this.condition = name
this.search = {tagIds}
}
this.page.current = 1
this.getTableData()
},
initDialogData() {
//用于优化初始化数据
this.getAccountRoles()
this.searchSysAll()
},
getAccountRoles() {
this.accountRoles.length == 0 && this.instance.post("/admin/role-acc/list-all").then(res => {
if (res?.data) {
this.accountRoles = res.data
}
})
},
batchAllot() {
this.dialog = true
this.dialogForm = {areaId: this.user.info.areaId, sysUserIds: this.ids}
},
appAllot(row) {
this.dialog = true
this.dialogForm = JSON.parse(JSON.stringify({
...row,
sysUserIds: [row.sysUserId],
areaId: row.areaId || this.user.info.areaId
}));
},
// 获取党组织树形
searchSysAll() {
if (this.user.info.organizationId && this.optionsParty.length == 0) {
this.instance.post('/app/partyOrganization/queryPartyOrganizationServiceList').then((res) => {
if (res?.data) {
res.data = res.data.map(a => {
return {...a, label: a.name}
});
this.optionsParty = res.data.filter(e => !e.parentId)
this.optionsParty.map(t => this.addChild(t, res.data));
}
})
}
},
// 修改
updateAccount() {
this.$refs.updateAccountForm.validate(v => {
if (v) {
if (this.lock) return this.$message.error("请勿多次提交!")
this.lock = true
this.instance.post("/app/wxcp/wxuser/empower", this.dialogForm).then(res => {
this.lock = false
if (res?.code == 0) {
this.dialog = false;
this.$message.success("修改成功")
this.getTableData();
} else {
this.$message.error(res?.msg)
}
}).catch(() => {
this.lock = false
})
}
})
},
syncMembers() {
const {departmentId = 1} = this.search;
let loading = this.$loading({
text: "正在同步成员...",
spinner: 'el-icon-loading',
background: "rgba(0,0,0,.8)"
})
this.instance.post(`/app/wxcp/wxdepartment/syncUser`, null, {
timeout: 1000000,
params: {departmentId}
}).then(res => {
if (res?.code == 0) {
this.$message.success('同步成功')
this.getList()
}
}).finally(() => loading.close())
},
syncDept() {
let loading = this.$loading({
text: "正在同步部门...",
spinner: 'el-icon-loading',
background: "rgba(0,0,0,.8)"
})
this.instance.post(`/app/wxcp/wxdepartment/syncDepart`).then(res => {
if (res?.code == 0) {
this.$message.success('同步成功')
this.getTree()
}
}).finally(() => loading.close())
}
},
created() {
this.dict.load('wxUserStatus')
}
}
</script>
<style lang="scss" scoped>
.AppAccount {
height: 100%;
::v-deep .avatar {
width: 40px;
height: 40px;
margin-right: 10px;
}
::v-deep .ai-list__content--left {
margin-right: 2px;
}
::v-deep .el-form {
.el-cascader {
width: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<section class="AppAccountRole">
<add-account-role v-if="showDetail"/>
<account-role-list v-else/>
</section>
</template>
<script>
import AddAccountRole from "./addAccountRole";
import AccountRoleList from "./accountRoleList";
export default {
name: "AppAccountRole",
label: "账号角色管理",
props: {
instance: Function
},
components: {AccountRoleList, AddAccountRole},
computed: {
showDetail() {
return this.$route.hash == "#add"
}
},
provide() {
let {instance} = this
return {instance}
}
}
</script>
<style lang="scss" scoped>
.AppAccountRole {
height: 100%;
}
</style>

View File

@@ -0,0 +1,372 @@
<template>
<ai-list class="accountRoleList">
<ai-title slot="title" title="账号角色管理" isShowBottomBorder/>
<template #content>
<ai-search-bar>
<template #left>
<el-button
type="primary"
icon="el-icon-circle-plus"
@click="$router.push({hash:'#add'})"
v-if="$permissions('admin_sysaccountrole_add')"
>添加
</el-button>
<el-button
icon="el-icon-delete"
class="delete-btn del-btn-list"
@click="allDelete"
:disabled="!Boolean(multipleSelection.length)"
v-if="$permissions('admin_sysaccountrole_del')"
>删除
</el-button>
</template>
<template #right>
<el-input
size="small"
v-model="searchInfo"
placeholder="角色名称"
clearable
@keyup.enter.native="mhSearch()"
prefix-icon="iconfont iconSearch"
/>
<el-button
type="primary"
icon="iconfont iconSearch"
@click="mhSearch()"
>查询
</el-button
>
<el-button
icon="el-icon-refresh-right"
style="padding: 8px 13.5px"
@click="resetConditon"
>重置
</el-button
>
</template>
</ai-search-bar>
<el-table
:data="tableData"
@selection-change="handleSelectionChange"
header-cell-class-name="table-header"
tooltip-effect="light"
max-height="calc(100% - 80px)"
row-class-name="table-row"
cell-class-name="table-cell"
>
<el-table-column type="selection" width="55"/>
<el-table-column prop="name" label="角色名" width="300"/>
<el-table-column label="权限明细" prop="appRoleList">
<template slot-scope="scope">
<el-tooltip
content="更多角色用户请点击详情按钮"
placement="top"
effect="light"
v-if="scope.row.appRoleList.length > 3"
>
<span>{{ scope.row.appRoleListHtml }}</span>
</el-tooltip>
<span
v-for="(item, index) in scope.row.appRoleList"
:key="index"
v-else
>
<span>{{ item.appName }}-</span>
<span>{{ item.name }}</span>
</span>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" align="center">
<template class="operation_icon" slot-scope="scope">
<el-button
type="text"
size="small"
@click="beforeCopy(scope.row)"
v-if="$permissions('admin_sysaccountrole_add')"
>复制角色
</el-button>
<el-button
type="text"
size="small"
@click="detailShow(scope.row)"
v-if="$permissions('admin_sysaccountrole_detail')"
>详情
</el-button>
<el-button
type="text"
size="small"
@click="edit(scope.row)"
v-if="$permissions('admin_sysaccountrole_edit')"
>编辑
</el-button
>
<el-button
type="text"
size="small"
@click="beforeDelete(scope.row)"
v-if="$permissions('admin_sysaccountrole_del')"
>删除
</el-button
>
</template>
</el-table-column>
<div slot="empty" class="no-data"></div>
</el-table>
<div class="pagination">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
:page-size="pageSize"
:page-sizes="[10, 20, 50, 100, 200]"
layout="total,prev, pager, next,sizes, jumper"
:total="total"
/>
</div>
<el-dialog
class="editStyle"
:visible.sync="copyDialog"
width="520px"
@close="dataInit()"
title="复制角色"
>
<el-form :model="form" label-width="80px">
<el-form-item
label="角色名"
:rules="[{ required: true, message: '', trigger: 'blur' }]"
>
<el-input
v-model="roleName"
placeholder="请输入..."
size="small"
clearable
/>
</el-form-item>
</el-form>
<div slot="footer" style="text-align: center">
<el-button
style="width: 92px"
size="small"
class="delete-btn"
@click="copyDialog = false"
>取消
</el-button
>
<el-button
style="width: 92px"
size="small"
type="primary"
@click="copyFn()"
:disabled="!Boolean(roleName)"
>确认
</el-button>
</div>
</el-dialog>
<ai-dialog title="账号角色详情" :visible.sync="viewShow" customFooter>
<ai-card title="基本信息">
<template #content>
<ai-wrapper>
<ai-info-item label="账号角色名称" :value="row.name"/>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="权限信息">
<template #content>
<div
class="view_info"
v-for="(item, index) in row.appRoleList"
:key="index"
>
<i class="iconfont iconProlife" style="color: #999"/>&nbsp;
{{ [item.appName, item.name].join(" / ") }}
</div>
</template>
</ai-card>
<template #footer>
<el-button
type="primary"
@click="edit(row)"
v-if="$permissions('admin_sysaccountrole_edit')"
>编辑角色
</el-button>
</template>
</ai-dialog>
</template>
</ai-list>
</template>
<script>
export default {
name: "accountRoleList",
inject:{
instance:{}
},
data() {
return {
searchInfo: "",
pageSize: 10,
tableData: [],
multipleSelection: [],
total: 0,
pageNum: 1,
row: {},
deleteIds: [],
copyDialog: false,
roleName: "",
viewShow: false,
titleDel: "",
form: {},
};
},
methods: {
getTableList() {
this.tableData = [];
this.instance.post(`/admin/role-acc/page`, null, {
params: {
pageSize: this.pageSize,
pageNum: this.pageNum,
roleName: this.searchInfo,
},
}).then((res) => {
if (res.code == 0) {
this.tableData = res.data.records;
this.total = res.data.total;
if (this.tableData.length) {
this.tableData.map((item) => {
if (item.appRoleList.length > 3) {
item.appRoleListHtml = `${item.appRoleList[0].appName}-${item.appRoleList[0].name}${item.appRoleList[1].appName}-${item.appRoleList[1].name}${item.appRoleList[2].appName}-${item.appRoleList[2].name}`;
}
});
}
}
});
},
dataInit() {
this.deleteIds = [];
this.multipleSelection = [];
this.row = {};
},
handleSelectionChange(val) {
this.deleteIds = [];
this.multipleSelection = val;
this.multipleSelection.forEach((e) => {
this.deleteIds.push(e.id);
});
},
allDelete() {
this.titleDel = "确定要执行删除操作吗?";
this.deleteRole();
},
beforeDelete(row) {
this.deleteIds = [];
this.row = row;
this.titleDel = "确定需要删除该角色吗?";
this.deleteIds.push(row.id);
this.deleteRole();
},
beforeCopy(row) {
this.row = row;
this.copyDialog = true;
},
detailShow(row) {
this.row = row;
this.viewShow = true;
},
edit(row) {
this.$router.push({
hash: "#add",
query: {
info: row,
},
});
},
copyFn() {
let crr = [];
let appRoleList = this.row.appRoleList;
appRoleList.forEach((e) => {
crr.push(e.id);
});
this.instance.post(`/admin/role-acc/modify?appRoles=${crr}`, null, {
params: {
roleName: this.roleName,
},
})
.then((res) => {
if (res.code == 0) {
this.$message({message: "复制成功", type: "success"});
this.copyDialog = false;
this.pageNum = 1;
this.getTableList();
}
});
},
deleteRole() {
this.$confirm(this.titleDel, {
type: "error",
})
.then(() => {
this.instance
.post(`/admin/role-acc/del?ids=${this.deleteIds}`, null, {})
.then((res) => {
if (res.code == 0) {
this.$message.success("删掉角色成功");
this.getTableList();
}
});
})
.catch(() => {
});
},
mhSearch() {
this.pageNum = 1;
this.getTableList();
},
// 重置
resetConditon() {
this.pageNum = 1;
this.searchInfo = "";
this.getTableList();
},
handleSizeChange(val) {
this.pageSize = val;
this.getTableList();
},
handleCurrentChange(val) {
this.pageNum = val;
this.getTableList();
},
},
created() {
this.getTableList();
},
};
</script>
<style lang="scss" scoped>
.accountRoleList {
height: 100%;
::v-deep.ai-card {
box-shadow: none;
border: 1px solid #eee;
.aibar {
height: 40px;
background: #f3f6f9;
}
.ai-card__body {
padding: 0 16px;
}
}
.view_info {
color: #333;
padding-left: 20px;
margin-bottom: 10px;
.info-title {
margin-bottom: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,170 @@
<template>
<section class="addAccountRole">
<ai-detail>
<ai-title slot="title" :title="pageTitle" isShowBottomBorder isShowBack @onBackClick="$router.push({})"/>
<template #content>
<ai-card title="基本信息">
<template #content>
<el-form size="small" label-width="120px">
<el-form-item required label="账号角色名称">
<el-input clearable v-model="roleName" placeholder="请输入..."/>
</el-form-item>
</el-form>
</template>
</ai-card>
<ai-card title="角色信息(必选)">
<template #content>
<el-form size="small" label-width="120px">
<el-form-item required label="角色列表">
<div class="roleList">
<p class="input">
<el-input placeholder="请输入..." size="small" v-model="filterText" suffix-icon="iconfont iconSearch">
</el-input>
<el-button icon="iconfont iconDelete" size="small" @click="filterText=''">清空</el-button>
</p>
<div class="tree_list">
<el-tree
class="filter-tree"
:data="roleList"
show-checkbox
:props="defaultProps"
default-expand-all
:check-strictly="true"
node-key="id"
:default-checked-keys="defaultId"
:filter-node-method="filterNode"
ref="tree">
</el-tree>
</div>
</div>
</el-form-item>
</el-form>
</template>
</ai-card>
</template>
<template #footer>
<el-button @click="toAppRole">取消</el-button>
<el-button type="primary" @click="confirm">保存</el-button>
</template>
</ai-detail>
</section>
</template>
<script>
export default {
name: "addAccountRole",
inject: {instance: {}},
data() {
return {
roleName: '',
id: '',
roleList: [],
searchVal: '',
defaultProps: {
children: 'roles',
label: 'name'
},
treeList: [],
defaultId: [],
filterText: ''
}
},
computed: {
isAdd() {
return !this.$route.query?.info?.id
},
pageTitle() {
return this.isAdd ? "新增账号角色" : "编辑账号角色"
}
},
watch: {
filterText(val) {
this.$refs.tree.filter(val);
}
},
created() {
this.getAppList();
if (JSON.stringify(this.$route.query) != '{}') {
this.roleName = this.$route.query.info.name;
this.id = this.$route.query.info.id;
this.$route.query.info.appRoleList.forEach(e => {
this.defaultId.push(e.id)
})
}
},
methods: {
//查询下拉列表
getAppList() {
this.instance.post(`/admin/role-app/list-all`).then(res => {
if (res.code == 0) {
this.roleList = res.data;
this.roleList.forEach(e => {
e.disabled = true;
})
}
})
},
filterNode(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
},
confirm() {
let crr = [];
let arrCheckList = [];
arrCheckList = this.$refs.tree.getCheckedKeys();
for (let i = 0; i < arrCheckList.length; i++) {
crr.push(arrCheckList[i]);
}
if (!this.roleName) {
this.$message.error('请输入账号角色名称')
return;
}
if (crr.length == 0) {
this.$message.error('请选择角色列表')
return;
}
this.instance.post(`/admin/role-acc/modify?appRoles=${crr}`, null, {
params: {
roleName: this.roleName,
id: this.id
}
}).then(res => {
if (res?.code == 0) {
this.$message({message: '保存成功', type: 'success'});
this.toAppRole()
}
})
},
//取消 返回
toAppRole() {
this.$router.push({})
}
}
}
</script>
<style lang="scss" scoped>
.addAccountRole {
height: 100%;
.roleList {
display: inline-block;
width: 340px;
height: 420px;
background-color: #fcfcfc;
border-radius: 2px;
border: solid 1px #d0d4dc;
.input {
display: flex;
justify-content: space-between;
padding: 5px;
margin: 0;
}
.tree_list {
height: 370px;
overflow: auto;
}
}
}
</style>

View File

@@ -0,0 +1,156 @@
<template>
<section class="AppDictionary">
<ai-list v-if="!showDetail">
<ai-title slot="title" title="数据字典" isShowBottomBorder/>
<template #content>
<ai-search-bar>
<template #left>
<el-button type="primary" size="small" icon="iconfont iconAdd" @click="addDict"
v-if="$permissions('admin_sysdictionary_add')">添加
</el-button>
</template>
<template #right>
<el-input size="small" v-model="search.condition" placeholder="数据项" clearable
@change="page.current=1,getDicts()" prefix-icon="iconfont iconSearch"/>
<el-button type="primary" size="small" icon="iconfont iconSearch"
@click="page.current=1,getDicts()">查询
</el-button>
<el-button size="small" icon="el-icon-refresh-right" @click="resetSearch">重置</el-button>
</template>
</ai-search-bar>
<el-table size="mini" :data="dictList" header-cell-class-name="table-header" tooltip-effect="light"
row-class-name="table-row" cell-class-name="table-cell" @expand-change="getDictInfo">
<el-table-column type="expand">
<el-row slot-scope="{row}" type="flex" align="middle" style="flex-wrap: wrap">
<el-tag v-for="(op,i) in row.detail||[]" :key="i" style="margin: 4px">{{ op.dictValue }}{{ op.dictName }}
{{ op.dictColor ? '| ' + op.dictColor : '' }}
</el-tag>
</el-row>
</el-table-column>
<el-table-column align="center" label="数据项" prop="code"/>
<el-table-column align="center" label="数据项名称" prop="name"/>
<el-table-column align="center" label="操作">
<div slot-scope="{row}">
<el-button type="text" @click="openDetail(row.id)" v-text="'编辑'"
v-if="$permissions('admin_sysdictionary_edit')"/>
<el-button type="text" @click="handleDelete(row.id)" v-text="'删除'"
v-if="$permissions('admin_sysdictionary_del')"/>
</div>
</el-table-column>
<div slot="empty" class="no-data"></div>
</el-table>
<div class="pagination">
<el-pagination background :current-page.sync="page.current" :total="page.total"
layout="total,prev, pager, next,sizes, jumper"
@size-change="handleSizeChange"
:page-size="page.size"
:page-sizes="[10, 20, 50, 100,200]"
@current-change="getDicts"/>
</div>
</template>
</ai-list>
<dict-detail v-else :instance="instance" :permissions="permissions"/>
</section>
</template>
<script>
import DictDetail from "./dictDetail";
export default {
name: "AppDictionary",
components: {DictDetail},
label: "数据字典",
props: {
instance: Function,
permissions: Function
},
computed: {
showDetail() {
return this.$route.hash == "#add"
}
},
data() {
return {
page: {
current: 1,
total: 0,
size: 10
},
search: {
condition: ""
},
dictList: [],
id: ''
}
},
methods: {
resetSearch() {
this.page.current = 1;
this.search.condition = '';
this.getDicts();
},
getDicts() {
this.instance.post("/admin/dictionary/queryDictList", null, {
params: {
...this.page,
name: this.search.condition
}
}).then(res => {
this.dictList = res.data.records.map(e => {
return {...e, detail: []}
})
this.page.total = res.data.total
})
},
addDict() {
this.$router.push({hash: "#add"})
},
handleDelete(id) {
this.$confirm("确定要删除该数据项吗?", {
type: "error"
}).then(() => {
this.instance.post("/admin/dictionary/deleteDict", null, {
params: {id}
}).then(res => {
if (res?.code == 0) {
this.getDicts();
this.$message.success("删除成功!")
}
})
}).catch(() => 0)
},
openDetail(id) {
this.$router.push({query: {id}, hash: "#add"})
},
handleSizeChange(val) {
this.page.size = val;
this.getDicts();
},
getDictInfo(row) {
if (row.detail.length) {
row.detail = []
} else {
this.getDict(row.id).then(res => {
if (res && res.data) {
row.detail = res.data.dictionaryDetails || []
}
})
}
},
getDict(dictionaryId) {
return this.instance.post("/admin/dictionary/queryDictDetail", null, {
params: {dictionaryId}
})
},
},
created() {
this.getDicts()
},
}
</script>
<style lang="scss" scoped>
.AppDictionary {
height: 100%;
}
</style>

View File

@@ -0,0 +1,202 @@
<template>
<section class="dictDetail">
<ai-detail>
<ai-title slot="title" title="字典信息" isShowBottomBorder isShowBack @onBackClick="$router.push({})"/>
<template #content>
<ai-card title="基本信息">
<template #content>
<el-form ref="dictDetailForm" :model="form" :rules="rules" size="small" label-width="110px">
<el-form-item required label="数据项:" prop="code">
<el-input v-model="form.code" style="width: 259px;" clearable
placeholder="请输入..."/>
</el-form-item>
<el-form-item required label="数据项名称:" prop="name">
<el-input v-model="form.name" style="width: 259px;" clearable
placeholder="请输入..."/>
</el-form-item>
</el-form>
</template>
</ai-card>
<ai-card title="数据值" v-if="$route.query.id">
<template #right>
<el-button type="text" icon="iconfont iconAdd"
@click="form.dictionaryDetails.push({name:'',value:'',editable:true})"> 添加
</el-button>
</template>
<template #content>
<el-table border :data="form.dictionaryDetails" header-cell-class-name="table-header"
cell-class-name="table-cell">
<el-table-column align="center" label="值">
<div slot-scope="{row}">
<el-input size="small" v-if="row.editable" v-model="row.value" clearable/>
<span v-else>{{ row.dictValue }}</span>
</div>
</el-table-column>
<el-table-column align="center" label="描述">
<div slot-scope="{row}">
<el-input size="small" v-if="row.editable" v-model="row.name" clearable/>
<span v-else>{{ row.dictName }}</span>
</div>
</el-table-column>
<el-table-column align="center" label="颜色">
<div slot-scope="{row}">
<el-color-picker v-if="row.editable" v-model="row.dictColor" size="medium"></el-color-picker>
<span v-else>{{ row.dictColor || '未设置' }}</span>
</div>
</el-table-column>
<el-table-column align="center" label="操作" width="109px">
<div slot-scope="{row,$index}">
<section v-if="row.editable">
<el-button style="color: #2EA222" type="text" icon="iconfont iconCorrect"
@click="addDict(row)"/>
<el-button style="color: #f46" type="text" icon="iconfont iconClean"
@click="cancelEdit(row,$index)"/>
</section>
<section v-else>
<el-button class="dict-detail-operation" type="text" icon="iconfont iconEdit"
@click="editDetail(row)"/>
<el-button class="dict-detail-operation" type="text" icon="iconfont iconDelete"
@click="delDictValue(row.id)"/>
</section>
</div>
</el-table-column>
</el-table>
</template>
</ai-card>
</template>
<template #footer>
<el-button @click="$router.push({})">返回</el-button>
<el-button type="primary" @click="modifyDict">保存</el-button>
</template>
</ai-detail>
</section>
</template>
<script>
export default {
name: "dictDetail",
props: {
instance: Function,
permissions: Function
},
computed: {
rules() {
return {
code: [
{required: true, message: "请填写数据项"}
],
name: [
{required: true, message: "请填写数据项名称"}
],
// dictionaryDetails: [
// {
// validator: (r, v, cb) => {
// if (v.every(item => item.dictName && item.dictValue)) {
// cb()
// }
// }
// }
// ]
}
}
},
data() {
return {
form: {
code: "",
name: "",
dictionaryDetails: []
},
}
},
created() {
if (this.$route.query.id) this.getDict()
},
methods: {
getDict() {
this.instance.post("/admin/dictionary/queryDictDetail", null, {
params: {dictionaryId: this.$route.query.id}
}).then(res => {
if (res?.data) {
res.data.dictionaryDetails = res.data.dictionaryDetails.map(d => {
return {
...d,
editable: false,
name: "",
value: ""
}
})
this.form = res.data
}
})
},
delDictValue(id) {
this.$confirm("是否要删除该字典值", {
type: 'error'
}).then(() => {
this.instance.post("/admin/dictionary/deletevalue", null, {
params: {id}
}).then(res => {
if (res?.code == 0) {
this.$message.success("删除成功!")
this.getDict()
}
})
}).catch(() => 0)
},
editDetail(row) {
row.editable = true
row.name = row.dictName
row.value = row.dictValue
},
addDict(row) {
row.dictValue = row.value
row.dictName = row.name
row.dictionaryId = this.form.id
this.instance.post("/admin/dictionary/updateDetail", row).then(res => {
row.editable = false
row = res.data.data
this.$message.success("提交成功!")
})
},
cancelEdit(row, index) {
if (row.id) {
row.editable = false
} else {
this.form.dictionaryDetails.splice(index, 1)
}
},
modifyDict() {
this.$refs.dictDetailForm.validate(v => {
if (v) {
this.instance.post("/admin/dictionary/updateDict", this.form).then(res => {
if (res?.code == 0) {
this.$message.success("提交成功!")
this.$router.push({})
}
})
}
})
}
}
}
</script>
<style lang="scss" scoped>
.dictDetail {
height: 100%;
::v-deep .el-table__row {
.el-input__inner {
padding: 0 30px;
border: none;
text-align: center;
background: #ddd;
font-size: 14px;
}
}
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<section class="AppMenuManager">
<component :is="currentPage" v-bind="$props"/>
</section>
</template>
<script>
import List from "./list";
import IntroPage from "./introPage";
export default {
name: "AppMenuManager",
components: {IntroPage, List},
label: "菜单管理",
props: {
instance: Function,
dict: {default: () => ({})}
},
computed: {
currentPage() {
const {hash} = this.$route
return hash == "#intro" ? IntroPage : List
}
},
created() {
this.dict.load("menuType", "yesOrNo")
}
}
</script>
<style lang="scss" scoped>
.AppMenuManager {
height: 100%;
}
</style>

View File

@@ -0,0 +1,96 @@
<template>
<section class="introPage">
<ai-detail :list="!edit">
<ai-title slot="title" title="引导页配置" isShowBottomBorder isShowBack @onBackClick="$router.push({})">
<template #rightBtn>
<ai-edit-btn @edit="edit=true,getConfigs()" @cancel="edit=false" @submit="submit"/>
</template>
</ai-title>
<template #content>
<el-form v-if="edit" :model="form" ref="IntroForm" size="small" :rules="rules" label-width="120px">
<ai-card title="基本信息">
<template #content>
<el-form-item label="副标题" prop="subtitle">
<el-input v-model="form.subtitle" clearable placeholder="请输入"/>
</el-form-item>
<el-form-item label="操作示例链接" prop="operationExamples">
<el-input v-model="form.operationExamples" clearable placeholder="请输入"/>
</el-form-item>
</template>
</ai-card>
<ai-card title="引导内容">
<template #content>
<el-form-item label-width="0" prop="guideContent">
<ai-editor :instance="instance" v-model="form.guideContent" placeholder="请输入" action="/oms/api/file/add" :params="{withoutToken:true}"/>
</el-form-item>
</template>
</ai-card>
</el-form>
<ai-intro v-else :id="$route.query.id" v-bind="$props"/>
</template>
</ai-detail>
</section>
</template>
<script>
import AiEditBtn from "../../../components/AiEditBtn";
export default {
name: "introPage",
components: {AiEditBtn},
props: {
instance: Function,
dict: {default: () => ({})}
},
data() {
return {
form: {},
rules: {
subtitle: {required: true, message: "请输入副标题"},
guideContent: {required: true, message: "请输入引导内容"},
},
edit: false
}
},
methods: {
getConfigs() {
const {id} = this.$route.query
this.instance.post("/admin/sysappguideconfig/queryDetailById", null, {
params: {id}
}).then(res => {
if (res?.data) {
this.form = res.data
}
})
},
submit(cb) {
this.$refs.IntroForm.validate(v => {
if (v) {
const {form, $route: {query: {id}}} = this
this.instance.post("/admin/sysappguideconfig/addOrUpdate", {...form, id}).then(res => {
if (res?.code == 0) {
this.$message.success("提交成功!")
cb()
this.edit = false
}
})
}
})
}
}
}
</script>
<style lang="scss" scoped>
.introPage {
height: 100%;
::v-deep.ai-detail__content--wrapper {
min-height: 100%;
&.list {
padding-top: 0;
}
}
}
</style>

View File

@@ -0,0 +1,268 @@
<template>
<section class="list">
<ai-list>
<ai-title slot="title" title="菜单配置" isShowBottomBorder/>
<template #content>
<ai-search-bar>
<template #left>
<el-button type="primary" icon="el-icon-circle-plus" @click="addRootMenu">添加一级目录</el-button>
</template>
<template #right>
<el-input size="small" v-model="search" clearable @change="$refs.MenuTree.filter(search)"
placeholder="菜单名称"/>
<el-button icon="iconfont iconResetting" @click="getData">刷新</el-button>
</template>
</ai-search-bar>
<el-row type="flex" class="headerRow">
<div class="menuName" v-text="`菜单名称`"/>
<el-row type="flex" align="middle" class="info">
<div class="style" v-text="`图标`"/>
<div class="type" v-text="`菜单类型`"/>
<div class="component" v-text="`应用模块`"/>
<div class="status" v-text="`是否显示`"/>
<div class="showIndex" v-text="`排序`"/>
</el-row>
<div class="operation" v-text="`操作`"/>
</el-row>
<el-scrollbar>
<el-tree ref="MenuTree" :data="treeData" :props="{children:'subSet'}" highlight-current node-key="id"
:filter-node-method="handleSearch">
<el-row type="flex" align="middle" slot-scope="{node,data}" class="menuItem">
<div class="menuName" v-text="data.name"/>
<el-row type="flex" align="middle" class="info">
<div class="style" :class="data.style"/>
<div class="type" v-text="dict.getLabel('menuType',data.type)"/>
<div class="component" v-text="data.component"/>
<div class="status" v-text="dict.getLabel('yesOrNo',data.status)"/>
<div class="showIndex" v-text="data.showIndex"/>
</el-row>
<el-row type="flex" align="middle" class="operation">
<div v-if="node.isLeaf" class="opBtn del" v-text="`删除`" @click="handleDelete(data)"/>
<div v-if="data.component&&data.type==1" class="opBtn" v-text="`引导页`" @click="$router.push({hash:'#intro',query:{id:data.id}})"/>
<div v-if="data.type<2" class="opBtn" v-text="`添加下级`" @click="addMenu(data)"/>
<div class="opBtn" v-text="`编辑`" @click="handleEdit(data)"/>
</el-row>
</el-row>
</el-tree>
</el-scrollbar>
</template>
</ai-list>
<ai-dialog :visible.sync="dialog" title="菜单设置" width="500px" @onConfirm="handleSubmit"
@closed="form={},selected={}">
<el-form ref="MenuForm" :model="form" size="small" label-width="100px" :rules="rules">
<el-form-item label="菜单名称" prop="name">
<el-input v-model="form.name" placeholder="请输入" clearable/>
</el-form-item>
<el-form-item label="菜单类型" prop="type">
<ai-select v-model="form.type" clearable :selectList="dict.getDict('menuType')"/>
</el-form-item>
<template v-if="form.type==0">
<el-form-item label="菜单图标" prop="style">
<el-input v-model="form.style" placeholder="请输入" clearable/>
</el-form-item>
</template>
<template v-if="form.type==1">
<el-form-item label="路由名" prop="route">
<span v-text="form.route||'提交保存后会自动生成'"/>
</el-form-item>
<el-form-item label="菜单应用" prop="component">
<el-input v-model="form.component" placeholder="请输入" clearable/>
</el-form-item>
<el-form-item label="路径(path)" prop="path">
<el-input v-model="form.path" placeholder="请输入" clearable/>
</el-form-item>
</template>
<template v-if="form.type==2">
<el-form-item label="权限码" prop="permission">
<el-input v-model="form.permission" placeholder="请输入" clearable/>
</el-form-item>
</template>
<el-form-item label="显示菜单" prop="status">
<ai-select v-model="form.status" clearable :selectList="dict.getDict('yesOrNo')"/>
</el-form-item>
<el-form-item v-if="form.type<2" label="排序" prop="showIndex">
<el-input v-model="form.showIndex" placeholder="请输入" clearable/>
</el-form-item>
</el-form>
</ai-dialog>
</section>
</template>
<script>
export default {
name: "list",
props: {
instance: Function,
dict: {default: () => ({})}
},
data() {
return {
treeData: [],
dialog: false,
form: {},
selected: {},
rules: {
name: [{required: true, message: "请输入 菜单名称"}],
type: [{required: true, message: "请选择 菜单类型"}],
status: [{required: true, message: "请选择 显示菜单"}],
showIndex: [{required: true, message: "请输入 排序"}],
permission: [{required: true, message: "请输入 权限码"}],
},
search: ""
}
},
methods: {
getData() {
return this.instance.post("/admin/menu/menuTree").then(res => {
if (res?.data) {
this.treeData = res.data
}
})
},
handleSubmit() {
this.$refs.MenuForm.validate(v => {
if (v) {
this.instance.post("/admin/menu/addOrUpdate", this.form).then(res => {
if (res?.code == 0) {
this.$message.success("提交成功!")
this.dialog = false
if (!!this.form.id) {
let node = this.$refs.MenuTree.getNode(this.form)
node.data = this.form
} else if (!!this.form.parentId) {
this.$refs.MenuTree.append(this.form, this.selected)
} else this.getData()
}
})
}
})
},
handleDelete(data) {
let {id} = data
this.$confirm("是否要删除该菜单").then(() => {
this.instance.post("/admin/menu/delete", null, {
params: {id}
}).then(res => {
if (res?.code == 0) {
this.$message.success("删除成功!")
this.dialog = false
this.$refs.MenuTree.remove(data)
}
})
}).catch(() => 0)
},
addRootMenu(row) {
this.dialog = true
this.selected = row
},
addMenu(row) {
this.dialog = true
this.form = {parentId: row.id}
this.selected = row
},
handleEdit(row) {
this.dialog = true
this.form = JSON.parse(JSON.stringify(row))
this.selected = row
},
handleSearch(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
}
},
created() {
this.getData()
}
}
</script>
<style lang="scss" scoped>
.list {
height: 100%;
::v-deep .ai-list__content--right-wrapper {
height: calc(100% - 10px);
display: flex;
flex-direction: column;
.el-tree {
width: 100%;
height: 100%;
font-size: 14px;
.menuItem {
flex: 1;
min-width: 0;
}
.el-tree-node:nth-of-type(2n) {
background: rgba(#26f, .05);
}
.el-tree-node__content {
border-bottom: 1px solid #d0d4dc;
}
}
.el-scrollbar {
flex: 1;
min-height: 0;
.el-scrollbar__wrap {
overflow-x: auto;
}
}
.headerRow {
background: #f3f4f5;
color: #666;
font-weight: bold;
align-items: center;
height: 40px;
.menuName {
padding-left: 16px;
}
}
.info {
gap: 16px;
text-align: center;
.showIndex, .status, .type, .style {
width: 80px;
}
.component {
width: 300px;
}
}
.operation {
width: 300px;
flex-shrink: 0;
justify-content: flex-end;
text-align: center;
.opBtn {
cursor: pointer;
width: 60px;
font-size: 14px;
color: #26f;
&.del {
color: #f46;
}
}
}
.menuName {
flex: 1;
min-width: 0;
}
}
}
</style>

View File

@@ -0,0 +1,36 @@
<template>
<section class="AppQyWxConfig">
<component :is="currentPage" v-bind="$props"/>
</section>
</template>
<script>
import List from "./list";
import ThemeSetting from "./themeSetting";
export default {
name: "AppQyWxConfig",
components: {ThemeSetting, List},
label: "企业微信配置",
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
currentPage() {
const {hash} = this.$route
return hash == "#theme" ? ThemeSetting : List
}
},
created() {
this.dict.load("yesOrNo", "themeWeb", 'themeMp', "themeWxwork")
}
}
</script>
<style lang="scss" scoped>
.AppQyWxConfig {
height: 100%;
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<section class="AiSelectCard" flex>
<div class="checkCard" v-for="op in list" :key="op[props.value]" :class="{checked:op.dictValue==value}">
<el-image :src="op.thumb"/>
<el-row type="flex" class="bottomPane">
<b class="label fill" v-text="op[props.label]"/>
<el-button type="text" @click.stop="$emit('change',op[props.value])">使用</el-button>
</el-row>
</div>
</section>
</template>
<script>
export default {
name: "AiSelectCard",
model: {
prop: "value",
event: "change"
},
props: {
value: {default: ""},
ops: {default: () => []},
type: {default: "web"},
dict: String,
prop: {default: () => ({})}
},
computed: {
list: v => (v.dict ? v.$dict.getDict(v.dict) : v.ops || []).map(e => ({
...e,
thumb: `https://cdn.cunwuyun.cn/theme/thumb/${v.type}_${e[v.props.value]}.png`
})),
props: v => ({value: 'dictValue', label: 'dictName', ...v.prop})
}
}
</script>
<style lang="scss" scoped>
.AiSelectCard {
display: flex;
flex-wrap: wrap;
gap: 16px;
.checkCard {
width: 360px;
background: #fff;
border-radius: 8px;
overflow: hidden;
position: relative;
.label {
user-select: none;
}
&.checked:before {
position: absolute;
content: "应用中";
top: 16px;
left: 16px;
z-index: 9;
padding: 8px 16px;
background: rgba(#000, .7);
color: #fff;
border-radius: 8px;
}
.bottomPane {
height: 48px;
align-items: center;
padding: 0 16px;
border-top: 1px solid #eee;
}
}
}
</style>

View File

@@ -0,0 +1,481 @@
<template>
<section class="list">
<ai-list>
<ai-title slot="title" title="企业微信配置" isShowBottomBorder/>
<template #content>
<ai-search-bar>
<template #left>
<el-button type="primary" @click="add" icon="iconfont iconAdd">新增</el-button>
</template>
<template #right>
<el-input size="small" placeholder="搜索名称" v-model="search.name" clearable
@clear="page.current = 1,search.name = '', getTableData()"
v-throttle="() => {page.current = 1, getTableData()}"/>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :total="page.total" :current.sync="page.current" :size.sync="page.size"
@getList="getTableData" :col-configs="colConfigs">
<el-table-column type="expand" slot="expand">
<template slot-scope="{row}">
<ai-wrapper>
<ai-info-item labelWidth="200px" v-for="op in desConfigs" :key="op.prop" :value="row[op.prop]"
v-bind="op"/>
</ai-wrapper>
</template>
</el-table-column>
<el-table-column slot="status" align="center" label="状态" width="150">
<template v-slot="{ row }">
<el-switch v-model="row.status" @change="onChange(row)" active-value="1" inactive-value="0"
active-color="#5088FF" inactive-color="#D0D4DC"></el-switch>
</template>
</el-table-column>
<el-table-column slot="miniappStatus" align="center" label="小程序状态" width="150">
<template v-slot="{ row }">
<el-switch v-model="row.miniappStatus" @change="onMiniappStatusChange(row)" active-value="1"
inactive-value="0"
active-color="#5088FF" inactive-color="#D0D4DC"></el-switch>
</template>
</el-table-column>
<el-table-column slot="options" align="center" label="操作" width="400">
<el-row type="flex" justify="center" align="middle" slot-scope="{row}">
<el-button type="text" @click="detail(row)">详情</el-button>
<el-button type="text" @click="del(row)">删除</el-button>
<el-button type="text" @click="handleSystemInfo(row.id)">系统信息</el-button>
<el-button type="text" @click="handlePush(row.id)">推送随手拍样式</el-button>
<el-button type="text" @click="handleTheme(row.id)">主题样式</el-button>
</el-row>
</el-table-column>
</ai-table>
</template>
</ai-list>
<ai-dialog title="新增" :visible.sync="dialog" width="800px" @onConfirm="confirm">
<el-form ref="form" :model="dialogForm" :rules="rules" size="small" label-width="180px">
<el-form-item required label="名称" prop="name">
<el-input v-model.trim="dialogForm.name" placeholder="请输入名称" show-word-limit maxlength="100"></el-input>
</el-form-item>
<el-form-item label="企业微信ID" prop="corpId">
<el-input v-model.trim="dialogForm.corpId" placeholder="请输入企业微信ID" show-word-limit maxlength="32"></el-input>
</el-form-item>
<el-form-item label="企业微信通讯录SECRET" prop="corpAddressBookSecret">
<el-input v-model.trim="dialogForm.corpAddressBookSecret" placeholder="请输入企业微信通讯录SECRET" show-word-limit
maxlength="64"></el-input>
</el-form-item>
<el-form-item label="企业微信AESKEY" prop="corpAeskey">
<el-input v-model.trim="dialogForm.corpAeskey" placeholder="请输入企业微信AESKEY" show-word-limit
maxlength="64"></el-input>
</el-form-item>
<el-form-item label="企业微信AGENTID" prop="corpAgentId">
<el-input v-model.trim="dialogForm.corpAgentId" placeholder="请输入企业微信AGENTID" show-word-limit
maxlength="100"></el-input>
</el-form-item>
<el-form-item label="随手拍AGENTID" prop="clapAgentId">
<el-input v-model.trim="dialogForm.clapAgentId" placeholder="请输入随手拍AGENTID" show-word-limit
maxlength="100"></el-input>
</el-form-item>
<el-form-item label="企业微信SECRET" prop="corpSecret">
<el-input v-model.trim="dialogForm.corpSecret" placeholder="请输入企业微信SECRET" show-word-limit
maxlength="255"></el-input>
</el-form-item>
<el-form-item label="企业微信TOKEN" prop="corpToken">
<el-input v-model.trim="dialogForm.corpToken" placeholder="请输入企业微信TOKEN" show-word-limit
maxlength="32"></el-input>
</el-form-item>
<el-form-item label="小程序APPID" prop="miniappAppid">
<el-input v-model.trim="dialogForm.miniappAppid" placeholder="请输入小程序APPID" show-word-limit
maxlength="32"></el-input>
</el-form-item>
<el-form-item label="小程序SECRET" prop="miniappSecret">
<el-input v-model.trim="dialogForm.miniappSecret" placeholder="请输入小程序SECRET" show-word-limit
maxlength="32"></el-input>
</el-form-item>
<el-form-item label="企微访问域名" prop="dvcpUrl">
<el-input v-model.trim="dialogForm.dvcpUrl" placeholder="请输入企微访问域名" show-word-limit maxlength="128">
<template slot="prepend">Http://</template>
</el-input>
</el-form-item>
<el-form-item label="web访问域名" prop="webUrl">
<el-input v-model.trim="dialogForm.webUrl" placeholder="请输入web访问域名" show-word-limit maxlength="128">
<template slot="prepend">Http://</template>
</el-input>
</el-form-item>
<el-form-item label="地区" prop="areaId">
<ai-area-select :instance="instance" v-model="dialogForm.areaId" alwaysShow
@name="(e)=>dialogForm.areaName=e"/>
</el-form-item>
<el-form-item label="地图中心点" prop="lat">
<el-button type="primary" icon="iconfont iconAdd" @click="showMap=true">设置地点</el-button>
<div v-if="dialogForm.lat">{{ dialogForm.address }}</div>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model.trim="dialogForm.status">
<el-radio label="1">启用</el-radio>
<el-radio label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="小程序状态" prop="miniappStatus">
<el-radio-group v-model.trim="dialogForm.miniappStatus">
<el-radio label="1">启用</el-radio>
<el-radio label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="系统配置信息" prop="systemInfo">
<el-input type="textarea" :rows="2" placeholder="请输入系统配置信息" v-model="dialogForm.systemInfo"
maxlength="50000"/>
</el-form-item>
</el-form>
</ai-dialog>
<ai-dialog title="地图" :visible.sync="showMap" @opened="initMap" width="800px" class="mapDialog"
@onConfirm="selectMap">
<div id="map"></div>
<el-input id="searchPlaceInput" size="medium" class="searchPlaceInput" clearable v-model="searchPlace"
autocomplete="on"
@change="placeSearch.search(searchPlace)">
<el-button type="primary" slot="append" @click="placeSearch.search(searchPlace)">搜索</el-button>
</el-input>
<div id="searchPlaceOutput"/>
</ai-dialog>
<ai-dialog title="系统信息设置" :visible.sync="sysInfoDialog" width="600px" @onConfirm="submitSystemInfo"
@closed="sysInfo={}">
<el-form size="small" label-width="140px">
<el-form-item label="页签标题">
<el-input v-model="sysInfo.title" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="系统标题">
<el-input v-model="sysInfo.fullTitle" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="logo">
<el-input v-model="sysInfo.logo" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="登录页左上角标题">
<el-input v-model="sysInfo.name" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="登录页副标题">
<el-input type="textarea" rows="5" v-model="sysInfo.desc" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="版权所有">
<el-input v-model="sysInfo.recordDesc" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="备案号">
<el-input v-model="sysInfo.recordNo" placeholder="请输入..." clearable/>
</el-form-item>
<el-form-item label="备案跳转链接">
<el-input v-model="sysInfo.recordURL" placeholder="请输入..." clearable/>
</el-form-item>
</el-form>
</ai-dialog>
</section>
</template>
<script>
import {mapState} from "vuex";
import AMapLoader from "@amap/amap-jsapi-loader"
export default {
name: "list",
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
...mapState(['user']),
colConfigs() {
return [
{slot: 'expand'},
{prop: "name", label: "名称"},
{prop: "corpId", label: "企业微信ID", width: 180},
{slot: "status",},
{slot: "miniappStatus"},
{prop: "createTime", label: "创建时间"},
{slot: "options"},
]
},
desConfigs() {
let isLine = true
return [
{prop: "corpAddressBookSecret", label: "企业微信通讯录SECRET", width: 200},
{prop: "corpAgentId", label: "企业微信AGENTID", width: 150},
{prop: "corpSecret", label: "企业微信SECRET", isLine},
{prop: "corpToken", label: "企业微信TOKEN", width: 150},
{prop: "corpAeskey", label: "企业微信AESKEY", width: 150},
{prop: "miniappAppid", label: "小程序APPID", width: 150},
{prop: "miniappSecret", label: "小程序SECRET", width: 150},
{prop: "areaId", label: "地区编码", width: 150, isLine},
{prop: "lat", label: "纬度", width: 100},
{prop: "lng", label: "经度", width: 100},
{prop: "address", label: "中心点", width: 100, isLine},
{prop: "webUrl", label: "管理端地址", width: 100},
{prop: "dvcpUrl", label: "企微端地址", width: 100},
]
},
rules() {
return {
name: [{required: true, message: "请填写名称"}],
corpId: [{required: true, message: "请填写企业微信ID"}],
corpAddressBookSecret: [{required: true, message: "请填写企业微信通讯录SECRET"}],
corpAeskey: [{required: true, message: "请填写企业微信AESKEY"}],
corpAgentId: [{required: true, message: "请填写企业微信AGENTID"}],
corpSecret: [{required: true, message: "请填写企业微信SECRET"}],
corpToken: [{required: true, message: "请填写企业微信TOKEN"}],
miniappAppid: [{required: true, message: "请填写小程序APPID"}],
miniappSecret: [{required: true, message: "请填写小程序SECRET"}],
dvcpUrl: [{required: true, message: "请填写企微访问域名"}],
webUrl: [{required: true, message: "请填写web访问域名"}],
status: [{required: true, message: "请选择状态", trigger: "change"}],
miniappStatus: [{required: true, message: "请选择小程序状态", trigger: "change"}],
areaId: [{required: true, message: "请选择地区", trigger: "change"}],
lat: [{required: true, message: "请选择中心点"}],
systemInfo: [{required: true, message: "请输入系统配置信息"}],
}
},
},
data() {
return {
page: {current: 1, size: 10, total: 0},
dialog: false,
showMap: false,
map: null,
placeSearch: null,
placeDetail: {},
searchPlace: "",
dialogForm: {},
tableData: [],
search: {
name: ""
},
sysInfo: {},
sysInfoDialog: false
}
},
methods: {
selectMap() {
Object.keys(this.placeDetail).map(e => this.dialogForm[e] = this.placeDetail[e]);
this.showMap = false;
},
initMap() {
AMapLoader.load({
key: 'b553334ba34f7ac3cd09df9bc8b539dc',
version: '2.0',
plugins: ['AMap.PlaceSearch', 'AMap.AutoComplete', 'AMap.Geocoder'],
}).then(AMap => {
this.map = new AMap.Map('map', {
resizeEnable: true,
zooms: [6, 20],
center: [116.394681, 39.910283],
zoom: 11
})
this.placeSearch = new AMap.PlaceSearch({map: this.map})
new AMap.AutoComplete({
input: "searchPlaceInput",
output: 'searchPlaceOutput',
}).on('select', e => {
if (e?.poi) {
this.placeSearch.setCity(e.poi.adcode);
this.movePosition(e.poi.location)
}
})
this.map.on('click', e => {
new AMap.Geocoder().getAddress(e.lnglat, (sta, res) => {
if (res?.regeocode) {
this.placeDetail = {
lng: e.lnglat?.lng,
lat: e.lnglat?.lat,
address: res.regeocode.formattedAddress
}
}
})
this.movePosition(e.lnglat)
})
})
},
movePosition(center) {
if (this.map) {
this.map.clearMap()
this.map.panTo(center)
this.map.add([
new AMap.Marker({
position: center,
clickable: true
})
])
this.map.setFitView()
}
},
onChange(row) {
this.instance.post(`/app/appdvcpconfig/setStatus`, null, {
params: {
id: row.id,
status: row.status
}
}).then((res) => {
if (res.code == 0) {
this.$message.success(+row.status ? '已启用' : '已禁用');
this.getTableData();
}
})
},
onMiniappStatusChange(row) {
this.instance.post(`/app/appdvcpconfig/setMiniappStatus`, null, {
params: {
id: row.id,
status: row.miniappStatus
}
}).then((res) => {
if (res.code == 0) {
this.$message.success(+row.miniappStatus ? '已启用' : '已禁用');
this.getTableData();
}
})
},
add() {
this.dialogForm = {};
this.dialog = true;
},
del(row) {
this.$confirm("是否要删除?").then(_ => {
this.instance.post("/app/appdvcpconfig/delete", null, {
params: {
ids: row.id
}
}).then(res => {
if (res.code == 0) {
this.$message.success("删除成功");
this.dialog = false;
this.getTableData();
}
})
})
},
detail(row) {
this.instance.post("/app/appdvcpconfig/detail", null, {
params: {
id: row.id
}
}).then(res => {
if (res && res.data) {
this.dialogForm = {...res.data};
this.dialog = true;
}
})
},
getTableData() {
this.instance.post("/app/appdvcpconfig/list", null, {
params: {...this.page, ...this.search}
}).then(res => {
if (res?.data) {
this.tableData = res.data?.records
this.page.total = res.data.total
}
})
},
confirm() {
this.$refs["form"].validate(valid => {
if (valid) {
this.instance.post("/app/appdvcpconfig/addOrUpdate", {
...this.dialogForm,
}).then(res => {
if (res.code == 0) {
this.$message.success(this.dialogForm.id ? "修改成功" : "新增成功");
this.dialog = false;
this.getTableData();
}
})
}
})
},
handleSystemInfo(id) {
this.sysInfoDialog = true
this.getSystemInfo(id)
},
getSystemInfo(id) {
this.instance.post("/app/appdvcpconfig/getSystemInfo", null, {
params: {id}
}).then(res => {
if (res?.data) {
this.sysInfo = JSON.parse(res.data)
this.sysInfo.id = id
}
})
},
submitSystemInfo() {
let {id} = this.sysInfo
this.instance.post("/app/appdvcpconfig/updateSystemInfo", this.sysInfo, {params: {id}}).then(res => {
if (res?.code == 0) {
this.$message.success("提交成功!")
this.sysInfoDialog = false
}
})
},
handlePush(id) {
this.instance.post("/app/appclapeventinfo/setAppWorkbench", null, {params: {id}}).then(res => {
if (res?.code == 0) {
this.$message.success("推送成功!")
}
})
},
handleTheme(id) {
this.$router.push({hash: "#theme", query: {id}})
}
},
created() {
this.getTableData()
}
}
</script>
<style lang="scss" scoped>
.list {
height: 100%;
::v-deep .mapDialog {
.el-dialog__body {
padding: 0;
.ai-dialog__content {
padding: 0;
}
.ai-dialog__content--wrapper {
padding: 0 !important;
position: relative;
}
#map {
width: 100%;
height: 500px;
}
.searchPlaceInput {
position: absolute;
width: 250px;
top: 30px;
left: 25px;
}
#searchPlaceOutput {
position: absolute;
width: 250px;
left: 25px;
height: initial;
top: 80px;
background: white;
z-index: 250;
max-height: 300px;
overflow-y: auto;
.auto-item {
text-align: left;
font-size: 14px;
padding: 8px;
box-sizing: border-box;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,83 @@
<template>
<section class="themeSetting">
<ai-detail list>
<ai-title slot="title" title="主题样式" isShowBottomBorder isShowBack @back="cancel">
<template #rightBtn>
<span class="mar-r8" v-text="'灰色滤镜'"/>
<el-switch size="mini" v-model="form.enableGreyFilter" name="灰色滤镜" border active-value="1" inactive-value="0"/>
</template>
</ai-title>
<template #content>
<el-form size="small" :model="form" ref="ThemeForm">
<ai-title title="WEB后台"/>
<ai-select-card dict="themeWeb" v-model="form.colorScheme.web"/>
</el-form>
</template>
<template #footer>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="submit">提交</el-button>
</template>
</ai-detail>
</section>
</template>
<script>
import AiSelectCard from "./components/AiSelectCard";
export default {
name: "themeSetting",
components: {AiSelectCard},
props: {
instance: Function,
dict: Object,
permissions: Function
},
data() {
return {
form: {enableGreyFilter: '0', colorScheme: {}}
}
},
methods: {
getDetail() {
const {id} = this.$route.query
this.instance.post("/app/appdvcpconfig/detail", null, {params: {id}}).then(res => {
if (res?.data) {
let {colorScheme, enableGreyFilter} = res.data
colorScheme = JSON.parse(colorScheme) || {web: 'blue'}
this.form = {colorScheme, enableGreyFilter}
}
})
},
cancel() {
return this.$router.push({})
},
submit() {
this.$refs.ThemeForm.validate(v => {
if (v) {
let {colorScheme} = this.form
colorScheme = JSON.stringify(colorScheme)
this.instance.post("/app/appdvcpconfig/updateSysColorScheme", {...this.form, colorScheme}).then(res => {
if (res?.code == 0) {
this.$message.success("保存成功!")
this.cancel().then(() => location.reload())
}
})
}
})
}
},
created() {
this.getDetail()
}
}
</script>
<style lang="scss" scoped>
.themeSetting {
height: 100%;
.mar-r8 {
margin-right: 8px;
}
}
</style>

View File

@@ -0,0 +1,443 @@
<template>
<section class="AppRightsManager">
<ai-list v-if="!showDetail">
<ai-title slot="title" title="权限管理" isShowBottomBorder/>
<template #content>
<ai-search-bar>
<template #left>
<ai-select placeholder="请选择应用" v-model="search.appId" :selectList="appList"
@change="searchList"/>
</template>
<template #right>
<el-input
size="small"
v-model="search.roleName"
placeholder="角色名称"
clearable
@change="searchList()"
suffix-icon="iconfont iconSearch"/>
</template>
</ai-search-bar>
<ai-search-bar>
<template #left>
<el-button size="small" type="primary" icon="iconfont iconAdd"
@click="$router.push({hash:'#add'})"
v-if="$permissions('admin_sysapprole_add')">
添加
</el-button>
<el-button
size="small"
icon="iconfont iconDelete"
:disabled="!multipleSelection.length"
class="del-btn-list"
@click="deleteApp('all')"
v-if="$permissions('admin_sysapprole_del')"
>删除
</el-button>
</template>
</ai-search-bar>
<ai-table :tableData="adminList" :colConfigs="colConfigs" :total="total" :current.sync="page.pageNum"
:size.sync="page.pageSize"
@getList="getTableData" :col-configs="colConfigs" :dict="dict"
@selection-change="v=>multipleSelection=v">
<el-table-column label="角色用户" slot="users" align="center">
<template slot-scope="scope">
<el-tooltip
effect="light"
placement="top"
:disabled="scope.row.users.length <= 2"
content="更多角色用户请点击详情按钮">
<span v-if="scope.row.users.length">
{{
scope.row.users
.slice(0, 2)
.map((e) => e.name + "(" + e.phone + ")")
.join(";")
}}
<span v-if="scope.row.users.length > 2">...</span>
</span>
<span v-else>-</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column slot="options" label="操作" fixed="right" align="center">
<template slot-scope="{row}">
<el-button type="text" @click="beforeCopy(row)" v-if="$permissions('admin_sysapprole_edit')">复制
</el-button>
<el-button type="text" @click="viewApp(row)" v-if="$permissions('admin_sysapprole_detail')">详情
</el-button>
<el-button type="text" @click="openRightsGraph(row)" v-if="$permissions('admin_sysapprole_detail')">关系图
</el-button>
<el-button type="text" @click="toAddAppRole(row)" v-if="$permissions('admin_sysapprole_edit')">编辑
</el-button>
<el-button type="text" @click="deleteApp(row)" v-if="$permissions('admin_sysapprole_del')">删除</el-button>
</template>
</el-table-column>
</ai-table>
<ai-dialog
title="应用角色详情"
:visible.sync="viewShow"
width="600px"
customFooter>
<ai-card title="基本信息">
<template #content>
<ai-wrapper>
<ai-info-item label="应用角色名称" :value="viewInfo.name"/>
<ai-info-item label="应用名称" :value="viewInfo.appName"/>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="权限信息">
<template #content>
<ai-wrapper>
<ai-info-item :label="viewInfo.appName" isLine>
{{ roleList.map(e => e.label).join('、') }}
</ai-info-item>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="角色账号">
<template #right>
<span style="text-align: right; color: #999"
>共<span
style="color: #26f"
v-text="userList.length"
/>个账号</span
>
</template>
<template #content>
<div class="datail-table-body" v-if="userList.length">
<div class="datail-item" v-for="(item, index) in userList" :key="index">
<span class="item-name">{{ item.name }}</span>
<span style="color: #999">{{ item.phone }}</span>
</div>
</div>
</template>
</ai-card>
<template #footer>
<el-button
type="primary"
@click="toAddAppRole(viewInfo)"
v-if="$permissions('admin_sysapprole_edit')"
>编辑角色
</el-button>
</template>
</ai-dialog>
<ai-dialog title="权限关系图" :visible.sync="rightsGraph" class="rightsGraphDialog" customFooter>
<rights-graph :instance="instance" :dict="dict" :app="selectApp"/>
<el-button slot="footer" @click="rightsGraph=false">关闭</el-button>
</ai-dialog>
<!--复制角色-->
<el-dialog
class="editStyle"
:visible.sync="copyDialog"
width="520px"
@close="dataInit()"
title="复制角色">
<el-form :model="form" label-width="80px">
<el-form-item label="角色名" :rules="[{ required: true, message: '', trigger: 'blur' }]">
<el-input
v-model="editName"
placeholder="请输入..."
size="small"
clearable
/>
</el-form-item>
</el-form>
<div slot="footer" style="text-align: center">
<el-button
style="width: 92px"
size="small"
@click="copyDialog = false"
>取消
</el-button
>
<el-button
style="width: 92px"
size="small"
type="primary"
@click="copyFn()"
:disabled="!editName"
>
确认
</el-button>
</div>
</el-dialog>
</template>
</ai-list>
<rights-add v-else :instance="instance" :dict="dict" :permissions="permissions"/>
</section>
</template>
<script>
import RightsAdd from "./rightsAdd";
import RightsGraph from "./rightsGraph";
export default {
name: "AppRightsManager",
components: {RightsGraph, RightsAdd},
label: "权限管理",
provide() {
return {
top: this
}
},
props: {
instance: Function,
dict: Object,
permissions: Function,
actions: {
default: () => ({
list: '/admin/role-app/page',
apps: '/admin/role-app/list-all',
delete: '/admin/role-app/del',
detail: '/admin/role-app/queryById-checked',
modify: '/admin/role-app/modify',
})
}
},
computed: {
colConfigs() {
return [
{type: "selection"},
{label: "应用", prop: "appName", width: '120px'},
{label: "角色名", prop: "name", width: '100px'},
{label: "用户数量", prop: "roleCount", align: 'center', width: '80px'},
{slot: "users"},
{slot: "options"}
]
},
showDetail() {
return this.$route.hash == "#add"
}
},
data() {
return {
page: {pageNum: 1, pageSize: 10},
search: {appId: '', roleName: ''},
adminList: [], //列表数据
total: 0,
appList: [], //下拉选择列表
multipleSelection: [],
delShow: false,
delParams: "",
delIds: [],
viewShow: false,
viewInfo: {},
roleList: [], //详情权限列表
row: {},
copyDialog: false,
titleDel: "",
form: {},
editName: "",
userList: [],
rightsGraph: false,
selectApp: {}
};
},
created() {
this.getTableData();
this.getAppList();
},
methods: {
//查询table列表
getTableData() {
this.adminList = [];
this.instance.post(this.actions.list, null, {
params: {...this.page, ...this.search}
}).then(res => {
if (res?.data) {
this.total = res.data.total;
this.adminList = res.data.records;
}
})
},
//查询下拉列表
getAppList() {
this.instance.post(this.actions.apps).then(res => {
if (res?.data) {
this.appList = res.data?.map(e => ({dictName: e.name, dictValue: e.id}))
}
})
},
//查询
searchList() {
this.page.pageNum = 1;
this.getTableData();
},
//添加按钮
toAddAppRole(item) {
this.$router.push({
hash: "#add",
query: {
id: item.id,
name: item.name,
appId: item.appId,
},
});
},
//删除
deleteApp(e) {
if (e == "all") {
this.multipleSelection.map((item) => {
this.delIds.push(item.id);
});
this.delParams = `ids=${this.delIds}`;
this.titleDel = "确定要执行删除操作吗";
} else {
this.delParams = `ids=${e.id}`;
this.titleDel = "确定需要删除该角色吗";
}
this.$confirm(this.titleDel, {
type: "error",
}).then(() => {
this.instance.post(`${this.actions.delete}?${this.delParams}`).then(res => {
if (res?.msg == "success") {
this.getTableData();
} else {
this.$message.error(res.msg);
}
});
}).catch(() => 0);
},
//查看信息
viewApp(e) {
this.userList = e.users;
this.viewInfo = e;
this.viewShow = true;
this.getRowInfo(this.viewInfo.appId, this.viewInfo.id);
},
//查询 row 信息
getRowInfo(appId, id) {
this.roleList = [];
this.instance.post(`${this.actions.detail}?id=${appId}&roleId=${id}`)
.then(res => {
if (res?.data) {
this.roleList = res.data.list.filter(e => e.checked)
}
})
},
//复制
beforeCopy(row) {
this.row = row;
this.copyDialog = true;
this.getRowInfo(this.row.appId, this.row.id);
},
//确认复制
copyFn() {
let crr = [];
let appRoleList = this.roleList;
for (let i = 0; i < appRoleList.length; i++) {
if (appRoleList[i].checked) {
crr.push(appRoleList[i].id);
if (appRoleList[i].list.length) {
for (let j = 0; j < appRoleList[i].list.length; j++) {
if (appRoleList[i].list[j].checked) {
crr.push(appRoleList[i].list[j].id);
}
}
}
}
}
this.instance.post(`${this.actions.modify}?menus=${crr}`, null, {
params: {
roleName: this.editName,
appId: this.row.appId,
},
})
.then((res) => {
if (res.code == 0) {
this.$message({message: "复制成功", type: "success"});
this.copyDialog = false;
this.searchList()
}
});
},
dataInit() {
this.multipleSelection = [];
this.row = {};
},
handleSelectionChange(val) {
this.multipleSelection = val;
},
openRightsGraph(row) {
this.rightsGraph = true
this.selectApp = row
}
},
}
</script>
<style lang="scss" scoped>
.AppRightsManager {
height: 100%;
::v-deep .ai-dialog {
.ai-card {
box-shadow: none;
border: 1px solid #eee;
.aibar {
height: 40px;
background: #f3f6f9;
}
.ai-card__body {
padding: 0 16px;
}
}
}
::v-deep .rightsGraphDialog {
.el-dialog__body {
padding: 0;
}
.ai-dialog__content {
padding-bottom: 0;
}
}
::v-deep .datail-table-body {
width: 100%;
height: auto;
margin-bottom: 16px;
display: flex;
flex-wrap: wrap;
.datail-item {
flex-shrink: 0;
width: 50%;
height: 24px;
line-height: 24px;
span {
display: inline-block;
font-size: 12px;
}
.item-name {
width: 102px;
padding-left: 16px;
color: #333;
}
}
.datail-item:nth-of-type(2n - 1) {
border-right: 1px solid rgba(208, 212, 220, 1);
width: calc(50% - 1px);
}
}
.padd-l0 {
padding-left: 0 !important;
}
.pad-l16 {
padding-left: 16px;
}
}
</style>

View File

@@ -0,0 +1,195 @@
<template>
<ai-detail class="rightsAdd">
<ai-title :title="addTitle" slot="title" isShowBottomBorder isShowBack @onBackClick="back"/>
<template #content>
<el-form size="small" ref="rightsForm" :model="form" label-width="120px" :rules="rules">
<ai-card title="基本信息">
<template #content>
<el-form-item label="应用角色名称" prop="roleName">
<el-input v-model="form.roleName" placeholder="请输入应用角色名称" clearable/>
</el-form-item>
</template>
</ai-card>
<ai-card title="权限信息">
<template #content>
<el-form-item label="应用" prop="appId">
<ai-select placeholder="请选择应用" v-model="form.appId" :selectList="top.appList"
@change="getPermissions"/>
</el-form-item>
<el-form-item label="权限列表" prop="menus" v-if="form.appId">
<div class="roleList">
<el-input v-model="filterText" placeholder="请输入..." clearable suffix-icon="iconfont iconSearch"
@change="$refs.tree.filter(filterText)" :validate-event="false"/>
<div class="tree_list">
<el-tree class="filter-tree" ref="roleTree"
:data="roleList"
show-checkbox
:props="defaultProps"
default-expand-all
:check-strictly="false"
node-key="id"
:default-checked-keys="form.menus"
:filter-node-method="filterNode"
@check="handleMenusSelect"/>
</div>
</div>
</el-form-item>
</template>
</ai-card>
</el-form>
</template>
<template #footer>
<el-button @click="back()">取消</el-button>
<el-button type="primary" @click="confirm">保存</el-button>
</template>
</ai-detail>
</template>
<script>
export default {
name: "rightsAdd",
inject: ['top'],
props: {
instance: Function,
dict: Object,
permissions: Function
},
data() {
return {
form: {},
roleName: '',
id: '',
appList: [],
roleList: [],
defaultProps: {
children: 'list',
label: 'name'
},
treeList: [],
filterText: '',
msgTitle: '添加',
}
},
created() {
if (this.isEdit) {
let {id, appId, name: roleName} = this.$route.query
this.form = {appId, menus: [], id, roleName}
this.getPermissions()
this.msgTitle = '编辑'
}
},
computed: {
isEdit() {
return this.$route.query.id
},
addTitle() {
return this.isEdit ? '编辑应用角色' : '新增应用角色'
},
rules() {
return {
roleName: {required: true, message: '请输入应用角色名称'},
appId: {required: true, message: '请选择应用'},
menus: {required: true, message: '请选择权限列表内容'},
}
}
},
methods: {
filterNode(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
},
//应用名称选择 获取权限列表
getId(data) {
if (data.list.length) {
data.list.forEach(item => {
this.getId(item)
})
} else {
if (data.checked) {
this.form.menus?.push(data.id)
}
}
},
getPermissions() {
this.filterText = ''
let {appId: id, id: roleId} = this.form
this.instance.post(this.top.actions.detail, null, {
params: {id, roleId}
}).then(res => {
if (res?.data) {
this.roleList = [res.data];
if (this.isEdit) {
this.roleList.forEach(e => this.getId(e))
}
this.roleList = this.roleList.filter(item => !(item.component && item.isApp == 0 && item.isMenu == 0))
}
})
},
handleMenusSelect(node, selected) {
this.$set(this.form, 'menus', [...selected?.checkedKeys])
this.$refs.rightsForm.validateField('menus')
},
//保存提交
confirm() {
this.$refs.rightsForm.validate(v => {
if (v) {
let menus = [this.$refs.roleTree?.getHalfCheckedKeys(), this.$refs.roleTree?.getCheckedKeys()]?.flat()?.toString()
this.instance.post(this.top.actions.modify, null, {
params: {...this.form, menus}
}).then(res => {
if (res?.code == 0) {
this.$message.success(`${this.msgTitle}应用角色成功`)
this.back()
this.top.searchList()
}
})
}
})
},
//取消 返回
back() {
this.$router.push({})
}
}
}
</script>
<style lang="scss" scoped>
.rightsAdd {
width: 100%;
height: 100%;
position: relative;
.el-form-item {
.el-select {
width: 100%;
}
&.is-error {
.roleList {
border-color: #f46;
}
}
}
.roleList {
background-color: #fcfcfc;
border-radius: 2px;
border: solid 1px #d0d4dc;
padding: 8px;
.input {
display: flex;
justify-content: space-between;
padding: 5px;
margin: 0;
}
.tree_list {
padding: 5px;
height: 370px;
overflow: auto;
}
}
}
</style>

View File

@@ -0,0 +1,192 @@
<template>
<section class="rightsGraph">
<div id="RightGraph"/>
</section>
</template>
<script>
import * as echarts from "echarts";
export default {
name: "rightsGraph",
inject: ['top'],
props: {
instance: Function,
dict: Object,
app: Object
},
computed: {
graphData() {
let data = [...this.users, ...this.nodes].map(e => {
if (e.x) {
return e
} else return {...e, ...this.renderPosition(e)}
})
return [
{
data,
links: this.links,
categories: data.map(e => e.category).flat().map(name => ({name}))
}
]
}
},
data() {
return {
graph: null,
nodes: [],
links: [],
users: []
}
},
watch: {
graphData: {
deep: true, handler() {
this.refreshGraph()
}
}
},
methods: {
initGraph() {
let dom = document.querySelector("#RightGraph")
if (dom) {
this.graph = echarts.init(dom)
this.graph.setOption({
tooltip: {},
series: [
{
type: 'graph',
layout: 'none',
roam: true,
label: {
show: true,
position: 'right',
formatter: '{b}'
},
labelLayout: {
hideOverlap: true,
},
scaleLimit: {
min: 0.4,
max: 4
},
lineStyle: {
color: 'target',
curveness: 0.1
},
emphasis: {
focus: 'adjacency',
lineStyle: {
width: 5
}
}
}
]
})
this.graph.on('click', this.handleNodeClick)
}
},
refreshGraph() {
this.graph?.setOption({
series: this.graphData
})
},
getAppRoles(role) {
if (role) {
this.nodes.push({...role, category: '应用角色', value: "应用角色", symbolSize: 15})
} else this.instance.post(this.top.actions.list, null, {
params: {pageSize: 999}
}).then(res => {
if (res?.data) {
res.data.records.map(e => {
this.getUsers(e.users, e.id)
this.nodes.push({...e, category: '应用角色', value: "应用角色"})
})
}
})
},
getUsers(pending, source) {
pending?.map(e => {
if (!this.users.some(u => u.id == e.phone)) {
this.users.push({id: e.phone, name: e.name, symbolSize: 5, category: '用户', value: "用户"})
}
this.links.push({source, target: e.phone})
})
},
getPermissions({appId: id, id: roleId, name: category, dataIndex}) {
const addNodes = (list, source, category, pos) => {
list?.map(e => {
let node = {
...e,
symbolSize: 5,
category,
value: e.list?.length > 0 ? "应用" : "权限",
}
node = {...node, ...this.renderPosition(pos || node)}
this.nodes.splice(dataIndex, 0, node)
this.links.push({source, target: e.id})
addNodes(e.list, e.id, e.label, node)
})
}
id && this.instance.post(this.top.actions.detail, null, {
params: {id, roleId}
}).then(res => {
if (res?.data) {
addNodes(res.data.list, roleId, category)
}
})
},
handleNodeClick(v) {
let {data: role, dataIndex} = v
if (!this.nodes.some(e => e.category == role?.name)) {
role && this.getPermissions({...role, dataIndex})
}
},
renderPosition(node) {
node = JSON.parse(JSON.stringify(node))
let pos = {x: 0, y: 0}
if (node?.x) {
pos.x = Math.max(this.graph?.getWidth() / 3 * 2, node.x) + 100 + Math.random() * 50
pos.y = node.y + Math.random() * 100 - 50
} else if (node.value == '应用角色') {
pos.x = this.graph?.getWidth() / 3 - 200 + Math.random() * 200
pos.y = this.graph?.getHeight() / 2 - 100 + Math.random() * 200
} else if (node.value == '应用') {
pos.x = this.graph?.getWidth() / 3 * 2 - 100 + Math.random() * 100
pos.y = Math.random() * this.graph?.getHeight()
} else if (node.value == '用户') {
pos.x = Math.random() * 50
pos.y = Math.random() * this.graph?.getHeight()
} else {
pos.x = this.graph?.getWidth() - 100 + Math.random() * 100
pos.y = Math.random() * this.graph?.getHeight()
}
return pos
}
},
created() {
this.getAppRoles(this.app)
if (this.app?.id) {
this.getUsers(this.app.users, this.app.id)
this.getPermissions(this.app)
}
},
mounted() {
this.$nextTick(() => {
this.initGraph()
})
}
}
</script>
<style lang="scss" scoped>
.rightsGraph {
height: 100%;
::v-deep #RightGraph {
width: 100%;
height: 100%;
min-height: 500px;
}
}
</style>

View File

@@ -0,0 +1,211 @@
<template>
<section class="AppSystemAccount">
<ai-list>
<ai-title slot="title" title="账号管理" isShowBottomBorder/>
<template #content>
<ai-search-bar>
<template #left>
<el-button type="primary" icon="iconfont iconAdd" @click="dialog=true">添加</el-button>
<!-- <el-button type="primary" :disabled="!ids.toString()" @click="batchAllot">功能分配</el-button>-->
</template>
<template #right>
<el-input size="small" placeholder="搜索姓名、手机号" v-model="search.condition" clearable
@change="page.pageNum=1,getTableData()"/>
</template>
</ai-search-bar>
<ai-table :tableData="tableData" :total="page.total" :current.sync="page.pageNum" :size.sync="page.pageSize"
@getList="getTableData" :col-configs="colConfigs" :dict="dict"
@selection-change="v=>ids=v.map(e=>e.id)">
<el-table-column slot="name" label="姓名" width="180px">
<el-row type="flex" align="middle" slot-scope="{row}">
<el-image class="avatar" :src="row.avatar" :preview-src-list="[row.avatar]">
<el-image slot="error" src="https://cdn.cunwuyun.cn/dvcp/h5/defaultAvatar.png" alt=""/>
</el-image>
<div>{{ row.name }}</div>
</el-row>
</el-table-column>
<el-table-column slot="options" align="center" label="操作" fixed="right" width="160px">
<el-row type="flex" justify="center" align="middle" slot-scope="{row}">
<el-button type="text" @click="appAllot(row)">功能分配</el-button>
<el-button type="text" @click="handleDelete(row.id)">删除</el-button>
</el-row>
</el-table-column>
</ai-table>
</template>
</ai-list>
<!--添加账号功能分配-->
<ai-dialog :title="dialogTitle" :visible.sync="dialog" width="600px" @open="initDialogData"
@onConfirm="updateAccount" @closed="dialogForm={}">
<el-form ref="updateAccountForm" :model="dialogForm" :rules="rules" size="small"
label-width="120px">
<el-form-item required label="姓名" prop="name">
<el-input v-model.trim="dialogForm.name" placeholder="请输入..." clearable
:maxLength="15"/>
</el-form-item>
<el-form-item required label="手机号码" prop="phone">
<el-input v-model.trim="dialogForm.phone" placeholder="请输入..." clearable
:maxLength="11" :disabled="isEdit"/>
</el-form-item>
<el-form-item required label="角色" prop="roleId">
<el-select size="small" placeholder="请选择角色" :value="dialogForm.roleId" filterable
v-model="dialogForm.roleId" clearable>
<el-option v-for="(op,i) in accountRoles" :key="i" :label="op.name" :value="op.id"/>
</el-select>
</el-form-item>
<el-form-item label="行政地区" prop="areaId">
<ai-area-get v-model="dialogForm.areaId" :instance="instance" @select="handleAreaSelect"/>
</el-form-item>
</el-form>
</ai-dialog>
</section>
</template>
<script>
import {mapState} from "vuex";
export default {
name: "AppSystemAccount",
label: "账号管理",
props: {
instance: Function,
dict: Object,
permissions: Function
},
computed: {
...mapState(['user']),
cascaderProps() {
return {
value: 'id',
checkStrictly: true,
emitPath: false
}
},
isEdit() {
return !!this.dialogForm.id
},
dialogTitle() {
return this.isEdit ? '功能分配' : '添加账号'
},
colConfigs() {
return [
// {type: 'selection', align: 'center'},
{label: "姓名", slot: "name"},
{label: "联系方式", prop: "phone", align: 'center'},
{label: "角色", prop: "roleName", align: 'center'},
{label: "地区", prop: "areaName"},
{slot: "options"}
]
},
rules() {
return {
name: [{required: true, message: "请填写姓名"}],
// organizationId: [{required: true, message: "请选择党组织"}],
// unitId: [{required: true, message: "请选择单位"}],
// areaId: [{required: true, message: '请选择地区', trigger: 'change'}],
roleId: [{required: true, message: "请选择角色"}],
phone: [{required: true, message: "请输入手机号码"}]
}
},
disabledLevel() {
return this.user.info.areaList?.length || 0
}
},
data() {
return {
accountRoles: [],
page: {pageNum: 1, pageSize: 10, total: 0},
dialog: false,
dialogForm: {},
tableData: [],
search: {condition: ""},
ids: []
}
},
methods: {
getTableData() {
this.instance.post("/admin/user/page", null, {
params: {...this.page, ...this.search}
}).then(res => {
if (res?.data) {
this.tableData = res.data?.records
this.page.total = res.data.total
}
})
},
initDialogData() {
//用于优化初始化数据
this.getAccountRoles()
},
getAccountRoles() {
this.accountRoles.length == 0 && this.instance.post("/admin/role/list-all").then(res => {
if (res?.data) {
this.accountRoles = res.data
}
})
},
batchAllot() {
this.dialog = true
this.dialogForm = {areaId: this.user.info.areaId, ids: this.ids}
},
appAllot(row) {
this.dialog = true
this.dialogForm = JSON.parse(JSON.stringify({
...row,
areaId: row.areaId || this.user.info.areaId
}));
},
// 修改
updateAccount() {
this.$refs.updateAccountForm.validate(v => {
if (v) {
this.instance.post("/admin/user/addOrEdit", this.dialogForm).then(res => {
if (res?.code == 0) {
this.dialog = false;
this.$message.success("提交成功")
this.getTableData();
} else {
this.$message.error(res?.msg)
}
})
}
})
},
handleDelete(ids) {
this.$confirm("是否要删除该账号?").then(() => {
this.instance.post("/admin/user/del", null, {
params: {ids}
}).then(res => {
if (res?.code == 0) {
this.getTableData();
this.$message.success("删除成功!");
}
})
}).catch(() => 0)
},
handleAreaSelect(v) {
this.dialogForm.areaName = v?.[0]?.label
}
},
created() {
this.getTableData()
}
}
</script>
<style lang="scss" scoped>
.AppSystemAccount {
height: 100%;
::v-deep .avatar {
width: 40px;
height: 40px;
margin-right: 10px;
}
::v-deep .el-form {
.el-cascader, .el-select {
width: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,193 @@
<template>
<section class="AppUserInfo">
<ai-detail>
<ai-title slot="title" title="个人中心" isShowBottomBorder/>
<template #content>
<ai-card title="个人资料">
<template #right>
<span style="color:#999" v-text="'(如需修改基本信息,请联系管理员)'"/>
</template>
<template #content>
<el-row type="flex" justify="space-between">
<ai-wrapper>
<ai-info-item label="姓名" :value="user.info.name"/>
<ai-info-item label="手机号码" :value="user.info.phone"/>
<ai-info-item label="角色" :value="user.info.roleName"/>
<ai-info-item label="部门" :value="user.info.departName"/>
<ai-info-item label="职位" :value="user.info.position"/>
</ai-wrapper>
<ai-avatar :value="user.info.avatar" :editable="false"/>
</el-row>
</template>
</ai-card>
<ai-card title="数据权限">
<template #right>
<span style="color:#999" v-text="'(如需修改基本信息,请联系管理员)'"/>
</template>
<template #content>
<ai-wrapper>
<ai-info-item label="所属地区" :value="user.info.areaList.join('')" isLine/>
<ai-info-item label="所属党组织" :value="user.info.organizationName" isLine/>
</ai-wrapper>
</template>
</ai-card>
<ai-card title="密码设置">
<template #right>
<el-button type="text" @click="submitForm">保存</el-button>
</template>
<template #content>
<el-form :model="form" ref="ruleForm" :rules="rules" label-width="100px" size="small">
<el-form-item label="绑定手机:" prop="phone">
<el-row type="flex" align="middle">
<span>{{ user.info.phone }}</span>
<el-button type="primary" style="margin-left: 8px" @click="getCode(user.info.phone,true)"
:disabled="codeBtn">获取验证码
<span v-if="num>0&&codeBtn" style="color:red;">({{ num }}s)</span>
</el-button>
</el-row>
</el-form-item>
<el-form-item label="验证码:" prop="code">
<el-input v-model.trim="form.code" clearable placeholder="请输入短信验证码"/>
<el-input style="position: fixed; bottom: -9999px"></el-input>
</el-form-item>
<el-form-item label="新密码:" prop="pass">
<el-input type="password" auto-complete="new-password" v-model.trim="form.pass" clearable
placeholder="8-16位需要包含字母和数字及特殊字符(~!@#$%^&*,.?_-)"
show-password/>
</el-form-item>
<el-form-item label="确认密码:" prop="checkPass">
<el-input type="password" auto-complete="new-password" placeholder="再次输入密码"
v-model.trim="form.checkPass" clearable
show-password/>
</el-form-item>
</el-form>
</template>
</ai-card>
</template>
</ai-detail>
</section>
</template>
<script>
import {mapMutations, mapState} from "vuex";
export default {
name: "AppUserInfo",
label: "个人中心",
props: {
instance: Function,
},
computed: {
...mapState(['user']),
rules() {
const validatePass = (rule, value, callback) => {
const reg = /^(?=.*\d)(?=.*[a-zA-Z])(?=.*[~!@#$%^&*,.?_-])[\da-zA-Z~!@#$%^&*,.?_-]{8,16}$/;
if (!reg.test(value)) {
callback(new Error('数字和字母及特殊字符(~!@#$%^&*,.?_-)组合,长度8到16位'));
} else {
if (this.form.checkPass !== '') {
this.$refs.ruleForm.validateField('checkPass');
}
callback();
}
}
return {
currentPass: [
{required: true, message: '请填写当前密码', trigger: 'blur'}
],
code: [
{required: true, message: '请填写验证码', trigger: 'blur'}
],
pass: [
{validator: validatePass, trigger: 'blur', required: true}
],
checkPass: [
{
validator: (r, v, cb) => v ? v != this.form.pass ? cb('两次输入密码不一致') : cb() : cb('请再次输入密码'),
trigger: 'blur',
required: true
}
],
}
}
},
data() {
return {
form: {},
timer: null,
codeBtn: false,
num: 60,
}
},
methods: {
...mapMutations(['SignOut']),
getCode(phone, flag) {
const TEL_REGEXP = /^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\d{8}$/;
if (TEL_REGEXP.test(phone)) {
this.instance.post(`/admin/user/checkPhone`, null, {
params: {phone, flag}
}).then(res => {
if (res.code == 0) {
this.$message.success("请查看手机短信!");
this.timer = setInterval(() => {
if (this.num > 0) {
this.codeBtn = true;
this.num--;
} else if (this.num == 0) {
this.codeBtn = false;
this.num = 60;
clearInterval(this.timer);
}
}, 1000)
} else {
this.$message.error("验证码发送失败!");
}
})
} else {
this.$message.error("手机号格式错误!");
}
},
submitForm() {
this.$refs.ruleForm.validate(v => {
if (v) {
let {pass: newPwd, code} = this.form
this.instance.post(`/admin/user/update-pwd`, null, {
params: {
phone: this.user.info.phone,
newPwd, code
}
}).then(res => {
if (res?.code == 0) {
this.$confirm("村微提醒您,更换密码成功!", {
showCancelButton: false
}).then(() => this.SignOut()).catch(() => 0)
}
})
}
})
}
}
}
</script>
<style lang="scss" scoped>
.AppUserInfo {
height: 100%;
::v-deep .ai-list__content--wrapper {
flex-direction: column;
}
::v-deep .el-input__inner {
-webkit-text-security: disc !important;
}
}
</style>