第一次提交
This commit is contained in:
233
src/views/system/access-key/components/accesskey-edit.vue
Normal file
233
src/views/system/access-key/components/accesskey-edit.vue
Normal file
@@ -0,0 +1,233 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
width="400px"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="'手机验证'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
:maskClosable="false"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form class="login-form">
|
||||
<a-form-item label="绑定的手机号码" name="phone">
|
||||
{{ getMobile(form.phone) }}
|
||||
</a-form-item>
|
||||
<a-form-item label="校验码" name="code">
|
||||
<div class="login-input-group">
|
||||
<a-input
|
||||
allow-clear
|
||||
type="text"
|
||||
:maxlength="6"
|
||||
v-model:value="form.code"
|
||||
>
|
||||
</a-input>
|
||||
<a-button
|
||||
class="login-captcha"
|
||||
:disabled="!!countdownTime"
|
||||
@click="openImgCodeModal"
|
||||
>
|
||||
<span v-if="!countdownTime">发送验证码</span>
|
||||
<span v-else>已发送 {{ countdownTime }} s</span>
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch, computed, onBeforeUnmount } from "vue";
|
||||
import { Form, message, Modal, SelectProps } from "ant-design-vue";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import type { AccessKey } from "@/api/system/access-key/model";
|
||||
import { addAccessKey, updateAccessKey } from "@/api/system/access-key";
|
||||
import { FILE_SERVER } from "@/config/setting";
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
import { RuleObject } from "ant-design-vue/es/form";
|
||||
import { isImage } from "@/utils/common";
|
||||
import { listUsers } from '@/api/system/user';
|
||||
import { getMobile } from '@/utils/common';
|
||||
import { sendSmsCaptcha } from '@/api/login';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: AccessKey | null;
|
||||
}>();
|
||||
|
||||
const userStore = useUserStore();
|
||||
// 当前登录用户信息
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const disabled = ref(false);
|
||||
// 选项卡位置
|
||||
const activeKey = ref("1");
|
||||
const promoter = ref<any>(undefined);
|
||||
const commander = ref(undefined);
|
||||
const appid = ref(undefined);
|
||||
|
||||
/* 打开选择弹窗 */
|
||||
const content = ref("");
|
||||
// 图形验证码地址
|
||||
const captcha = ref("");
|
||||
// 验证码倒计时定时器
|
||||
let countdownTimer: number | null = null;
|
||||
// 验证码倒计时时间
|
||||
const countdownTime = ref(0);
|
||||
// 图形验证码
|
||||
const imgCode = ref("");
|
||||
// 发送验证码按钮loading
|
||||
const codeLoading = ref(false);
|
||||
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "done", form: AccessKey): void;
|
||||
(e: "update:visible", value: boolean): void;
|
||||
}>();
|
||||
// 已上传数据, 可赋初始值用于回显
|
||||
const avatar = ref(<any>[]);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 用户信息
|
||||
const form = reactive<AccessKey>({
|
||||
id: 0,
|
||||
phone: "",
|
||||
accessKey: "",
|
||||
accessSecret: "",
|
||||
code: undefined,
|
||||
createTime: ""
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit("update:visible", value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请输入工单名称",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
taskType: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请选择工单类型",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
content: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请输入工单内容",
|
||||
trigger: "blur",
|
||||
validator: async (_rule: RuleObject, value: string) => {
|
||||
if (content.value == "") {
|
||||
return Promise.reject("请输入文字内容");
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 显示发送短信验证码弹窗 */
|
||||
const openImgCodeModal = () => {
|
||||
if (!form.phone) {
|
||||
message.error("手机号码有误");
|
||||
return;
|
||||
}
|
||||
// imgCode.value = "";
|
||||
sendCode();
|
||||
// visible.value = true;
|
||||
};
|
||||
|
||||
/* 发送短信验证码 */
|
||||
const sendCode = () => {
|
||||
codeLoading.value = true;
|
||||
sendSmsCaptcha({ phone: form.phone }).then((res) => {
|
||||
console.log(res);
|
||||
message.success("短信验证码发送成功, 请注意查收!");
|
||||
codeLoading.value = false;
|
||||
countdownTime.value = 30;
|
||||
// 开始对按钮进行倒计时
|
||||
countdownTimer = window.setInterval(() => {
|
||||
if (countdownTime.value <= 1) {
|
||||
countdownTimer && clearInterval(countdownTimer);
|
||||
countdownTimer = null;
|
||||
}
|
||||
countdownTime.value--;
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
countdownTimer && clearInterval(countdownTimer);
|
||||
});
|
||||
|
||||
const { validate, validateInfos } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
updateVisible(false);
|
||||
const { code,phone } = form;
|
||||
emit("done", { code,phone });
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
};
|
||||
|
||||
const query = () => {
|
||||
listUsers({username: 'admin'}).then(res => {
|
||||
form.phone = res[0].phone;
|
||||
})
|
||||
}
|
||||
query();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.login-form{
|
||||
padding: 0 20px;
|
||||
}
|
||||
.login-form-right .login-form {
|
||||
margin: 0 15% 0 auto;
|
||||
}
|
||||
|
||||
.login-form-left .login-form {
|
||||
margin: 0 auto 0 15%;
|
||||
}
|
||||
|
||||
/* 验证码 */
|
||||
.login-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.login-captcha {
|
||||
margin-left: 10px;
|
||||
padding: 0 10px;
|
||||
|
||||
& > img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
151
src/views/system/access-key/index.vue
Normal file
151
src/views/system/access-key/index.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<a-page-header :ghost="false" title="秘钥管理">
|
||||
<div class="ele-text-secondary">
|
||||
AccessKey ID 和 AccessKey Secret 是您访WebSoft-API
|
||||
的密钥,具有该账户完全的权限,请您妥善保管。
|
||||
</div>
|
||||
</a-page-header>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false">
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="logId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:where="defaultWhere"
|
||||
cache-key="userBalanceLogTable"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
<span>创建 AccessKey</span>
|
||||
</a-button>
|
||||
<a-button @click="reset">刷新</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'accessSecret'">
|
||||
<span v-if="record.accessSecret">
|
||||
{{ record.accessSecret }}
|
||||
</span>
|
||||
<a @click="openEdit(record)" v-else>查看 Secret</a>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">查看 Secret</a>
|
||||
<!-- <a-divider type="vertical" />-->
|
||||
<!-- <a @click="resetPsw(record)">禁用</a>-->
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
</div>
|
||||
<!-- 编辑弹窗 -->
|
||||
<AccessKeyEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { EleProTable } from 'ele-admin-pro/es';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import AccessKeyEdit from './components/accesskey-edit.vue';
|
||||
import { toDateString } from 'ele-admin-pro/es';
|
||||
import {
|
||||
addAccessKey,
|
||||
pageAccessKey
|
||||
} from '@/api/system/access-key';
|
||||
import { AccessKey, AccessKeyParam } from '@/api/system/access-key/model';
|
||||
import { App } from '@/api/app/model';
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'AccessKey ID',
|
||||
key: 'accessKey',
|
||||
dataIndex: 'accessKey',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: 'AccessSecret',
|
||||
key: 'accessSecret',
|
||||
dataIndex: 'accessSecret',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
customRender: ({ text }) => toDateString(text)
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<AccessKey[]>([]);
|
||||
const searchText = ref('');
|
||||
const userId = ref<number>(0);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<App | null>(null);
|
||||
|
||||
// 默认搜索条件
|
||||
const defaultWhere = reactive({
|
||||
code: '',
|
||||
phone: '',
|
||||
username: '',
|
||||
nickname: '',
|
||||
userId: undefined
|
||||
});
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
return pageAccessKey({ ...where, ...orders, page, limit }).catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
userId.value = 0;
|
||||
searchText.value = '';
|
||||
reload();
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: App) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: AccessKeyParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const add = () => {
|
||||
addAccessKey({})
|
||||
.then((res) => {
|
||||
reload();
|
||||
})
|
||||
.catch((err) => {
|
||||
message.error(err.message);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'AccessKeyIndex'
|
||||
};
|
||||
</script>
|
||||
225
src/views/system/appstore/components/buy.vue
Normal file
225
src/views/system/appstore/components/buy.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="900"
|
||||
:visible="visible"
|
||||
v-if="data"
|
||||
:confirm-loading="loading"
|
||||
:title="`购买插件(${data.appName})`"
|
||||
:maxable="true"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-tabs type="card" v-model:activeKey="activeKey" tabPosition="top">
|
||||
<a-tab-pane key="1" tab="专业版">
|
||||
<div class="pay-box">
|
||||
<div class="qrcode">
|
||||
<ele-qr-code
|
||||
:value="text"
|
||||
:size="200"
|
||||
:margin="2"
|
||||
:image-settings="image"
|
||||
/>
|
||||
</div>
|
||||
<div class="pay-info">
|
||||
<a-alert
|
||||
message="提示:支付后请耐心等待支付结果,请勿刷新浏览器,否则将会导致购买异常。"
|
||||
banner
|
||||
closable
|
||||
style="margin-bottom: 12px"
|
||||
/>
|
||||
<a-descriptions title="" :column="{ xs: 1, sm: 1, md: 1 }">
|
||||
<a-descriptions-item label="模块名称">{{
|
||||
data.appName
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="当前账号">{{
|
||||
loginUser.nickname
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="订单金额"
|
||||
><span class="ele-text-warning"
|
||||
>¥{{ data.price }} /1年</span
|
||||
></a-descriptions-item
|
||||
>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="企业版">
|
||||
<div class="pay-box">
|
||||
<div class="qrcode">
|
||||
<ele-qr-code
|
||||
:value="text"
|
||||
:size="200"
|
||||
:margin="2"
|
||||
:image-settings="image"
|
||||
/>
|
||||
</div>
|
||||
<div class="pay-info">
|
||||
<a-alert
|
||||
message="提示:支付后请耐心等待支付结果,请勿刷新浏览器,否则将会导致购买异常。"
|
||||
banner
|
||||
closable
|
||||
style="margin-bottom: 12px"
|
||||
/>
|
||||
<a-descriptions title="" :column="{ xs: 1, sm: 1, md: 1 }">
|
||||
<a-descriptions-item label="模块名称">{{
|
||||
data.appName
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="当前账号">{{
|
||||
loginUser.nickname
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="订单金额"
|
||||
><span class="ele-text-warning"
|
||||
>¥{{ data.price }} /1年</span
|
||||
></a-descriptions-item
|
||||
>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="应用介绍">
|
||||
<div class="app-info">
|
||||
<!-- 编辑器 -->
|
||||
<tinymce-editor
|
||||
v-model:value="content"
|
||||
:disabled="true"
|
||||
:init="config"
|
||||
/>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch, computed } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import type { App } from '@/api/dashboard/appstore/model';
|
||||
import type { ImageSettings } from 'ele-admin-pro/es/ele-qr-code/types';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { installApp } from '@/api/system/menu';
|
||||
import type { Menu } from '@/api/system/menu/model';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: App | null;
|
||||
showView?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<Menu>({
|
||||
menuId: undefined,
|
||||
parentId: undefined,
|
||||
title: '',
|
||||
menuType: 0,
|
||||
openType: 0,
|
||||
icon: '',
|
||||
path: '',
|
||||
component: '',
|
||||
authority: '',
|
||||
sortNumber: undefined,
|
||||
hide: 0,
|
||||
meta: '',
|
||||
appId: undefined
|
||||
});
|
||||
|
||||
const userStore = useUserStore();
|
||||
// 当前用户信息
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
const content = ref('');
|
||||
const activeKey = ref('1');
|
||||
const loading = ref(false);
|
||||
const text = ref('weixin://wxpay/bizpayurl?pr=r7C2C7Ozz');
|
||||
const image = reactive<ImageSettings>({
|
||||
src: 'https://cdn.eleadmin.com/20200610/logo-radius.png',
|
||||
width: 28,
|
||||
height: 28
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const config = ref({
|
||||
toolbar: false,
|
||||
menubar: false,
|
||||
height: 700
|
||||
});
|
||||
|
||||
const { resetFields, validate } = useForm(form);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const data = {
|
||||
title: props.data?.appName,
|
||||
parentId: props.data?.parentId,
|
||||
menuType: Number(props.data?.menuType),
|
||||
icon: props.data?.appIcon,
|
||||
path: props.data?.path,
|
||||
sortNumber: props.data?.sortNumber,
|
||||
component: props.data?.component,
|
||||
authority: props.data?.authority,
|
||||
meta: props.data?.meta,
|
||||
appId: props.data?.appId
|
||||
};
|
||||
installApp(data)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
content.value = String(props.data?.content);
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="less">
|
||||
.tab-pane {
|
||||
min-height: 100px;
|
||||
}
|
||||
.card-head {
|
||||
display: flex;
|
||||
height: 40px;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.pay-box {
|
||||
display: flex;
|
||||
.qrcode {
|
||||
border: 1px solid #f3f3f3;
|
||||
width: 204px;
|
||||
height: auto;
|
||||
}
|
||||
.pay-info {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
<!-- 角色选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
allow-clear
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@blur="onBlur"
|
||||
@change="onChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
(e: 'change'): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择客户跟进状态'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = getDictionaryOptions('customerFollowStatus');
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
|
||||
/* 选择事件 */
|
||||
const onChange = (e) => {
|
||||
emit('change', e);
|
||||
};
|
||||
</script>
|
||||
51
src/views/system/appstore/components/dict/source-select.vue
Normal file
51
src/views/system/appstore/components/dict/source-select.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<!-- 客户来源选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
allow-clear
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@blur="onBlur"
|
||||
@change="onChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
(e: 'change'): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择客户来源'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = getDictionaryOptions('customerSource');
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
|
||||
/* 选择事件 */
|
||||
const onChange = (e) => {
|
||||
emit('change', e);
|
||||
};
|
||||
</script>
|
||||
45
src/views/system/appstore/components/dict/status-select.vue
Normal file
45
src/views/system/appstore/components/dict/status-select.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<!-- 角色选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
allow-clear
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择状态'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = getDictionaryOptions('status');
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
44
src/views/system/appstore/components/dict/type-select.vue
Normal file
44
src/views/system/appstore/components/dict/type-select.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<!-- 角色选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
allow-clear
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择客户类型'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = getDictionaryOptions('customerType');
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
76
src/views/system/appstore/components/dict/user-select.vue
Normal file
76
src/views/system/appstore/components/dict/user-select.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- 角色选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
allow-clear
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@search="onSearch"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { listUsers } from '@/api/system/user';
|
||||
import type { SelectProps } from 'ant-design-vue';
|
||||
import { UserParam } from '@/api/system/user/model';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择客户类型'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = ref<SelectProps['options']>([]);
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
|
||||
// 默认搜索条件
|
||||
const where = ref<UserParam>({});
|
||||
|
||||
const search = () => {
|
||||
/* 获取用户列 */
|
||||
listUsers({ ...where?.value })
|
||||
.then((result) => {
|
||||
data.value = result?.map((d) => {
|
||||
return {
|
||||
value: d.userId,
|
||||
label: d.nickname
|
||||
};
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const onSearch = (e) => {
|
||||
where.value.nickname = e;
|
||||
search();
|
||||
};
|
||||
|
||||
search();
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
400
src/views/system/appstore/components/edit.vue
Normal file
400
src/views/system/appstore/components/edit.vue
Normal file
@@ -0,0 +1,400 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="740"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '编辑应用' : '创建应用'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 6 }, sm: { span: 4 }, xs: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 18 }, sm: { span: 20 }, xs: { span: 24 } }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :md="12" :sm="24" :xs="24">
|
||||
<a-form-item label="应用分类" v-bind="validateInfos.appType">
|
||||
<a-select
|
||||
optionFilterProp="label"
|
||||
placeholder="请选择应用分类"
|
||||
allow-clear
|
||||
v-model:value="form.appType"
|
||||
>
|
||||
<template v-for="item in appTypeDict">
|
||||
<a-select-option :value="item.value">{{ item.label }}</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="应用名称" v-bind="validateInfos.appName">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入应用名称"
|
||||
v-model:value="form.appName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="应用入口" v-bind="validateInfos.component">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="/dashboard/workplace"
|
||||
v-model:value="form.component"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="24" :xs="24">
|
||||
<a-form-item label="应用标识" v-bind="validateInfos.appCode">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="goods"
|
||||
v-model:value="form.appCode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="授权价格" v-bind="validateInfos.price">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="授权价格"
|
||||
v-model:value="form.price"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" v-role="'superAdmin'" v-bind="validateInfos.sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="99999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item
|
||||
label="应用简介"
|
||||
v-bind="validateInfos.comments"
|
||||
:label-col="{ md: { span: 3 }, sm: { span: 4 }, xs: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 21 }, sm: { span: 20 }, xs: { span: 24 } }"
|
||||
>
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入应用简介"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
<div style="margin-bottom: 22px">
|
||||
<a-divider />
|
||||
</div>
|
||||
<a-row :gutter="16">
|
||||
<a-col :md="12" :sm="24" :xs="24">
|
||||
<a-form-item label="应用图标" v-bind="validateInfos.appIcon">
|
||||
<ele-icon-picker
|
||||
v-model:value="form.appIcon"
|
||||
allow-clear
|
||||
placeholder="请选择图标"
|
||||
:disabled="form.appType === 1"
|
||||
:data="iconData"
|
||||
>
|
||||
<template #icon="{ icon }">
|
||||
<component :is="icon"/>
|
||||
</template>
|
||||
</ele-icon-picker>
|
||||
</a-form-item>
|
||||
<a-form-item label="下载地址" v-bind="validateInfos.downUrl">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="模块代码下载地址"
|
||||
v-model:value="form.downUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="24" :xs="24">
|
||||
<a-form-item label="当前版本" v-bind="validateInfos.edition">
|
||||
<a-select
|
||||
optionFilterProp="label"
|
||||
placeholder="请选择版本"
|
||||
allow-clear
|
||||
v-model:value="form.edition"
|
||||
>
|
||||
<a-select-option value="正式版">正式版</a-select-option>
|
||||
<a-select-option value="开发版">开发版</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="应用状态">
|
||||
<a-switch
|
||||
checked-children="启用"
|
||||
un-checked-children="禁用"
|
||||
:checked="form.status === 0"
|
||||
@update:checked="updateHideValue"
|
||||
/>
|
||||
<a-tooltip
|
||||
title="禁用后提示:模块维护中"
|
||||
>
|
||||
<question-circle-outlined
|
||||
style="vertical-align: -4px; margin-left: 16px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, reactive, watch} from 'vue';
|
||||
import {Form, message} from 'ant-design-vue';
|
||||
import type {RuleObject} from 'ant-design-vue/es/form';
|
||||
import iconData from 'ele-admin-pro/es/ele-icon-picker/icons';
|
||||
import {assignObject, isExternalLink} from 'ele-admin-pro';
|
||||
import {addApp, updateApp} from '@/api/dashboard/appstore';
|
||||
import type {App} from '@/api/dashboard/appstore/model';
|
||||
import {ItemType} from "ele-admin-pro/es/ele-image-upload/types";
|
||||
import {uploadFile} from "@/api/system/file";
|
||||
import {getDictionaryOptions} from "@/utils/common";
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: App | null;
|
||||
// 上级应用id
|
||||
parentId?: number;
|
||||
}>();
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
const icon = ref('');
|
||||
const appTypeDict = getDictionaryOptions('appstoreType');
|
||||
// 编辑器内容,双向绑定
|
||||
const content = ref('');
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<App>({
|
||||
// 应用id
|
||||
appId: undefined,
|
||||
// 应用名称
|
||||
appName: '',
|
||||
// 上级id, 0是顶级
|
||||
parentId: 0,
|
||||
// 应用编号
|
||||
appCode: '',
|
||||
// 应用图标
|
||||
appIcon: '',
|
||||
// 应用类型
|
||||
appType: undefined,
|
||||
// 应用地址
|
||||
appUrl: '',
|
||||
// 下载地址
|
||||
downUrl: '',
|
||||
// 应用包名
|
||||
packageName: '',
|
||||
// 点击次数
|
||||
clicks: '',
|
||||
// 安装次数
|
||||
installs: '',
|
||||
// 应用介绍
|
||||
content: '',
|
||||
// 开发者(个人)
|
||||
developer: '官方',
|
||||
// 页面路径
|
||||
component: undefined,
|
||||
// 软件授权价格
|
||||
price: '',
|
||||
// 评分
|
||||
score: '',
|
||||
// 星级
|
||||
star: '',
|
||||
// 排序
|
||||
sortNumber: 100,
|
||||
// 备注
|
||||
comments: '',
|
||||
// 权限标识
|
||||
authority: '',
|
||||
// 打开位置
|
||||
target: '',
|
||||
// 是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)
|
||||
hide: undefined,
|
||||
// 菜单侧栏选中的path
|
||||
active: '',
|
||||
// 其它路由元信息
|
||||
meta: '',
|
||||
// 版本
|
||||
edition: '开发版',
|
||||
// 版本号
|
||||
version: 'v1.0',
|
||||
// 创建时间
|
||||
createTime: '',
|
||||
// 状态
|
||||
status: 1,
|
||||
// 发布者
|
||||
userId: undefined,
|
||||
// 发布者昵称
|
||||
nickname: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
appName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入应用名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
component: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入组件路径',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
appType: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请选择应用分类',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
appCode: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入应用标识(英文)',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
comments: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入应用简介',
|
||||
}
|
||||
],
|
||||
sortNumber: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请输入排序号',
|
||||
}
|
||||
],
|
||||
price: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入软件授权价格',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
meta: [
|
||||
{
|
||||
type: 'string',
|
||||
trigger: 'blur',
|
||||
validator: async (_rule: RuleObject, value: string) => {
|
||||
if (value) {
|
||||
const msg = '请输入正确的JSON格式';
|
||||
try {
|
||||
const obj = JSON.parse(value);
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return Promise.reject(msg);
|
||||
}
|
||||
} catch (_e) {
|
||||
return Promise.reject(msg);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const {resetFields, validate, validateInfos} = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
parentId: form.parentId || 0,
|
||||
content: content.value
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateApp : addApp;
|
||||
saveOrUpdate(appForm)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
};
|
||||
|
||||
const onUpload = (d: ItemType) => {
|
||||
uploadFile(<File>d.file)
|
||||
.then((result) => {
|
||||
form.appIcon = result.path;
|
||||
message.success('上传成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const updateHideValue = (value: boolean) => {
|
||||
form.status = value ? 0 : 1;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
const isExternal = isExternalLink(props.data.path);
|
||||
const isInner = isExternalLink(props.data.component);
|
||||
content.value = String(props.data.content);
|
||||
form.price = props.data.price;
|
||||
assignObject(form, {
|
||||
...props.data,
|
||||
openType: isExternal ? 2 : isInner ? 1 : 0
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
form.parentId = props.parentId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<script lang="ts">
|
||||
import * as icons from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
components: icons
|
||||
};
|
||||
</script>
|
||||
96
src/views/system/appstore/components/search.vue
Normal file
96
src/views/system/appstore/components/search.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<!-- <a-button-->
|
||||
<!-- v-role="'superAdmin'"-->
|
||||
<!-- type="primary"-->
|
||||
<!-- class="ele-btn-icon"-->
|
||||
<!-- @click="add"-->
|
||||
<!-- >-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <plus-outlined />-->
|
||||
<!-- </template>-->
|
||||
<!-- <span>创建应用</span>-->
|
||||
<!-- </a-button>-->
|
||||
<a-input-search
|
||||
allow-clear
|
||||
placeholder="请输入应用名称"
|
||||
v-model:value="searchText"
|
||||
@pressEnter="search"
|
||||
@search="search"
|
||||
>
|
||||
<template #addonBefore>
|
||||
<a-select v-model:value="type" style="width: 100px; margin: -5px -12px">
|
||||
<a-select-option value="appName">应用名称</a-select-option>
|
||||
<a-select-option value="appCode">应用标识</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-input-search>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
SearchOutlined,
|
||||
DeleteOutlined,
|
||||
UpSquareOutlined,
|
||||
DownSquareOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import type { AppParam } from '@/api/dashboard/appstore/model';
|
||||
import { ref, watch } from 'vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: AppParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { where } = useSearch<AppParam>({
|
||||
appName: '',
|
||||
appCode: ''
|
||||
});
|
||||
// 下拉选项
|
||||
const type = ref('appName');
|
||||
// 搜索内容
|
||||
const searchText = ref('');
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
assignObject(where, {});
|
||||
if (type.value == 'appName') {
|
||||
where.appName = searchText.value;
|
||||
}
|
||||
if (type.value == 'appCode') {
|
||||
where.appCode = searchText.value;
|
||||
}
|
||||
emit('search', where);
|
||||
};
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
// 批量删除
|
||||
const removeBatch = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
83
src/views/system/appstore/components/setting.vue
Normal file
83
src/views/system/appstore/components/setting.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:visible="visible"
|
||||
v-if="data"
|
||||
:title="`${data.appName}`"
|
||||
:maxable="true"
|
||||
@update:visible="updateVisible"
|
||||
:footer="null"
|
||||
>
|
||||
<a-tabs type="card" v-model:activeKey="activeKey" tabPosition="top">
|
||||
<a-tab-pane key="0" tab="插件介绍">
|
||||
<Basic :data="data" :appId="data.appId" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="1" tab="菜单管理">
|
||||
<Menu :data="data" :menuType="0" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="按钮管理">
|
||||
<Authority :data="data" :menuType="1" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab="插件设置">
|
||||
<Setting :data="data" :appId="data.appId" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import type { App } from '@/api/dashboard/appstore/model';
|
||||
import Authority from './setting/authority.vue';
|
||||
import Menu from './setting/menu.vue';
|
||||
import Basic from './setting/basic.vue';
|
||||
import Setting from './setting/setting.vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: App | null;
|
||||
showView?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const activeKey = ref('0');
|
||||
// 编辑器内容,双向绑定
|
||||
const content = ref<any>('');
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 保存设置
|
||||
const save = () => {
|
||||
message.warn('待开发...');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
content.value = props.data?.content;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="less">
|
||||
.tab-pane {
|
||||
min-height: 100px;
|
||||
}
|
||||
.card-head {
|
||||
display: flex;
|
||||
height: 40px;
|
||||
align-items: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
166
src/views/system/appstore/components/setting/authority-edit.vue
Normal file
166
src/views/system/appstore/components/setting/authority-edit.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="400"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '修改按钮' : '添加按钮'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 6 }, sm: { span: 4 }, xs: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 18 }, sm: { span: 20 }, xs: { span: 24 } }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :md="23" :sm="24" :xs="24">
|
||||
<a-form-item label="按钮名称" v-bind="validateInfos.appName">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入按钮名称"
|
||||
v-model:value="form.appName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="权限标识" v-bind="validateInfos.authority">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="sys:article:list"
|
||||
v-model:value="form.authority"
|
||||
@pressEnter="save"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { addApp, updateApp } from '@/api/dashboard/appstore';
|
||||
import type { App } from '@/api/dashboard/appstore/model';
|
||||
import { assignObject, isExternalLink } from 'ele-admin-pro';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: App | null;
|
||||
parentId?: number;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<App>({
|
||||
appId: undefined,
|
||||
// 菜单id
|
||||
appName: '',
|
||||
// 上级id, 0是顶级
|
||||
parentId: 0,
|
||||
// 菜单类型, 0菜单, 1按钮
|
||||
menuType: 1,
|
||||
// 排序号
|
||||
sortNumber: 100,
|
||||
// 权限标识
|
||||
authority: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
appName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入按钮名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
authority: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入权限标识',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { resetFields, validate, validateInfos } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
parentId: props.parentId,
|
||||
appID: props.data?.appId,
|
||||
menuType: Number(form.menuType),
|
||||
appName: form.appName,
|
||||
authority: form.authority
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateApp : addApp;
|
||||
saveOrUpdate(appForm)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data?.appId) {
|
||||
const isExternal = isExternalLink(props.data.path);
|
||||
const isInner = isExternalLink(props.data.component);
|
||||
assignObject(form, {
|
||||
...props.data,
|
||||
openType: isExternal ? 2 : isInner ? 1 : 0,
|
||||
parentId: props.data?.parentId
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
isUpdate.value = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<script lang="ts">
|
||||
import * as icons from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
components: icons
|
||||
};
|
||||
</script>
|
||||
172
src/views/system/appstore/components/setting/authority.vue
Normal file
172
src/views/system/appstore/components/setting/authority.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-space v-role="'superAdmin'" style="margin-bottom: 10px">
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
v-role="'superAdmin'"
|
||||
v-if="data"
|
||||
@click="openEdit"
|
||||
>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="appId"
|
||||
:columns="columns"
|
||||
class="sys-org-table"
|
||||
:toolbar="false"
|
||||
:datasource="datasource"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'menuType'">
|
||||
<a-tag v-if="record.menuType === '0'" color="blue">菜单</a-tag>
|
||||
<a-tag v-else-if="record.menuType === '1'">按钮</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space v-role="'superAdmin'">
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</div>
|
||||
<!-- 编辑弹窗 -->
|
||||
<AuthorityEdit
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:parentId="parentId"
|
||||
@done="reload"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import AuthorityEdit from './authority-edit.vue';
|
||||
import { App, AppParam } from '@/api/dashboard/appstore/model';
|
||||
import { listApp, removeApp } from '@/api/dashboard/appstore';
|
||||
|
||||
const props = defineProps<{
|
||||
// 菜单类型
|
||||
menuType?: number;
|
||||
// 修改回显的数据
|
||||
data?: App | null;
|
||||
showView?: boolean;
|
||||
}>();
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '名称',
|
||||
key: 'appName',
|
||||
dataIndex: 'appName',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '权限标识',
|
||||
dataIndex: 'authority',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 表格数据源 */
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where.parentId = props.data?.appId;
|
||||
where.menuType = props.menuType;
|
||||
return listApp({ ...where, ...orders, page, limit });
|
||||
};
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<App | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 上级菜单id
|
||||
const parentId = ref<number>();
|
||||
|
||||
// 菜单数据
|
||||
// const menuData = ref<App[]>([]);
|
||||
|
||||
// 表格展开的行
|
||||
// const expandedRowKeys = ref<number[]>([]);
|
||||
|
||||
/* 表格渲染完成回调 */
|
||||
// const onDone: EleProTableDone<App> = ({ data }) => {
|
||||
// menuData.value = data;
|
||||
// };
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = (where?: AppParam) => {
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: App | null, id?: number) => {
|
||||
parentId.value = props.data?.appId;
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: App) => {
|
||||
if (row.children?.length) {
|
||||
message.error('请先删除子节点');
|
||||
return;
|
||||
}
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeApp(row.appId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data?.appId,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as MenuIcons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
name: 'Authority',
|
||||
components: MenuIcons
|
||||
};
|
||||
</script>
|
||||
111
src/views/system/appstore/components/setting/basic.vue
Normal file
111
src/views/system/appstore/components/setting/basic.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 编辑器 -->
|
||||
<byte-md-editor
|
||||
v-model:value="content"
|
||||
:locale="zh_Hans"
|
||||
:plugins="plugins"
|
||||
uploadImages
|
||||
height="500px"
|
||||
mode="split"
|
||||
:editorConfig="{ lineNumbers: true }"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
style="margin-top: 10px"
|
||||
@click="saveContent"
|
||||
>
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { App } from '@/api/dashboard/appstore/model';
|
||||
import { updateApp } from '@/api/dashboard/appstore';
|
||||
// 中文语言文件
|
||||
import zh_Hans from 'bytemd/locales/zh_Hans.json';
|
||||
// 链接、删除线、复选框、表格等的插件
|
||||
import gfm from '@bytemd/plugin-gfm';
|
||||
// 插件的中文语言文件
|
||||
import zh_HansGfm from '@bytemd/plugin-gfm/locales/zh_Hans.json';
|
||||
// 预览界面的样式,这里用的 github 的 markdown 主题
|
||||
import 'github-markdown-css/github-markdown-light.css';
|
||||
import highlight from '@bytemd/plugin-highlight';
|
||||
import { assignObject, isExternalLink } from 'ele-admin-pro';
|
||||
|
||||
const props = defineProps<{
|
||||
// 应用id
|
||||
appId?: number | 0;
|
||||
// 修改回显的数据
|
||||
data?: App | null;
|
||||
}>();
|
||||
|
||||
// 编辑器内容,双向绑定
|
||||
const content = ref<any>('');
|
||||
|
||||
// 插件
|
||||
const plugins = ref([
|
||||
gfm({
|
||||
locale: zh_HansGfm
|
||||
}),
|
||||
highlight()
|
||||
]);
|
||||
|
||||
// 保存应用详情
|
||||
const saveContent = () => {
|
||||
const appForm = {
|
||||
appId: props.data?.appId,
|
||||
content: content.value
|
||||
};
|
||||
updateApp(appForm)
|
||||
.then((msg) => {
|
||||
message.success('保存成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
// 加载内容
|
||||
const setContent = () => {
|
||||
content.value = props.data?.content;
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
if (props.data) {
|
||||
const isExternal = isExternalLink(props.data.path);
|
||||
const isInner = isExternalLink(props.data.component);
|
||||
content.value = String(props.data.content);
|
||||
} else {
|
||||
}
|
||||
};
|
||||
|
||||
setContent();
|
||||
|
||||
if (props.appId) {
|
||||
reload();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.data?.appId,
|
||||
(data) => {
|
||||
if (data) {
|
||||
reload();
|
||||
} else {
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as MenuIcons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
name: 'Authority',
|
||||
components: MenuIcons
|
||||
};
|
||||
</script>
|
||||
87
src/views/system/appstore/components/setting/content.vue
Normal file
87
src/views/system/appstore/components/setting/content.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 编辑器 -->
|
||||
<byte-md-editor
|
||||
v-model:value="content"
|
||||
:locale="zh_Hans"
|
||||
:plugins="plugins"
|
||||
uploadImages
|
||||
height="500px"
|
||||
:editorConfig="{ lineNumbers: true }"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
style="margin-top: 10px"
|
||||
@click="saveContent"
|
||||
>
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { App } from '@/api/dashboard/appstore/model';
|
||||
import { updateApp } from '@/api/dashboard/appstore';
|
||||
// 中文语言文件
|
||||
import zh_Hans from 'bytemd/locales/zh_Hans.json';
|
||||
// 链接、删除线、复选框、表格等的插件
|
||||
import gfm from '@bytemd/plugin-gfm';
|
||||
// 插件的中文语言文件
|
||||
import zh_HansGfm from '@bytemd/plugin-gfm/locales/zh_Hans.json';
|
||||
// 预览界面的样式,这里用的 github 的 markdown 主题
|
||||
import 'github-markdown-css/github-markdown-light.css';
|
||||
import highlight from '@bytemd/plugin-highlight';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: App | null;
|
||||
showView?: boolean;
|
||||
}>();
|
||||
|
||||
// 编辑器内容,双向绑定
|
||||
const content = ref<any>('');
|
||||
|
||||
// 插件
|
||||
const plugins = ref([
|
||||
gfm({
|
||||
locale: zh_HansGfm
|
||||
}),
|
||||
highlight()
|
||||
]);
|
||||
|
||||
// 保存应用详情
|
||||
const saveContent = () => {
|
||||
const appForm = {
|
||||
appId: props.data?.appId,
|
||||
content: content.value
|
||||
};
|
||||
updateApp(appForm)
|
||||
.then((msg) => {
|
||||
message.success('保存成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
// 加载内容
|
||||
const setContent = () => {
|
||||
content.value = props.data?.content;
|
||||
};
|
||||
|
||||
setContent();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as MenuIcons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
name: 'Authority',
|
||||
components: MenuIcons
|
||||
};
|
||||
</script>
|
||||
188
src/views/system/appstore/components/setting/menu-edit.vue
Normal file
188
src/views/system/appstore/components/setting/menu-edit.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="400"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '修改组件' : '添加组件'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 6 }, sm: { span: 4 }, xs: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 18 }, sm: { span: 20 }, xs: { span: 24 } }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :md="23" :sm="24" :xs="24">
|
||||
<a-form-item label="组件名称" v-bind="validateInfos.appName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:placeholder="`文章详情`"
|
||||
v-model:value="form.appName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="组件路径" v-bind="validateInfos.path">
|
||||
<a-input
|
||||
allow-clear
|
||||
:placeholder="`/article/detail/:id`"
|
||||
v-model:value="form.path"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="组件路径" v-bind="validateInfos.component">
|
||||
<a-input
|
||||
allow-clear
|
||||
:placeholder="`/article/detail`"
|
||||
v-model:value="form.component"
|
||||
@pressEnter="save"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="路由元数据" v-bind="validateInfos.meta">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入JSON格式的路由元数据"
|
||||
v-model:value="form.meta"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { addApp, updateApp } from '@/api/dashboard/appstore';
|
||||
import type { App } from '@/api/dashboard/appstore/model';
|
||||
import { assignObject, isExternalLink } from 'ele-admin-pro';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: App | null;
|
||||
parentId?: number;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
// 表单数据
|
||||
const form = reactive<App>({
|
||||
appId: undefined,
|
||||
// 菜单id
|
||||
appName: '',
|
||||
// 上级id, 0是顶级
|
||||
parentId: 0,
|
||||
// 菜单类型, 0菜单, 1按钮
|
||||
menuType: 0,
|
||||
// 排序号
|
||||
sortNumber: 100,
|
||||
path: '',
|
||||
component: '',
|
||||
meta: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
appName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入按钮名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
path: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入路由地址',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
component: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输组件路径',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { resetFields, validate, validateInfos } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
parentId: props.parentId,
|
||||
appID: props.data?.appId,
|
||||
menuType: Number(form.menuType),
|
||||
appName: form.appName
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateApp : addApp;
|
||||
saveOrUpdate(appForm)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data?.appId) {
|
||||
const isExternal = isExternalLink(props.data.path);
|
||||
const isInner = isExternalLink(props.data.component);
|
||||
assignObject(form, {
|
||||
...props.data,
|
||||
openType: isExternal ? 2 : isInner ? 1 : 0,
|
||||
parentId: props.data?.parentId
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
isUpdate.value = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<script lang="ts">
|
||||
import * as icons from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
components: icons
|
||||
};
|
||||
</script>
|
||||
174
src/views/system/appstore/components/setting/menu.vue
Normal file
174
src/views/system/appstore/components/setting/menu.vue
Normal file
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-space style="margin-bottom: 10px">
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
v-if="data"
|
||||
@click="openEdit"
|
||||
>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="appId"
|
||||
:columns="columns"
|
||||
class="sys-org-table"
|
||||
:toolbar="false"
|
||||
:datasource="datasource"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'menuType'">
|
||||
<a-tag v-if="record.menuType === '0'" color="blue">菜单</a-tag>
|
||||
<a-tag v-else-if="record.menuType === '1'">按钮</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</div>
|
||||
<!-- 编辑弹窗 -->
|
||||
<MenuEdit
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:parentId="parentId"
|
||||
@done="reload"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import MenuEdit from './menu-edit.vue';
|
||||
import { App, AppParam } from '@/api/dashboard/appstore/model';
|
||||
import { listApp, removeApp } from '@/api/dashboard/appstore';
|
||||
|
||||
const props = defineProps<{
|
||||
// 菜单类型
|
||||
menuType?: number;
|
||||
// 修改回显的数据
|
||||
data?: App | null;
|
||||
showView?: boolean;
|
||||
}>();
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '组件名称',
|
||||
key: 'appName',
|
||||
dataIndex: 'appName',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '路由地址',
|
||||
dataIndex: 'path'
|
||||
},
|
||||
{
|
||||
title: '组件路径',
|
||||
dataIndex: 'component'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 表格数据源 */
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where.parentId = props.data?.appId;
|
||||
where.menuType = props.menuType;
|
||||
return listApp({ ...where, ...orders, page, limit });
|
||||
};
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<App | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 上级菜单id
|
||||
const parentId = ref<number>();
|
||||
|
||||
// 菜单数据
|
||||
// const menuData = ref<App[]>([]);
|
||||
|
||||
// 表格展开的行
|
||||
// const expandedRowKeys = ref<number[]>([]);
|
||||
|
||||
/* 表格渲染完成回调 */
|
||||
// const onDone: EleProTableDone<App> = ({ data }) => {
|
||||
// menuData.value = data;
|
||||
// };
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = (where?: AppParam) => {
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: App | null, id?: number) => {
|
||||
parentId.value = props.data?.appId;
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: App) => {
|
||||
if (row.children?.length) {
|
||||
message.error('请先删除子节点');
|
||||
return;
|
||||
}
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeApp(row.appId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data?.appId,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as MenuIcons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
name: 'Authority',
|
||||
components: MenuIcons
|
||||
};
|
||||
</script>
|
||||
405
src/views/system/appstore/components/setting/setting.vue
Normal file
405
src/views/system/appstore/components/setting/setting.vue
Normal file
@@ -0,0 +1,405 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 6 }, sm: { span: 4 }, xs: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 18 }, sm: { span: 20 }, xs: { span: 24 } }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :md="12" :sm="24" :xs="24">
|
||||
<a-form-item label="应用分类" v-bind="validateInfos.appType">
|
||||
<a-select
|
||||
optionFilterProp="label"
|
||||
placeholder="请选择应用分类"
|
||||
allow-clear
|
||||
v-model:value="form.appType"
|
||||
>
|
||||
<template v-for="item in appTypeDict">
|
||||
<a-select-option :value="item.value">{{ item.label }}</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="应用名称" v-bind="validateInfos.appName">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入应用名称"
|
||||
v-model:value="form.appName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="应用入口" v-bind="validateInfos.component">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="/dashboard/workplace"
|
||||
v-model:value="form.component"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="24" :xs="24">
|
||||
<a-form-item label="应用标识" v-bind="validateInfos.appCode">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="goods"
|
||||
v-model:value="form.appCode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="授权价格" v-bind="validateInfos.price">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="授权价格"
|
||||
v-model:value="form.price"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" v-role="'superAdmin'" v-bind="validateInfos.sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="99999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item
|
||||
label="应用简介"
|
||||
v-bind="validateInfos.comments"
|
||||
:label-col="{ md: { span: 3 }, sm: { span: 4 }, xs: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 21 }, sm: { span: 20 }, xs: { span: 24 } }"
|
||||
>
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入应用简介"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
<div style="margin-bottom: 22px">
|
||||
<a-divider />
|
||||
</div>
|
||||
<a-row :gutter="16">
|
||||
<a-col :md="12" :sm="24" :xs="24">
|
||||
<a-form-item label="应用图标" v-bind="validateInfos.appIcon">
|
||||
<ele-icon-picker
|
||||
v-model:value="form.appIcon"
|
||||
allow-clear
|
||||
placeholder="请选择图标"
|
||||
:disabled="form.appType === 1"
|
||||
:data="iconData"
|
||||
>
|
||||
<template #icon="{ icon }">
|
||||
<component :is="icon"/>
|
||||
</template>
|
||||
</ele-icon-picker>
|
||||
</a-form-item>
|
||||
<a-form-item label="下载地址" v-bind="validateInfos.downUrl">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="模块代码下载地址"
|
||||
v-model:value="form.downUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="24" :xs="24">
|
||||
<a-form-item label="当前版本" v-bind="validateInfos.edition">
|
||||
<a-select
|
||||
optionFilterProp="label"
|
||||
placeholder="请选择版本"
|
||||
allow-clear
|
||||
v-model:value="form.edition"
|
||||
>
|
||||
<a-select-option value="正式版">正式版</a-select-option>
|
||||
<a-select-option value="开发版">开发版</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="应用状态">
|
||||
<a-switch
|
||||
checked-children="启用"
|
||||
un-checked-children="禁用"
|
||||
:checked="form.status === 0"
|
||||
@update:checked="updateHideValue"
|
||||
/>
|
||||
<a-tooltip
|
||||
title="禁用后提示:模块维护中"
|
||||
>
|
||||
<question-circle-outlined
|
||||
style="vertical-align: -4px; margin-left: 16px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div style="margin-bottom: 22px">
|
||||
<a-divider />
|
||||
</div>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
@click="save"
|
||||
>
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, reactive, watch} from 'vue';
|
||||
import {Form, message} from 'ant-design-vue';
|
||||
import type {RuleObject} from 'ant-design-vue/es/form';
|
||||
import iconData from 'ele-admin-pro/es/ele-icon-picker/icons';
|
||||
import {assignObject, isExternalLink} from 'ele-admin-pro';
|
||||
import {addApp, updateApp} from '@/api/dashboard/appstore';
|
||||
import type {App} from '@/api/dashboard/appstore/model';
|
||||
import {ItemType} from "ele-admin-pro/es/ele-image-upload/types";
|
||||
import {uploadFile} from "@/api/system/file";
|
||||
import {getDictionaryOptions} from "@/utils/common";
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 修改回显的数据
|
||||
data?: App | null;
|
||||
// 应用id
|
||||
appId?: number | 0;
|
||||
}>();
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
const icon = ref('');
|
||||
const appTypeDict = getDictionaryOptions('appstoreType');
|
||||
// 编辑器内容,双向绑定
|
||||
const content = ref('');
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<App>({
|
||||
// 应用id
|
||||
appId: undefined,
|
||||
// 应用名称
|
||||
appName: '',
|
||||
// 上级id, 0是顶级
|
||||
parentId: 0,
|
||||
// 应用编号
|
||||
appCode: '',
|
||||
// 应用图标
|
||||
appIcon: '',
|
||||
// 应用类型
|
||||
appType: undefined,
|
||||
// 应用地址
|
||||
appUrl: '',
|
||||
// 下载地址
|
||||
downUrl: '',
|
||||
// 应用包名
|
||||
packageName: '',
|
||||
// 点击次数
|
||||
clicks: '',
|
||||
// 安装次数
|
||||
installs: '',
|
||||
// 应用介绍
|
||||
content: '',
|
||||
// 开发者(个人)
|
||||
developer: '官方',
|
||||
// 页面路径
|
||||
component: undefined,
|
||||
// 软件授权价格
|
||||
price: '',
|
||||
// 评分
|
||||
score: '',
|
||||
// 星级
|
||||
star: '',
|
||||
// 排序
|
||||
sortNumber: 100,
|
||||
// 备注
|
||||
comments: '',
|
||||
// 权限标识
|
||||
authority: '',
|
||||
// 打开位置
|
||||
target: '',
|
||||
// 是否隐藏, 0否, 1是(仅注册路由不显示在左侧菜单)
|
||||
hide: undefined,
|
||||
// 菜单侧栏选中的path
|
||||
active: '',
|
||||
// 其它路由元信息
|
||||
meta: '',
|
||||
// 版本
|
||||
edition: '开发版',
|
||||
// 版本号
|
||||
version: 'v1.0',
|
||||
// 创建时间
|
||||
createTime: '',
|
||||
// 状态
|
||||
status: 1,
|
||||
// 发布者
|
||||
userId: undefined,
|
||||
// 发布者昵称
|
||||
nickname: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
appName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入应用名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
component: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入组件路径',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
appType: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请选择应用分类',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
appCode: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入应用标识(英文)',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
comments: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入应用简介',
|
||||
}
|
||||
],
|
||||
sortNumber: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请输入排序号',
|
||||
}
|
||||
],
|
||||
price: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入软件授权价格',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
meta: [
|
||||
{
|
||||
type: 'string',
|
||||
trigger: 'blur',
|
||||
validator: async (_rule: RuleObject, value: string) => {
|
||||
if (value) {
|
||||
const msg = '请输入正确的JSON格式';
|
||||
try {
|
||||
const obj = JSON.parse(value);
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return Promise.reject(msg);
|
||||
}
|
||||
} catch (_e) {
|
||||
return Promise.reject(msg);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const {resetFields, validate, validateInfos} = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
parentId: form.parentId || 0,
|
||||
content: content.value
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateApp : addApp;
|
||||
saveOrUpdate(appForm)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
};
|
||||
|
||||
const onUpload = (d: ItemType) => {
|
||||
uploadFile(<File>d.file)
|
||||
.then((result) => {
|
||||
form.appIcon = result.path;
|
||||
message.success('上传成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const updateHideValue = (value: boolean) => {
|
||||
form.status = value ? 0 : 1;
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
if (props.data) {
|
||||
const isExternal = isExternalLink(props.data.path);
|
||||
const isInner = isExternalLink(props.data.component);
|
||||
content.value = String(props.data.content);
|
||||
form.price = props.data.price;
|
||||
assignObject(form, {
|
||||
...props.data,
|
||||
openType: isExternal ? 2 : isInner ? 1 : 0
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
if( props.appId ) {
|
||||
reload();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.data?.appId,
|
||||
(data) => {
|
||||
if (data) {
|
||||
reload();
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<script lang="ts">
|
||||
import * as icons from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
components: icons
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { sm: 5, xs: 24 } : { flex: '130px' }"
|
||||
:wrapper-col="styleResponsive ? { sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item label="手机号码" name="phone">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="11"
|
||||
v-model:value="form.phone"
|
||||
placeholder="请输入手机号码"
|
||||
>
|
||||
<template #addonBefore> +86 </template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="验证码" name="code">
|
||||
<div class="login-input-group">
|
||||
<a-input
|
||||
placeholder="请输入验证码"
|
||||
v-model:value="form.code"
|
||||
:maxlength="6"
|
||||
allow-clear
|
||||
/>
|
||||
<a-button
|
||||
class="login-captcha"
|
||||
:disabled="!!countdownTime"
|
||||
@click="openImgCodeModal"
|
||||
>
|
||||
<span v-if="!countdownTime">发送验证码</span>
|
||||
<span v-else>已发送 {{ countdownTime }} s</span>
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:wrapper-col="styleResponsive ? { sm: { offset: 5 } } : { offset: 4 }"
|
||||
style="margin-top: 24px"
|
||||
>
|
||||
<a-space size="middle">
|
||||
<a-button @click="back">上一步</a-button>
|
||||
<a-button type="primary" :loading="loading" @click="submit">
|
||||
下一步
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<!-- 编辑弹窗 -->
|
||||
<a-modal
|
||||
:width="340"
|
||||
:footer="null"
|
||||
title="发送验证码"
|
||||
v-model:visible="visible"
|
||||
>
|
||||
<div class="login-input-group" style="margin-bottom: 16px">
|
||||
<a-input
|
||||
v-model:value="imgCode"
|
||||
:maxlength="5"
|
||||
placeholder="请输入图形验证码"
|
||||
allow-clear
|
||||
@pressEnter="sendCode"
|
||||
/>
|
||||
<a-button class="login-captcha">
|
||||
<img alt="" :src="captcha" @click="changeCaptcha" />
|
||||
</a-button>
|
||||
</div>
|
||||
<a-button
|
||||
block
|
||||
size="large"
|
||||
type="primary"
|
||||
:loading="codeLoading"
|
||||
@click="sendCode"
|
||||
>
|
||||
立即发送
|
||||
</a-button>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import type { StepForm } from '../model';
|
||||
import { phoneReg } from 'ele-admin-pro';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { getCaptcha, sendSmsCaptcha } from '@/api/passport/login';
|
||||
import { getMobile } from '@/utils/common';
|
||||
import { addUser } from '@/api/system/user';
|
||||
import { User } from '@/api/system/user/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 修改回显的数据
|
||||
data?: StepForm | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: StepForm): void;
|
||||
(e: 'back'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示图形验证码弹窗
|
||||
const visible = ref(false);
|
||||
// 图形验证码
|
||||
const imgCode = ref('');
|
||||
// 发送验证码按钮loading
|
||||
const codeLoading = ref(false);
|
||||
// 验证码倒计时时间
|
||||
const countdownTime = ref(0);
|
||||
// 图形验证码地址
|
||||
const captcha = ref('');
|
||||
const text = ref('');
|
||||
// 验证码倒计时定时器
|
||||
let countdownTimer: number | null = null;
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
/* 发送短信验证码 */
|
||||
const sendCode = () => {
|
||||
if (!imgCode.value) {
|
||||
message.error('请输入图形验证码');
|
||||
return;
|
||||
}
|
||||
if (text.value !== imgCode.value) {
|
||||
message.error('图形验证码不正确');
|
||||
return;
|
||||
}
|
||||
codeLoading.value = true;
|
||||
sendSmsCaptcha({ phone: form.phone })
|
||||
.then(() => {
|
||||
message.success('短信验证码发送成功, 请注意查收!');
|
||||
visible.value = false;
|
||||
codeLoading.value = false;
|
||||
countdownTime.value = 60;
|
||||
// 开始对按钮进行倒计时
|
||||
countdownTimer = window.setInterval(() => {
|
||||
if (countdownTime.value <= 1) {
|
||||
countdownTimer && clearInterval(countdownTimer);
|
||||
countdownTimer = null;
|
||||
}
|
||||
countdownTime.value--;
|
||||
}, 1000);
|
||||
})
|
||||
.catch((e) => {
|
||||
codeLoading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<User>({
|
||||
phone: '',
|
||||
username: '',
|
||||
nickname: '',
|
||||
roles: [],
|
||||
organizationName: '',
|
||||
password: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
phone: [
|
||||
{
|
||||
pattern: phoneReg,
|
||||
message: '手机号格式不正确',
|
||||
type: 'string'
|
||||
}
|
||||
],
|
||||
code: [
|
||||
{
|
||||
required: true,
|
||||
message: '请填写短信验证码',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 获取图形验证码 */
|
||||
const changeCaptcha = () => {
|
||||
// 这里演示的验证码是后端返回base64格式的形式, 如果后端地址直接是图片请参考忘记密码页面
|
||||
getCaptcha()
|
||||
.then((data) => {
|
||||
captcha.value = data.base64;
|
||||
// 实际项目后端一般会返回验证码的key而不是直接返回验证码的内容, 登录用key去验证, 你可以根据自己后端接口修改
|
||||
text.value = data.text;
|
||||
// 自动回填验证码, 实际项目去掉这个
|
||||
// form.code = data.text;
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 显示发送短信验证码弹窗 */
|
||||
const openImgCodeModal = () => {
|
||||
if (!form.phone) {
|
||||
message.error('请输入手机号码');
|
||||
return;
|
||||
}
|
||||
imgCode.value = '';
|
||||
changeCaptcha();
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
?.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const addData = {
|
||||
...form,
|
||||
username: props.data?.username,
|
||||
password: props.data?.password,
|
||||
nickname: getMobile(form.phone),
|
||||
roles: [{ roleId: 5 }]
|
||||
};
|
||||
addUser(addData)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
message.success('注册成功');
|
||||
emit('done', form);
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const back = () => {
|
||||
emit('back');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
/* 验证码 */
|
||||
.login-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.login-captcha {
|
||||
width: 102px;
|
||||
margin-left: 10px;
|
||||
padding: 0;
|
||||
|
||||
& > img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { sm: 5, xs: 24 } : { flex: '130px' }"
|
||||
:wrapper-col="styleResponsive ? { sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item label="账户类型" name="applyType">
|
||||
<a-radio-group v-model:value="form.applyType">
|
||||
<a-radio :value="0">个人账号</a-radio>
|
||||
<a-radio :value="1">企业账号</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="企业名称"
|
||||
name="organizationName"
|
||||
v-if="form.applyType === 1"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入企业名称"
|
||||
v-model:value="form.organizationName"
|
||||
:maxlength="25"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- <div style="margin-bottom: 22px" v-if="form.applyType === 1">-->
|
||||
<!-- <a-divider />-->
|
||||
<!-- </div>-->
|
||||
<a-form-item label="登录账号" name="username">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="16"
|
||||
v-model:value="form.username"
|
||||
placeholder="请输入登录账号"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="登录密码" name="password">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
type="password"
|
||||
v-model:value="form.password"
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="确认密码" name="password2">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
type="password"
|
||||
v-model:value="form.password2"
|
||||
placeholder="请输入确认密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- <a-form-item label=" " name="register" v-if="form.applyType === 1">-->
|
||||
<!-- 我已阅读并同意 <a>用户协议</a> 和 <a>隐私权政策</a>-->
|
||||
<!-- </a-form-item>-->
|
||||
<a-form-item
|
||||
:wrapper-col="styleResponsive ? { sm: { offset: 5 } } : { offset: 4 }"
|
||||
>
|
||||
<a-button type="primary" :loading="loading" @click="submit">
|
||||
下一步
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import type { StepForm } from '../model';
|
||||
import type { RuleObject } from 'ant-design-vue/es/form';
|
||||
import { createCode } from '@/utils/common';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: StepForm): void;
|
||||
}>();
|
||||
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<User>({
|
||||
applyType: 0,
|
||||
phone: '',
|
||||
username: createCode(),
|
||||
nickname: '',
|
||||
organizationName: '',
|
||||
password: '',
|
||||
password2: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
applyType: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择账户类型',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: '请填写登录账号',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '密码组合应该包含字母、数字,并且长度不少于8位',
|
||||
min: 8,
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
password2: [
|
||||
{
|
||||
required: true,
|
||||
validator: async (_rule: RuleObject, value: string) => {
|
||||
if (!value) {
|
||||
return Promise.reject('请再次输入新密码');
|
||||
}
|
||||
if (value !== form.password) {
|
||||
return Promise.reject('两次输入密码不一致');
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
],
|
||||
organizationName: [
|
||||
{
|
||||
required: true,
|
||||
validator: async (_rule: RuleObject, value: string) => {
|
||||
if (!value) {
|
||||
return Promise.reject('请输入企业名称');
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 步骤一提交 */
|
||||
const submit = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
emit('done', form);
|
||||
}, 300);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
/* 验证码 */
|
||||
.login-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.login-captcha {
|
||||
width: 102px;
|
||||
margin-left: 10px;
|
||||
padding: 0;
|
||||
|
||||
& > img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-result
|
||||
title="注册成功"
|
||||
status="success"
|
||||
sub-title="请妥善保管您的账号,如忘记可通过手机短信验证码找回密码"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import type { StepForm } from '../model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
defineProps<{
|
||||
// 修改回显的数据
|
||||
data?: StepForm | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'back'): void;
|
||||
}>();
|
||||
|
||||
const back = () => {
|
||||
emit('back');
|
||||
};
|
||||
</script>
|
||||
112
src/views/system/appstore/components/step/index.vue
Normal file
112
src/views/system/appstore/components/step/index.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="740"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="`安装应用`"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
:footer="null"
|
||||
@ok="save"
|
||||
>
|
||||
<a-page-header :ghost="false" :title="data.appName">
|
||||
<div class="ele-text-secondary" v-html="data.comments"> </div>
|
||||
</a-page-header>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false">
|
||||
<div style="max-width: 700px; margin: 0 auto">
|
||||
<div style="margin: 10px 0 30px 0">
|
||||
<a-steps
|
||||
:current="active"
|
||||
direction="horizontal"
|
||||
:responsive="styleResponsive"
|
||||
>
|
||||
<a-step title="第一步" description="下载更新包" />
|
||||
<a-step title="第二步" description="数据初始化" />
|
||||
<a-step title="第三步" description="安装成功" />
|
||||
</a-steps>
|
||||
</div>
|
||||
<div class="step-next" v-if="active === 0">
|
||||
<a-button type="primary" @click="onDone()"> 下一步 </a-button>
|
||||
</div>
|
||||
<div class="step-next" v-if="active === 1">
|
||||
<a-button type="primary" @click="onNext()"> 下一步 </a-button>
|
||||
</div>
|
||||
<div class="step-next" v-if="active === 2">
|
||||
<a-button type="primary" @click="onSuccess()"> 完成 </a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import StepEdit from './components/step-edit.vue';
|
||||
import StepConfirm from './components/step-confirm.vue';
|
||||
import StepSuccess from './components/step-success.vue';
|
||||
import type { StepForm } from './model';
|
||||
import { App } from '@/api/dashboard/appstore/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: App | null;
|
||||
// 上级应用id
|
||||
parentId?: number;
|
||||
}>();
|
||||
|
||||
// 选中步骤
|
||||
const active = ref(0);
|
||||
|
||||
//
|
||||
const form = reactive<StepForm>({});
|
||||
|
||||
//
|
||||
const onDone = (data: StepForm) => {
|
||||
Object.assign(form, data);
|
||||
active.value = 1;
|
||||
};
|
||||
|
||||
//
|
||||
const onNext = (data: StepForm) => {
|
||||
Object.assign(form, data);
|
||||
active.value = 2;
|
||||
};
|
||||
|
||||
const onSuccess = () => {};
|
||||
|
||||
//
|
||||
const onBack = () => {
|
||||
active.value = 0;
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'FormStep'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.step-next {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
11
src/views/system/appstore/components/step/model/index.ts
Normal file
11
src/views/system/appstore/components/step/model/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface StepForm {
|
||||
applyType?: number;
|
||||
apply?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
password2?: string;
|
||||
phone?: string;
|
||||
code?: string;
|
||||
nickname?: string;
|
||||
companyName?: string;
|
||||
}
|
||||
54
src/views/system/appstore/index.vue
Normal file
54
src/views/system/appstore/index.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<a-page-header :ghost="false" title="扩展插件">
|
||||
<div class="ele-text-secondary"> 通过扩展插件可以满足更多个性化需求 </div>
|
||||
</a-page-header>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<a-tabs type="card" tabPosition="top" v-model:activeKey="activeKey">
|
||||
<a-tab-pane v-for="(d, index) in data" :key="index" :tab="d.label">
|
||||
<list :activeKey="activeKey" :type="d" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import List from './list.vue';
|
||||
import { appstoreType } from '@/api/system/appstore';
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 当前选项卡
|
||||
const activeKey = ref(0);
|
||||
|
||||
// 获取字典数据
|
||||
const data = appstoreType();
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'AppStore'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sys-organization-list {
|
||||
padding: 12px 6px;
|
||||
height: calc(100vh - 242px);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
508
src/views/system/appstore/list.vue
Normal file
508
src/views/system/appstore/list.vue
Normal file
@@ -0,0 +1,508 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="appId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:expanded-row-keys="expandedRowKeys"
|
||||
:expand-icon-column-index="1"
|
||||
tool-class="ele-toolbar-form"
|
||||
:scroll="{ x: 800 }"
|
||||
class="sys-org-table"
|
||||
@done="onDone"
|
||||
@expand="onExpand"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'appName'">
|
||||
<component :is="record.appIcon" class="ant-menu-item-icon" />
|
||||
<a-tooltip title="插件详情">
|
||||
<span
|
||||
class="app-name cursor-pointer ele-text-primary"
|
||||
@click="openSetting(record)"
|
||||
>{{ record.appName }}</span
|
||||
>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'appType'">
|
||||
<a-tag v-if="isExternalLink(record.path)" color="orange">
|
||||
外链
|
||||
</a-tag>
|
||||
<a-tag v-else-if="isExternalLink(record.component)" color="green">
|
||||
内链
|
||||
</a-tag>
|
||||
<a-tag v-else-if="record.menuType === 0" color="blue">菜单</a-tag>
|
||||
<a-tag v-else-if="record.menuType === 1">按钮</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'nickname'">
|
||||
<a-tooltip :title="`${record.nickname}`">
|
||||
<a-avatar :src="record.userAvatar" size="small" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'price'">
|
||||
<span class="ele-text-warning" v-if="record.price > 0"
|
||||
>¥{{ formatNumber(record.price) }}</span
|
||||
>
|
||||
<span class="ele-text-success" v-else>免费</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'createTime'">
|
||||
<a-tooltip :title="`${toDateString(record.createTime)}`">
|
||||
{{ timeAgo(record.createTime) }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'edition'">
|
||||
<div v-if="record.edition == '正式版'" class="ele-text-success">
|
||||
正式版
|
||||
</div>
|
||||
<span v-if="record.edition == '开发版'" class="ele-text-info">
|
||||
开发版
|
||||
<a-tooltip
|
||||
:title="`${record.version}`"
|
||||
@click="openUrl('https://www.gxwebsoft.com')"
|
||||
>
|
||||
<question-circle-outlined style="margin-left: 16px" />
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">正常</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="red">待审核</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space v-role="'admin'">
|
||||
<!-- 已安装 -->
|
||||
<template v-if="appIds.indexOf(record.appId) > -1">
|
||||
<a @click="uninstallApp(record)">卸载</a>
|
||||
</template>
|
||||
<!-- 未安装 -->
|
||||
<template v-else>
|
||||
<template v-if="record.price > 0">
|
||||
<a class="ele-text-warning" @click="onBuy(record)">购买</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a @click="install(record)">安装</a>
|
||||
</template>
|
||||
</template>
|
||||
</a-space>
|
||||
|
||||
<!-- 开发者权限 -->
|
||||
<a-space v-permission="'sys:user:add'">
|
||||
<a @click="openSetting(record)">编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
<!-- <a @click="openEdit(record)" class="ele-text-danger">编辑</a>-->
|
||||
<!-- <a-divider type="vertical" />-->
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
<!-- 编辑弹窗 -->
|
||||
<Edit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
<!-- 应用设置 -->
|
||||
<Setting v-model:visible="showSetting" :data="current" @done="reload" />
|
||||
<!-- 购买弹窗 -->
|
||||
<Buy v-model:visible="showBuy" :data="current" @done="reload" />
|
||||
<!-- 安装弹窗 -->
|
||||
<Step v-model:visible="showStep" :data="current" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import {
|
||||
formatNumber,
|
||||
toDateString,
|
||||
isExternalLink,
|
||||
messageLoading
|
||||
} from 'ele-admin-pro';
|
||||
import Search from './components/search.vue';
|
||||
import Edit from './components/edit.vue';
|
||||
import Buy from './components/buy.vue';
|
||||
import Step from './components/step/index.vue';
|
||||
import Setting from './components/setting.vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import {
|
||||
listApp,
|
||||
pageApp,
|
||||
removeApp,
|
||||
removeBatchApp,
|
||||
saveMenu
|
||||
} from '@/api/dashboard/appstore';
|
||||
import { timeAgo } from 'ele-admin-pro';
|
||||
import type { App, AppParam } from '@/api/dashboard/appstore/model';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { EleProTableDone } from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { openUrl } from '@/utils/common';
|
||||
import type { Menu } from '@/api/system/menu/model';
|
||||
// import type { RoleMenu } from '@/api/system/menu/model/role-menu';
|
||||
|
||||
const userStore = useUserStore();
|
||||
// 当前用户信息
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
// const appType = localStorage.getItem('appType');
|
||||
const props = defineProps<{
|
||||
// 机构 id
|
||||
type?: any;
|
||||
// 当前选项卡
|
||||
activeKey?: any;
|
||||
}>();
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<App[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<App | null>(null); // 当前编辑数据
|
||||
// 菜单数据
|
||||
const menuData = ref<App[]>([]);
|
||||
const expandedRowKeys = ref();
|
||||
const appIds = ref<any>([]);
|
||||
// 上级菜单id
|
||||
const parentId = ref<number>();
|
||||
// 获取字典数据
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示设置弹窗
|
||||
const showSetting = ref(false);
|
||||
// 是否显示购买弹窗
|
||||
const showBuy = ref(false);
|
||||
const showStep = ref(false);
|
||||
const authorityData = ref<Menu[]>([] || null);
|
||||
// const roleMenuData = ref<RoleMenu[]>([] || null);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.progress = filters.progress;
|
||||
where.appstoreSource = filters.appstoreSource;
|
||||
where.appType = filters.appType;
|
||||
where.status = filters.status;
|
||||
where.edition = filters.edition;
|
||||
}
|
||||
// 搜索条件
|
||||
if (props.activeKey > 0) {
|
||||
// 指定应用分类
|
||||
where.appType = props.type.value;
|
||||
} else {
|
||||
// 全部应用
|
||||
where.appType = undefined;
|
||||
}
|
||||
where.menuType = 0;
|
||||
where.parentId = 0;
|
||||
return pageApp({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '应用名称',
|
||||
dataIndex: 'appName',
|
||||
key: 'appName',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '应用标识',
|
||||
dataIndex: 'appCode'
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'comments',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '价格',
|
||||
dataIndex: 'price',
|
||||
key: 'price'
|
||||
},
|
||||
{
|
||||
title: '开发商',
|
||||
dataIndex: 'developer',
|
||||
hideInTable: true
|
||||
},
|
||||
{
|
||||
title: '版本号',
|
||||
key: 'edition',
|
||||
filters: [
|
||||
{
|
||||
text: '正式版',
|
||||
value: '正式版'
|
||||
},
|
||||
{
|
||||
text: '开发版',
|
||||
value: '开发版'
|
||||
}
|
||||
],
|
||||
filterMultiple: false
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
hideInTable: true,
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
hideInTable: true,
|
||||
customRender: ({ text }) => toDateString(text)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 表格渲染完成回调 */
|
||||
const onDone: EleProTableDone<App> = ({ data }) => {
|
||||
menuData.value = data;
|
||||
};
|
||||
|
||||
/* 安装应用 */
|
||||
const install2 = (row?: App) => {
|
||||
current.value = row ?? null;
|
||||
showStep.value = true;
|
||||
};
|
||||
|
||||
// 安装与卸载软件
|
||||
const install = (data) => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要安装吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = messageLoading('模块安装中...', 0);
|
||||
saveMenu(data)
|
||||
.then((menuId) => {
|
||||
// 获取权限列表
|
||||
setTimeout(() => {
|
||||
listApp({ parentId: data.appId }).then((list) => {
|
||||
authorityData.value = list.map((d) => {
|
||||
return {
|
||||
title: d.appName,
|
||||
menuType: d.menuType,
|
||||
authority: d.authority,
|
||||
parentId: menuId
|
||||
};
|
||||
});
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
// 收集菜单ID
|
||||
setTimeout(() => {
|
||||
// saveAuthority(authorityData.value).then((response) => {
|
||||
// roleMenuData.value = response?.data.map((d) => {
|
||||
// return {
|
||||
// menuId: d.menuId,
|
||||
// roleId: 22
|
||||
// };
|
||||
// });
|
||||
// });
|
||||
}, 1000);
|
||||
|
||||
// 添加应用权限
|
||||
setTimeout(() => {
|
||||
// saveRoleMenu(roleMenuData.value).then((result) => {
|
||||
// hide();
|
||||
// message.success('安装成功');
|
||||
// });
|
||||
}, 1000);
|
||||
})
|
||||
.catch(() => {
|
||||
hide();
|
||||
message.error('安装失败 请勿重复安装');
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const uninstallApp = (data) => {
|
||||
console.log(data);
|
||||
// removeMenu(data);
|
||||
};
|
||||
|
||||
/* 点击展开图标时触发 */
|
||||
const onExpand = (expanded: boolean, record: App) => {
|
||||
if (expanded) {
|
||||
expandedRowKeys.value = [
|
||||
...expandedRowKeys.value,
|
||||
record.appId as number
|
||||
];
|
||||
} else {
|
||||
expandedRowKeys.value = expandedRowKeys.value.filter(
|
||||
(d) => d !== record.appId
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: AppParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: App) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开设置弹窗 */
|
||||
const openSetting = (row?: App) => {
|
||||
current.value = row ?? null;
|
||||
showSetting.value = true;
|
||||
};
|
||||
|
||||
/* 进入应用 */
|
||||
const enterApp = (row?: App) => {
|
||||
openUrl(row?.component);
|
||||
};
|
||||
|
||||
/* 购买应用 */
|
||||
const onBuy = (row?: App) => {
|
||||
current.value = row ?? null;
|
||||
showBuy.value = true;
|
||||
};
|
||||
|
||||
/* 打开应用详情弹窗 */
|
||||
// const openDetail = (row?: App) => {
|
||||
// current.value = row ?? null;
|
||||
// showInfo.value = true;
|
||||
// };
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: App) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeApp(row.appId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchApp(
|
||||
selection.value.map((d) => {
|
||||
if (loginUser.value.userId === d.userId) {
|
||||
return d.appId;
|
||||
}
|
||||
})
|
||||
)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 已安装的应用
|
||||
const getAppIds = () => {
|
||||
// listMenus({}).then((res) => {
|
||||
// console.log(res, 'sdfsdfsdf>>>>');
|
||||
// });
|
||||
appIds.value =
|
||||
loginUser.value.authorities
|
||||
?.filter((d) => !!d.appId && d.appId > 0)
|
||||
?.map((d) => d.appId) ?? [];
|
||||
};
|
||||
|
||||
getAppIds();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as MenuIcons from './menu-icons';
|
||||
export default {
|
||||
name: 'App',
|
||||
components: MenuIcons
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-name {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.sys-org-table :deep(.ant-table-body) {
|
||||
overflow: auto !important;
|
||||
overflow: overlay !important;
|
||||
}
|
||||
|
||||
.sys-org-table :deep(.ant-table-pagination.ant-pagination) {
|
||||
padding: 0 4px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
56
src/views/system/appstore/menu-icons.ts
Normal file
56
src/views/system/appstore/menu-icons.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/** 菜单用到的图标 */
|
||||
export {
|
||||
HomeOutlined,
|
||||
SettingOutlined,
|
||||
TeamOutlined,
|
||||
DesktopOutlined,
|
||||
FileTextOutlined,
|
||||
TableOutlined,
|
||||
AppstoreOutlined,
|
||||
CheckCircleOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
UserOutlined,
|
||||
TagOutlined,
|
||||
IdcardOutlined,
|
||||
BarChartOutlined,
|
||||
AuditOutlined,
|
||||
PicLeftOutlined,
|
||||
CloseCircleOutlined,
|
||||
QuestionCircleOutlined,
|
||||
SoundOutlined,
|
||||
ApartmentOutlined,
|
||||
DashboardOutlined,
|
||||
OneToOneOutlined,
|
||||
DragOutlined,
|
||||
InteractionOutlined,
|
||||
BankOutlined,
|
||||
BlockOutlined,
|
||||
CheckSquareOutlined,
|
||||
ProfileOutlined,
|
||||
WarningOutlined,
|
||||
FolderOutlined,
|
||||
YoutubeOutlined,
|
||||
ControlOutlined,
|
||||
EllipsisOutlined,
|
||||
CalendarOutlined,
|
||||
AppstoreAddOutlined,
|
||||
FileSearchOutlined,
|
||||
EnvironmentOutlined,
|
||||
CompassOutlined,
|
||||
FontSizeOutlined,
|
||||
SketchOutlined,
|
||||
BgColorsOutlined,
|
||||
PrinterOutlined,
|
||||
QrcodeOutlined,
|
||||
BarcodeOutlined,
|
||||
PictureOutlined,
|
||||
LinkOutlined,
|
||||
AlertOutlined,
|
||||
HistoryOutlined,
|
||||
ChromeOutlined,
|
||||
CodeOutlined,
|
||||
ReadOutlined,
|
||||
LaptopOutlined,
|
||||
ShoppingCartOutlined,
|
||||
SkinOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
165
src/views/system/cache/components/cache-edit.vue
vendored
Normal file
165
src/views/system/cache/components/cache-edit.vue
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
<!-- 缓存编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="460"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '修改缓存' : '添加缓存'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 5, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="KEY" name="key">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入缓存名称"
|
||||
v-model:value="form.key"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="CONTENT" name="content">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入KEY的值"
|
||||
v-model:value="form.content"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="过期时间" name="expireTime">
|
||||
<a-input-number
|
||||
placeholder="默认永不过期"
|
||||
style="width: 200px"
|
||||
v-model:value="form.expireTime"
|
||||
/>
|
||||
<span class="ml-10">分钟</span>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { getCache, updateCache } from '@/api/system/cache';
|
||||
import type { Cache } from '@/api/system/cache/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Cache | null;
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Cache>({
|
||||
key: undefined,
|
||||
content: '',
|
||||
expireTime: undefined
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入KEY',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
content: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入KEY的值',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
updateCache(form)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
assignFields(props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.tab-pane {
|
||||
min-height: 300px;
|
||||
}
|
||||
.ml-10 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
233
src/views/system/cache/components/send-sms.vue
vendored
Normal file
233
src/views/system/cache/components/send-sms.vue
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
width="400px"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="'手机验证'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
:maskClosable="false"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form class="login-form">
|
||||
<a-form-item label="绑定的手机号码" name="phone">
|
||||
{{ getMobile(form.phone) }}
|
||||
</a-form-item>
|
||||
<a-form-item label="校验码" name="code">
|
||||
<div class="login-input-group">
|
||||
<a-input
|
||||
allow-clear
|
||||
type="text"
|
||||
:maxlength="6"
|
||||
v-model:value="form.code"
|
||||
>
|
||||
</a-input>
|
||||
<a-button
|
||||
class="login-captcha"
|
||||
:disabled="!!countdownTime"
|
||||
@click="openImgCodeModal"
|
||||
>
|
||||
<span v-if="!countdownTime">发送验证码</span>
|
||||
<span v-else>已发送 {{ countdownTime }} s</span>
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch, computed, onBeforeUnmount } from "vue";
|
||||
import { Form, message, Modal, SelectProps } from "ant-design-vue";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import type { AccessKey } from "@/api/system/access-key/model";
|
||||
import { addAccessKey, updateAccessKey } from "@/api/system/access-key";
|
||||
import { FILE_SERVER } from "@/config/setting";
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
import { RuleObject } from "ant-design-vue/es/form";
|
||||
import { isImage } from "@/utils/common";
|
||||
import { listUsers } from '@/api/system/user';
|
||||
import { getMobile } from '@/utils/common';
|
||||
import { sendSmsCaptcha } from '@/api/login';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: AccessKey | null;
|
||||
}>();
|
||||
|
||||
const userStore = useUserStore();
|
||||
// 当前登录用户信息
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const disabled = ref(false);
|
||||
// 选项卡位置
|
||||
const activeKey = ref("1");
|
||||
const promoter = ref<any>(undefined);
|
||||
const commander = ref(undefined);
|
||||
const appid = ref(undefined);
|
||||
|
||||
/* 打开选择弹窗 */
|
||||
const content = ref("");
|
||||
// 图形验证码地址
|
||||
const captcha = ref("");
|
||||
// 验证码倒计时定时器
|
||||
let countdownTimer: number | null = null;
|
||||
// 验证码倒计时时间
|
||||
const countdownTime = ref(0);
|
||||
// 图形验证码
|
||||
const imgCode = ref("");
|
||||
// 发送验证码按钮loading
|
||||
const codeLoading = ref(false);
|
||||
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "done", form: AccessKey): void;
|
||||
(e: "update:visible", value: boolean): void;
|
||||
}>();
|
||||
// 已上传数据, 可赋初始值用于回显
|
||||
const avatar = ref(<any>[]);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 用户信息
|
||||
const form = reactive<AccessKey>({
|
||||
id: 0,
|
||||
phone: "",
|
||||
accessKey: "",
|
||||
accessSecret: "",
|
||||
code: undefined,
|
||||
createTime: ""
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit("update:visible", value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请输入工单名称",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
taskType: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请选择工单类型",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
content: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请输入工单内容",
|
||||
trigger: "blur",
|
||||
validator: async (_rule: RuleObject, value: string) => {
|
||||
if (content.value == "") {
|
||||
return Promise.reject("请输入文字内容");
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 显示发送短信验证码弹窗 */
|
||||
const openImgCodeModal = () => {
|
||||
if (!form.phone) {
|
||||
message.error("手机号码有误");
|
||||
return;
|
||||
}
|
||||
// imgCode.value = "";
|
||||
sendCode();
|
||||
// visible.value = true;
|
||||
};
|
||||
|
||||
/* 发送短信验证码 */
|
||||
const sendCode = () => {
|
||||
codeLoading.value = true;
|
||||
sendSmsCaptcha({ phone: form.phone }).then((res) => {
|
||||
console.log(res);
|
||||
message.success("短信验证码发送成功, 请注意查收!");
|
||||
codeLoading.value = false;
|
||||
countdownTime.value = 30;
|
||||
// 开始对按钮进行倒计时
|
||||
countdownTimer = window.setInterval(() => {
|
||||
if (countdownTime.value <= 1) {
|
||||
countdownTimer && clearInterval(countdownTimer);
|
||||
countdownTimer = null;
|
||||
}
|
||||
countdownTime.value--;
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
countdownTimer && clearInterval(countdownTimer);
|
||||
});
|
||||
|
||||
const { validate, validateInfos } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
updateVisible(false);
|
||||
const { code,phone } = form;
|
||||
emit("done", { code,phone });
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
};
|
||||
|
||||
const query = () => {
|
||||
listUsers({username: 'admin'}).then(res => {
|
||||
form.phone = res[0].phone;
|
||||
})
|
||||
}
|
||||
query();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.login-form{
|
||||
padding: 0 20px;
|
||||
}
|
||||
.login-form-right .login-form {
|
||||
margin: 0 15% 0 auto;
|
||||
}
|
||||
|
||||
.login-form-left .login-form {
|
||||
margin: 0 auto 0 15%;
|
||||
}
|
||||
|
||||
/* 验证码 */
|
||||
.login-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.login-captcha {
|
||||
margin-left: 10px;
|
||||
padding: 0 10px;
|
||||
|
||||
& > img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
167
src/views/system/cache/index.vue
vendored
Normal file
167
src/views/system/cache/index.vue
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<a-page-header :ghost="false" title="缓存管理">
|
||||
<div class="ele-text-secondary">
|
||||
启用缓存可以缓解服务器压力,提升加载速度。
|
||||
</div>
|
||||
</a-page-header>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false">
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="key"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:where="defaultWhere"
|
||||
cache-key="userBalanceLogTable"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
<a-button @click="reset">刷新</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'content'">
|
||||
<div class="ele-text-secondary">
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>{{ record.content }}</template>
|
||||
{{ record.content }}
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="remove(record)">删除</a>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
</div>
|
||||
<!-- 编辑弹窗 -->
|
||||
<CacheEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, createVNode } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
import type { EleProTable } from 'ele-admin-pro/es';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import CacheEdit from './components/cache-edit.vue';
|
||||
import { updateCache, listCache, removeCache } from '@/api/system/cache';
|
||||
import { Cache, CacheParam } from '@/api/system/cache/model';
|
||||
import { App } from '@/api/app/model';
|
||||
import {
|
||||
PlusOutlined,
|
||||
ExclamationCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { messageLoading } from 'ele-admin-pro';
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'KEY',
|
||||
dataIndex: 'key'
|
||||
},
|
||||
{
|
||||
title: 'CONTENT',
|
||||
dataIndex: 'content',
|
||||
key: 'content',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<Cache[]>([]);
|
||||
const searchText = ref('');
|
||||
const userId = ref<number>(0);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<App | null>(null);
|
||||
|
||||
// 默认搜索条件
|
||||
const defaultWhere = reactive({
|
||||
code: '',
|
||||
phone: '',
|
||||
username: '',
|
||||
nickname: '',
|
||||
userId: undefined
|
||||
});
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = () => {
|
||||
return listCache({ key: '*' });
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
userId.value = 0;
|
||||
searchText.value = '';
|
||||
reload();
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: CacheParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const add = () => {
|
||||
showEdit.value = true;
|
||||
// setCache({})
|
||||
// .then((res) => {
|
||||
// reload();
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// message.error(err.message);
|
||||
// });
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: Cache) => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeCache(row.key)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'CacheIndex'
|
||||
};
|
||||
</script>
|
||||
165
src/views/system/dict/components/dict-data-edit.vue
Normal file
165
src/views/system/dict/components/dict-data-edit.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<!-- 字典项编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="460"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
:title="isUpdate ? '修改字典项' : '添加字典项'"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 6 }, sm: { span: 6 }, xs: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 18 }, sm: { span: 18 }, xs: { span: 24 } }"
|
||||
>
|
||||
<a-form-item label="字典项名称" v-bind="validateInfos.dictDataName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="100"
|
||||
placeholder="请输入字典项名称"
|
||||
v-model:value="form.dictDataName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="字典项值" v-bind="validateInfos.dictDataCode">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="100"
|
||||
placeholder="请输入字典项值"
|
||||
v-model:value="form.dictDataCode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" v-bind="validateInfos.sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入备注"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import { addDictData, updateDictData } from '@/api/system/dict-data';
|
||||
import type { DictData } from '@/api/system/dict-data/model';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: DictData | null;
|
||||
// 字典id
|
||||
dictId: number;
|
||||
}>();
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<DictData>({
|
||||
dictDataId: undefined,
|
||||
dictDataName: '',
|
||||
dictDataCode: '',
|
||||
sortNumber: 100,
|
||||
comments: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
dictDataName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典项名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
dictDataCode: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典项值',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sortNumber: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入排序号',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { resetFields, validate, validateInfos } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const saveOrUpdate = isUpdate.value ? updateDictData : addDictData;
|
||||
saveOrUpdate({
|
||||
...form,
|
||||
dictId: props.dictId
|
||||
})
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
70
src/views/system/dict/components/dict-data-search.vue
Normal file
70
src/views/system/dict/components/dict-data-search.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-row :gutter="16">
|
||||
<a-col :xl="6" :lg="8" :md="11" :sm="24" :xs="24">
|
||||
<a-input
|
||||
v-model:value.trim="where.keywords"
|
||||
placeholder="输入关键字搜索"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :xl="18" :lg="16" :md="13" :sm="24" :xs="24">
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="search">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
<span>查询</span>
|
||||
</a-button>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
<span>新建</span>
|
||||
</a-button>
|
||||
<a-button danger type="primary" class="ele-btn-icon" @click="remove">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
<span>删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
PlusOutlined,
|
||||
DeleteOutlined,
|
||||
SearchOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import type { DictDataParam } from '@/api/system/dict-data/model';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: DictDataParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { where } = useSearch<DictDataParam>({
|
||||
keywords: ''
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', where);
|
||||
};
|
||||
|
||||
/* 添加 */
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
/* 删除 */
|
||||
const remove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
</script>
|
||||
229
src/views/system/dict/components/dict-data.vue
Normal file
229
src/views/system/dict/components/dict-data.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="dictDataId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
tool-class="ele-toolbar-form"
|
||||
v-model:selection="selection"
|
||||
:customRow="customRow"
|
||||
:row-selection="{ columnWidth: 48 }"
|
||||
:scroll="{ x: 800 }"
|
||||
height="calc(100vh - 290px)"
|
||||
tools-theme="default"
|
||||
bordered
|
||||
class="sys-dict-data-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<dict-data-search
|
||||
@search="reload"
|
||||
@add="openEdit()"
|
||||
@remove="removeBatch"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'comments'">
|
||||
<span class="ele-text-placeholder">{{ record.comments }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此字典项吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
<!-- 编辑弹窗 -->
|
||||
<dict-data-edit
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:dict-id="dictId"
|
||||
@done="reload"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref, watch } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import DictDataSearch from './dict-data-search.vue';
|
||||
import DictDataEdit from './dict-data-edit.vue';
|
||||
import {
|
||||
pageDictData,
|
||||
removeDictData,
|
||||
removeDictDataBatch
|
||||
} from '@/api/system/dict-data';
|
||||
import type { DictData, DictDataParam } from '@/api/system/dict-data/model';
|
||||
import { Dict } from "@/api/system/dict/model";
|
||||
|
||||
const props = defineProps<{
|
||||
// 字典id
|
||||
dictId: number;
|
||||
}>();
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '字典项名称',
|
||||
dataIndex: 'dictDataName',
|
||||
ellipsis: true,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '字典项值',
|
||||
dataIndex: 'dictDataCode',
|
||||
ellipsis: true,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'comments',
|
||||
key: 'comments',
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '排序号',
|
||||
dataIndex: 'sortNumber',
|
||||
sorter: true,
|
||||
width: 120,
|
||||
align: 'center'
|
||||
},
|
||||
// {
|
||||
// title: '创建时间',
|
||||
// dataIndex: 'createTime',
|
||||
// sorter: true,
|
||||
// ellipsis: true,
|
||||
// customRender: ({ text }) => toDateString(text)
|
||||
// },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 130,
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<DictData[]>([]);
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<DictData | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
return pageDictData({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit,
|
||||
dictId: props.dictId
|
||||
});
|
||||
};
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = (where?: DictDataParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: DictData) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: DictData) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeDictData(row.dictDataId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的字典项吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeDictDataBatch(selection.value.map((d) => d.dictDataId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Dict) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// 监听字典id变化
|
||||
watch(
|
||||
() => props.dictId,
|
||||
() => {
|
||||
reload();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sys-dict-data-table :deep(.ant-table-body) {
|
||||
overflow: auto !important;
|
||||
overflow: overlay !important;
|
||||
}
|
||||
|
||||
.sys-dict-data-table :deep(.ant-table-pagination.ant-pagination) {
|
||||
padding: 0 4px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
160
src/views/system/dict/components/dict-edit.vue
Normal file
160
src/views/system/dict/components/dict-edit.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<!-- 字典编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="460"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '修改字典' : '添加字典'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 5 }, sm: { span: 5 }, xs: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 19 }, sm: { span: 19 }, xs: { span: 24 } }"
|
||||
>
|
||||
<a-form-item label="字典名称" v-bind="validateInfos.dictName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入字典名称"
|
||||
v-model:value="form.dictName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="字典标识" v-bind="validateInfos.dictCode">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入字典标识"
|
||||
v-model:value="form.dictCode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" v-bind="validateInfos.sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入备注"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import { addDict, updateDict } from '@/api/system/dict';
|
||||
import type { Dict } from '@/api/system/dict/model';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Dict | null;
|
||||
}>();
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<Dict>({
|
||||
dictId: undefined,
|
||||
dictName: '',
|
||||
dictCode: '',
|
||||
sortNumber: 100,
|
||||
comments: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
dictName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
dictCode: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典标识',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sortNumber: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入排序号',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { resetFields, validate, validateInfos } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const saveOrUpdate = isUpdate.value ? updateDict : addDict;
|
||||
saveOrUpdate(form)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
192
src/views/system/dict/index.vue
Normal file
192
src/views/system/dict/index.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-split-layout
|
||||
width="266px"
|
||||
allow-collapse
|
||||
:right-style="{ overflow: 'hidden' }"
|
||||
:style="{ minHeight: 'calc(100vh - 152px)' }"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="dictId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
v-model:current="current"
|
||||
selection-type="radio"
|
||||
:row-selection="{ columnWidth: 32 }"
|
||||
:need-page="false"
|
||||
:toolkit="[]"
|
||||
height="calc(100vh - 290px)"
|
||||
tools-theme="default"
|
||||
class="sys-dict-table"
|
||||
@done="done"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space :size="10">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="openEdit()">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
<span>新建</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!current"
|
||||
class="ele-btn-icon"
|
||||
@click="openEdit(current)"
|
||||
>
|
||||
<template #icon>
|
||||
<edit-outlined />
|
||||
</template>
|
||||
<span>修改</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
danger
|
||||
type="primary"
|
||||
:disabled="!current"
|
||||
class="ele-btn-icon"
|
||||
@click="remove"
|
||||
>
|
||||
<template #icon>
|
||||
<delete-outlined />
|
||||
</template>
|
||||
<span>删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dictName'">
|
||||
{{ record.dictName }}
|
||||
<span class="ele-text-placeholder">
|
||||
{{ record.dictCode }}
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
<template #content>
|
||||
<dict-data
|
||||
v-if="current && current.dictId"
|
||||
:dict-id="current.dictId"
|
||||
/>
|
||||
</template>
|
||||
</ele-split-layout>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<dict-edit v-model:visible="showEdit" :data="editData" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
ExclamationCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem,
|
||||
EleProTableDone
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import DictData from './components/dict-data.vue';
|
||||
import DictEdit from './components/dict-edit.vue';
|
||||
import { listDictionaries, removeDict } from '@/api/system/dict';
|
||||
import type { Dict } from '@/api/system/dict/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 35,
|
||||
ellipsis: true,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '字典名称',
|
||||
dataIndex: 'dictName',
|
||||
key: 'dictName'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const current = ref<Dict | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 编辑回显数据
|
||||
const editData = ref<Dict | null>(null);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = () => {
|
||||
return listDictionaries();
|
||||
};
|
||||
|
||||
/* 表格渲染完成回调 */
|
||||
const done: EleProTableDone<Dict> = (res) => {
|
||||
if (res.data?.length) {
|
||||
current.value = res.data[0];
|
||||
}
|
||||
};
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = () => {
|
||||
tableRef?.value?.reload();
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Dict | null) => {
|
||||
editData.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除 */
|
||||
const remove = () => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的字典吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeDict(current.value?.dictId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemDict'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sys-dict-table :deep(.ant-table-body) {
|
||||
overflow: auto !important;
|
||||
overflow: overlay !important;
|
||||
}
|
||||
.dict-code {
|
||||
color: #cccccc;
|
||||
}
|
||||
</style>
|
||||
210
src/views/system/dict/list.vue
Normal file
210
src/views/system/dict/list.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<ele-split-layout
|
||||
width="266px"
|
||||
allow-collapse
|
||||
:right-style="{ overflow: 'hidden' }"
|
||||
:style="{ minHeight: 'calc(100vh - 152px)' }"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="dictId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
v-model:current="current"
|
||||
:customRow="customRow"
|
||||
selection-type="radio"
|
||||
:row-selection="{ columnWidth: 32 }"
|
||||
:need-page="false"
|
||||
:toolkit="[]"
|
||||
height="calc(100vh - 290px)"
|
||||
tools-theme="default"
|
||||
bordered
|
||||
class="sys-dict-table"
|
||||
@done="done"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space :size="10">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="openEdit()">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
<span>新建</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!current"
|
||||
class="ele-btn-icon"
|
||||
@click="openEdit(current)"
|
||||
>
|
||||
<template #icon>
|
||||
<edit-outlined />
|
||||
</template>
|
||||
<span>修改</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
danger
|
||||
type="primary"
|
||||
:disabled="!current"
|
||||
class="ele-btn-icon"
|
||||
@click="remove"
|
||||
>
|
||||
<template #icon>
|
||||
<delete-outlined />
|
||||
</template>
|
||||
<span>删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dictName'">
|
||||
<a-space :size="size">
|
||||
<a-tooltip :title="`${record.dictCode}`">
|
||||
{{ record.dictName }}
|
||||
<!-- <span class="dict-code">{{ record.dictCode }}</span>-->
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
<template #content>
|
||||
<dict-data
|
||||
v-if="current && current.dictId"
|
||||
:dict-id="current.dictId"
|
||||
/>
|
||||
</template>
|
||||
</ele-split-layout>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<dict-edit v-model:visible="showEdit" :data="editData" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
ExclamationCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem,
|
||||
EleProTableDone
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import DictData from './components/dict-data.vue';
|
||||
import DictEdit from './components/dict-edit.vue';
|
||||
import { listDictionaries, removeDict } from '@/api/system/dict';
|
||||
import type { Dict } from '@/api/system/dict/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 45,
|
||||
ellipsis: true,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '字典名称',
|
||||
dataIndex: 'dictName',
|
||||
key: 'dictName'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const current = ref<Dict | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 编辑回显数据
|
||||
const editData = ref<Dict | null>(null);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = () => {
|
||||
return listDictionaries();
|
||||
};
|
||||
|
||||
/* 表格渲染完成回调 */
|
||||
const done: EleProTableDone<Dict> = (res) => {
|
||||
if (res.data?.length) {
|
||||
current.value = res.data[0];
|
||||
}
|
||||
};
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = () => {
|
||||
tableRef?.value?.reload();
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Dict | null) => {
|
||||
editData.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除 */
|
||||
const remove = () => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的字典吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeDict(current.value?.dictId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Dict) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemDict'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sys-dict-table :deep(.ant-table-body) {
|
||||
overflow: auto !important;
|
||||
overflow: overlay !important;
|
||||
}
|
||||
.dict-code {
|
||||
color: #cccccc;
|
||||
}
|
||||
</style>
|
||||
170
src/views/system/dictionary/components/dict-data-edit.vue
Normal file
170
src/views/system/dictionary/components/dict-data-edit.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<!-- 字典项编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="460"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
:title="isUpdate ? '修改字典项' : '添加字典项'"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 6 }, sm: { span: 6 }, xs: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 18 }, sm: { span: 18 }, xs: { span: 24 } }"
|
||||
>
|
||||
<a-form-item label="字典项名称" v-bind="validateInfos.dictDataName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入字典项名称"
|
||||
v-model:value="form.dictDataName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="字典项值" v-bind="validateInfos.dictDataCode">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入字典项值"
|
||||
v-model:value="form.dictDataCode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" v-bind="validateInfos.sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入备注"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import {
|
||||
addDictionaryData,
|
||||
updateDictionaryData
|
||||
} from '@/api/system/dictionary-data';
|
||||
import type { DictionaryData } from '@/api/system/dictionary-data/model';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: DictionaryData | null;
|
||||
// 字典id
|
||||
dictId: number;
|
||||
}>();
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<DictionaryData>({
|
||||
dictDataId: undefined,
|
||||
dictDataName: '',
|
||||
dictDataCode: '',
|
||||
sortNumber: 100,
|
||||
comments: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
dictDataName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典项名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
dictDataCode: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典项值',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sortNumber: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入排序号',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { resetFields, validate, validateInfos } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const saveOrUpdate = isUpdate.value
|
||||
? updateDictionaryData
|
||||
: addDictionaryData;
|
||||
saveOrUpdate({
|
||||
...form,
|
||||
dictId: props.dictId
|
||||
})
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
70
src/views/system/dictionary/components/dict-data-search.vue
Normal file
70
src/views/system/dictionary/components/dict-data-search.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-row :gutter="16">
|
||||
<a-col :xl="6" :lg="8" :md="11" :sm="24" :xs="24">
|
||||
<a-input
|
||||
v-model:value.trim="where.keywords"
|
||||
placeholder="输入关键字搜索"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :xl="18" :lg="16" :md="13" :sm="24" :xs="24">
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="search">
|
||||
<template #icon>
|
||||
<search-outlined />
|
||||
</template>
|
||||
<span>查询</span>
|
||||
</a-button>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
<span>新建</span>
|
||||
</a-button>
|
||||
<a-button danger type="primary" class="ele-btn-icon" @click="remove">
|
||||
<template #icon>
|
||||
<delete-outlined />
|
||||
</template>
|
||||
<span>删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
PlusOutlined,
|
||||
DeleteOutlined,
|
||||
SearchOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import type { DictionaryDataParam } from '@/api/system/dictionary-data/model';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: DictionaryDataParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { where } = useSearch<DictionaryDataParam>({
|
||||
keywords: ''
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', where);
|
||||
};
|
||||
|
||||
/* 添加 */
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
/* 删除 */
|
||||
const remove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
</script>
|
||||
207
src/views/system/dictionary/components/dict-data.vue
Normal file
207
src/views/system/dictionary/components/dict-data.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="dictDataId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
tool-class="ele-toolbar-form"
|
||||
v-model:selection="selection"
|
||||
:row-selection="{ columnWidth: 48 }"
|
||||
:scroll="{ x: 800 }"
|
||||
height="calc(100vh - 290px)"
|
||||
tools-theme="default"
|
||||
bordered
|
||||
class="sys-dict-data-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<dict-data-search
|
||||
@search="reload"
|
||||
@add="openEdit()"
|
||||
@remove="removeBatch"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此字典项吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
<!-- 编辑弹窗 -->
|
||||
<dict-data-edit
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:dict-id="dictId"
|
||||
@done="reload"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref, watch } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import DictDataSearch from './dict-data-search.vue';
|
||||
import DictDataEdit from './dict-data-edit.vue';
|
||||
import {
|
||||
pageDictionaryData,
|
||||
removeDictionaryData,
|
||||
removeDictionaryDataBatch
|
||||
} from '@/api/system/dictionary-data';
|
||||
import type {
|
||||
DictionaryData,
|
||||
DictionaryDataParam
|
||||
} from '@/api/system/dictionary-data/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 字典id
|
||||
dictId: number;
|
||||
}>();
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '字典项名称',
|
||||
dataIndex: 'dictDataName',
|
||||
ellipsis: true,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '字典项值',
|
||||
dataIndex: 'dictDataCode',
|
||||
ellipsis: true,
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '排序号',
|
||||
dataIndex: 'sortNumber',
|
||||
sorter: true,
|
||||
width: 120,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 130,
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<DictionaryData[]>([]);
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<DictionaryData | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
return pageDictionaryData({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit,
|
||||
dictId: props.dictId
|
||||
});
|
||||
};
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = (where?: DictionaryDataParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: DictionaryData) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: DictionaryData) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeDictionaryData(row.dictDataId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的字典项吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeDictionaryDataBatch(selection.value.map((d) => d.dictDataId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 监听字典id变化
|
||||
watch(
|
||||
() => props.dictId,
|
||||
() => {
|
||||
reload();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sys-dict-data-table :deep(.ant-table-body) {
|
||||
overflow: auto !important;
|
||||
overflow: overlay !important;
|
||||
}
|
||||
|
||||
.sys-dict-data-table :deep(.ant-table-pagination.ant-pagination) {
|
||||
padding: 0 4px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
160
src/views/system/dictionary/components/dict-edit.vue
Normal file
160
src/views/system/dictionary/components/dict-edit.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<!-- 字典编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="460"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '修改字典' : '添加字典'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 5 }, sm: { span: 5 }, xs: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 19 }, sm: { span: 19 }, xs: { span: 24 } }"
|
||||
>
|
||||
<a-form-item label="字典名称" v-bind="validateInfos.dictName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入字典名称"
|
||||
v-model:value="form.dictName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="字典标识" v-bind="validateInfos.dictCode">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入字典标识"
|
||||
v-model:value="form.dictCode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" v-bind="validateInfos.sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入备注"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import { addDictionary, updateDictionary } from '@/api/system/dictionary';
|
||||
import type { Dictionary } from '@/api/system/dictionary/model';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Dictionary | null;
|
||||
}>();
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<Dictionary>({
|
||||
dictId: undefined,
|
||||
dictName: '',
|
||||
dictCode: '',
|
||||
sortNumber: 100,
|
||||
comments: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
dictName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
dictCode: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典标识',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sortNumber: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入排序号',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { resetFields, validate, validateInfos } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const saveOrUpdate = isUpdate.value ? updateDictionary : addDictionary;
|
||||
saveOrUpdate(form)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
195
src/views/system/dictionary/index.vue
Normal file
195
src/views/system/dictionary/index.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-split-layout
|
||||
width="266px"
|
||||
allow-collapse
|
||||
:right-style="{ overflow: 'hidden' }"
|
||||
:style="{ minHeight: 'calc(100vh - 152px)' }"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="dictId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
v-model:current="current"
|
||||
selection-type="radio"
|
||||
:row-selection="{ columnWidth: 32 }"
|
||||
:need-page="false"
|
||||
:toolkit="[]"
|
||||
height="calc(100vh - 290px)"
|
||||
tools-theme="default"
|
||||
bordered
|
||||
class="sys-dict-table"
|
||||
@done="done"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space :size="10">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="openEdit()">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
<span>新建</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!current"
|
||||
class="ele-btn-icon"
|
||||
@click="openEdit(current)"
|
||||
>
|
||||
<template #icon>
|
||||
<edit-outlined />
|
||||
</template>
|
||||
<span>修改</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
danger
|
||||
type="primary"
|
||||
:disabled="!current"
|
||||
class="ele-btn-icon"
|
||||
@click="remove"
|
||||
>
|
||||
<template #icon>
|
||||
<delete-outlined />
|
||||
</template>
|
||||
<span>删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dictName'">
|
||||
<a-space :size="size">
|
||||
<a-tooltip :title="`${record.dictCode}`">
|
||||
{{ record.dictName }}
|
||||
<!-- <span class="dict-code">{{ record.dictCode }}</span>-->
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
<template #content>
|
||||
<dict-data
|
||||
v-if="current && current.dictId"
|
||||
:dict-id="current.dictId"
|
||||
/>
|
||||
</template>
|
||||
</ele-split-layout>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<dict-edit v-model:visible="showEdit" :data="editData" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
ExclamationCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem,
|
||||
EleProTableDone
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import DictData from './components/dict-data.vue';
|
||||
import DictEdit from './components/dict-edit.vue';
|
||||
import { listDictionaries, removeDictionary } from '@/api/system/dictionary';
|
||||
import type { Dictionary } from '@/api/system/dictionary/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 45,
|
||||
ellipsis: true,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '字典名称',
|
||||
dataIndex: 'dictName',
|
||||
key: 'dictName'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const current = ref<Dictionary | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 编辑回显数据
|
||||
const editData = ref<Dictionary | null>(null);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = () => {
|
||||
return listDictionaries();
|
||||
};
|
||||
|
||||
/* 表格渲染完成回调 */
|
||||
const done: EleProTableDone<Dictionary> = (res) => {
|
||||
if (res.data?.length) {
|
||||
current.value = res.data[0];
|
||||
}
|
||||
};
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = () => {
|
||||
tableRef?.value?.reload();
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Dictionary | null) => {
|
||||
editData.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除 */
|
||||
const remove = () => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的字典吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeDictionary(current.value?.dictId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemDictionary'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sys-dict-table :deep(.ant-table-body) {
|
||||
overflow: auto !important;
|
||||
overflow: overlay !important;
|
||||
}
|
||||
.dict-code {
|
||||
color: #cccccc;
|
||||
}
|
||||
</style>
|
||||
195
src/views/system/dictionary/list.vue
Normal file
195
src/views/system/dictionary/list.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<ele-split-layout
|
||||
width="266px"
|
||||
allow-collapse
|
||||
:right-style="{ overflow: 'hidden' }"
|
||||
:style="{ minHeight: 'calc(100vh - 152px)' }"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="dictId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
v-model:current="current"
|
||||
selection-type="radio"
|
||||
:row-selection="{ columnWidth: 32 }"
|
||||
:need-page="false"
|
||||
:toolkit="[]"
|
||||
height="calc(100vh - 290px)"
|
||||
tools-theme="default"
|
||||
bordered
|
||||
class="sys-dict-table"
|
||||
@done="done"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space :size="10">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="openEdit()">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
<span>新建</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!current"
|
||||
class="ele-btn-icon"
|
||||
@click="openEdit(current)"
|
||||
>
|
||||
<template #icon>
|
||||
<edit-outlined />
|
||||
</template>
|
||||
<span>修改</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
danger
|
||||
type="primary"
|
||||
:disabled="!current"
|
||||
class="ele-btn-icon"
|
||||
@click="remove"
|
||||
>
|
||||
<template #icon>
|
||||
<delete-outlined />
|
||||
</template>
|
||||
<span>删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dictName'">
|
||||
<a-space :size="size">
|
||||
<a-tooltip :title="`${record.dictCode}`">
|
||||
{{ record.dictName }}
|
||||
<!-- <span class="dict-code">{{ record.dictCode }}</span>-->
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
<template #content>
|
||||
<dict-data
|
||||
v-if="current && current.dictId"
|
||||
:dict-id="current.dictId"
|
||||
/>
|
||||
</template>
|
||||
</ele-split-layout>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<dict-edit v-model:visible="showEdit" :data="editData" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
ExclamationCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem,
|
||||
EleProTableDone
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import DictData from './components/dict-data.vue';
|
||||
import DictEdit from './components/dict-edit.vue';
|
||||
import { listDictionaries, removeDictionary } from '@/api/system/dictionary';
|
||||
import type { Dictionary } from '@/api/system/dictionary/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 45,
|
||||
ellipsis: true,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '字典名称',
|
||||
dataIndex: 'dictName',
|
||||
key: 'dictName'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const current = ref<Dictionary | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 编辑回显数据
|
||||
const editData = ref<Dictionary | null>(null);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = () => {
|
||||
return listDictionaries();
|
||||
};
|
||||
|
||||
/* 表格渲染完成回调 */
|
||||
const done: EleProTableDone<Dictionary> = (res) => {
|
||||
if (res.data?.length) {
|
||||
current.value = res.data[0];
|
||||
}
|
||||
};
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = () => {
|
||||
tableRef?.value?.reload();
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Dictionary | null) => {
|
||||
editData.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除 */
|
||||
const remove = () => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的字典吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeDictionary(current.value?.dictId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemDictionary'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sys-dict-table :deep(.ant-table-body) {
|
||||
overflow: auto !important;
|
||||
overflow: overlay !important;
|
||||
}
|
||||
.dict-code {
|
||||
color: #cccccc;
|
||||
}
|
||||
</style>
|
||||
106
src/views/system/file/components/file-search.vue
Normal file
106
src/views/system/file/components/file-search.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-form
|
||||
:label-col="
|
||||
styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' }
|
||||
"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-row :gutter="8">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="文件名称">
|
||||
<a-input
|
||||
v-model:value.trim="form.name"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="文件路径">
|
||||
<a-input
|
||||
v-model:value.trim="form.path"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="上传人">
|
||||
<a-input
|
||||
v-model:value.trim="form.createNickname"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type { FileRecordParam } from '@/api/system/file/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: FileRecordParam): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields } = useFormData<FileRecordParam>({
|
||||
name: '',
|
||||
path: '',
|
||||
createNickname: ''
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', form);
|
||||
};
|
||||
|
||||
/* 重置 */
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
search();
|
||||
};
|
||||
</script>
|
||||
245
src/views/system/file/index.vue
Normal file
245
src/views/system/file/index.vue
Normal file
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false">
|
||||
<!-- 搜索表单 -->
|
||||
<file-search @search="reload" />
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
v-model:selection="selection"
|
||||
:scroll="{ x: 800 }"
|
||||
cache-key="proSystemFileTable"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-upload :show-upload-list="false" :customRequest="onUpload">
|
||||
<a-button type="primary" class="ele-btn-icon">
|
||||
<template #icon>
|
||||
<upload-outlined />
|
||||
</template>
|
||||
<span>上传</span>
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<a-button
|
||||
danger
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
@click="removeBatch"
|
||||
>
|
||||
<template #icon>
|
||||
<delete-outlined />
|
||||
</template>
|
||||
<span>删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'path'">
|
||||
<a :href="record.path" target="_blank">
|
||||
{{ record.path }}
|
||||
</a>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a :href="record.url" target="_blank">下载</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
placement="topRight"
|
||||
title="确定要删除此文件吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
import {
|
||||
UploadOutlined,
|
||||
DeleteOutlined,
|
||||
ExclamationCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro/es';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { messageLoading, toDateString } from 'ele-admin-pro/es';
|
||||
import FileSearch from './components/file-search.vue';
|
||||
import {
|
||||
pageFiles,
|
||||
removeFile,
|
||||
removeFiles,
|
||||
uploadFile
|
||||
} from '@/api/system/file';
|
||||
import type { FileRecord, FileRecordParam } from '@/api/system/file/model';
|
||||
import { getUrl } from '@/utils/common';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '文件名称',
|
||||
dataIndex: 'name',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '文件路径',
|
||||
key: 'path',
|
||||
dataIndex: 'path',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '文件大小',
|
||||
dataIndex: 'length',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => {
|
||||
if (text < 1024) {
|
||||
return text + 'B';
|
||||
} else if (text < 1024 * 1024) {
|
||||
return (text / 1024).toFixed(1) + 'KB';
|
||||
} else if (text < 1024 * 1024 * 1024) {
|
||||
return (text / 1024 / 1024).toFixed(1) + 'M';
|
||||
} else {
|
||||
return (text / 1024 / 1024 / 1024).toFixed(1) + 'G';
|
||||
}
|
||||
},
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '上传人',
|
||||
dataIndex: 'createNickname',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true,
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '上传时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text),
|
||||
width: 160
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 120,
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<FileRecord[]>([]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
return pageFiles({ ...where, ...orders, page, limit });
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: FileRecordParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: FileRecord) => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeFile(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的文件吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeFiles(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 上传 */
|
||||
const onUpload = ({ file }) => {
|
||||
if (file.size / 1024 / 1024 > 100) {
|
||||
message.error('大小不能超过 100MB');
|
||||
return false;
|
||||
}
|
||||
const hide = messageLoading({
|
||||
content: '上传中..',
|
||||
duration: 0,
|
||||
mask: true
|
||||
});
|
||||
uploadFile(file)
|
||||
.then(() => {
|
||||
hide();
|
||||
message.success('上传成功');
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
return false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemFile'
|
||||
};
|
||||
</script>
|
||||
115
src/views/system/login-record/components/login-record-search.vue
Normal file
115
src/views/system/login-record/components/login-record-search.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-form
|
||||
:label-col="
|
||||
styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' }
|
||||
"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-row :gutter="8">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="用户账号">
|
||||
<a-input
|
||||
v-model:value.trim="form.username"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="昵称">
|
||||
<a-input
|
||||
v-model:value.trim="form.nickname"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="登录时间">
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
value-format="YYYY-MM-DD"
|
||||
class="ele-fluid"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type { LoginRecordParam } from '@/api/system/login-record/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: LoginRecordParam): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields } = useFormData<LoginRecordParam>({
|
||||
username: '',
|
||||
nickname: ''
|
||||
});
|
||||
|
||||
// 日期范围选择
|
||||
const dateRange = ref<[string, string]>(['', '']);
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
const [d1, d2] = dateRange.value ?? [];
|
||||
emit('search', {
|
||||
...form,
|
||||
createTimeStart: d1 ? d1 + ' 00:00:00' : '',
|
||||
createTimeEnd: d2 ? d2 + ' 23:59:59' : ''
|
||||
});
|
||||
};
|
||||
|
||||
/* 重置 */
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
dateRange.value = ['', ''];
|
||||
search();
|
||||
};
|
||||
</script>
|
||||
235
src/views/system/login-record/index.vue
Normal file
235
src/views/system/login-record/index.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false">
|
||||
<!-- 搜索表单 -->
|
||||
<login-record-search @search="reload" />
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:scroll="{ x: 900 }"
|
||||
cache-key="proSystemLoginRecordTable"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="exportData">
|
||||
<template #icon>
|
||||
<download-outlined />
|
||||
</template>
|
||||
<span>导出</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'loginType'">
|
||||
<a-tag v-if="record.loginType === 0" color="green">登录成功</a-tag>
|
||||
<a-tag v-else-if="record.loginType === 1" color="red">
|
||||
登录失败
|
||||
</a-tag>
|
||||
<a-tag v-else-if="record.loginType === 2">退出登录</a-tag>
|
||||
<a-tag v-else-if="record.loginType === 3" color="orange">
|
||||
刷新TOKEN
|
||||
</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { utils, writeFile } from 'xlsx';
|
||||
import { DownloadOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro/es';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { messageLoading, toDateString } from 'ele-admin-pro/es';
|
||||
import LoginRecordSearch from './components/login-record-search.vue';
|
||||
import {
|
||||
pageLoginRecords,
|
||||
listLoginRecords
|
||||
} from '@/api/system/login-record';
|
||||
import type { LoginRecordParam } from '@/api/system/login-record/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '账号',
|
||||
dataIndex: 'username',
|
||||
sorter: true,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'nickname',
|
||||
sorter: true,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: 'IP地址',
|
||||
dataIndex: 'ip',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '设备型号',
|
||||
dataIndex: 'device',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '操作系统',
|
||||
dataIndex: 'os',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '浏览器',
|
||||
dataIndex: 'browser',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '操作类型',
|
||||
key: 'loginType',
|
||||
dataIndex: 'loginType',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
width: 120,
|
||||
filters: [
|
||||
{
|
||||
text: '登录成功',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
text: '登录失败',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
text: '退出登录',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
text: '刷新TOKEN',
|
||||
value: 3
|
||||
}
|
||||
],
|
||||
filterMultiple: false
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'comments',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '登录时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text)
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
return pageLoginRecords({
|
||||
...where,
|
||||
...orders,
|
||||
...filters,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = (where?: LoginRecordParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 导出数据 */
|
||||
const exportData = () => {
|
||||
const array = [
|
||||
[
|
||||
'账号',
|
||||
'昵称',
|
||||
'IP地址',
|
||||
'设备型号',
|
||||
'操作系统',
|
||||
'浏览器',
|
||||
'操作类型',
|
||||
'备注',
|
||||
'登录时间'
|
||||
]
|
||||
];
|
||||
// 请求查询全部接口
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
tableRef.value?.doRequest(({ where, orders, filters }) => {
|
||||
listLoginRecords({ ...where, ...orders, ...filters })
|
||||
.then((data) => {
|
||||
hide();
|
||||
data.forEach((d) => {
|
||||
array.push([
|
||||
d.username,
|
||||
d.nickname,
|
||||
d.ip,
|
||||
d.device,
|
||||
d.os,
|
||||
d.browser,
|
||||
['登录成功', '登录失败', '退出登录', '刷新TOKEN'][d.loginType],
|
||||
d.comments,
|
||||
toDateString(d.createTime)
|
||||
]);
|
||||
});
|
||||
writeFile(
|
||||
{
|
||||
SheetNames: ['Sheet1'],
|
||||
Sheets: {
|
||||
Sheet1: utils.aoa_to_sheet(array)
|
||||
}
|
||||
},
|
||||
'登录日志.xlsx'
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemLoginRecord'
|
||||
};
|
||||
</script>
|
||||
186
src/views/system/menu/components/clone.vue
Normal file
186
src/views/system/menu/components/clone.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="540"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="`菜单克隆`"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 5, sm: 4, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="选择模板" name="tenantId">
|
||||
<a-input
|
||||
placeholder="请输入要克隆的租户ID"
|
||||
v-model:value="form.tenantId"
|
||||
@pressEnter="save"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { isExternalLink } from 'ele-admin-pro/es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { clone } from '@/api/system/menu';
|
||||
import type { Menu } from '@/api/system/menu/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Menu | null;
|
||||
// 上级菜单id
|
||||
parentId?: number;
|
||||
// 全部菜单数据
|
||||
menuList: Menu[];
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Menu>({
|
||||
title: '',
|
||||
icon: '',
|
||||
path: '',
|
||||
component: '',
|
||||
tenantId: undefined
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
tenantId: [
|
||||
{
|
||||
required: true,
|
||||
message: '克隆后原有的菜单将会抹除,请慎用!',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const menuForm = {
|
||||
...form
|
||||
};
|
||||
clone(menuForm)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
/* 判断是否是目录 */
|
||||
const isDirectory = (d: Menu) => {
|
||||
return !!d.children?.length && !d.component;
|
||||
};
|
||||
|
||||
// 查询租户列表
|
||||
// const tenantList = ref<Tenant[]>([]);
|
||||
// const reload = (tenantName?: any) => {
|
||||
// listTenant({ tenantName }).then((result) => {
|
||||
// tenantList.value = result;
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// reload();
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
const isExternal = isExternalLink(props.data.path);
|
||||
const isInner = isExternalLink(props.data.component);
|
||||
// menuType 对应的值与后端不一致在前端处理
|
||||
const menuType =
|
||||
props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1;
|
||||
assignFields({
|
||||
...props.data,
|
||||
menuType,
|
||||
openType: isExternal ? 2 : isInner ? 1 : 0,
|
||||
parentId:
|
||||
props.data.parentId === 0 ? undefined : props.data.parentId
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
form.parentId = props.parentId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as icons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
components: icons,
|
||||
data() {
|
||||
return {
|
||||
iconData: [
|
||||
{
|
||||
title: '已引入的图标',
|
||||
icons: Object.keys(icons)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
416
src/views/system/menu/components/menu-edit.vue
Normal file
416
src/views/system/menu/components/menu-edit.vue
Normal file
@@ -0,0 +1,416 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="740"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '修改菜单' : '新建菜单'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 6, sm: 4, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="上级菜单" name="parentId">
|
||||
<a-tree-select
|
||||
allow-clear
|
||||
:tree-data="menuList"
|
||||
tree-default-expand-all
|
||||
placeholder="请选择上级菜单"
|
||||
:value="form.parentId || undefined"
|
||||
:dropdown-style="{ maxHeight: '360px', overflow: 'auto' }"
|
||||
@update:value="(value?: number) => (form.parentId = value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="菜单名称" name="title">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入菜单名称"
|
||||
v-model:value="form.title"
|
||||
@pressEnter="save"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="菜单类型" name="menuType">
|
||||
<a-radio-group
|
||||
v-model:value="form.menuType"
|
||||
@change="onMenuTypeChange"
|
||||
>
|
||||
<a-radio :value="0">目录</a-radio>
|
||||
<a-radio :value="1">菜单</a-radio>
|
||||
<a-radio :value="2">按钮</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="打开方式">
|
||||
<a-radio-group
|
||||
v-model:value="form.openType"
|
||||
:disabled="form.menuType === 0 || form.menuType === 2"
|
||||
@change="onOpenTypeChange"
|
||||
>
|
||||
<a-radio :value="0">组件</a-radio>
|
||||
<a-radio :value="1">内链</a-radio>
|
||||
<a-radio :value="2">外链</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div style="margin-bottom: 22px">
|
||||
<a-divider />
|
||||
</div>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="菜单图标" name="icon">
|
||||
<ele-icon-picker
|
||||
:data="iconData"
|
||||
:allow-search="false"
|
||||
v-model:value="form.icon"
|
||||
placeholder="请选择菜单图标"
|
||||
:disabled="form.menuType === 2"
|
||||
>
|
||||
<template #icon="{ icon }">
|
||||
<component :is="icon" />
|
||||
</template>
|
||||
</ele-icon-picker>
|
||||
</a-form-item>
|
||||
<a-form-item name="path">
|
||||
<template #label>
|
||||
<a-tooltip
|
||||
v-if="form.openType === 2"
|
||||
title="需要以`http://`、`https://`、`//`开头"
|
||||
>
|
||||
<question-circle-outlined
|
||||
style="vertical-align: -2px; margin-right: 4px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<span>{{ form.openType === 2 ? '外链地址' : '路由地址' }}</span>
|
||||
</template>
|
||||
<a-input
|
||||
allow-clear
|
||||
v-model:value="form.path"
|
||||
:disabled="form.menuType === 2"
|
||||
:placeholder="
|
||||
form.openType === 2 ? '请输入外链地址' : '请输入路由地址'
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item name="component">
|
||||
<template #label>
|
||||
<a-tooltip
|
||||
v-if="form.openType === 1"
|
||||
title="需要以`http://`、`https://`、`//`开头"
|
||||
>
|
||||
<question-circle-outlined
|
||||
style="vertical-align: -2px; margin-right: 4px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<span>{{ form.openType === 1 ? '内链地址' : '组件路径' }}</span>
|
||||
</template>
|
||||
<a-input
|
||||
allow-clear
|
||||
v-model:value="form.component"
|
||||
:disabled="
|
||||
form.menuType === 0 ||
|
||||
form.menuType === 2 ||
|
||||
form.openType === 2
|
||||
"
|
||||
:placeholder="
|
||||
form.openType === 1 ? '请输入内链地址' : '请输入组件路径'
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="权限标识" name="authority">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入权限标识"
|
||||
v-model:value="form.authority"
|
||||
:disabled="
|
||||
form.menuType === 0 ||
|
||||
(form.menuType === 1 && form.openType === 2)
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" name="sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="99999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
@pressEnter="save"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否展示">
|
||||
<a-switch
|
||||
checked-children="是"
|
||||
un-checked-children="否"
|
||||
:checked="form.hide === 0"
|
||||
:disabled="form.menuType === 2"
|
||||
@update:checked="updateHideValue"
|
||||
/>
|
||||
<a-tooltip
|
||||
title="选择不展示只注册路由不展示在侧边栏, 比如添加页面应该选择不展示"
|
||||
>
|
||||
<question-circle-outlined
|
||||
style="vertical-align: -4px; margin-left: 16px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item
|
||||
label="路由元数据"
|
||||
name="meta"
|
||||
:label-col="
|
||||
styleResponsive ? { md: 3, sm: 4, xs: 24 } : { flex: '90px' }
|
||||
"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 21, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入JSON格式的路由元数据"
|
||||
v-model:value="form.meta"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { isExternalLink } from 'ele-admin-pro/es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addMenu, updateMenu } from '@/api/system/menu';
|
||||
import type { Menu } from '@/api/system/menu/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Menu | null;
|
||||
// 上级菜单id
|
||||
parentId?: number;
|
||||
// 全部菜单数据
|
||||
menuList: Menu[];
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Menu>({
|
||||
menuId: undefined,
|
||||
parentId: undefined,
|
||||
title: '',
|
||||
menuType: 0,
|
||||
openType: 0,
|
||||
icon: '',
|
||||
path: '',
|
||||
component: '',
|
||||
authority: '',
|
||||
sortNumber: undefined,
|
||||
hide: 0,
|
||||
meta: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入菜单名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sortNumber: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入排序号',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
meta: [
|
||||
{
|
||||
type: 'string',
|
||||
validator: async (_rule: Rule, value: string) => {
|
||||
if (value) {
|
||||
const msg = '请输入正确的JSON格式';
|
||||
try {
|
||||
const obj = JSON.parse(value);
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return Promise.reject(msg);
|
||||
}
|
||||
} catch (_e) {
|
||||
return Promise.reject(msg);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const menuForm = {
|
||||
...form,
|
||||
// menuType 对应的值与后端不一致在前端处理
|
||||
menuType: form.menuType === 2 ? 1 : 0,
|
||||
parentId: form.parentId || 0
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateMenu : addMenu;
|
||||
saveOrUpdate(menuForm)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
/* menuType选择改变 */
|
||||
const onMenuTypeChange = () => {
|
||||
if (form.menuType === 0) {
|
||||
form.authority = '';
|
||||
form.openType = 0;
|
||||
form.component = '';
|
||||
} else if (form.menuType === 1) {
|
||||
if (form.openType === 2) {
|
||||
form.authority = '';
|
||||
}
|
||||
} else {
|
||||
form.openType = 0;
|
||||
form.icon = '';
|
||||
form.path = '';
|
||||
form.component = '';
|
||||
form.hide = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/* openType选择改变 */
|
||||
const onOpenTypeChange = () => {
|
||||
if (form.openType === 2) {
|
||||
form.component = '';
|
||||
form.authority = '';
|
||||
}
|
||||
};
|
||||
|
||||
const updateHideValue = (value: boolean) => {
|
||||
form.hide = value ? 0 : 1;
|
||||
};
|
||||
|
||||
/* 判断是否是目录 */
|
||||
const isDirectory = (d: Menu) => {
|
||||
return !!d.children?.length && !d.component;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
const isExternal = isExternalLink(props.data.path);
|
||||
const isInner = isExternalLink(props.data.component);
|
||||
// menuType 对应的值与后端不一致在前端处理
|
||||
const menuType =
|
||||
props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1;
|
||||
assignFields({
|
||||
...props.data,
|
||||
menuType,
|
||||
openType: isExternal ? 2 : isInner ? 1 : 0,
|
||||
parentId:
|
||||
props.data.parentId === 0 ? undefined : props.data.parentId
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
form.parentId = props.parentId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as icons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
components: icons,
|
||||
data() {
|
||||
return {
|
||||
iconData: [
|
||||
{
|
||||
title: '已引入的图标',
|
||||
icons: Object.keys(icons)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
168
src/views/system/menu/components/menu-plug.vue
Normal file
168
src/views/system/menu/components/menu-plug.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="520"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
title="发布插件"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
cancelText="取消"
|
||||
okText="确定发布"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form ref="formRef" :model="form" :rules="rules">
|
||||
<a-alert
|
||||
:description="`审核通过后,该插件将展示在扩展插件模块,可供其他用户安装和使用后,获得销售分成。`"
|
||||
type="success"
|
||||
closable
|
||||
show-icon
|
||||
>
|
||||
<template #icon><SmileOutlined /></template>
|
||||
</a-alert>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { createPlug } from '@/api/system/plug';
|
||||
import type { Plug } from '@/api/system/plug/model';
|
||||
import { RuleObject } from 'ant-design-vue/es/form';
|
||||
import { isExternalLink } from 'ele-admin-pro';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Plug | null;
|
||||
// 上级菜单id
|
||||
parentId?: number;
|
||||
// 全部菜单数据
|
||||
menuList: Plug[];
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
const appid = ref(undefined);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Plug>({
|
||||
parentId: undefined
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
appId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择要绑定的应用',
|
||||
type: 'number',
|
||||
trigger: 'blur',
|
||||
validator: async (_rule: RuleObject, value: string) => {
|
||||
if (appid.value == undefined) {
|
||||
return Promise.reject('请选择要绑定的应用');
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
form.parentId = props.data?.menuId;
|
||||
createPlug(form)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
/* 判断是否是目录 */
|
||||
const isDirectory = (d: Plug) => {
|
||||
return !!d.children?.length && !d.component;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
const isExternal = isExternalLink(props.data.path);
|
||||
const isInner = isExternalLink(props.data.component);
|
||||
// menuType 对应的值与后端不一致在前端处理
|
||||
const menuType =
|
||||
props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1;
|
||||
assignFields({
|
||||
...props.data,
|
||||
menuType,
|
||||
openType: isExternal ? 2 : isInner ? 1 : 0,
|
||||
parentId:
|
||||
props.data.parentId === 0 ? undefined : props.data.parentId
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
form.parentId = props.parentId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as icons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
components: icons,
|
||||
data() {
|
||||
return {
|
||||
iconData: [
|
||||
{
|
||||
title: '已引入的图标',
|
||||
icons: Object.keys(icons)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
106
src/views/system/menu/components/menu-search.vue
Normal file
106
src/views/system/menu/components/menu-search.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-form
|
||||
:label-col="
|
||||
styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' }
|
||||
"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-row :gutter="8">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="菜单名称">
|
||||
<a-input
|
||||
v-model:value.trim="form.title"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="菜单地址">
|
||||
<a-input
|
||||
v-model:value.trim="form.path"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="权限标识">
|
||||
<a-input
|
||||
v-model:value.trim="form.authority"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type { MenuParam } from '@/api/system/menu/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: MenuParam): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields } = useFormData<MenuParam>({
|
||||
title: '',
|
||||
path: '',
|
||||
authority: ''
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', form);
|
||||
};
|
||||
|
||||
/* 重置 */
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
search();
|
||||
};
|
||||
</script>
|
||||
355
src/views/system/menu/index.vue
Normal file
355
src/views/system/menu/index.vue
Normal file
@@ -0,0 +1,355 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false">
|
||||
<!-- 搜索表单 -->
|
||||
<menu-search @search="reload" />
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="menuId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:parse-data="parseData"
|
||||
:need-page="false"
|
||||
:expand-icon-column-index="1"
|
||||
:expanded-row-keys="expandedRowKeys"
|
||||
:scroll="{ x: 1200 }"
|
||||
cache-key="proSystemMenuTable"
|
||||
@done="onDone"
|
||||
@expand="onExpand"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="openEdit()">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
<span>新建</span>
|
||||
</a-button>
|
||||
<a-button type="dashed" class="ele-btn-icon" @click="expandAll">
|
||||
展开全部
|
||||
</a-button>
|
||||
<a-button type="dashed" class="ele-btn-icon" @click="foldAll">
|
||||
折叠全部
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
class="ele-btn-icon"
|
||||
@click="cloneMenu"
|
||||
v-role="'admin'"
|
||||
>
|
||||
一键克隆
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'menuType'">
|
||||
<a-tag v-if="isExternalLink(record.path)" color="red">外链</a-tag>
|
||||
<a-tag v-else-if="isExternalLink(record.component)" color="orange">
|
||||
内链
|
||||
</a-tag>
|
||||
<a-tag v-else-if="isDirectory(record)" color="blue">目录</a-tag>
|
||||
<a-tag v-else-if="record.menuType === 0" color="green">菜单</a-tag>
|
||||
<a-tag v-else-if="record.menuType === 1">按钮</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'title'">
|
||||
<component v-if="record.icon" :is="record.icon" />
|
||||
<a-tooltip title="点击复制">
|
||||
<span style="padding-left: 8px" @click="copyText(record.title)">
|
||||
{{ record.title }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(null, record.menuId)">添加</a>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<!-- <a @click="addPlug(record)">插件</a>-->
|
||||
<!-- <a-divider type="vertical" />-->
|
||||
<a-popconfirm
|
||||
placement="topRight"
|
||||
title="确定要删除此菜单吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
<template v-if="column.key === 'path'">
|
||||
<a-tooltip title="点击复制">
|
||||
<span @click="copyText(record.path)">
|
||||
{{ record.path }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'component'">
|
||||
<a-tooltip title="点击复制">
|
||||
<span @click="copyText(record.component)">
|
||||
{{ record.component }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'authority'">
|
||||
<a-tooltip title="点击复制">
|
||||
<span @click="copyText(record.authority)">
|
||||
{{ record.authority }}
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<menu-edit
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:parent-id="parentId"
|
||||
:menu-list="menuData"
|
||||
@done="reload"
|
||||
/>
|
||||
<!-- 制作插件 -->
|
||||
<menu-plug
|
||||
v-model:visible="showPlug"
|
||||
:data="current"
|
||||
:parent-id="parentId"
|
||||
:menu-list="menuData"
|
||||
@done="reload"
|
||||
/>
|
||||
<clone v-model:visible="showClone" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem,
|
||||
EleProTableDone
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import MenuSearch from './components/menu-search.vue';
|
||||
import {
|
||||
messageLoading,
|
||||
toDateString,
|
||||
isExternalLink,
|
||||
toTreeData,
|
||||
eachTreeData
|
||||
} from 'ele-admin-pro/es';
|
||||
import type { EleProTable } from 'ele-admin-pro/es';
|
||||
import MenuEdit from './components/menu-edit.vue';
|
||||
import MenuPlug from './components/menu-plug.vue';
|
||||
import Clone from './components/clone.vue';
|
||||
import { listMenus, removeMenu } from '@/api/system/menu';
|
||||
import type { Menu, MenuParam } from '@/api/system/menu/model';
|
||||
import { copyText } from '@/utils/common';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '菜单名称',
|
||||
key: 'title',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '路由地址',
|
||||
dataIndex: 'path',
|
||||
key: 'path',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '组件路径',
|
||||
dataIndex: 'component',
|
||||
key: 'component',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '权限标识',
|
||||
dataIndex: 'authority',
|
||||
key: 'authority',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sortNumber',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '可见',
|
||||
dataIndex: 'hide',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
customRender: ({ text }) => ['是', '否'][text],
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
key: 'menuType',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<Menu | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
const showPlug = ref(false);
|
||||
const showClone = ref(false);
|
||||
|
||||
// 上级菜单id
|
||||
const parentId = ref<number>();
|
||||
|
||||
// 菜单数据
|
||||
const menuData = ref<Menu[]>([]);
|
||||
|
||||
// 表格展开的行
|
||||
const expandedRowKeys = ref<number[]>([]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ where }) => {
|
||||
return listMenus({ ...where });
|
||||
};
|
||||
|
||||
/* 数据转为树形结构 */
|
||||
const parseData = (data: Menu[]) => {
|
||||
return toTreeData({
|
||||
data: data.map((d) => {
|
||||
return { ...d, key: d.menuId, value: d.menuId };
|
||||
}),
|
||||
idField: 'menuId',
|
||||
parentIdField: 'parentId'
|
||||
});
|
||||
};
|
||||
|
||||
/* 表格渲染完成回调 */
|
||||
const onDone: EleProTableDone<Menu> = ({ data }) => {
|
||||
menuData.value = data;
|
||||
};
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = (where?: MenuParam) => {
|
||||
tableRef?.value?.reload({ where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Menu | null, id?: number) => {
|
||||
current.value = row ?? null;
|
||||
parentId.value = id;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const addPlug = (row?: Menu) => {
|
||||
current.value = row ?? null;
|
||||
showPlug.value = true;
|
||||
};
|
||||
|
||||
/* 一键克隆 */
|
||||
const cloneMenu = (row?: Menu | null, id?: number) => {
|
||||
current.value = row ?? null;
|
||||
parentId.value = id;
|
||||
showClone.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: Menu) => {
|
||||
if (row.children?.length) {
|
||||
message.error('请先删除子节点');
|
||||
return;
|
||||
}
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeMenu(row.menuId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 展开全部 */
|
||||
const expandAll = () => {
|
||||
let keys: number[] = [];
|
||||
eachTreeData(menuData.value, (d) => {
|
||||
if (d.children && d.children.length && d.menuId) {
|
||||
keys.push(d.menuId);
|
||||
}
|
||||
});
|
||||
expandedRowKeys.value = keys;
|
||||
};
|
||||
|
||||
/* 折叠全部 */
|
||||
const foldAll = () => {
|
||||
expandedRowKeys.value = [];
|
||||
};
|
||||
|
||||
/* 点击展开图标时触发 */
|
||||
const onExpand = (expanded: boolean, record: Menu) => {
|
||||
if (expanded) {
|
||||
expandedRowKeys.value = [
|
||||
...expandedRowKeys.value,
|
||||
record.menuId as number
|
||||
];
|
||||
} else {
|
||||
expandedRowKeys.value = expandedRowKeys.value.filter(
|
||||
(d) => d !== record.menuId
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/* 判断是否是目录 */
|
||||
const isDirectory = (d: Menu) => {
|
||||
return !!d.children?.length && !d.component;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as MenuIcons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
name: 'SystemMenu',
|
||||
components: MenuIcons
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,131 @@
|
||||
<!-- 详情弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
title="详情"
|
||||
:width="640"
|
||||
:footer="null"
|
||||
:visible="visible"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<a-form
|
||||
class="ele-form-detail"
|
||||
:label-col="{ sm: { span: 8 }, xs: { span: 6 } }"
|
||||
:wrapper-col="{ sm: { span: 16 }, xs: { span: 18 } }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :sm="12" :xs="24">
|
||||
<a-form-item label="操作人">
|
||||
<div class="ele-text-secondary">
|
||||
{{ data.nickname }}({{ data.username }})
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="操作模块">
|
||||
<div class="ele-text-secondary">
|
||||
{{ data.module }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="操作时间">
|
||||
<div class="ele-text-secondary">
|
||||
{{ toDateString(data.createTime) }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="请求方式">
|
||||
<div class="ele-text-secondary">
|
||||
{{ data.requestMethod }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :sm="12" :xs="24">
|
||||
<a-form-item label="IP地址">
|
||||
<div class="ele-text-secondary">
|
||||
{{ data.ip }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="操作功能">
|
||||
<div class="ele-text-secondary">
|
||||
{{ data.description }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="请求耗时">
|
||||
<div v-if="!isNaN(data.spendTime)" class="ele-text-secondary">
|
||||
{{ data.spendTime / 1000 }}s
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="请求状态">
|
||||
<a-tag :color="['green', 'red'][data.status]">
|
||||
{{ ['正常', '异常'][data.status] }}
|
||||
</a-tag>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div style="margin: 12px 0">
|
||||
<a-divider />
|
||||
</div>
|
||||
<a-form-item
|
||||
label="请求地址"
|
||||
:label-col="{ sm: { span: 4 }, xs: { span: 6 } }"
|
||||
:wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }"
|
||||
>
|
||||
<div class="ele-text-secondary">
|
||||
{{ data.url }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="调用方法"
|
||||
:label-col="{ sm: { span: 4 }, xs: { span: 6 } }"
|
||||
:wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }"
|
||||
>
|
||||
<div class="ele-text-secondary">
|
||||
{{ data.method }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="请求参数"
|
||||
:label-col="{ sm: { span: 4 }, xs: { span: 6 } }"
|
||||
:wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }"
|
||||
>
|
||||
<div class="ele-text-secondary">
|
||||
{{ data.params }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="data.status === 0"
|
||||
label="返回结果"
|
||||
:label-col="{ sm: { span: 4 }, xs: { span: 6 } }"
|
||||
:wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }"
|
||||
>
|
||||
<text-ellipsis :content="data.result" class="ele-text-secondary" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-else
|
||||
label="异常信息"
|
||||
:label-col="{ sm: { span: 4 }, xs: { span: 6 } }"
|
||||
:wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }"
|
||||
>
|
||||
<text-ellipsis :content="data.error" class="ele-text-secondary" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toDateString } from 'ele-admin-pro/es';
|
||||
import type { OperationRecord } from '@/api/system/operation-record/model';
|
||||
import TextEllipsis from './text-ellipsis.vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible?: boolean;
|
||||
// 修改回显的数据
|
||||
data: OperationRecord;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,112 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-form
|
||||
:label-col="
|
||||
styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' }
|
||||
"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-row :gutter="8">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="用户账号">
|
||||
<a-input
|
||||
v-model:value.trim="form.username"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="操作模块">
|
||||
<a-input
|
||||
v-model:value.trim="form.module"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="操作时间">
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
:show-time="true"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
class="ele-fluid"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type { OperationRecordParam } from '@/api/system/operation-record/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: OperationRecordParam): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields } = useFormData<OperationRecordParam>({
|
||||
username: '',
|
||||
module: ''
|
||||
});
|
||||
|
||||
// 日期范围选择
|
||||
const dateRange = ref<[string, string]>(['', '']);
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
const [createTimeStart, createTimeEnd] = dateRange.value;
|
||||
emit('search', { ...form, createTimeStart, createTimeEnd });
|
||||
};
|
||||
|
||||
/* 重置 */
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
dateRange.value = ['', ''];
|
||||
search();
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,59 @@
|
||||
<!-- 文本超出隐藏 -->
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'demo-text-ellipsis ele-bg-white ele-border-split',
|
||||
{ expanded: expanded }
|
||||
]"
|
||||
>
|
||||
<div>{{ content }}</div>
|
||||
<div
|
||||
class="demo-text-ellipsis-footer ele-border-split ele-bg-white"
|
||||
@click="expanded = !expanded"
|
||||
>
|
||||
<up-outlined v-if="expanded" />
|
||||
<down-outlined v-else />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
defineProps<{
|
||||
content?: string;
|
||||
}>();
|
||||
|
||||
const expanded = ref(false);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.demo-text-ellipsis {
|
||||
border-radius: 4px;
|
||||
padding: 6px 12px 20px 12px;
|
||||
position: relative;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
word-break: break-all;
|
||||
|
||||
&:not(.expanded) {
|
||||
max-height: 192px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-text-ellipsis-footer {
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
273
src/views/system/operation-record/index.vue
Normal file
273
src/views/system/operation-record/index.vue
Normal file
@@ -0,0 +1,273 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false">
|
||||
<!-- 搜索表单 -->
|
||||
<operation-record-search @search="reload" />
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:scroll="{ x: 1000 }"
|
||||
cache-key="proSystemOperationRecordTable"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="exportData">
|
||||
<template #icon>
|
||||
<download-outlined />
|
||||
</template>
|
||||
<span>导出</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">正常</a-tag>
|
||||
<a-tag v-else-if="record.status === 1" color="red">异常</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a @click="openDetail(record)">详情</a>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
<!-- 详情弹窗 -->
|
||||
<operation-record-detail v-model:visible="showInfo" :data="current" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { DownloadOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro/es';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { utils, writeFile } from 'xlsx';
|
||||
import { messageLoading, toDateString } from 'ele-admin-pro/es';
|
||||
import OperationRecordSearch from './components/operation-record-search.vue';
|
||||
import OperationRecordDetail from './components/operation-record-detail.vue';
|
||||
import {
|
||||
pageOperationRecords,
|
||||
listOperationRecords
|
||||
} from '@/api/system/operation-record';
|
||||
import type {
|
||||
OperationRecord,
|
||||
OperationRecordParam
|
||||
} from '@/api/system/operation-record/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '账号',
|
||||
dataIndex: 'username',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'nickname',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '操作模块',
|
||||
dataIndex: 'module',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '操作功能',
|
||||
dataIndex: 'description',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '请求地址',
|
||||
dataIndex: 'url',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '请求方式',
|
||||
dataIndex: 'requestMethod',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
width: 100,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
dataIndex: 'status',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
width: 100,
|
||||
filters: [
|
||||
{
|
||||
text: '正常',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
text: '异常',
|
||||
value: 1
|
||||
}
|
||||
],
|
||||
filterMultiple: false,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '耗时',
|
||||
dataIndex: 'spendTime',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
width: 100,
|
||||
customRender: ({ text }) => text / 1000 + 's'
|
||||
},
|
||||
{
|
||||
title: '操作时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text),
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 90,
|
||||
align: 'center',
|
||||
fixed: 'right'
|
||||
}
|
||||
]);
|
||||
|
||||
// 当前选中数据
|
||||
const current = ref<OperationRecord>({
|
||||
module: '',
|
||||
description: '',
|
||||
url: '',
|
||||
requestMethod: '',
|
||||
method: '',
|
||||
params: '',
|
||||
result: '',
|
||||
error: '',
|
||||
spendTime: 0,
|
||||
os: '',
|
||||
device: '',
|
||||
browser: '',
|
||||
ip: '',
|
||||
status: 0,
|
||||
createTime: '',
|
||||
nickname: '',
|
||||
username: ''
|
||||
});
|
||||
|
||||
// 是否显示查看弹窗
|
||||
const showInfo = ref(false);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
return pageOperationRecords({
|
||||
...where,
|
||||
...orders,
|
||||
...filters,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = (where?: OperationRecordParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 详情 */
|
||||
const openDetail = (row: OperationRecord) => {
|
||||
current.value = row;
|
||||
showInfo.value = true;
|
||||
};
|
||||
|
||||
/* 导出数据 */
|
||||
const exportData = () => {
|
||||
const array = [
|
||||
[
|
||||
'账号',
|
||||
'昵称',
|
||||
'操作模块',
|
||||
'操作功能',
|
||||
'请求地址',
|
||||
'请求方式',
|
||||
'状态',
|
||||
'耗时',
|
||||
'操作时间'
|
||||
]
|
||||
];
|
||||
// 请求查询全部(不分页)的接口
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
tableRef.value?.doRequest(({ where, orders, filters }) => {
|
||||
listOperationRecords({ ...where, ...orders, ...filters })
|
||||
.then((data) => {
|
||||
hide();
|
||||
data.forEach((d) => {
|
||||
array.push([
|
||||
d.username,
|
||||
d.nickname,
|
||||
d.module,
|
||||
d.description,
|
||||
d.url,
|
||||
d.requestMethod,
|
||||
['正常', '异常'][d.status],
|
||||
d.spendTime / 1000 + 's',
|
||||
toDateString(d.createTime)
|
||||
]);
|
||||
});
|
||||
writeFile(
|
||||
{
|
||||
SheetNames: ['Sheet1'],
|
||||
Sheets: {
|
||||
Sheet1: utils.aoa_to_sheet(array)
|
||||
}
|
||||
},
|
||||
'操作日志.xlsx'
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemOperationRecord'
|
||||
};
|
||||
</script>
|
||||
229
src/views/system/organization/components/org-edit.vue
Normal file
229
src/views/system/organization/components/org-edit.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<!-- 部门编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="620"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '修改部门' : '添加部门'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 7, sm: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 17, sm: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="上级部门" name="parentId">
|
||||
<org-select
|
||||
:data="organizationList"
|
||||
placeholder="请选择上级部门"
|
||||
v-model:value="form.parentId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="部门简称" name="organizationName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入部门名称"
|
||||
v-model:value="form.organizationName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="部门全称" name="organizationFullName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="100"
|
||||
placeholder="请输入部门全称"
|
||||
v-model:value="form.organizationFullName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="部门代码">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入部门代码"
|
||||
v-model:value="form.organizationCode"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="类型" name="organizationType">
|
||||
<org-type-select v-model:value="form.organizationType" />
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" name="sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="99999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入备注"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import OrgSelect from './org-select.vue';
|
||||
import OrgTypeSelect from './org-type-select.vue';
|
||||
import {
|
||||
addOrganization,
|
||||
updateOrganization
|
||||
} from '@/api/system/organization';
|
||||
import type { Organization } from '@/api/system/organization/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Organization | null;
|
||||
// 部门id
|
||||
organizationId?: number;
|
||||
// 全部部门
|
||||
organizationList: Organization[];
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Organization>({
|
||||
organizationId: undefined,
|
||||
parentId: undefined,
|
||||
organizationName: '',
|
||||
organizationFullName: '',
|
||||
organizationCode: '',
|
||||
organizationType: undefined,
|
||||
sortNumber: undefined,
|
||||
comments: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
organizationName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入部门名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
organizationFullName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入部门全称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
organizationType: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择部门类型',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sortNumber: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入排序号',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const orgData = {
|
||||
...form,
|
||||
parentId: form.parentId || 0
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value
|
||||
? updateOrganization
|
||||
: addOrganization;
|
||||
saveOrUpdate(orgData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
assignFields(props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
form.parentId = props.organizationId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
39
src/views/system/organization/components/org-select.vue
Normal file
39
src/views/system/organization/components/org-select.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<!-- 机构选择下拉框 -->
|
||||
<template>
|
||||
<a-tree-select
|
||||
allow-clear
|
||||
tree-default-expand-all
|
||||
:placeholder="placeholder"
|
||||
:value="value || undefined"
|
||||
:tree-data="data"
|
||||
:dropdown-style="{ maxHeight: '360px', overflow: 'auto' }"
|
||||
@update:value="updateValue"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Organization } from '@/api/system/organization/model';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value?: number): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
// 选中的数据(v-modal)
|
||||
value?: number;
|
||||
// 提示信息
|
||||
placeholder?: string;
|
||||
// 机构数据
|
||||
data: Organization[];
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择角色'
|
||||
}
|
||||
);
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value?: number) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
</script>
|
||||
39
src/views/system/organization/components/org-type-select.vue
Normal file
39
src/views/system/organization/components/org-type-select.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<!-- 机构类型选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
allow-clear
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
>
|
||||
<a-select-option v-for="item in data" :key="item.label" :value="item.value">
|
||||
{{ item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择机构类型'
|
||||
}
|
||||
);
|
||||
|
||||
// 机构类型数据
|
||||
const data = getDictionaryOptions('organizationType');
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
</script>
|
||||
295
src/views/system/organization/components/org-user-edit.vue
Normal file
295
src/views/system/organization/components/org-user-edit.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '修改用户' : '新建用户'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 7, sm: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 17, sm: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="所属部门">
|
||||
<org-select
|
||||
:data="organizationList"
|
||||
placeholder="请选择所属部门"
|
||||
v-model:value="form.organizationId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="账号" name="username">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入用户账号"
|
||||
v-model:value="form.username"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="姓名" name="realName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入真实姓名"
|
||||
v-model:value="form.realName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="性别" name="sex">
|
||||
<sex-select v-model:value="form.sex" />
|
||||
</a-form-item>
|
||||
<a-form-item label="角色" name="roleIds">
|
||||
<role-select v-model:value="form.roles" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="11"
|
||||
placeholder="请输入手机号"
|
||||
v-model:value="form.phone"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="100"
|
||||
placeholder="请输入邮箱"
|
||||
v-model:value="form.email"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="出生日期">
|
||||
<a-date-picker
|
||||
class="ele-fluid"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择出生日期"
|
||||
v-model:value="form.birthday"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="!isUpdate" label="登录密码" name="password">
|
||||
<a-input-password
|
||||
:maxlength="20"
|
||||
v-model:value="form.password"
|
||||
placeholder="请输入登录密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="个人简介">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入个人简介"
|
||||
v-model:value="form.introduction"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { emailReg, phoneReg } from 'ele-admin-pro/es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import OrgSelect from './org-select.vue';
|
||||
import RoleSelect from '../../user/components/role-select.vue';
|
||||
import SexSelect from '../../user/components/sex-select.vue';
|
||||
import { addUser, updateUser, checkExistence } from '@/api/system/user';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
import type { Organization } from '@/api/system/organization/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
// 全部部门
|
||||
organizationList: Organization[];
|
||||
// 部门id
|
||||
organizationId?: number;
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<User>({
|
||||
userId: undefined,
|
||||
organizationId: undefined,
|
||||
username: '',
|
||||
nickname: '',
|
||||
realName: '',
|
||||
sex: undefined,
|
||||
roles: [],
|
||||
email: '',
|
||||
phone: '',
|
||||
password: '',
|
||||
introduction: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
validator: (_rule: Rule, value: string) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!value) {
|
||||
return reject('请输入用户账号');
|
||||
}
|
||||
checkExistence('username', value, props.data?.userId)
|
||||
.then(() => {
|
||||
reject('账号已经存在');
|
||||
})
|
||||
.catch(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
nickname: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入昵称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
realName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入真实姓名',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
// sex: [
|
||||
// {
|
||||
// required: true,
|
||||
// message: '请选择性别',
|
||||
// type: 'string',
|
||||
// trigger: 'blur'
|
||||
// }
|
||||
// ],
|
||||
// roleIds: [
|
||||
// {
|
||||
// required: true,
|
||||
// message: '请选择角色',
|
||||
// type: 'array',
|
||||
// trigger: 'blur'
|
||||
// }
|
||||
// ],
|
||||
email: [
|
||||
{
|
||||
pattern: emailReg,
|
||||
message: '邮箱格式不正确',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
validator: async (_rule: Rule, value: string) => {
|
||||
if (isUpdate.value || /^[\S]{5,18}$/.test(value)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject('密码必须为5-18位非空白字符');
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
phone: [
|
||||
{
|
||||
pattern: phoneReg,
|
||||
message: '手机号格式不正确',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
form.nickname = form.realName;
|
||||
form.alias = form.realName;
|
||||
const saveOrUpdate = isUpdate.value ? updateUser : addUser;
|
||||
saveOrUpdate(form)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
assignFields(props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
form.organizationId = props.organizationId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
239
src/views/system/organization/components/org-user-list.vue
Normal file
239
src/views/system/organization/components/org-user-list.vue
Normal file
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="userId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
height="calc(100vh - 290px)"
|
||||
tool-class="ele-toolbar-form"
|
||||
:scroll="{ x: 800 }"
|
||||
tools-theme="default"
|
||||
bordered
|
||||
cache-key="proSystemOrgUserTable"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<org-user-search @search="reload" @add="openEdit()" />
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'roles'">
|
||||
<a-tag v-for="item in record.roles" :key="item.roleId" color="blue">
|
||||
{{ item.roleName }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<a-switch
|
||||
:checked="record.status === 0"
|
||||
@change="(checked: boolean) => editStatus(checked, record)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'balance'">
|
||||
<span class="ele-text-success">
|
||||
¥{{ formatNumber(record.balance) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
placement="topRight"
|
||||
title="确定要删除此用户吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
<!-- 编辑弹窗 -->
|
||||
<org-user-edit
|
||||
:data="current"
|
||||
v-model:visible="showEdit"
|
||||
:organization-list="organizationList"
|
||||
:organization-id="organizationId"
|
||||
@done="reload"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { EleProTable } from 'ele-admin-pro/es';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { messageLoading } from 'ele-admin-pro/es';
|
||||
import OrgUserSearch from './org-user-search.vue';
|
||||
import OrgUserEdit from './org-user-edit.vue';
|
||||
import { timeAgo } from 'ele-admin-pro';
|
||||
import { formatNumber } from 'ele-admin-pro/es';
|
||||
import { pageUsers, removeUser, updateUserStatus } from '@/api/system/user';
|
||||
import type { User, UserParam } from '@/api/system/user/model';
|
||||
import type { Organization } from '@/api/system/organization/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 部门 id
|
||||
organizationId?: number;
|
||||
// 部门列表
|
||||
organizationList: Organization[];
|
||||
}>();
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '用户ID',
|
||||
dataIndex: 'userId',
|
||||
sorter: true,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '用户账号',
|
||||
dataIndex: 'username',
|
||||
sorter: true,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'realName',
|
||||
sorter: true,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '余额',
|
||||
dataIndex: 'balance',
|
||||
key: 'balance',
|
||||
sorter: true,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'sexName',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
showSorterTooltip: false,
|
||||
hideInTable: true,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
key: 'roles'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
hideInTable: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => timeAgo(text)
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
width: 80,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<User | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
if (props.organizationId) {
|
||||
where.organizationId = props.organizationId;
|
||||
}
|
||||
return pageUsers({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: UserParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: User) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: User) => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeUser(row.userId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 修改用户状态 */
|
||||
const editStatus = (checked: boolean, row: User) => {
|
||||
const status = checked ? 0 : 1;
|
||||
updateUserStatus(row.userId, status)
|
||||
.then((msg) => {
|
||||
row.status = status;
|
||||
message.success(msg);
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
// 监听部门 id 变化
|
||||
watch(
|
||||
() => props.organizationId,
|
||||
() => {
|
||||
reload();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sys-org-table :deep(.ant-table-body) {
|
||||
overflow: auto !important;
|
||||
overflow: overlay !important;
|
||||
}
|
||||
|
||||
.sys-org-table :deep(.ant-table-pagination.ant-pagination) {
|
||||
padding: 0 4px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
83
src/views/system/organization/components/org-user-search.vue
Normal file
83
src/views/system/organization/components/org-user-search.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive ? { xl: 6, lg: 8, md: 12, sm: 24, xs: 24 } : { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-input
|
||||
v-model:value.trim="form.keywords"
|
||||
placeholder="请输入关键词"
|
||||
allow-clear
|
||||
/>
|
||||
</a-col>
|
||||
<!-- <a-col-->
|
||||
<!-- v-bind="-->
|
||||
<!-- styleResponsive ? { xl: 6, lg: 8, md: 12, sm: 24, xs: 24 } : { span: 6 }-->
|
||||
<!-- "-->
|
||||
<!-- >-->
|
||||
<!-- <a-input-->
|
||||
<!-- v-model:value.trim="form.nickname"-->
|
||||
<!-- placeholder="请输入昵称"-->
|
||||
<!-- allow-clear-->
|
||||
<!-- />-->
|
||||
<!-- </a-col>-->
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 12, lg: 8, md: 24, sm: 24, xs: 24 }
|
||||
: { span: 12 }
|
||||
"
|
||||
>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="search">
|
||||
<template #icon>
|
||||
<search-outlined />
|
||||
</template>
|
||||
<span>查询</span>
|
||||
</a-button>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
<span>新建</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined, SearchOutlined } from '@ant-design/icons-vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type { UserParam } from '@/api/system/user/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: UserParam): void;
|
||||
(e: 'add'): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { form } = useFormData<UserParam>({
|
||||
keywords: '',
|
||||
username: '',
|
||||
nickname: ''
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', form);
|
||||
};
|
||||
|
||||
/* 添加 */
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
</script>
|
||||
71
src/views/system/organization/components/role-select.vue
Normal file
71
src/views/system/organization/components/role-select.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<!-- 角色选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
allow-clear
|
||||
mode="multiple"
|
||||
:value="roleIds"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@blur="onBlur"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in data"
|
||||
:key="item.roleId"
|
||||
:value="item.roleId"
|
||||
>
|
||||
{{ item.roleName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { listRoles } from '@/api/system/role';
|
||||
import type { Role } from '@/api/system/role/model';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: Role[]): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
value?: Role[];
|
||||
//
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择角色'
|
||||
}
|
||||
);
|
||||
|
||||
// 选中的角色id
|
||||
const roleIds = computed(() => props.value?.map((d) => d.roleId as number));
|
||||
|
||||
// 角色数据
|
||||
const data = ref<Role[]>([]);
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: number[]) => {
|
||||
emit(
|
||||
'update:value',
|
||||
value.map((v) => ({ roleId: v }))
|
||||
);
|
||||
};
|
||||
|
||||
/* 获取角色数据 */
|
||||
listRoles()
|
||||
.then((list) => {
|
||||
data.value = list;
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
45
src/views/system/organization/components/sex-select.vue
Normal file
45
src/views/system/organization/components/sex-select.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<!-- 角色选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
allow-clear
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择性别'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = getDictionaryOptions('sex');
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
211
src/views/system/organization/index.vue
Normal file
211
src/views/system/organization/index.vue
Normal file
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-split-layout
|
||||
width="266px"
|
||||
allow-collapse
|
||||
:right-style="{ overflow: 'hidden' }"
|
||||
:style="{ minHeight: 'calc(100vh - 152px)' }"
|
||||
>
|
||||
<div>
|
||||
<ele-toolbar theme="default">
|
||||
<a-space :size="10">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="openEdit()">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
<span>新建</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!current"
|
||||
class="ele-btn-icon"
|
||||
@click="openEdit(current)"
|
||||
>
|
||||
<template #icon>
|
||||
<edit-outlined />
|
||||
</template>
|
||||
<span>修改</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
danger
|
||||
type="primary"
|
||||
:disabled="!current"
|
||||
class="ele-btn-icon"
|
||||
@click="remove"
|
||||
>
|
||||
<template #icon>
|
||||
<delete-outlined />
|
||||
</template>
|
||||
<span>删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</ele-toolbar>
|
||||
<div class="ele-border-split sys-organization-list">
|
||||
<a-tree
|
||||
:tree-data="(data as any)"
|
||||
v-model:expanded-keys="expandedRowKeys"
|
||||
v-model:selected-keys="selectedRowKeys"
|
||||
@select="onTreeSelect"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template #content>
|
||||
<org-user-list
|
||||
v-if="current"
|
||||
:organization-list="data"
|
||||
:organization-id="current.organizationId"
|
||||
/>
|
||||
</template>
|
||||
</ele-split-layout>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<org-edit
|
||||
v-model:visible="showEdit"
|
||||
:data="editData"
|
||||
:organization-list="data"
|
||||
:organization-id="current?.organizationId"
|
||||
@done="query"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
ExclamationCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { messageLoading, toTreeData, eachTreeData } from 'ele-admin-pro/es';
|
||||
import OrgUserList from './components/org-user-list.vue';
|
||||
import OrgEdit from './components/org-edit.vue';
|
||||
import {
|
||||
listOrganizations,
|
||||
removeOrganization
|
||||
} from '@/api/system/organization';
|
||||
import type { Organization } from '@/api/system/organization/model';
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 树形数据
|
||||
const data = ref<Organization[]>([]);
|
||||
|
||||
// 树展开的key
|
||||
const expandedRowKeys = ref<number[]>([]);
|
||||
|
||||
// 树选中的key
|
||||
const selectedRowKeys = ref<number[]>([]);
|
||||
|
||||
// 选中数据
|
||||
const current = ref<Organization | null>(null);
|
||||
|
||||
// 是否显示表单弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 编辑回显数据
|
||||
const editData = ref<Organization | null>(null);
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
listOrganizations()
|
||||
.then((list) => {
|
||||
loading.value = false;
|
||||
const eks: number[] = [];
|
||||
list.forEach((d, i) => {
|
||||
if (d.parentId > 0) {
|
||||
d.title = i + '.' + d.organizationName;
|
||||
} else {
|
||||
d.title = d.organizationName;
|
||||
}
|
||||
d.key = d.organizationId;
|
||||
d.value = d.organizationId;
|
||||
|
||||
if (typeof d.key === 'number') {
|
||||
eks.push(d.key);
|
||||
}
|
||||
});
|
||||
expandedRowKeys.value = eks;
|
||||
data.value = toTreeData({
|
||||
data: list,
|
||||
idField: 'organizationId',
|
||||
parentIdField: 'parentId'
|
||||
});
|
||||
if (list.length) {
|
||||
if (typeof list[0].key === 'number') {
|
||||
selectedRowKeys.value = [list[0].key];
|
||||
}
|
||||
current.value = list[0];
|
||||
current.value.organizationId = 0;
|
||||
} else {
|
||||
selectedRowKeys.value = [];
|
||||
current.value = null;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 选择数据 */
|
||||
const onTreeSelect = () => {
|
||||
eachTreeData(data.value, (d) => {
|
||||
if (typeof d.key === 'number' && selectedRowKeys.value.includes(d.key)) {
|
||||
current.value = d;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (item?: Organization | null) => {
|
||||
editData.value = item ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除 */
|
||||
const remove = () => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的部门吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeOrganization(current.value?.organizationId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
query();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemOrganization'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sys-organization-list {
|
||||
padding: 12px 6px;
|
||||
height: calc(100vh - 242px);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
234
src/views/system/payment/components/clerk.vue
Normal file
234
src/views/system/payment/components/clerk.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<a-drawer
|
||||
:width="700"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:maxable="maxAble"
|
||||
:title="'成员管理'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
:footer="null"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="userId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
v-model:selection="selection"
|
||||
:where="defaultWhere"
|
||||
cache-key="proSystemUserTable"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<UserMerchantClerk @select="onSelect" />
|
||||
<a-button type="primary" class="ele-btn-icon" @click="save">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'nickname'">
|
||||
<a-tooltip :title="`ID:${record.userId}`">
|
||||
<a-avatar
|
||||
:size="30"
|
||||
:src="`${record.user?.avatar}`"
|
||||
style="margin-right: 4px"
|
||||
>
|
||||
<template #icon>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
</a-avatar>
|
||||
{{ record.user?.nickname }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'username'">
|
||||
{{ record.user?.username }}
|
||||
</template>
|
||||
<template v-if="column.key === 'phone'">
|
||||
{{ record.user?.phone }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a class="ele-text-danger" @click="remove(record)">移除</a>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject, EleProTable } from 'ele-admin-pro';
|
||||
import type {
|
||||
MerchantClerk,
|
||||
MerchantClerkParam
|
||||
} from '@/api/merchant/clerk/model';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { UserOutlined } from '@ant-design/icons-vue';
|
||||
import {
|
||||
addMerchantClerk,
|
||||
pageMerchantClerks,
|
||||
removeMerchantClerk
|
||||
} from '@/api/merchant/clerk';
|
||||
import UserMerchantClerk from '@/components/UserChoose/index.vue';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: MerchantClerk | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '姓名',
|
||||
key: 'nickname',
|
||||
dataIndex: 'nickname'
|
||||
},
|
||||
{
|
||||
title: '登录账号',
|
||||
key: 'username',
|
||||
dataIndex: 'username'
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
key: 'phone',
|
||||
dataIndex: 'phone'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 默认搜索条件
|
||||
const defaultWhere = reactive({
|
||||
username: '',
|
||||
nickname: ''
|
||||
});
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
where.merchantCode = props.data?.merchantCode;
|
||||
return pageMerchantClerks({ ...where, ...orders, page, limit });
|
||||
};
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
/* 搜索 */
|
||||
const reload = (where?: MerchantClerkParam) => {
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
// 用户信息
|
||||
const clerk = reactive<MerchantClerk>({
|
||||
clerkId: undefined,
|
||||
// 商户名称
|
||||
merchantName: '',
|
||||
// 商户编号
|
||||
merchantCode: '',
|
||||
// 店员真实姓名
|
||||
realName: '',
|
||||
// 用户id
|
||||
userId: undefined,
|
||||
// 昵称
|
||||
nickname: '',
|
||||
// 登录账号
|
||||
username: '',
|
||||
// 创建时间
|
||||
createTime: ''
|
||||
});
|
||||
|
||||
// 请求状态
|
||||
const loading = ref(true);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxAble = ref(true);
|
||||
// 选项卡位置
|
||||
const { resetFields } = useForm(clerk);
|
||||
// 选中用户
|
||||
const userId = ref(undefined);
|
||||
|
||||
const onSelect = (user) => {
|
||||
userId.value = user.userId;
|
||||
};
|
||||
|
||||
// 添加门店成员
|
||||
const save = () => {
|
||||
const data = {
|
||||
merchantCode: props.data?.merchantCode,
|
||||
userId: userId.value
|
||||
};
|
||||
addMerchantClerk(data)
|
||||
.then((msg) => {
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: MerchantClerk) => {
|
||||
console.log(row);
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeMerchantClerk(row.clerkId)
|
||||
.then(() => {
|
||||
hide();
|
||||
message.success('移除成功');
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
loading.value = false;
|
||||
assignObject(clerk, props.data);
|
||||
reload();
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="less">
|
||||
.tab-pane {
|
||||
min-height: 100px;
|
||||
}
|
||||
.important {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
369
src/views/system/payment/components/edit.vue
Normal file
369
src/views/system/payment/components/edit.vue
Normal file
@@ -0,0 +1,369 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:maxable="maxAble"
|
||||
:title="isUpdate ? '编辑' : '添加'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
:maskClosable="false"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="{ md: { span: 8 }, sm: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 24 }, sm: { span: 24 } }"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item label="支付方式" name="method">
|
||||
<DictSelect
|
||||
dict-code="payMethod"
|
||||
v-model:value="form.method"
|
||||
placeholder="选择支付方式"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- 支付宝支付开始 -->
|
||||
<a-form-item
|
||||
label="支付宝应用"
|
||||
name="appId"
|
||||
v-if="form.method === '支付宝'"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="支付宝分配给开发者的应用ID"
|
||||
v-model:value="form.appId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="签名算法 (signType)"
|
||||
name="signType"
|
||||
v-if="form.method === '支付宝'"
|
||||
>
|
||||
<a-radio-group v-model:value="form.signType">
|
||||
<a-radio value="RSA2">RSA2</a-radio>
|
||||
<a-radio value="RSA">RSA</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="加签模式"
|
||||
name="signMode"
|
||||
v-if="form.method === '支付宝'"
|
||||
>
|
||||
<a-radio-group v-model:value="form.signMode">
|
||||
<a-radio value="公钥证书">
|
||||
<text>公钥证书</text>
|
||||
<a-tag class="ml-5" color="green">推荐</a-tag>
|
||||
</a-radio>
|
||||
<a-radio value="公钥">公钥</a-radio>
|
||||
</a-radio-group>
|
||||
<div class="form-item-help">
|
||||
<small>如需使用资金支出类的接口,则必须使用公钥证书模式,</small>
|
||||
<small>配置指南 https://opendocs.alipay.com/open/200/105310</small>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="应用公钥证书"
|
||||
name="appCertPublicKey"
|
||||
v-if="form.method === '支付宝' && form.signMode === '公钥证书'"
|
||||
>
|
||||
<Upload accept=".crt" v-model:value="form.appCertPublicKey" />
|
||||
<div class="form-item-help">
|
||||
<small>请上传 "appCertPublicKey.crt" 文件</small>
|
||||
</div>
|
||||
{{ form.appCertPublicKey }}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="支付宝公钥证书"
|
||||
name="alipayCertPublicKey"
|
||||
v-if="form.method === '支付宝' && form.signMode === '公钥证书'"
|
||||
>
|
||||
<Upload accept=".crt" v-model:value="form.alipayCertPublicKey" />
|
||||
<div class="form-item-help">
|
||||
<small>请上传 "alipayCertPublicKey_RSA2.crt" 文件</small>
|
||||
</div>
|
||||
{{ form.alipayCertPublicKey }}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="支付宝根证书"
|
||||
name="alipayRootCert"
|
||||
v-if="form.method === '支付宝' && form.signMode === '公钥证书'"
|
||||
>
|
||||
<Upload accept=".crt" v-model:value="form.alipayRootCert" />
|
||||
<div class="form-item-help">
|
||||
<small>请上传 "alipayRootCert.crt" 文件</small>
|
||||
</div>
|
||||
{{ form.alipayRootCert }}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="支付宝公钥"
|
||||
name="alipayPublicKey"
|
||||
v-if="form.method === '支付宝' && form.signMode === '公钥'"
|
||||
>
|
||||
<a-textarea
|
||||
:rows="6"
|
||||
placeholder="请输入alipayPublicKey"
|
||||
v-model:value="form.alipayPublicKey"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="应用私钥"
|
||||
name="privateKey"
|
||||
v-if="form.method === '支付宝'"
|
||||
>
|
||||
<a-textarea
|
||||
:rows="6"
|
||||
placeholder="请输入privateKey"
|
||||
v-model:value="form.privateKey"
|
||||
/>
|
||||
<div class="form-item-help">
|
||||
<small
|
||||
>查看 "应用私钥_RSA2_PKCS8.txt" 文件,将全部内容复制到此处</small
|
||||
>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<!-- 支付宝支付结束 -->
|
||||
<a-form-item label="排序号" name="sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="99999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号(数字越小越靠前)"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { addPayment, updatePayment } from '@/api/system/payment';
|
||||
import { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { Payment } from '@/api/system/payment/model';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import Upload from '@/components/UploadFile/index.vue';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxAble = ref(true);
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Payment | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 已上传数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields } = useFormData<Payment>({
|
||||
paymentId: undefined,
|
||||
method: undefined,
|
||||
appId: '',
|
||||
icon: '',
|
||||
signType: 'RSA2',
|
||||
signMode: '公钥证书',
|
||||
// 公钥模式
|
||||
alipayPublicKey: '',
|
||||
privateKey: '',
|
||||
// 公钥证书模式
|
||||
appCertPublicKey: '',
|
||||
alipayCertPublicKey: '',
|
||||
alipayRootCert: '',
|
||||
// json数据
|
||||
config: undefined,
|
||||
createTime: '',
|
||||
comments: '',
|
||||
sortNumber: 100,
|
||||
status: 0
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
method: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入支付方式名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
signType: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写签名算法',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
appCertPublicKey: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请上传应用公钥证书',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
alipayCertPublicKey: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请上传支付宝公钥证书',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
alipayRootCert: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请上传支付宝根证书',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
signMode: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请选择加签模式',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
comments: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写支付方式简介',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
status: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请选择支付方式状态',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sortNumber: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请输入排序号',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
appId: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写应用ID',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
alipayPublicKey: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写支付宝公钥',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
privateKey: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写应用私钥',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const merchantForm = {
|
||||
...form,
|
||||
config: JSON.stringify(form)
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updatePayment : addPayment;
|
||||
saveOrUpdate(merchantForm)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
loading.value = false;
|
||||
form.config = undefined;
|
||||
if (props.data.config) {
|
||||
const config = JSON.parse(String(props.data.config));
|
||||
config.paymentId = props.data.paymentId;
|
||||
assignObject(form, config);
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="less">
|
||||
.tab-pane {
|
||||
min-height: 300px;
|
||||
}
|
||||
.ml-10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.ml-5 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.form-item-help {
|
||||
color: #c5c5c5;
|
||||
}
|
||||
.flex-sb {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
101
src/views/system/payment/components/search.vue
Normal file
101
src/views/system/payment/components/search.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
v-permission="'shop:payment:save'"
|
||||
@click="add"
|
||||
>
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import type { MerchantParam } from '@/api/merchant/model';
|
||||
// import CustomerSelect from '@/components/CustomerSelect/index.vue';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: MerchantParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 勾选的项目
|
||||
selection?: [];
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { where, resetFields } = useSearch<MerchantParam>({
|
||||
merchantId: undefined,
|
||||
merchantName: '',
|
||||
merchantCode: ''
|
||||
});
|
||||
|
||||
// 下来选项
|
||||
const type = ref('merchantName');
|
||||
// tabType
|
||||
const listType = ref('all');
|
||||
// 搜索内容
|
||||
const searchText = ref('');
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
if (type.value == 'merchantName') {
|
||||
where.merchantName = searchText.value;
|
||||
where.merchantCode = undefined;
|
||||
}
|
||||
if (type.value == 'merchantCode') {
|
||||
where.merchantCode = searchText.value;
|
||||
where.merchantName = undefined;
|
||||
}
|
||||
emit('search', where);
|
||||
};
|
||||
|
||||
// const handleTabs = (e) => {
|
||||
// listType.value = e.target.value;
|
||||
// if (listType.value == 'all') {
|
||||
// resetFields();
|
||||
// }
|
||||
// if (listType.value == 'onSale') {
|
||||
// where.status = 0;
|
||||
// }
|
||||
// if (listType.value == 'offSale') {
|
||||
// where.status = 1;
|
||||
// }
|
||||
// if (listType.value == 'soldOut') {
|
||||
// where.stockTotal = 0;
|
||||
// }
|
||||
// emit('search', where);
|
||||
// };
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
// 批量删除
|
||||
// const removeBatch = () => {
|
||||
// emit('remove');
|
||||
// };
|
||||
|
||||
/* 重置 */
|
||||
// const reset = () => {
|
||||
// resetFields();
|
||||
// search();
|
||||
// };
|
||||
|
||||
// 监听字典id变化
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
284
src/views/system/payment/index.vue
Normal file
284
src/views/system/payment/index.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<a-page-header :ghost="false" title="支付管理">
|
||||
<!-- <div class="ele-text-secondary"> 商户管理 </div>-->
|
||||
</a-page-header>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false">
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="paymentId"
|
||||
:columns="columns"
|
||||
:height="tableHeight"
|
||||
:datasource="datasource"
|
||||
v-model:selection="selection"
|
||||
:scroll="{ x: 800 }"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
:selection="selection"
|
||||
@search="reload"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'logo'">
|
||||
<a-image :src="record.logo" :preview="false" :width="80" />
|
||||
</template>
|
||||
<template v-if="column.key === 'paymentName'">
|
||||
<a-tooltip
|
||||
:title="`商户编号:${record.paymentCode}`"
|
||||
placement="topLeft"
|
||||
>
|
||||
<p class="twoline-hide">
|
||||
{{ record.paymentName }}
|
||||
</p>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">营业中</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="orange">停业中</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'comments'">
|
||||
<a-tooltip :title="record.comments" placement="topLeft">
|
||||
{{ record.comments }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'createTime'">
|
||||
<a-tooltip :title="`${toDateString(record.createTime)}`">
|
||||
{{ timeAgo(record.createTime) }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="onPay(record)">支付测试</a>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="openEdit(record)">编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<Edit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
<!-- 账号管理 -->
|
||||
<payment-user v-model:visible="showUser" :data="current" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!--suppress TypeScriptValidateTypes -->
|
||||
<script lang="ts" setup>
|
||||
import { timeAgo } from 'ele-admin-pro';
|
||||
import { createVNode, computed, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import Search from './components/search.vue';
|
||||
import Edit from './components/edit.vue';
|
||||
import PaymentUser from './components/clerk.vue';
|
||||
import { Category } from '@/api/goods/category/model';
|
||||
import {
|
||||
pagePayment,
|
||||
removePayment,
|
||||
removeBatchPayment,
|
||||
alipay
|
||||
} from '@/api/system/payment';
|
||||
import type { Payment, PaymentParam } from '@/api/system/payment/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 当前用户信息
|
||||
// const brand = getDictionaryOptions('serverBrand');
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'method'
|
||||
},
|
||||
// {
|
||||
// title: '支付方式',
|
||||
// dataIndex: 'paymentCode'
|
||||
// },
|
||||
// {
|
||||
// title: '支付模板',
|
||||
// dataIndex: 'templateId',
|
||||
// key: 'templateId'
|
||||
// },
|
||||
// {
|
||||
// title: '默认',
|
||||
// dataIndex: 'isDefault',
|
||||
// key: 'isDefault'
|
||||
// },
|
||||
// {
|
||||
// title: '状态',
|
||||
// dataIndex: 'status',
|
||||
// sorter: true,
|
||||
// key: 'status',
|
||||
// showSorterTooltip: false,
|
||||
// customRender: ({ text }) => ['上架', '下架'][text]
|
||||
// },
|
||||
// {
|
||||
// title: '排序',
|
||||
// dataIndex: 'sortNumber',
|
||||
// hideInTable: true,
|
||||
// sorter: true
|
||||
// },
|
||||
// {
|
||||
// title: '创建时间',
|
||||
// dataIndex: 'createTime',
|
||||
// key: 'createTime',
|
||||
// sorter: true,
|
||||
// ellipsis: true,
|
||||
// hideInTable: true,
|
||||
// customRender: ({ text }) => toDateString(text)
|
||||
// },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<Payment[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<Payment | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
const showUser = ref(false);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.brand = filters.brand;
|
||||
}
|
||||
return pagePayment({ ...where, ...orders, page, limit });
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: PaymentParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
// 搜索是否展开
|
||||
const searchExpand = ref(false);
|
||||
|
||||
// 表格固定高度
|
||||
const fixedHeight = ref(false);
|
||||
|
||||
// 表格高度
|
||||
const tableHeight = computed(() => {
|
||||
return fixedHeight.value
|
||||
? searchExpand.value
|
||||
? 'calc(100vh - 618px)'
|
||||
: 'calc(100vh - 562px)'
|
||||
: void 0;
|
||||
});
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Payment) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onPay = (row: PaymentParam) => {
|
||||
alipay(row).then((res) => {
|
||||
console.log(res, '>>>>>00');
|
||||
});
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: Payment) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removePayment(row.paymentId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchPayment(selection.value.map((d) => d.paymentId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'PaymentIndex'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// 文字超出隐藏(两行)
|
||||
// 需要设置文字容器的宽度
|
||||
.twoline-hide {
|
||||
width: 320px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
186
src/views/system/plug/components/clone.vue
Normal file
186
src/views/system/plug/components/clone.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="540"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="`菜单克隆`"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 5, sm: 4, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="选择模板" name="tenantId">
|
||||
<a-input
|
||||
placeholder="请输入要克隆的租户ID"
|
||||
v-model:value="form.tenantId"
|
||||
@pressEnter="save"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { isExternalLink } from 'ele-admin-pro/es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { clone } from '@/api/system/menu';
|
||||
import type { Menu } from '@/api/system/menu/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Menu | null;
|
||||
// 上级菜单id
|
||||
parentId?: number;
|
||||
// 全部菜单数据
|
||||
menuList: Menu[];
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Menu>({
|
||||
title: '',
|
||||
icon: '',
|
||||
path: '',
|
||||
component: '',
|
||||
tenantId: undefined
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
tenantId: [
|
||||
{
|
||||
required: true,
|
||||
message: '克隆后原有的菜单将会抹除,请慎用!',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const menuForm = {
|
||||
...form
|
||||
};
|
||||
clone(menuForm)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
/* 判断是否是目录 */
|
||||
const isDirectory = (d: Menu) => {
|
||||
return !!d.children?.length && !d.component;
|
||||
};
|
||||
|
||||
// 查询租户列表
|
||||
// const tenantList = ref<Tenant[]>([]);
|
||||
// const reload = (tenantName?: any) => {
|
||||
// listTenant({ tenantName }).then((result) => {
|
||||
// tenantList.value = result;
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// reload();
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
const isExternal = isExternalLink(props.data.path);
|
||||
const isInner = isExternalLink(props.data.component);
|
||||
// menuType 对应的值与后端不一致在前端处理
|
||||
const menuType =
|
||||
props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1;
|
||||
assignFields({
|
||||
...props.data,
|
||||
menuType,
|
||||
openType: isExternal ? 2 : isInner ? 1 : 0,
|
||||
parentId:
|
||||
props.data.parentId === 0 ? undefined : props.data.parentId
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
form.parentId = props.parentId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as icons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
components: icons,
|
||||
data() {
|
||||
return {
|
||||
iconData: [
|
||||
{
|
||||
title: '已引入的图标',
|
||||
icons: Object.keys(icons)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
416
src/views/system/plug/components/menu-edit.vue
Normal file
416
src/views/system/plug/components/menu-edit.vue
Normal file
@@ -0,0 +1,416 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="740"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '修改菜单' : '新建菜单'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 6, sm: 4, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="上级菜单" name="parentId">
|
||||
<a-tree-select
|
||||
allow-clear
|
||||
:tree-data="menuList"
|
||||
tree-default-expand-all
|
||||
placeholder="请选择上级菜单"
|
||||
:value="form.parentId || undefined"
|
||||
:dropdown-style="{ maxHeight: '360px', overflow: 'auto' }"
|
||||
@update:value="(value?: number) => (form.parentId = value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="菜单名称" name="title">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入菜单名称"
|
||||
v-model:value="form.title"
|
||||
@pressEnter="save"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="菜单类型" name="menuType">
|
||||
<a-radio-group
|
||||
v-model:value="form.menuType"
|
||||
@change="onMenuTypeChange"
|
||||
>
|
||||
<a-radio :value="0">目录</a-radio>
|
||||
<a-radio :value="1">菜单</a-radio>
|
||||
<a-radio :value="2">按钮</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="打开方式">
|
||||
<a-radio-group
|
||||
v-model:value="form.openType"
|
||||
:disabled="form.menuType === 0 || form.menuType === 2"
|
||||
@change="onOpenTypeChange"
|
||||
>
|
||||
<a-radio :value="0">组件</a-radio>
|
||||
<a-radio :value="1">内链</a-radio>
|
||||
<a-radio :value="2">外链</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div style="margin-bottom: 22px">
|
||||
<a-divider />
|
||||
</div>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="菜单图标" name="icon">
|
||||
<ele-icon-picker
|
||||
:data="iconData"
|
||||
:allow-search="false"
|
||||
v-model:value="form.icon"
|
||||
placeholder="请选择菜单图标"
|
||||
:disabled="form.menuType === 2"
|
||||
>
|
||||
<template #icon="{ icon }">
|
||||
<component :is="icon" />
|
||||
</template>
|
||||
</ele-icon-picker>
|
||||
</a-form-item>
|
||||
<a-form-item name="path">
|
||||
<template #label>
|
||||
<a-tooltip
|
||||
v-if="form.openType === 2"
|
||||
title="需要以`http://`、`https://`、`//`开头"
|
||||
>
|
||||
<question-circle-outlined
|
||||
style="vertical-align: -2px; margin-right: 4px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<span>{{ form.openType === 2 ? '外链地址' : '路由地址' }}</span>
|
||||
</template>
|
||||
<a-input
|
||||
allow-clear
|
||||
v-model:value="form.path"
|
||||
:disabled="form.menuType === 2"
|
||||
:placeholder="
|
||||
form.openType === 2 ? '请输入外链地址' : '请输入路由地址'
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item name="component">
|
||||
<template #label>
|
||||
<a-tooltip
|
||||
v-if="form.openType === 1"
|
||||
title="需要以`http://`、`https://`、`//`开头"
|
||||
>
|
||||
<question-circle-outlined
|
||||
style="vertical-align: -2px; margin-right: 4px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<span>{{ form.openType === 1 ? '内链地址' : '组件路径' }}</span>
|
||||
</template>
|
||||
<a-input
|
||||
allow-clear
|
||||
v-model:value="form.component"
|
||||
:disabled="
|
||||
form.menuType === 0 ||
|
||||
form.menuType === 2 ||
|
||||
form.openType === 2
|
||||
"
|
||||
:placeholder="
|
||||
form.openType === 1 ? '请输入内链地址' : '请输入组件路径'
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="权限标识" name="authority">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入权限标识"
|
||||
v-model:value="form.authority"
|
||||
:disabled="
|
||||
form.menuType === 0 ||
|
||||
(form.menuType === 1 && form.openType === 2)
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" name="sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="99999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
@pressEnter="save"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否展示">
|
||||
<a-switch
|
||||
checked-children="是"
|
||||
un-checked-children="否"
|
||||
:checked="form.hide === 0"
|
||||
:disabled="form.menuType === 2"
|
||||
@update:checked="updateHideValue"
|
||||
/>
|
||||
<a-tooltip
|
||||
title="选择不展示只注册路由不展示在侧边栏, 比如添加页面应该选择不展示"
|
||||
>
|
||||
<question-circle-outlined
|
||||
style="vertical-align: -4px; margin-left: 16px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item
|
||||
label="路由元数据"
|
||||
name="meta"
|
||||
:label-col="
|
||||
styleResponsive ? { md: 3, sm: 4, xs: 24 } : { flex: '90px' }
|
||||
"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 21, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入JSON格式的路由元数据"
|
||||
v-model:value="form.meta"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { isExternalLink } from 'ele-admin-pro/es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addMenu, updateMenu } from '@/api/system/menu';
|
||||
import type { Menu } from '@/api/system/menu/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Menu | null;
|
||||
// 上级菜单id
|
||||
parentId?: number;
|
||||
// 全部菜单数据
|
||||
menuList: Menu[];
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Menu>({
|
||||
menuId: undefined,
|
||||
parentId: undefined,
|
||||
title: '',
|
||||
menuType: 0,
|
||||
openType: 0,
|
||||
icon: '',
|
||||
path: '',
|
||||
component: '',
|
||||
authority: '',
|
||||
sortNumber: undefined,
|
||||
hide: 0,
|
||||
meta: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入菜单名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sortNumber: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入排序号',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
meta: [
|
||||
{
|
||||
type: 'string',
|
||||
validator: async (_rule: Rule, value: string) => {
|
||||
if (value) {
|
||||
const msg = '请输入正确的JSON格式';
|
||||
try {
|
||||
const obj = JSON.parse(value);
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return Promise.reject(msg);
|
||||
}
|
||||
} catch (_e) {
|
||||
return Promise.reject(msg);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const menuForm = {
|
||||
...form,
|
||||
// menuType 对应的值与后端不一致在前端处理
|
||||
menuType: form.menuType === 2 ? 1 : 0,
|
||||
parentId: form.parentId || 0
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateMenu : addMenu;
|
||||
saveOrUpdate(menuForm)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
/* menuType选择改变 */
|
||||
const onMenuTypeChange = () => {
|
||||
if (form.menuType === 0) {
|
||||
form.authority = '';
|
||||
form.openType = 0;
|
||||
form.component = '';
|
||||
} else if (form.menuType === 1) {
|
||||
if (form.openType === 2) {
|
||||
form.authority = '';
|
||||
}
|
||||
} else {
|
||||
form.openType = 0;
|
||||
form.icon = '';
|
||||
form.path = '';
|
||||
form.component = '';
|
||||
form.hide = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/* openType选择改变 */
|
||||
const onOpenTypeChange = () => {
|
||||
if (form.openType === 2) {
|
||||
form.component = '';
|
||||
form.authority = '';
|
||||
}
|
||||
};
|
||||
|
||||
const updateHideValue = (value: boolean) => {
|
||||
form.hide = value ? 0 : 1;
|
||||
};
|
||||
|
||||
/* 判断是否是目录 */
|
||||
const isDirectory = (d: Menu) => {
|
||||
return !!d.children?.length && !d.component;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
const isExternal = isExternalLink(props.data.path);
|
||||
const isInner = isExternalLink(props.data.component);
|
||||
// menuType 对应的值与后端不一致在前端处理
|
||||
const menuType =
|
||||
props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1;
|
||||
assignFields({
|
||||
...props.data,
|
||||
menuType,
|
||||
openType: isExternal ? 2 : isInner ? 1 : 0,
|
||||
parentId:
|
||||
props.data.parentId === 0 ? undefined : props.data.parentId
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
form.parentId = props.parentId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as icons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
components: icons,
|
||||
data() {
|
||||
return {
|
||||
iconData: [
|
||||
{
|
||||
title: '已引入的图标',
|
||||
icons: Object.keys(icons)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
106
src/views/system/plug/components/menu-search.vue
Normal file
106
src/views/system/plug/components/menu-search.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-form
|
||||
:label-col="
|
||||
styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' }
|
||||
"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-row :gutter="8">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="菜单名称">
|
||||
<a-input
|
||||
v-model:value.trim="form.title"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="菜单地址">
|
||||
<a-input
|
||||
v-model:value.trim="form.path"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="权限标识">
|
||||
<a-input
|
||||
v-model:value.trim="form.authority"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type { MenuParam } from '@/api/system/menu/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: MenuParam): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields } = useFormData<MenuParam>({
|
||||
title: '',
|
||||
path: '',
|
||||
authority: ''
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', form);
|
||||
};
|
||||
|
||||
/* 重置 */
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
search();
|
||||
};
|
||||
</script>
|
||||
253
src/views/system/plug/components/plug-edit.vue
Normal file
253
src/views/system/plug/components/plug-edit.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="780"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
title="安装插件"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
okText="安装插件"
|
||||
:footer="footer"
|
||||
@ok="save"
|
||||
>
|
||||
<template v-if="!isSuccess">
|
||||
<a-alert
|
||||
:description="`安装成功后,插件出现在左侧菜单中,请在 【系统设置->菜单管理】修改插件的名称及顺序`"
|
||||
type="success"
|
||||
closable
|
||||
show-icon
|
||||
style="margin-bottom: 20px"
|
||||
>
|
||||
<template #icon><SmileOutlined /></template>
|
||||
</a-alert>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="
|
||||
styleResponsive ? { md: 6, sm: 4, xs: 24 } : { flex: '90px' }
|
||||
"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="插件名称" name="parentId">
|
||||
<span class="ele-text-heading">{{ data.title }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="价格" name="comments">
|
||||
¥{{ data.price }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="插件ID" name="menuId">
|
||||
<span class="ele-text-secondary">{{ data.menuId }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="开发商" name="sortName">
|
||||
<span class="ele-text-secondary">
|
||||
{{ data.tenantId === 5 ? '官方' : data.shortName }}
|
||||
</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 24 }"
|
||||
>
|
||||
<a-form-item label="插件介绍" name="comments">
|
||||
<div class="ele-text-secondary" style="padding: 5px">
|
||||
<byte-md-viewer :value="data.comments" :plugins="plugins" />
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
<a-result
|
||||
status="success"
|
||||
title="安装成功"
|
||||
v-if="isSuccess"
|
||||
sub-title="请在 【系统设置->菜单管理】修改插件的名称及顺序"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button key="console" type="primary" @click="reset(data.path)"
|
||||
>立即前往</a-button
|
||||
>
|
||||
</template>
|
||||
</a-result>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { isExternalLink } from 'ele-admin-pro/es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type { Menu } from '@/api/system/menu/model';
|
||||
import { Plug } from '@/api/system/plug/model';
|
||||
import { SmileOutlined } from '@ant-design/icons-vue';
|
||||
import gfm from '@bytemd/plugin-gfm';
|
||||
import highlight from '@bytemd/plugin-highlight-ssr';
|
||||
// // 插件的中文语言文件
|
||||
import zh_HansGfm from '@bytemd/plugin-gfm/locales/zh_Hans.json';
|
||||
import { installPlug } from '@/api/system/menu';
|
||||
import { reloadPageTab } from '@/utils/page-tab-util';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const { push } = useRouter();
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Plug | null;
|
||||
// 上级插件id
|
||||
parentId?: number;
|
||||
// 全部插件数据
|
||||
menuList: Plug[];
|
||||
}>();
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
const appid = ref(undefined);
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const isSuccess = ref(false);
|
||||
const footer = ref();
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Plug>({
|
||||
plugId: undefined,
|
||||
menuId: undefined,
|
||||
parentId: undefined,
|
||||
title: '',
|
||||
menuType: 0,
|
||||
openType: 0,
|
||||
icon: '',
|
||||
path: '',
|
||||
component: '',
|
||||
authority: '',
|
||||
comments: '',
|
||||
sortNumber: undefined,
|
||||
hide: 0,
|
||||
meta: '',
|
||||
status: 10
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const menuForm = {
|
||||
...form,
|
||||
// menuType 对应的值与后端不一致在前端处理
|
||||
menuType: form.menuType === 2 ? 1 : 0,
|
||||
parentId: form.parentId || 0
|
||||
};
|
||||
console.log(menuForm);
|
||||
installPlug(form.menuId)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
isSuccess.value = true;
|
||||
footer.value = null;
|
||||
// message.success(msg);
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 插件
|
||||
const plugins = ref([
|
||||
gfm({
|
||||
locale: zh_HansGfm
|
||||
}),
|
||||
highlight()
|
||||
]);
|
||||
|
||||
const reset = (url) => {
|
||||
console.log(url);
|
||||
push(url);
|
||||
};
|
||||
|
||||
/* 判断是否是目录 */
|
||||
const isDirectory = (d: Menu) => {
|
||||
return !!d.children?.length && !d.component;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
const isExternal = isExternalLink(props.data.path);
|
||||
const isInner = isExternalLink(props.data.component);
|
||||
// menuType 对应的值与后端不一致在前端处理
|
||||
const menuType =
|
||||
props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1;
|
||||
assignFields({
|
||||
...props.data,
|
||||
menuType,
|
||||
openType: isExternal ? 2 : isInner ? 1 : 0,
|
||||
parentId:
|
||||
props.data.parentId === 0 ? undefined : props.data.parentId
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
form.parentId = props.parentId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as icons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
components: icons,
|
||||
data() {
|
||||
return {
|
||||
iconData: [
|
||||
{
|
||||
title: '已引入的图标',
|
||||
icons: Object.keys(icons)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
67
src/views/system/plug/components/plug-search.vue
Normal file
67
src/views/system/plug/components/plug-search.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<!-- <a-radio-group :defaultValue="plugType" @change="handleTabs">-->
|
||||
<!-- <a-radio-button :value="0">插件市场</a-radio-button>-->
|
||||
<!-- <a-radio-button :value="10">我的插件</a-radio-button>-->
|
||||
<!-- </a-radio-group>-->
|
||||
<a-input-search
|
||||
allow-clear
|
||||
placeholder="请输入搜索关键词"
|
||||
v-model:value="searchText"
|
||||
@pressEnter="search"
|
||||
@search="search"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useSearch from '@/utils/use-search';
|
||||
import type { CustomerParam } from '@/api/oa/customer/model';
|
||||
import { ref, watch } from 'vue';
|
||||
import { AppParam } from '@/api/app/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: CustomerParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { where, resetFields } = useSearch<AppParam>({
|
||||
appId: undefined,
|
||||
userId: undefined,
|
||||
keywords: undefined,
|
||||
status: 0
|
||||
});
|
||||
// const plugType = ref<number>(0);
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
resetFields();
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
emit('search', where);
|
||||
};
|
||||
|
||||
// const reload = () => {
|
||||
// // 刷新当前路由
|
||||
// emit('search', where);
|
||||
// };
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
186
src/views/system/plug/create/components/clone.vue
Normal file
186
src/views/system/plug/create/components/clone.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="540"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="`菜单克隆`"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 5, sm: 4, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="选择模板" name="tenantId">
|
||||
<a-input
|
||||
placeholder="请输入要克隆的租户ID"
|
||||
v-model:value="form.tenantId"
|
||||
@pressEnter="save"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { isExternalLink } from 'ele-admin-pro/es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { clone } from '@/api/system/menu';
|
||||
import type { Menu } from '@/api/system/menu/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Menu | null;
|
||||
// 上级菜单id
|
||||
parentId?: number;
|
||||
// 全部菜单数据
|
||||
menuList: Menu[];
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Menu>({
|
||||
title: '',
|
||||
icon: '',
|
||||
path: '',
|
||||
component: '',
|
||||
tenantId: undefined
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
tenantId: [
|
||||
{
|
||||
required: true,
|
||||
message: '克隆后原有的菜单将会抹除,请慎用!',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const menuForm = {
|
||||
...form
|
||||
};
|
||||
clone(menuForm)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
/* 判断是否是目录 */
|
||||
const isDirectory = (d: Menu) => {
|
||||
return !!d.children?.length && !d.component;
|
||||
};
|
||||
|
||||
// 查询租户列表
|
||||
// const tenantList = ref<Tenant[]>([]);
|
||||
// const reload = (tenantName?: any) => {
|
||||
// listTenant({ tenantName }).then((result) => {
|
||||
// tenantList.value = result;
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// reload();
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
const isExternal = isExternalLink(props.data.path);
|
||||
const isInner = isExternalLink(props.data.component);
|
||||
// menuType 对应的值与后端不一致在前端处理
|
||||
const menuType =
|
||||
props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1;
|
||||
assignFields({
|
||||
...props.data,
|
||||
menuType,
|
||||
openType: isExternal ? 2 : isInner ? 1 : 0,
|
||||
parentId:
|
||||
props.data.parentId === 0 ? undefined : props.data.parentId
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
form.parentId = props.parentId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as icons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
components: icons,
|
||||
data() {
|
||||
return {
|
||||
iconData: [
|
||||
{
|
||||
title: '已引入的图标',
|
||||
icons: Object.keys(icons)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
262
src/views/system/plug/create/components/plug-edit.vue
Normal file
262
src/views/system/plug/create/components/plug-edit.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="740"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '插件管理' : '发布插件'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-alert
|
||||
:description="`审核通过后,插件将展示在插件市场,可供其他用户安装和使用后,获取销售分成。`"
|
||||
type="success"
|
||||
closable
|
||||
show-icon
|
||||
style="margin-bottom: 20px"
|
||||
>
|
||||
<template #icon><SmileOutlined /></template>
|
||||
</a-alert>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 6, sm: 4, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="选择模块" name="parentId">
|
||||
<a-tree-select
|
||||
allow-clear
|
||||
:tree-data="menuList"
|
||||
tree-default-expand-all
|
||||
placeholder="请选择上级插件"
|
||||
:value="form.parentId || undefined"
|
||||
:dropdown-style="{ maxHeight: '360px', overflow: 'auto' }"
|
||||
@update:value="(value?: number) => (form.parentId = value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="插件名称" name="title">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入插件名称"
|
||||
v-model:value="form.title"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="关联应用" name="appId">-->
|
||||
<!-- <SelectApp v-model:value="appId" @done="onApp" />-->
|
||||
<!-- </a-form-item>-->
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="插件价格" name="price">
|
||||
<a-input-number
|
||||
allow-clear
|
||||
:min="0"
|
||||
:precision="2"
|
||||
style="width: 200px"
|
||||
placeholder="请输入插件价格"
|
||||
v-model:value="form.price"
|
||||
/>
|
||||
<span class="ml-10">元</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div style="margin-bottom: 22px">
|
||||
<a-divider />
|
||||
</div>
|
||||
<a-form-item
|
||||
label="插件简介"
|
||||
name="comments"
|
||||
:label-col="
|
||||
styleResponsive ? { md: 3, sm: 4, xs: 24 } : { flex: '90px' }
|
||||
"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 21, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入插件简介"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { isExternalLink } from 'ele-admin-pro/es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type { Menu } from '@/api/system/menu/model';
|
||||
import { createPlug, updatePlug } from '@/api/system/plug';
|
||||
import { Plug } from '@/api/system/plug/model';
|
||||
import { SmileOutlined } from '@ant-design/icons-vue';
|
||||
import { addDocs, updateDocs } from '@/api/cms/docs';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Menu | null;
|
||||
// 上级插件id
|
||||
parentId?: number;
|
||||
// 全部插件数据
|
||||
menuList: Menu[];
|
||||
}>();
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Plug>({
|
||||
plugId: undefined,
|
||||
menuId: undefined,
|
||||
parentId: undefined,
|
||||
title: '',
|
||||
price: undefined,
|
||||
comments: '',
|
||||
status: undefined
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
parentId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择模块',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入插件名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form,
|
||||
status: 10
|
||||
// content: content.value
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updatePlug : createPlug;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
/* 判断是否是目录 */
|
||||
const isDirectory = (d: Menu) => {
|
||||
return !!d.children?.length && !d.component;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
const isExternal = isExternalLink(props.data.path);
|
||||
const isInner = isExternalLink(props.data.component);
|
||||
// menuType 对应的值与后端不一致在前端处理
|
||||
const menuType =
|
||||
props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1;
|
||||
assignFields({
|
||||
...props.data,
|
||||
menuType,
|
||||
openType: isExternal ? 2 : isInner ? 1 : 0,
|
||||
parentId:
|
||||
props.data.parentId === 0 ? undefined : props.data.parentId
|
||||
});
|
||||
form.parentId = props.parentId;
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
form.parentId = props.parentId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as icons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
components: icons,
|
||||
data() {
|
||||
return {
|
||||
iconData: [
|
||||
{
|
||||
title: '已引入的图标',
|
||||
icons: Object.keys(icons)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.tab-pane {
|
||||
min-height: 300px;
|
||||
}
|
||||
.ml-10 {
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
98
src/views/system/plug/create/components/plug-search.vue
Normal file
98
src/views/system/plug/create/components/plug-search.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
<span>发布插件</span>
|
||||
</a-button>
|
||||
<!-- <a-input-search-->
|
||||
<!-- allow-clear-->
|
||||
<!-- placeholder="请输入搜索关键词"-->
|
||||
<!-- v-model:value="searchText"-->
|
||||
<!-- @pressEnter="search"-->
|
||||
<!-- @search="search"-->
|
||||
<!-- />-->
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useSearch from '@/utils/use-search';
|
||||
import type { CustomerParam } from '@/api/oa/customer/model';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { AppParam } from '@/api/app/model';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: CustomerParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { where, resetFields } = useSearch<AppParam>({
|
||||
appId: undefined,
|
||||
userId: undefined,
|
||||
keywords: undefined,
|
||||
status: 0
|
||||
});
|
||||
const plugType = ref<number>(0);
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
const userId = ref(0);
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
resetFields();
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
emit('search', where);
|
||||
};
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
// 批量删除
|
||||
const removeBatch = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const handleTabs = (e) => {
|
||||
const index = Number(e.target.value);
|
||||
const userStore = useUserStore();
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
plugType.value = index;
|
||||
if (index > 0) {
|
||||
userId.value = Number(loginUser.value.userId);
|
||||
where.status = undefined;
|
||||
where.userId = Number(loginUser.value.userId);
|
||||
}
|
||||
if (index == 0) {
|
||||
where.userId = undefined;
|
||||
where.status = 20;
|
||||
}
|
||||
search();
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
// 刷新当前路由
|
||||
emit('search', where);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
296
src/views/system/plug/create/index.vue
Normal file
296
src/views/system/plug/create/index.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card title="插件管理" :bordered="false">
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="plugId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:expand-icon-column-index="1"
|
||||
:expanded-row-keys="expandedRowKeys"
|
||||
cache-key="proSystemPlugTable"
|
||||
@done="onDone"
|
||||
@expand="onExpand"
|
||||
>
|
||||
<template #toolbar>
|
||||
<PlugSearch
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'title'">
|
||||
<div class="app-box">
|
||||
<a-image
|
||||
:height="50"
|
||||
:width="50"
|
||||
:preview="false"
|
||||
:src="record.icon"
|
||||
fallback="https://file.wsdns.cn/20230218/550e610d43334dd2a7f66d5b20bd58eb.svg"
|
||||
/>
|
||||
<!-- <component v-if="record.icon" :is="record.icon" />-->
|
||||
<div class="app-info">
|
||||
<a class="ele-text-heading" @click="openEdit(record)">
|
||||
{{ record.title }}
|
||||
</a>
|
||||
<div class="ele-text-placeholder comments">
|
||||
{{ record.comments }}
|
||||
</div>
|
||||
<a-space size="large" class="ele-text-placeholder">
|
||||
<a
|
||||
class="ele-text-placeholder"
|
||||
:href="`${record.domain}`"
|
||||
target="_blank"
|
||||
>
|
||||
{{ record.companyName }}
|
||||
</a>
|
||||
<span>下载: {{ record.clicks ? record.clicks : 0 }}</span>
|
||||
<span>收藏: {{ record.installs ? record.installs : 0 }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'comments'">
|
||||
<span class="ele-text-secondary">
|
||||
{{ record.comments }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'appType'">
|
||||
<span class="ele-text-placeholder" v-if="record.appType === 'web'">
|
||||
网站应用
|
||||
</span>
|
||||
<span
|
||||
class="ele-text-placeholder"
|
||||
v-if="record.appType === 'mp-weixin'"
|
||||
>
|
||||
小程序
|
||||
</span>
|
||||
<span
|
||||
class="ele-text-placeholder"
|
||||
v-if="record.appType === 'h5-weixin'"
|
||||
>
|
||||
公众号
|
||||
</span>
|
||||
<span
|
||||
class="ele-text-placeholder"
|
||||
v-if="record.appType === 'app-plus'"
|
||||
>
|
||||
移动应用
|
||||
</span>
|
||||
<span class="ele-text-placeholder" v-if="record.appType === 'plug'">
|
||||
插件
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'price'">
|
||||
<a class="ele-text-warning">¥{{ record.price }}</a>
|
||||
</template>
|
||||
<template v-if="column.key === 'shortName'">
|
||||
<span class="ele-text-success">
|
||||
{{ record.sortName }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'score'">
|
||||
<a>{{ record.score.toFixed(1) }}</a>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">正常</a-tag>
|
||||
<a-tag v-if="record.status === 10" color="orange">待审核</a-tag>
|
||||
<a-tag v-if="record.status === 20" color="green">已通过</a-tag>
|
||||
<a-tag v-if="record.status === 30" color="red">已驳回</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">管理</a>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<PlugEdit
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:parent-id="parentId"
|
||||
:menu-list="menuData"
|
||||
@done="reload"
|
||||
/>
|
||||
<clone v-model:visible="showClone" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
const { push } = useRouter();
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
// EleProTableDone
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import PlugSearch from './components/plug-search.vue';
|
||||
import { toTreeData, toDateString } from 'ele-admin-pro/es';
|
||||
import type { EleProTable } from 'ele-admin-pro/es';
|
||||
import PlugEdit from './components/plug-edit.vue';
|
||||
import Clone from './components/clone.vue';
|
||||
import { pagePlug } from '@/api/system/plug';
|
||||
import type { Plug, PlugParam } from '@/api/system/plug/model';
|
||||
import { Menu } from '@/api/system/menu/model';
|
||||
import { listMenus } from '@/api/system/menu';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '插件ID',
|
||||
dataIndex: 'menuId',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '插件名称',
|
||||
dataIndex: 'title',
|
||||
key: 'title'
|
||||
},
|
||||
{
|
||||
title: '价格',
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
align: 'center',
|
||||
customRender: ({ text }) => '¥' + text
|
||||
},
|
||||
{
|
||||
title: '评分',
|
||||
dataIndex: 'score',
|
||||
align: 'center',
|
||||
key: 'score'
|
||||
},
|
||||
{
|
||||
title: '审核状态',
|
||||
dataIndex: 'status',
|
||||
align: 'center',
|
||||
key: 'status'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<Plug | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
const showClone = ref(false);
|
||||
// 上级菜单id
|
||||
const parentId = ref<number>();
|
||||
// 菜单数据
|
||||
const menuData = ref<Menu[]>([]);
|
||||
const userStore = useUserStore();
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
|
||||
// 表格展开的行
|
||||
const expandedRowKeys = ref<number[]>([]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ where }) => {
|
||||
where.userId = loginUser.value.userId;
|
||||
return pagePlug({ ...where });
|
||||
};
|
||||
|
||||
/* 表格渲染完成回调 */
|
||||
// const onDone: EleProTableDone<Plug> = ({ data }) => {
|
||||
// menuData.value = data;
|
||||
// };
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = (where?: PlugParam) => {
|
||||
tableRef?.value?.reload({ where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Plug | null, id?: number) => {
|
||||
console.log(row);
|
||||
current.value = row ?? null;
|
||||
parentId.value = row?.menuId;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 一键克隆 */
|
||||
const clonePlug = (row?: Plug | null, id?: number) => {
|
||||
current.value = row ?? null;
|
||||
parentId.value = id;
|
||||
showClone.value = true;
|
||||
};
|
||||
|
||||
const query = () => {
|
||||
listMenus({}).then((res) => {
|
||||
if (res) {
|
||||
menuData.value = parseData(res);
|
||||
}
|
||||
});
|
||||
};
|
||||
/* 数据转为树形结构 */
|
||||
const parseData = (data: Menu[]) => {
|
||||
return toTreeData({
|
||||
data: data
|
||||
.filter((d) => d.menuType == 0)
|
||||
.map((d) => {
|
||||
if (d.parentId != 0) {
|
||||
// d.disabled = true;
|
||||
}
|
||||
return { ...d, key: d.menuId, value: d.menuId };
|
||||
}),
|
||||
idField: 'menuId',
|
||||
parentIdField: 'parentId'
|
||||
});
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as PlugIcons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
name: 'SystemPlug',
|
||||
components: PlugIcons
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-box {
|
||||
display: flex;
|
||||
.app-info {
|
||||
display: flex;
|
||||
margin-left: 6px;
|
||||
margin-right: 6px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.sys-org-table :deep(.ant-table-body) {
|
||||
overflow: auto !important;
|
||||
overflow: overlay !important;
|
||||
}
|
||||
|
||||
.sys-org-table :deep(.ant-table-pagination.ant-pagination) {
|
||||
padding: 0 4px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ele-text-heading {
|
||||
}
|
||||
.comments {
|
||||
width: 420px;
|
||||
padding: 5px 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
276
src/views/system/plug/index.vue
Normal file
276
src/views/system/plug/index.vue
Normal file
@@ -0,0 +1,276 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card title="扩展插件" :bordered="false">
|
||||
<template #extra>
|
||||
<a-button class="ele-btn-icon" @click="push('/system/plug/create')">
|
||||
<span>发布插件</span>
|
||||
</a-button>
|
||||
</template>
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="plugId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:expand-icon-column-index="1"
|
||||
:expanded-row-keys="expandedRowKeys"
|
||||
cache-key="proSystemPlugTable"
|
||||
@done="onDone"
|
||||
@expand="onExpand"
|
||||
>
|
||||
<template #toolbar>
|
||||
<PlugSearch @search="reload" :selection="selection" @add="openEdit" />
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'title'">
|
||||
<div class="app-box">
|
||||
<a-image
|
||||
:height="50"
|
||||
:width="50"
|
||||
:preview="false"
|
||||
:src="record.icon"
|
||||
fallback="https://file.wsdns.cn/20230218/550e610d43334dd2a7f66d5b20bd58eb.svg"
|
||||
/>
|
||||
<!-- <component v-if="record.icon" :is="record.icon" />-->
|
||||
<div class="app-info">
|
||||
<a class="ele-text-heading" @click="openEdit(record)">
|
||||
{{ record.title }}
|
||||
</a>
|
||||
<div class="ele-text-placeholder comments">
|
||||
{{ record.comments }}
|
||||
</div>
|
||||
<a-space :size="50" class="ele-text-placeholder">
|
||||
<span>
|
||||
{{ record.companyName }}
|
||||
</span>
|
||||
<span>赞赏: {{ record.clicks ? record.clicks : 0 }}</span>
|
||||
<span>安装: {{ record.installs ? record.installs : 0 }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'comments'">
|
||||
<span class="ele-text-secondary">
|
||||
{{ record.comments }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'appType'">
|
||||
<span class="ele-text-placeholder" v-if="record.appType === 'web'">
|
||||
网站应用
|
||||
</span>
|
||||
<span
|
||||
class="ele-text-placeholder"
|
||||
v-if="record.appType === 'mp-weixin'"
|
||||
>
|
||||
小程序
|
||||
</span>
|
||||
<span
|
||||
class="ele-text-placeholder"
|
||||
v-if="record.appType === 'h5-weixin'"
|
||||
>
|
||||
公众号
|
||||
</span>
|
||||
<span
|
||||
class="ele-text-placeholder"
|
||||
v-if="record.appType === 'app-plus'"
|
||||
>
|
||||
移动应用
|
||||
</span>
|
||||
<span class="ele-text-placeholder" v-if="record.appType === 'plug'">
|
||||
插件
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'shortName'">
|
||||
<span class="ele-text-placeholder">
|
||||
{{ record.shortName }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'price'">
|
||||
<span class="ele-text-success" v-if="record.price === 0">免费</span>
|
||||
<span class="ele-text-warning" v-else>¥{{ record.price }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'score'">
|
||||
<a>{{ record.score.toFixed(1) }}</a>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">正常</a-tag>
|
||||
<a-tag v-if="record.status === 10" color="orange">待审核</a-tag>
|
||||
<a-tag v-if="record.status === 20" color="green">已通过</a-tag>
|
||||
<a-tag v-if="record.status === 30" color="red">已驳回</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">安装</a>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<PlugEdit
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:parent-id="parentId"
|
||||
:menu-list="menuData"
|
||||
@done="reload"
|
||||
/>
|
||||
<clone v-model:visible="showClone" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
const { push } = useRouter();
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import PlugSearch from './components/plug-search.vue';
|
||||
import { toTreeData } from 'ele-admin-pro/es';
|
||||
import type { EleProTable } from 'ele-admin-pro/es';
|
||||
import PlugEdit from './components/plug-edit.vue';
|
||||
import Clone from './components/clone.vue';
|
||||
import { pagePlug } from '@/api/system/plug';
|
||||
import type { Plug, PlugParam } from '@/api/system/plug/model';
|
||||
import { Menu } from '@/api/system/menu/model';
|
||||
import { listMenus } from '@/api/system/menu';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '插件ID',
|
||||
dataIndex: 'menuId',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '插件名称',
|
||||
dataIndex: 'title',
|
||||
key: 'title'
|
||||
},
|
||||
{
|
||||
title: '价格',
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
align: 'center',
|
||||
customRender: ({ text }) => '¥' + text
|
||||
},
|
||||
{
|
||||
title: '评分',
|
||||
dataIndex: 'score',
|
||||
align: 'center',
|
||||
key: 'score'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<Plug | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
const showClone = ref(false);
|
||||
|
||||
// 上级菜单id
|
||||
const parentId = ref<number>();
|
||||
|
||||
// 菜单数据
|
||||
const menuData = ref<Menu[]>([]);
|
||||
|
||||
// 表格展开的行
|
||||
const expandedRowKeys = ref<number[]>([]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ where }) => {
|
||||
where.status = 20;
|
||||
return pagePlug({ ...where });
|
||||
};
|
||||
|
||||
/* 表格渲染完成回调 */
|
||||
// const onDone: EleProTableDone<Plug> = ({ data }) => {
|
||||
// menuData.value = data;
|
||||
// };
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = (where?: PlugParam) => {
|
||||
tableRef?.value?.reload({ where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Plug | null, id?: number) => {
|
||||
current.value = row ?? null;
|
||||
parentId.value = id;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const query = () => {
|
||||
listMenus({}).then((res) => {
|
||||
if (res) {
|
||||
menuData.value = parseData(res);
|
||||
}
|
||||
});
|
||||
};
|
||||
/* 数据转为树形结构 */
|
||||
const parseData = (data: Menu[]) => {
|
||||
return toTreeData({
|
||||
data: data
|
||||
.filter((d) => d.menuType == 0)
|
||||
.map((d) => {
|
||||
return { ...d, key: d.menuId, value: d.menuId };
|
||||
}),
|
||||
idField: 'menuId',
|
||||
parentIdField: 'parentId'
|
||||
});
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as PlugIcons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
name: 'SystemPlug',
|
||||
components: PlugIcons
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-box {
|
||||
display: flex;
|
||||
.app-info {
|
||||
display: flex;
|
||||
margin-left: 6px;
|
||||
margin-right: 6px;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.sys-org-table :deep(.ant-table-body) {
|
||||
overflow: auto !important;
|
||||
overflow: overlay !important;
|
||||
}
|
||||
|
||||
.sys-org-table :deep(.ant-table-pagination.ant-pagination) {
|
||||
padding: 0 4px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ele-text-heading {
|
||||
}
|
||||
.comments {
|
||||
width: 420px;
|
||||
padding: 5px 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
232
src/views/system/profile/components/field.vue
Normal file
232
src/views/system/profile/components/field.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
width="500px"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="title"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form layout="horizontal">
|
||||
<template v-for="(value, key) in data" :key="key">
|
||||
<template v-if="field === key">
|
||||
<a-form-item :label="title">
|
||||
<template v-if="field === 'city'">
|
||||
<regions-select
|
||||
v-model:value="city"
|
||||
valueField="label"
|
||||
placeholder="请选择省市区"
|
||||
class="ele-fluid"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="field === 'industryParent'">
|
||||
<industry-select
|
||||
v-model:value="industry"
|
||||
valueField="label"
|
||||
placeholder="请选择所属行业"
|
||||
class="ele-fluid"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="field === 'companyType'">
|
||||
<a-select
|
||||
:options="companyType"
|
||||
:value="form.companyType"
|
||||
placeholder="请选择主体类型"
|
||||
@change="onCompanyType"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-input
|
||||
v-model:value="content"
|
||||
@input="onInput"
|
||||
@pressEnter="save"
|
||||
/>
|
||||
</template>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message, SelectProps } from "ant-design-vue";
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import { addCompany, updateCompany } from '@/api/system/company';
|
||||
import { Company } from '@/api/system/company/model';
|
||||
import { addDictData, updateDictData } from '@/api/system/dict-data';
|
||||
import DictSelect from '@/views/search/components/apps/components/dict-select.vue';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
data?: Company | null;
|
||||
// 修改回显的数据
|
||||
title?: string;
|
||||
field?: string | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
const content = ref();
|
||||
const placeholder = ref('请输入修改内容');
|
||||
// 用户信息
|
||||
const form = reactive<Company>({
|
||||
companyId: undefined,
|
||||
shortName: '',
|
||||
companyName: '',
|
||||
companyType: undefined,
|
||||
companyLogo: '',
|
||||
domain: '',
|
||||
phone: '',
|
||||
InvoiceHeader: '',
|
||||
startTime: '',
|
||||
expirationTime: '',
|
||||
version: undefined,
|
||||
members: undefined,
|
||||
departments: undefined,
|
||||
country: '',
|
||||
province: '',
|
||||
city: '',
|
||||
region: '',
|
||||
address: '',
|
||||
comments: '',
|
||||
authentication: undefined,
|
||||
status: undefined,
|
||||
userId: undefined,
|
||||
tenantId: undefined,
|
||||
code: undefined,
|
||||
createTime: '',
|
||||
updateTime: ''
|
||||
});
|
||||
|
||||
// 省市区
|
||||
const city = ref<string[]>([
|
||||
String(form.province),
|
||||
String(form.city),
|
||||
String(form.region)
|
||||
]);
|
||||
|
||||
const industry = ref<string[]>([
|
||||
String(form.industryParent),
|
||||
String(form.industryChild)
|
||||
]);
|
||||
|
||||
const companyType = ref<SelectProps['options']>([
|
||||
{
|
||||
value: '企业',
|
||||
label: '企业'
|
||||
},
|
||||
{
|
||||
value: '政府',
|
||||
label: '政府'
|
||||
},
|
||||
{
|
||||
value: '个人',
|
||||
label: '个人'
|
||||
},
|
||||
{
|
||||
value: '其他组织',
|
||||
label: '其他组织'
|
||||
}
|
||||
]);
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const { resetFields, validate } = useForm(form);
|
||||
|
||||
const onInput = () => {
|
||||
// 对象赋值
|
||||
Object.keys(form).forEach((key) => {
|
||||
if (key === props.field) {
|
||||
form[key] = content.value;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onCompanyType = (e) => {
|
||||
form.companyType = e;
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
form.province = city.value[0];
|
||||
form.city = city.value[1];
|
||||
form.region = city.value[2];
|
||||
form.industryParent = industry.value[0];
|
||||
form.industryChild = industry.value[1];
|
||||
form.authoritative = true;
|
||||
const saveOrUpdate = form.companyId ? updateCompany : addCompany;
|
||||
saveOrUpdate(form)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
message.success('保存成功');
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
loading.value = false;
|
||||
console.log(props.data);
|
||||
city.value[0] = String(props.data.province);
|
||||
city.value[1] = String(props.data.city);
|
||||
city.value[2] = String(props.data.region);
|
||||
industry.value[0] = String(props.data.industryParent);
|
||||
industry.value[1] = String(props.data.industryChild);
|
||||
assignObject(form, props.data);
|
||||
// 对象赋值
|
||||
Object.keys(form).forEach((key) => {
|
||||
if (key === props.field) {
|
||||
console.log(key);
|
||||
console.log(form[key]);
|
||||
content.value = form[key];
|
||||
}
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
|
||||
if (props.field == 'tenantCode') {
|
||||
placeholder.value = '请输入要绑定的主体编号';
|
||||
// content.value = undefined;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="less">
|
||||
.tab-pane {
|
||||
min-height: 300px;
|
||||
}
|
||||
</style>
|
||||
140
src/views/system/profile/components/markdown.vue
Normal file
140
src/views/system/profile/components/markdown.vue
Normal file
@@ -0,0 +1,140 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
width="800px"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="`修改内容`"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<!-- 编辑器 -->
|
||||
<byte-md-editor
|
||||
v-model:value="content"
|
||||
:locale="zh_Hans"
|
||||
:plugins="plugins"
|
||||
uploadImages
|
||||
height="600px"
|
||||
:editorConfig="{ lineNumbers: true }"
|
||||
/>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import { updateApp } from '@/api/app';
|
||||
import { App } from '@/api/app/model';
|
||||
import { reloadPageTab } from '@/utils/page-tab-util';
|
||||
import 'bytemd/dist/index.min.css';
|
||||
import 'github-markdown-css/github-markdown-light.css';
|
||||
// import TinymceEditor from '@/components/TinymceEditor/index.vue';
|
||||
import ByteMdEditor from '@/components/ByteMdEditor/index.vue';
|
||||
import highlight from '@bytemd/plugin-highlight';
|
||||
// 中文语言文件
|
||||
import zh_Hans from 'bytemd/locales/zh_Hans.json';
|
||||
// // 链接、删除线、复选框、表格等的插件
|
||||
import gfm from '@bytemd/plugin-gfm';
|
||||
// // 插件的中文语言文件
|
||||
import zh_HansGfm from '@bytemd/plugin-gfm/locales/zh_Hans.json';
|
||||
// // 预览界面的样式,这里用的 github 的 markdown 主题
|
||||
import 'github-markdown-css/github-markdown-light.css';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
data?: App | null;
|
||||
// 修改回显的数据
|
||||
field?: string | null;
|
||||
content?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 插件
|
||||
const plugins = ref([
|
||||
gfm({
|
||||
locale: zh_HansGfm
|
||||
}),
|
||||
highlight()
|
||||
]);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
const content = ref('');
|
||||
// 用户信息
|
||||
const form = reactive<App>({
|
||||
appId: undefined,
|
||||
content: '',
|
||||
requirement: ''
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const { resetFields, validate } = useForm(form);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
// 判断更新字段
|
||||
form.appId = props.data?.appId;
|
||||
if (props.field === 'content') {
|
||||
form.content = content.value;
|
||||
}
|
||||
if (props.field === 'requirement') {
|
||||
form.requirement = content.value;
|
||||
}
|
||||
updateApp(form)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
setTimeout(() => {
|
||||
reloadPageTab();
|
||||
}, 500);
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
loading.value = false;
|
||||
content.value = String(props.content);
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="less">
|
||||
.tab-pane {
|
||||
min-height: 300px;
|
||||
}
|
||||
</style>
|
||||
45
src/views/system/profile/components/sex-select.vue
Normal file
45
src/views/system/profile/components/sex-select.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<!-- 角色选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
allow-clear
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择性别'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = getDictionaryOptions('sex');
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
350
src/views/system/profile/index.vue
Normal file
350
src/views/system/profile/index.vue
Normal file
@@ -0,0 +1,350 @@
|
||||
<template>
|
||||
<div class="ele-body ele-body-card" style="max-width: 1000px">
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:body-style="{ paddingTop: '0px', minHeight: '800px' }"
|
||||
>
|
||||
<a-tabs v-model:active-key="active" size="large">
|
||||
<a-tab-pane tab="基本信息" key="info">
|
||||
<a-form
|
||||
:label-col="
|
||||
styleResponsive
|
||||
? { lg: 2, md: 6, sm: 4, xs: 24 }
|
||||
: { flex: '100px' }
|
||||
"
|
||||
:wrapper-col="styleResponsive ? { offset: 1 } : { offset: 1 }"
|
||||
style="margin-top: 20px"
|
||||
>
|
||||
<a-form-item labelAlign="right" label="企业logo">
|
||||
<ele-image-upload
|
||||
v-model:value="logo"
|
||||
:accept="'image/*'"
|
||||
:item-style="{ width: '50px', height: '50px' }"
|
||||
:limit="1"
|
||||
@upload="onUpload"
|
||||
@remove="onClose"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="企业简称">
|
||||
<a-space size="middle">
|
||||
<span>{{ form.shortName }}</span>
|
||||
<a @click="onEdit('企业简称', 'shortName', form.shortName)"
|
||||
>修改</a
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="企业全称">
|
||||
<a-space size="middle">
|
||||
<span>{{ form.companyName }}</span>
|
||||
<a-tag color="green" v-if="form.authentication"
|
||||
>已认证</a-tag
|
||||
>
|
||||
<a-tag color="orange" v-else>未认证</a-tag>
|
||||
<a @click="onEdit('企业全称', 'companyName', form.companyName)"
|
||||
>修改</a
|
||||
>
|
||||
</a-space>
|
||||
<!-- <div class="position-right">-->
|
||||
<!-- <a-button v-if="!form.authentication" @click="onEdit('企业全称', 'companyName', form.companyName)">前往认证</a-button>-->
|
||||
<!-- </div>-->
|
||||
</a-form-item>
|
||||
<a-form-item label="主体类型">
|
||||
<a-tag>{{ form.companyType }}</a-tag>
|
||||
<a @click="onEdit('主体类型', 'companyType', form.companyType)">修改</a>
|
||||
</a-form-item>
|
||||
<a-divider style="padding-bottom: 20px" />
|
||||
<a-form-item label="所属地区">
|
||||
<a-space size="middle">
|
||||
<span
|
||||
>{{ form.province }} {{ form.city }} {{ form.region }}</span
|
||||
>
|
||||
<a @click="onEdit('所属地区', 'city', form.city)">修改</a>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="企业地址">
|
||||
<a-space size="middle">
|
||||
<span>{{ form.address }}</span>
|
||||
<a @click="onEdit('企业地址', 'address', form.address)">修改</a>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="联系电话">
|
||||
<a-space size="middle">
|
||||
<span>{{ form.phone }}</span>
|
||||
<a @click="onEdit('联系电话', 'phone', form.phone)">修改</a>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="企业域名">
|
||||
<a-space size="middle">
|
||||
<span>{{ form.domain }}</span>
|
||||
<a @click="onEdit('企业域名', 'domain', form.domain)">修改</a>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-divider style="padding-bottom: 20px" />
|
||||
<a-form-item label="企业成员">
|
||||
<span>{{ form.users }}个成员</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="企业部门">
|
||||
<span>{{ form.departments }}</span>
|
||||
<span>个部门</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="人数上限">
|
||||
<span>{{ form.users }}/{{ form.members }}</span>
|
||||
<a-button type="link" v-if="form.authentication === false">
|
||||
去认证扩容
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item label="存储空间">
|
||||
{{ getFileSize(form.storage) }}/{{
|
||||
getFileSize(form.storageMax)
|
||||
}}
|
||||
</a-form-item>
|
||||
<a-divider style="padding-bottom: 20px" />
|
||||
<a-form-item label="行业类型">
|
||||
<a-space size="middle">
|
||||
<span>{{ form.industryParent }} {{ form.industryChild }}</span>
|
||||
<a @click="onEdit('行业类型', 'industryParent', form.industryParent)"
|
||||
>修改</a
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="应用版本">
|
||||
<span v-if="form.version === 10">体验版(试用期1个月)</span>
|
||||
<span v-if="form.version === 20">授权版</span>
|
||||
<div class="position-right">
|
||||
<a-button>前往升级</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="到期时间">
|
||||
<span>{{ form.expirationTime }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="创建时间">
|
||||
<span>{{ form.createTime }}</span>
|
||||
</a-form-item>
|
||||
<a-divider style="padding-bottom: 20px" />
|
||||
<a-form-item label="tenantId">
|
||||
<span>{{ form.tenantId }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="企业编号">
|
||||
<span>{{ form.tenantCode }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="企业注销"
|
||||
extra="注销后,企业在当前应用的数据将会删除,且不可恢复,请谨慎操作"
|
||||
>
|
||||
<a-button>注销</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
<!-- 头像裁剪弹窗 -->
|
||||
<ele-cropper-modal
|
||||
:src="form.avatar"
|
||||
v-model:visible="visible"
|
||||
:to-blob="true"
|
||||
:options="{ autoCropArea: 1, viewMode: 1, dragMode: 'move' }"
|
||||
@done="onDone"
|
||||
/>
|
||||
<Field
|
||||
v-model:visible="showEdit"
|
||||
:data="form"
|
||||
:title="title"
|
||||
:field="field"
|
||||
:content="content"
|
||||
@done="query"
|
||||
/>
|
||||
<Markdown
|
||||
v-model:visible="showMarkdown"
|
||||
:data="data"
|
||||
:field="field"
|
||||
:content="markdown"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed } from 'vue';
|
||||
import { Form, message } from "ant-design-vue";
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { getCompany, updateCompany } from "@/api/system/company";
|
||||
import Field from './components/field.vue';
|
||||
import Markdown from './components/markdown.vue';
|
||||
import { FILE_SERVER } from '@/config/setting';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import { getFileSize } from '@/utils/common';
|
||||
import { Company } from '@/api/system/company/model';
|
||||
import { ItemType } from "ele-admin-pro/es/ele-image-upload/types";
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
// tab 页选中
|
||||
const active = ref('info');
|
||||
|
||||
// 保存按钮 loading
|
||||
const loading = ref(false);
|
||||
|
||||
// 是否显示裁剪弹窗
|
||||
const visible = ref(false);
|
||||
const logo = ref<any>([]);
|
||||
const field = ref('');
|
||||
const title = ref('');
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
const content = ref('请输入要修改的内容');
|
||||
const markdown = ref('请输入应用介绍');
|
||||
const showMarkdown = ref(false);
|
||||
// 登录用户信息
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<Company>({
|
||||
companyId: undefined,
|
||||
shortName: '',
|
||||
companyName: '',
|
||||
companyType: undefined,
|
||||
companyLogo: '',
|
||||
domain: '',
|
||||
phone: '',
|
||||
InvoiceHeader: '',
|
||||
startTime: '',
|
||||
expirationTime: '',
|
||||
version: undefined,
|
||||
members: undefined,
|
||||
storage: undefined,
|
||||
storageMax: undefined,
|
||||
users: undefined,
|
||||
departments: undefined,
|
||||
country: '',
|
||||
province: '',
|
||||
city: '',
|
||||
region: '',
|
||||
address: '',
|
||||
comments: '',
|
||||
authentication: undefined,
|
||||
industryParent: undefined,
|
||||
industryChild: undefined,
|
||||
status: undefined,
|
||||
user_id: undefined,
|
||||
tenantId: undefined,
|
||||
tenantCode: '',
|
||||
code: undefined,
|
||||
createTime: '',
|
||||
updateTime: ''
|
||||
});
|
||||
// 省市区
|
||||
// const city = ref<string[]>([
|
||||
// String(loginUser.value.province),
|
||||
// String(loginUser.value.city),
|
||||
// String(loginUser.value.region)
|
||||
// ]);
|
||||
|
||||
// 省市区
|
||||
// const industry = ref<string[]>([
|
||||
// String(loginUser.value.province),
|
||||
// String(loginUser.value.city),
|
||||
// String(loginUser.value.region)
|
||||
// ]);
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入昵称',
|
||||
type: 'string'
|
||||
}
|
||||
],
|
||||
phone: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入昵称',
|
||||
type: 'string'
|
||||
}
|
||||
],
|
||||
nickname: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入昵称',
|
||||
type: 'string'
|
||||
}
|
||||
],
|
||||
email: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入邮箱',
|
||||
type: 'string'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const onEdit = (label, name, text) => {
|
||||
title.value = label;
|
||||
field.value = name;
|
||||
content.value = text;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onUpload = (d: ItemType) => {
|
||||
const file = d.file;
|
||||
if(file){
|
||||
if (file.size / 1024 > 100) {
|
||||
message.error('大小不能超过 100k');
|
||||
return;
|
||||
}
|
||||
}
|
||||
uploadFile(<File>d.file)
|
||||
.then((result) => {
|
||||
form.companyLogo = result.url;
|
||||
updateCompany(form).then(res => {
|
||||
message.success('上传成功');
|
||||
})
|
||||
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 打开图片裁剪 */
|
||||
const openCropper = () => {
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
const query = () => {
|
||||
logo.value = [];
|
||||
getCompany().then((response) => {
|
||||
if (response.companyLogo) {
|
||||
logo.value.push({
|
||||
uid: 1,
|
||||
url: response.companyLogo,
|
||||
status: ''
|
||||
});
|
||||
}
|
||||
assignObject(form, response);
|
||||
});
|
||||
};
|
||||
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemProfile'
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.position-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
159
src/views/system/role/components/role-auth.vue
Normal file
159
src/views/system/role/components/role-auth.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<!-- 角色权限分配弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="460"
|
||||
title="分配权限"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-spin :spinning="authLoading">
|
||||
<div style="height: 60vh" class="ele-scrollbar-hover">
|
||||
<a-tree
|
||||
:checkable="true"
|
||||
:show-icon="true"
|
||||
:tree-data="(authData as any)"
|
||||
v-model:expandedKeys="expandKeys"
|
||||
v-model:checkedKeys="checkedKeys"
|
||||
>
|
||||
<template #icon="{ menuIcon }">
|
||||
<component v-if="menuIcon" :is="menuIcon" />
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
</a-spin>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { toTreeData, eachTreeData } from 'ele-admin-pro/es';
|
||||
import { listRoleMenus, updateRoleMenus } from '@/api/system/role';
|
||||
import type { Role } from '@/api/system/role/model';
|
||||
import type { Menu } from '@/api/system/menu/model';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 当前角色数据
|
||||
data?: Role | null;
|
||||
}>();
|
||||
|
||||
// 权限数据
|
||||
const authData = ref<Menu[]>([]);
|
||||
|
||||
// 权限数据请求状态
|
||||
const authLoading = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 角色权限展开的keys
|
||||
const expandKeys = ref<number[]>([]);
|
||||
|
||||
// 角色权限选中的keys
|
||||
const checkedKeys = ref<number[]>([]);
|
||||
|
||||
/* 查询权限数据 */
|
||||
const query = () => {
|
||||
authData.value = [];
|
||||
expandKeys.value = [];
|
||||
checkedKeys.value = [];
|
||||
if (!props.data) {
|
||||
return;
|
||||
}
|
||||
authLoading.value = true;
|
||||
listRoleMenus(props.data.roleId)
|
||||
.then((data) => {
|
||||
authLoading.value = false;
|
||||
// 转成树形结构的数据
|
||||
authData.value = toTreeData({
|
||||
data: data?.map((d) => ({
|
||||
...d,
|
||||
key: d.menuId,
|
||||
icon: undefined,
|
||||
menuIcon: d.icon
|
||||
})),
|
||||
idField: 'menuId',
|
||||
parentIdField: 'parentId',
|
||||
addParentIds: true,
|
||||
parentIds: []
|
||||
});
|
||||
// 全部默认展开以及回显选中的数据
|
||||
nextTick(() => {
|
||||
const eks: number[] = [];
|
||||
const cks: number[] = [];
|
||||
eachTreeData(authData.value, (d) => {
|
||||
if (d.key) {
|
||||
if (d.children?.length) {
|
||||
eks.push(d.key);
|
||||
} else if (d.checked) {
|
||||
cks.push(d.key);
|
||||
}
|
||||
}
|
||||
});
|
||||
expandKeys.value = eks;
|
||||
checkedKeys.value = cks;
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
authLoading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 保存权限分配 */
|
||||
const save = () => {
|
||||
loading.value = true;
|
||||
// 获取选中的id,包含所有半选的父级的id
|
||||
const ids = new Set<number>();
|
||||
eachTreeData(authData.value, (d) => {
|
||||
if (d.key && checkedKeys.value.some((c) => c === d.key)) {
|
||||
ids.add(d.key);
|
||||
if (d.parentIds) {
|
||||
d.parentIds.forEach((id: number) => {
|
||||
ids.add(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
updateRoleMenus(props.data?.roleId, Array.from(ids))
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
query();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as MenuIcons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
components: MenuIcons
|
||||
};
|
||||
</script>
|
||||
158
src/views/system/role/components/role-edit.vue
Normal file
158
src/views/system/role/components/role-edit.vue
Normal file
@@ -0,0 +1,158 @@
|
||||
<!-- 角色编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="460"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '修改角色' : '添加角色'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 5, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="角色名称" name="roleName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入角色名称"
|
||||
v-model:value="form.roleName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="角色标识" name="roleCode">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入角色标识"
|
||||
v-model:value="form.roleCode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入备注"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addRole, updateRole } from '@/api/system/role';
|
||||
import type { Role } from '@/api/system/role/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Role | null;
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Role>({
|
||||
roleId: undefined,
|
||||
roleName: '',
|
||||
roleCode: '',
|
||||
comments: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
roleName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入角色名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
roleCode: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入角色标识',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const saveOrUpdate = isUpdate.value ? updateRole : addRole;
|
||||
saveOrUpdate(form)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
assignFields(props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
106
src/views/system/role/components/role-search.vue
Normal file
106
src/views/system/role/components/role-search.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-form
|
||||
:label-col="
|
||||
styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' }
|
||||
"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-row :gutter="8">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="角色名称">
|
||||
<a-input
|
||||
v-model:value.trim="form.roleName"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="角色标识">
|
||||
<a-input
|
||||
v-model:value.trim="form.roleCode"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="备注">
|
||||
<a-input
|
||||
v-model:value.trim="form.comments"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type { RoleParam } from '@/api/system/role/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: RoleParam): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields } = useFormData<RoleParam>({
|
||||
roleName: '',
|
||||
roleCode: '',
|
||||
comments: ''
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', form);
|
||||
};
|
||||
|
||||
/* 重置 */
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
search();
|
||||
};
|
||||
</script>
|
||||
212
src/views/system/role/index.vue
Normal file
212
src/views/system/role/index.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false">
|
||||
<!-- 搜索表单 -->
|
||||
<role-search @search="reload" />
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="roleId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
v-model:selection="selection"
|
||||
:scroll="{ x: 800 }"
|
||||
cache-key="proSystemRoleTable"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="openEdit()">
|
||||
<template #icon>
|
||||
<plus-outlined />
|
||||
</template>
|
||||
<span>新建</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
danger
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
@click="removeBatch"
|
||||
>
|
||||
<template #icon>
|
||||
<delete-outlined />
|
||||
</template>
|
||||
<span>删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="openAuth(record)">分配权限</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
placement="topRight"
|
||||
title="确定要删除此角色吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<role-edit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
<!-- 权限分配弹窗 -->
|
||||
<role-auth v-model:visible="showAuth" :data="current" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
import {
|
||||
PlusOutlined,
|
||||
DeleteOutlined,
|
||||
ExclamationCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro/es';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { messageLoading, toDateString } from 'ele-admin-pro/es';
|
||||
import RoleSearch from './components/role-search.vue';
|
||||
import RoleEdit from './components/role-edit.vue';
|
||||
import RoleAuth from './components/role-auth.vue';
|
||||
import { pageRoles, removeRole, removeRoles } from '@/api/system/role';
|
||||
import type { Role, RoleParam } from '@/api/system/role/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '角色名称',
|
||||
dataIndex: 'roleName',
|
||||
sorter: true,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '角色标识',
|
||||
dataIndex: 'roleCode',
|
||||
sorter: true,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'comments',
|
||||
sorter: true,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<Role[]>([]);
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<Role | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 是否显示权限分配弹窗
|
||||
const showAuth = ref(false);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
return pageRoles({ ...where, ...orders, page, limit });
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: RoleParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Role) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开权限分配弹窗 */
|
||||
const openAuth = (row?: Role) => {
|
||||
current.value = row ?? null;
|
||||
showAuth.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: Role) => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeRole(row.roleId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的角色吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeRoles(selection.value.map((d) => d.roleId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemRole'
|
||||
};
|
||||
</script>
|
||||
318
src/views/system/setting/components/basic.vue
Normal file
318
src/views/system/setting/components/basic.vue
Normal file
@@ -0,0 +1,318 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 3, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 9, sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item label="站点名称" name="siteName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="30"
|
||||
placeholder="请输入站点名称"
|
||||
v-model:value="form.siteName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="站点描述" name="remarks">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入站点描述"
|
||||
v-model:value="form.remarks"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="SEO" name="keyword">
|
||||
<a-select
|
||||
v-model:value="keyword"
|
||||
mode="tags"
|
||||
style="width: 100%"
|
||||
:token-separators="[',']"
|
||||
placeholder="请输入网站关键字"
|
||||
:options="options"
|
||||
@change="handleKeyword"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="ICP备案" name="icp">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="18"
|
||||
placeholder="请输入ICP备案号"
|
||||
v-model:value="form.icp"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="版权信息" name="copyright">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
placeholder="请输入版权信息"
|
||||
v-model:value="form.copyright"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="公司名称" name="company">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="30"
|
||||
placeholder="请输入公司名称"
|
||||
v-model:value="form.company"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="办公地址" name="address">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
placeholder="请输入公司办公地址"
|
||||
v-model:value="form.address"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="服务热线" name="phone">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
placeholder="请输入服务热线电话"
|
||||
v-model:value="form.phone"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="电子邮箱" name="email">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
placeholder="请输入电子邮箱"
|
||||
v-model:value="form.email"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="网站域名" name="domain">
|
||||
<a-input v-model:value="form.domain" placeholder="domain.com">
|
||||
<template #addonBefore>
|
||||
<a-select v-model:value="prefix" style="width: 90px">
|
||||
<a-select-option value="http://">http</a-select-option>
|
||||
<a-select-option value="https://">https</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="技术支持" name="support">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
placeholder="请输入技术支持"
|
||||
v-model:value="form.support"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="ico文件" name="logo">
|
||||
<ele-image-upload
|
||||
v-model:value="logo"
|
||||
:accept="'image/x-icon,image/svg'"
|
||||
:item-style="{ width: '40px', height: '40px' }"
|
||||
:limit="1"
|
||||
@upload="onUpload"
|
||||
@remove="onClose"
|
||||
/>
|
||||
<a-space direction="vertical">
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
style="margin-top: 10px;"
|
||||
@click="save"
|
||||
>
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Setting } from '@/api/system/setting/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addSetting, listSetting, updateSetting } from "@/api/system/setting";
|
||||
import { ItemType } from "ele-admin-pro/es/ele-image-upload/types";
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
import { FILE_SERVER } from "@/config/setting";
|
||||
import { Project } from "@/api/oa/project/model";
|
||||
import { Tenant } from "@/api/oa/tennat/model";
|
||||
|
||||
const props = defineProps<{
|
||||
value?: string;
|
||||
// 修改回显的数据
|
||||
data?: Setting | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', value): void;
|
||||
}>();
|
||||
|
||||
// 保存字段信息(设定好key和描述,content里的字段是随意加的会自动转为json保存到数据库)
|
||||
const settingId = ref(null);
|
||||
const settingKey = ref('');
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 编辑器内容,双向绑定
|
||||
const logo = ref<any>([]);
|
||||
const prefix = ref('https://');
|
||||
const suffix = ref('.com');
|
||||
const keyword = ref([]);
|
||||
const tenantId = localStorage.getItem('tenantId');
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Setting>({
|
||||
siteName: '',
|
||||
icp: '',
|
||||
copyright: '',
|
||||
keyword: '',
|
||||
remarks: '',
|
||||
fullName: '',
|
||||
company: '',
|
||||
address: '',
|
||||
domain: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
support: '',
|
||||
logo: '',
|
||||
tenantId: undefined
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
siteName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入系统名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
keyword: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入网站关键词',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
remarks: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入站点描述',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
icp: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入ICP备案号',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
copyright: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入版权信息',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
});
|
||||
|
||||
const onUpload = (d: ItemType) => {
|
||||
const file = d.file;
|
||||
console.log(file);
|
||||
if(file){
|
||||
if (file.size / 1024 / 1024 > 2) {
|
||||
message.error('大小不能超过 2MB');
|
||||
return;
|
||||
}
|
||||
}
|
||||
uploadFile(<File>d.file)
|
||||
.then((result) => {
|
||||
form.logo = result.url;
|
||||
message.success('上传成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyword = (keyword) => {
|
||||
keyword.value = keyword;
|
||||
form.keyword = JSON.stringify(keyword);
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
form.logo = undefined
|
||||
}
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
content: JSON.stringify(form),
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateSetting : addSetting;
|
||||
saveOrUpdate(appForm)
|
||||
.then((msg) => {
|
||||
message.success('保存成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if(data?.settingId){
|
||||
isUpdate.value = true
|
||||
// 表单赋值
|
||||
if(data.content){
|
||||
const jsonData = JSON.parse(data.content);
|
||||
assignFields(jsonData);
|
||||
// 头像赋值
|
||||
logo.value = [];
|
||||
if (jsonData.logo) {
|
||||
logo.value.push({ uid:1, url: jsonData.logo, status: '' });
|
||||
}
|
||||
if(jsonData.keyword){
|
||||
keyword.value = JSON.parse(jsonData.keyword)
|
||||
}
|
||||
}
|
||||
// 其他必要参数
|
||||
form.settingId = data.settingId
|
||||
form.settingKey = data.settingKey
|
||||
|
||||
} else {
|
||||
// 新增
|
||||
isUpdate.value = false
|
||||
resetFields();
|
||||
form.settingKey = props.value
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
</script>
|
||||
125
src/views/system/setting/components/chatGPT.vue
Normal file
125
src/views/system/setting/components/chatGPT.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 3, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 9, sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item label="KEY" name="chatKey">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入KEY"
|
||||
v-model:value="form.chatKey"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
style="margin-top: 10px"
|
||||
@click="save"
|
||||
>
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Setting } from '@/api/system/setting/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addSetting, updateSetting } from '@/api/system/setting';
|
||||
|
||||
const props = defineProps<{
|
||||
value?: string;
|
||||
// 修改回显的数据
|
||||
data?: Setting | null;
|
||||
}>();
|
||||
|
||||
// const emit = defineEmits<{
|
||||
// (e: 'done', value): void;
|
||||
// }>();
|
||||
|
||||
// 保存字段信息(设定好key和描述,content里的字段是随意加的会自动转为json保存到数据库)
|
||||
// const settingId = ref(null);
|
||||
// const settingKey = ref('');
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Setting>({
|
||||
chatKey: '',
|
||||
tenantId: localStorage.getItem('tenantId')
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
chatKey: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入KEY',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
content: JSON.stringify(form)
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateSetting : addSetting;
|
||||
saveOrUpdate(appForm)
|
||||
.then(() => {
|
||||
message.success('保存成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if (data?.settingId) {
|
||||
isUpdate.value = true;
|
||||
// 表单赋值
|
||||
if (data.content) {
|
||||
const jsonData = JSON.parse(data.content);
|
||||
assignFields(jsonData);
|
||||
}
|
||||
// 其他必要参数
|
||||
form.settingId = data.settingId;
|
||||
form.settingKey = data.settingKey;
|
||||
} else {
|
||||
// 新增
|
||||
isUpdate.value = false;
|
||||
resetFields();
|
||||
form.settingKey = props.value;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
137
src/views/system/setting/components/clear.vue
Normal file
137
src/views/system/setting/components/clear.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 3, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 9, sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<!-- <a-form-item label="缓存项目" name="clearCache">-->
|
||||
<!-- <a-checkbox-group v-model:value="form.clearCache">-->
|
||||
<!-- <a-checkbox value="setting">系统设置</a-checkbox>-->
|
||||
<!-- <a-checkbox value="dict">数据字典</a-checkbox>-->
|
||||
<!-- <a-checkbox value="category">商品分类</a-checkbox>-->
|
||||
<!-- <a-checkbox value="temp">临时图片</a-checkbox>-->
|
||||
<!-- </a-checkbox-group>-->
|
||||
<!-- </a-form-item>-->
|
||||
<a-form-item label="操作">
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
@click="save"
|
||||
>
|
||||
<span>更新缓存</span>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Setting } from '@/api/system/setting/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { ItemType } from "ele-admin-pro/es/ele-image-upload/types";
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
import { FILE_SERVER } from "@/config/setting";
|
||||
import { listDictionaries } from "@/api/system/dict";
|
||||
|
||||
const props = defineProps<{
|
||||
// 当前选项卡
|
||||
value?: string;
|
||||
// 修改回显的数据
|
||||
data?: Setting | null;
|
||||
}>();
|
||||
|
||||
// 保存字段信息(设定好key和描述,content里的字段是随意加的会自动转为json保存到数据库)
|
||||
const settingId = ref(undefined);
|
||||
const settingKey = ref('setting');
|
||||
const comments = ref('系统设置');
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 编辑器内容,双向绑定
|
||||
const logo = ref<any>([]);
|
||||
const value = ref('all')
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Setting>({
|
||||
clearCache: 'setting,dict,category,temp',
|
||||
tenantId: localStorage.getItem('tenantId')
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
clearCache: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择要清楚的项',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const onUpload = (d: ItemType) => {
|
||||
uploadFile(<File>d.file)
|
||||
.then((result) => {
|
||||
form.logo = result.path;
|
||||
message.success('上传成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const selectAll = (e) => {
|
||||
// if (e.target.value === 'all') {
|
||||
// form.clearCache = '"all","setting","dict","category","temp"'
|
||||
// }
|
||||
}
|
||||
const onClose = () => {
|
||||
form.logo = undefined
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
// 清除字典缓存
|
||||
listDictionaries().then(data => {
|
||||
data?.map(d => {
|
||||
localStorage.removeItem("__" + d.dictCode + "__");
|
||||
})
|
||||
})
|
||||
message.success('更新成功');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if(data?.settingId){
|
||||
isUpdate.value = true
|
||||
// 表单赋值
|
||||
if(data.content){
|
||||
const jsonData = JSON.parse(data.content);
|
||||
assignFields(jsonData);
|
||||
}
|
||||
// 其他必要参数
|
||||
form.settingId = data.settingId
|
||||
form.settingKey = data.settingKey
|
||||
} else {
|
||||
// 新增
|
||||
isUpdate.value = false
|
||||
resetFields();
|
||||
form.settingKey = props.value
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
204
src/views/system/setting/components/developer.vue
Normal file
204
src/views/system/setting/components/developer.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 3, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 9, sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item label="租户ID" name="tenantId">
|
||||
<span class="ele-text-heading">{{ tenantId }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="应用编码" name="tenantCode">
|
||||
<span class="ele-text-heading">{{ tenantCode }}</span>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Setting } from '@/api/system/setting/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addSetting, listSetting, updateSetting } from "@/api/system/setting";
|
||||
import { ItemType } from "ele-admin-pro/es/ele-image-upload/types";
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
import { FILE_SERVER } from "@/config/setting";
|
||||
import { Project } from "@/api/oa/project/model";
|
||||
import { Tenant } from "@/api/oa/tennat/model";
|
||||
|
||||
const props = defineProps<{
|
||||
value?: string;
|
||||
// 修改回显的数据
|
||||
data?: Setting | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', value): void;
|
||||
}>();
|
||||
|
||||
// 保存字段信息(设定好key和描述,content里的字段是随意加的会自动转为json保存到数据库)
|
||||
const settingId = ref(null);
|
||||
const settingKey = ref('developer');
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 编辑器内容,双向绑定
|
||||
const logo = ref<any>([]);
|
||||
const prefix = ref('https://');
|
||||
const suffix = ref('.com');
|
||||
const keyword = ref([]);
|
||||
const tenantId = localStorage.getItem('tenantId');
|
||||
const tenantCode = 'YKVBG4nwnHFXh9pe';
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Setting>({
|
||||
siteName: '',
|
||||
icp: '',
|
||||
copyright: '',
|
||||
keyword: '',
|
||||
remarks: '',
|
||||
fullName: '',
|
||||
company: '',
|
||||
address: '',
|
||||
domain: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
support: '',
|
||||
logo: '',
|
||||
tenantId: undefined
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
siteName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入系统名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
keyword: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入网站关键词',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
remarks: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入站点描述',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
icp: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入ICP备案号',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
copyright: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入版权信息',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
});
|
||||
|
||||
const onUpload = (d: ItemType) => {
|
||||
uploadFile(<File>d.file)
|
||||
.then((result) => {
|
||||
form.logo = result.path;
|
||||
message.success('上传成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyword = (keyword) => {
|
||||
keyword.value = keyword;
|
||||
form.keyword = JSON.stringify(keyword);
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
form.logo = undefined
|
||||
}
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
content: JSON.stringify(form),
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateSetting : addSetting;
|
||||
saveOrUpdate(appForm)
|
||||
.then((msg) => {
|
||||
message.success('保存成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if(data?.settingId){
|
||||
isUpdate.value = true
|
||||
// 表单赋值
|
||||
if(data.content){
|
||||
const jsonData = JSON.parse(data.content);
|
||||
assignFields(jsonData);
|
||||
// 头像赋值
|
||||
logo.value = [];
|
||||
if (jsonData.logo) {
|
||||
logo.value.push({ uid:1, url: FILE_SERVER + jsonData.logo, status: '' });
|
||||
}
|
||||
if(jsonData.keyword){
|
||||
keyword.value = JSON.parse(jsonData.keyword)
|
||||
}
|
||||
}
|
||||
// 其他必要参数
|
||||
form.settingId = data.settingId
|
||||
form.settingKey = data.settingKey
|
||||
|
||||
} else {
|
||||
// 新增
|
||||
isUpdate.value = false
|
||||
resetFields();
|
||||
form.settingKey = props.value
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
</script>
|
||||
217
src/views/system/setting/components/mp-weixin.vue
Normal file
217
src/views/system/setting/components/mp-weixin.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 3, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 9, sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item label="小程序 AppID" name="appId" extra="登录小程序平台,开发 - 开发管理 - 开发设置,记录AppID(小程序ID)">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入小程序AppID"
|
||||
v-model:value="form.appId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="小程序 AppSecret" name="appSecret" extra="登录小程序平台,开发 - 开发管理 - 开发设置,记录AppSecret(小程序密钥)">
|
||||
<a-input-password
|
||||
:maxlength="50"
|
||||
placeholder="请输入小程序AppSecret"
|
||||
v-model:value="form.appSecret"
|
||||
/>
|
||||
</a-form-item>
|
||||
<div style="margin-bottom: 22px; width: 750px;">
|
||||
<a-divider>授权域名设置</a-divider>
|
||||
</div>
|
||||
<a-form-item label="request合法域名" name="request">
|
||||
<a-input-group compact>
|
||||
<a-input :value="`https://open.gxwebsoft.com`" placeholder="请输入小程序AppSecret" style="width: calc(100% - 50px)" />
|
||||
<a-tooltip title="复制">
|
||||
<a-button @click="onCopyText(`https://open.gxwebsoft.com`)">
|
||||
<template #icon><CopyOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="socket合法域名" name="socket">
|
||||
<a-input-group compact>
|
||||
<a-input :value="`wss://open.gxwebsoft.com`" placeholder="请输入小程序AppSecret" style="width: calc(100% - 50px)" />
|
||||
<a-tooltip title="复制">
|
||||
<a-button @click="onCopyText(`wss://open.gxwebsoft.com`)">
|
||||
<template #icon><CopyOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="uploadFile合法域名" name="uploadFile">
|
||||
<a-input-group compact>
|
||||
<a-input :value="`https://open.gxwebsoft.com`" style="width: calc(100% - 50px)" />
|
||||
<a-tooltip title="复制">
|
||||
<a-button @click="onCopyText(`https://open.gxwebsoft.com`)">
|
||||
<template #icon><CopyOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="downloadFile合法域名" name="downloadFile">
|
||||
<a-input-group compact>
|
||||
<a-input :value="`https://open.gxwebsoft.com`" style="width: calc(100% - 50px)" />
|
||||
<a-tooltip title="复制">
|
||||
<a-button @click="onCopyText(`https://open.gxwebsoft.com`)">
|
||||
<template #icon><CopyOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-input-group>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
style="margin-top: 10px"
|
||||
@click="save"
|
||||
>
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { copyText } from '@/utils/common';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Setting } from '@/api/system/setting/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addSetting, updateSetting } from "@/api/system/setting";
|
||||
import { ItemType } from "ele-admin-pro/es/ele-image-upload/types";
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
import { FILE_SERVER } from "@/config/setting";
|
||||
import {
|
||||
CopyOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
value?: string;
|
||||
// 修改回显的数据
|
||||
data?: Setting | null;
|
||||
}>();
|
||||
|
||||
// 保存字段信息(设定好key和描述,content里的字段是随意加的会自动转为json保存到数据库)
|
||||
const settingId = ref(undefined);
|
||||
const settingKey = ref('mp-weixin');
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 编辑器内容,双向绑定
|
||||
const logo = ref<any>([]);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Setting>({
|
||||
appId: '',
|
||||
appSecret: '',
|
||||
tenantId: localStorage.getItem('tenantId')
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
appId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入appId',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
appSecret: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入appSecret',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const onCopyText = (text) => {
|
||||
copyText(text);
|
||||
}
|
||||
|
||||
const onUpload = (d: ItemType) => {
|
||||
uploadFile(<File>d.file)
|
||||
.then((result) => {
|
||||
form.logo = result.path;
|
||||
message.success('上传成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
form.logo = undefined
|
||||
}
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
content: JSON.stringify(form)
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateSetting : addSetting;
|
||||
saveOrUpdate(appForm)
|
||||
.then((msg) => {
|
||||
message.success('保存成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if(data?.settingId){
|
||||
isUpdate.value = true
|
||||
// 表单赋值
|
||||
if(data.content){
|
||||
const jsonData = JSON.parse(data.content);
|
||||
assignFields(jsonData);
|
||||
}
|
||||
// 其他必要参数
|
||||
form.settingId = data.settingId
|
||||
form.settingKey = data.settingKey
|
||||
} else {
|
||||
// 新增
|
||||
isUpdate.value = false
|
||||
resetFields();
|
||||
form.settingKey = props.value
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.small{
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
526
src/views/system/setting/components/payment.vue
Normal file
526
src/views/system/setting/components/payment.vue
Normal file
@@ -0,0 +1,526 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 3, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 9, sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item label="支付方式" name="payMethod">
|
||||
<DictRadio
|
||||
dict-code="payMethod"
|
||||
v-model:value="form.payMethod"
|
||||
:placeholder="`选择支付方式`"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- 余额支付 -->
|
||||
<a-form-item label="是否启用" name="balanceIsEnable" v-if="form.payMethod === '10'">
|
||||
<a-switch v-model:checked="form.balanceIsEnable" />
|
||||
</a-form-item>
|
||||
<!-- 微信支付开始 -->
|
||||
<template v-if="form.payMethod === '20'">
|
||||
<a-form-item label="是否启用" name="wechatIsEnable">
|
||||
<a-switch v-model:checked="form.wechatIsEnable" />
|
||||
</a-form-item>
|
||||
<a-form-item label="微信商户号类型" name="wechatType">
|
||||
<a-radio-group v-model:value="form.wechatType">
|
||||
<a-radio value="1">
|
||||
<text>普通商户</text>
|
||||
</a-radio>
|
||||
<a-radio value="2">子商户 (服务商模式)</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<template v-if="form.wechatType === '1'">
|
||||
<a-form-item
|
||||
label="应用ID (AppID)"
|
||||
name="wechatAppId"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="微信小程序或者微信公众号的APPID,APP支付需要填写开放平台的应用APPID"
|
||||
v-model:value="form.wechatAppId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="微信商户号 (MchId)" name="mchId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="微信支付的商户号"
|
||||
v-model:value="form.mchId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="支付密钥 (APIv3密钥)" name="wechatApiKey">
|
||||
<a-input-password
|
||||
allow-clear
|
||||
placeholder="设置APIv3密钥"
|
||||
v-model:value="form.wechatApiKey"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="证书文件 (CERT)"
|
||||
name="apiclientCert"
|
||||
extra='请上传 "apiclient_cert.pem" 文件'
|
||||
>
|
||||
<Upload accept=".crt" v-model:value="form.apiclientCert" />
|
||||
{{ form.apiclientCert }}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="证书文件 (KEY)"
|
||||
name="apiclientKey"
|
||||
extra='请上传 "apiclient_key.pem" 文件'
|
||||
>
|
||||
<Upload accept=".crt" v-model:value="form.apiclientKey" />
|
||||
{{ form.apiclientKey }}
|
||||
</a-form-item>
|
||||
<a-form-item label="商户证书序列号" name="merchantSerialNumber">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="商户证书序列号"
|
||||
v-model:value="form.merchantSerialNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="form.wechatType === '2'">
|
||||
<a-form-item
|
||||
label="服务商应用ID (AppID)"
|
||||
name="spAppId"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请填写微信支付服务商的AppID"
|
||||
v-model:value="form.spAppId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="服务商户号 (MchId)" name="spMchId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="微信支付服务商的商户号"
|
||||
v-model:value="form.spMchId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="服务商密钥 (APIKEY)" name="spApiKey">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="微信支付服务商的商户号"
|
||||
v-model:value="form.spApiKey"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="子商户应用ID (AppID)" name="spSubAppId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="微信小程序或者微信公众号的APPID,APP支付需要填写开放平台的应用APPID"
|
||||
v-model:value="form.spSubAppId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="子商户号 (MchId)" name="spSubMchId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="微信支付的商户号"
|
||||
v-model:value="form.spSubMchId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="服务商证书文件 (CERT)"
|
||||
name="spApiclientCert"
|
||||
extra='请上传 "apiclient_cert.pem" 文件'
|
||||
>
|
||||
<Upload accept=".crt" v-model:value="form.spApiclientCert" />
|
||||
{{ form.spApiclientCert }}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="服务商证书文件 (KEY)"
|
||||
name="spApiclientKey"
|
||||
extra='请上传 "apiclient_key.pem" 文件'
|
||||
>
|
||||
<Upload accept=".crt" v-model:value="form.spApiclientKey" />
|
||||
{{ form.spApiclientKey }}
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 支付宝支付开始 -->
|
||||
<template v-if="form.payMethod === '30'">
|
||||
<a-form-item label="是否启用" name="alipayIsEnable">
|
||||
<a-switch v-model:checked="form.alipayIsEnable" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="支付宝应用"
|
||||
name="alipayAppId"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="支付宝分配给开发者的应用ID"
|
||||
v-model:value="form.alipayAppId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="签名算法 (signType)"
|
||||
name="signType"
|
||||
>
|
||||
<a-radio-group v-model:value="form.signType">
|
||||
<a-radio value="RSA2">RSA2</a-radio>
|
||||
<a-radio value="RSA">RSA</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="加签模式"
|
||||
name="signMode"
|
||||
extra="如需使用资金支出类的接口,则必须使用公钥证书模式,配置指南 https://opendocs.alipay.com/open/200/105310"
|
||||
>
|
||||
<a-radio-group v-model:value="form.signMode">
|
||||
<a-radio value="公钥证书">
|
||||
<text>公钥证书</text>
|
||||
<a-tag class="ml-5" color="green">推荐</a-tag>
|
||||
</a-radio>
|
||||
<a-radio value="公钥">公钥</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="应用公钥证书"
|
||||
name="appCertPublicKey"
|
||||
v-if="form.signMode === '公钥证书'"
|
||||
extra='请上传 "appCertPublicKey.crt" 文件'
|
||||
>
|
||||
<Upload accept=".crt" v-model:value="form.appCertPublicKey" />
|
||||
{{ form.appCertPublicKey }}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="支付宝公钥证书"
|
||||
name="alipayCertPublicKey"
|
||||
v-if="form.signMode === '公钥证书'"
|
||||
extra='请上传 "alipayCertPublicKey_RSA2.crt" 文件'
|
||||
>
|
||||
<Upload accept=".crt" v-model:value="form.alipayCertPublicKey" />
|
||||
{{ form.alipayCertPublicKey }}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="支付宝根证书"
|
||||
name="alipayRootCert"
|
||||
v-if="form.signMode === '公钥证书'"
|
||||
extra='请上传 "alipayRootCert.crt" 文件'
|
||||
>
|
||||
<Upload accept=".crt" v-model:value="form.alipayRootCert" />
|
||||
{{ form.alipayRootCert }}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="支付宝公钥"
|
||||
name="alipayPublicKey"
|
||||
v-if="form.signMode === '公钥'"
|
||||
>
|
||||
<a-textarea
|
||||
:rows="6"
|
||||
placeholder="请输入alipayPublicKey"
|
||||
v-model:value="form.alipayPublicKey"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="应用私钥"
|
||||
name="privateKey"
|
||||
extra='查看 "应用私钥_RSA2_PKCS8.txt" 文件,将全部内容复制到此处'
|
||||
>
|
||||
<a-textarea
|
||||
:rows="6"
|
||||
placeholder="请输入privateKey"
|
||||
v-model:value="form.privateKey"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="接口内容加密方式"
|
||||
name="decryptKey"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入decryptKey"
|
||||
v-model:value="form.decryptKey"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item label="操作">
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
@click="save"
|
||||
>
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from "vue";
|
||||
import { copyText } from "@/utils/common";
|
||||
import { message } from "ant-design-vue";
|
||||
import { Setting } from "@/api/system/setting/model";
|
||||
import { useThemeStore } from "@/store/modules/theme";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { UploadOutlined } from '@ant-design/icons-vue';
|
||||
import { FormInstance } from "ant-design-vue/es/form";
|
||||
import useFormData from "@/utils/use-form-data";
|
||||
import { addSetting, updateSetting } from "@/api/system/setting";
|
||||
import { ItemType } from "ele-admin-pro/es/ele-image-upload/types";
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
import Upload from "@/components/UploadCert/index.vue";
|
||||
import { FILE_SERVER, TOKEN_STORE_NAME } from "@/config/setting";
|
||||
|
||||
const props = defineProps<{
|
||||
value?: string;
|
||||
// 修改回显的数据
|
||||
data?: Setting | null;
|
||||
}>();
|
||||
|
||||
// 保存字段信息(设定好key和描述,content里的字段是随意加的会自动转为json保存到数据库)
|
||||
const settingId = ref(undefined);
|
||||
const settingKey = ref("payment");
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 编辑器内容,双向绑定
|
||||
const logo = ref<any>([]);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
// token
|
||||
const token = localStorage.getItem(TOKEN_STORE_NAME);
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Setting>({
|
||||
payMethod: 10,
|
||||
signMode: "公钥证书",
|
||||
appId: "",
|
||||
signType: "RSA2",
|
||||
alipayAppId: "",
|
||||
appCertPublicKey: "",
|
||||
alipayCertPublicKey: "",
|
||||
alipayRootCert: "",
|
||||
alipayPublicKey: "",
|
||||
privateKey: "",
|
||||
decryptKey: "",
|
||||
balanceIsEnable: true,
|
||||
wechatIsEnable: false,
|
||||
alipayIsEnable: false,
|
||||
wechatType: '1',
|
||||
wechatAppId: '',
|
||||
wechatApiKey: '',
|
||||
apiclientCert: '',
|
||||
apiclientKey: '',
|
||||
mchId: undefined,
|
||||
spAppId: '',
|
||||
spMchId: '',
|
||||
spApiKey: '',
|
||||
spSubAppId: '',
|
||||
spSubMchId: '',
|
||||
spApiclientCert: '',
|
||||
spApiclientKey: '',
|
||||
merchantSerialNumber: '',
|
||||
tenantId: localStorage.getItem('tenantId')
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
payMethod: [
|
||||
{
|
||||
required: true,
|
||||
message: "请选择支付方式",
|
||||
type: "string",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
signType: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请填写签名算法",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
appCertPublicKey: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请上传应用公钥证书",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
alipayCertPublicKey: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请上传支付宝公钥证书",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
alipayRootCert: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请上传支付宝根证书",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
signMode: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请选择加签模式",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
balanceIsEnable: [
|
||||
{
|
||||
required: true,
|
||||
type: "boolean",
|
||||
message: "请设置是否启用余额支付",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
wechatIsEnable: [
|
||||
{
|
||||
required: true,
|
||||
type: "boolean",
|
||||
message: "请设置是否启用微信支付",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
alipayIsEnable: [
|
||||
{
|
||||
required: true,
|
||||
type: "boolean",
|
||||
message: "请设置是否启用支付宝支付",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
comments: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请填写支付方式简介",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
status: [
|
||||
{
|
||||
required: true,
|
||||
type: "number",
|
||||
message: "请选择支付方式状态",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
sortNumber: [
|
||||
{
|
||||
required: true,
|
||||
type: "number",
|
||||
message: "请输入排序号",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
alipayAppId: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请填写应用ID",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
alipayPublicKey: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请填写支付宝公钥",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
privateKey: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请填写应用私钥",
|
||||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const onCopyText = (text) => {
|
||||
copyText(text);
|
||||
};
|
||||
|
||||
const onApiclientKey = (e) => {
|
||||
const response = e.file.response
|
||||
const parse = JSON.parse(response);
|
||||
console.log(parse);
|
||||
form.apiclientKey = e.file.response
|
||||
}
|
||||
|
||||
const onUpload = (d: ItemType) => {
|
||||
uploadFile(<File>d.file)
|
||||
.then((result) => {
|
||||
form.logo = result.path;
|
||||
message.success("上传成功");
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
form.logo = undefined;
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
content: JSON.stringify(form)
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateSetting : addSetting;
|
||||
saveOrUpdate(appForm)
|
||||
.then((msg) => {
|
||||
message.success("保存成功");
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if (data?.settingId) {
|
||||
isUpdate.value = true;
|
||||
// 表单赋值
|
||||
if (data.content) {
|
||||
const jsonData = JSON.parse(data.content);
|
||||
assignFields(jsonData);
|
||||
}
|
||||
// 其他必要参数
|
||||
form.settingId = data.settingId;
|
||||
form.settingKey = data.settingKey;
|
||||
} else {
|
||||
// 新增
|
||||
isUpdate.value = false;
|
||||
resetFields();
|
||||
form.settingKey = props.value;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.small {
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 14px !important;
|
||||
}
|
||||
</style>
|
||||
214
src/views/system/setting/components/printer.vue
Normal file
214
src/views/system/setting/components/printer.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 3, sm: 7, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 9, sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item label="是否开启小票打印" name="isOpenPrinter">
|
||||
<a-radio-group v-model:value="form.isOpenPrinter">
|
||||
<a-radio value="1">开启</a-radio>
|
||||
<a-radio value="0">关闭</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<template v-if="form.isOpenPrinter === '1'">
|
||||
<a-form-item label="打印机类型" name="printerType">
|
||||
<a-radio-group v-model:value="form.printerType">
|
||||
<a-radio value="1">飞鹅打印机</a-radio>
|
||||
<a-radio value="2">365云打印</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="USER" name="printerUser" v-if="form.printerType === '1'" extra="飞鹅云后台注册用户名">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入飞鹅云后台注册用户名"
|
||||
v-model:value="form.printerUser"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="UKEY" name="printerUserKey" v-if="form.printerType === '1'" extra="飞鹅云后台登录生成的UKEY">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入飞鹅云后台登录生成的UKEY"
|
||||
v-model:value="form.printerUserKey"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="打印机编号" name="printerCode" extra="打印机编号为9位数字">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入打印机编号"
|
||||
v-model:value="form.printerCode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="打印机秘钥" name="printerKey" v-if="form.printerType === '2'">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入打印机编号"
|
||||
v-model:value="form.printerKey"
|
||||
/>
|
||||
<small class="small" v-if="form.printerType === '1'">打印机编号为9位数字,查看飞鹅打印机底部贴纸上面的编号</small>
|
||||
</a-form-item>
|
||||
<a-form-item label="打印联数" name="printerTimes" extra="同一订单,打印的次数">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
style="width: 180px"
|
||||
placeholder="请输入打印联数"
|
||||
v-model:value="form.printerTimes"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="订单打印方式" name="printerStatus">
|
||||
<a-checkbox-group v-model:value="form.printerStatus">
|
||||
<a-checkbox value="20">订单付款时</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item label="操作">
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
@click="save"
|
||||
>
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Setting } from '@/api/system/setting/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addSetting, updateSetting } from "@/api/system/setting";
|
||||
import { ItemType } from "ele-admin-pro/es/ele-image-upload/types";
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
import { FILE_SERVER } from "@/config/setting";
|
||||
|
||||
const props = defineProps<{
|
||||
// 当前选项卡
|
||||
value?: string;
|
||||
// 修改回显的数据
|
||||
data?: Setting | null;
|
||||
}>();
|
||||
|
||||
// 保存字段信息(设定好key和描述,content里的字段是随意加的会自动转为json保存到数据库)
|
||||
const settingId = ref(undefined);
|
||||
const settingKey = ref('printer');
|
||||
const comments = ref('打印设置');
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 编辑器内容,双向绑定
|
||||
const logo = ref<any>([]);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Setting>({
|
||||
isOpenPrinter: '0',
|
||||
printerType: '1',
|
||||
printerStatus: '20',
|
||||
printerUser: '',
|
||||
printerUserKey: '',
|
||||
printerCode: '',
|
||||
printerKey: '',
|
||||
printerTimes: '',
|
||||
tenantId: localStorage.getItem('tenantId')
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
siteName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入系统名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
comments: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入站点描述',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const onUpload = (d: ItemType) => {
|
||||
uploadFile(<File>d.file)
|
||||
.then((result) => {
|
||||
form.logo = result.path;
|
||||
message.success('上传成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
form.logo = undefined
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
content: JSON.stringify(form)
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateSetting : addSetting;
|
||||
saveOrUpdate(appForm)
|
||||
.then((msg) => {
|
||||
message.success('保存成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if(data?.settingId){
|
||||
isUpdate.value = true
|
||||
// 表单赋值
|
||||
if(data.content){
|
||||
const jsonData = JSON.parse(data.content);
|
||||
assignFields(jsonData);
|
||||
}
|
||||
// 其他必要参数
|
||||
form.settingId = data.settingId
|
||||
form.settingKey = data.settingKey
|
||||
} else {
|
||||
// 新增
|
||||
isUpdate.value = false
|
||||
resetFields();
|
||||
form.settingKey = props.value
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
236
src/views/system/setting/components/register.vue
Normal file
236
src/views/system/setting/components/register.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 3, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 9, sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item label="默认登录/注册方式" name="type">
|
||||
<a-radio-group v-model:value="form.type">
|
||||
<a-radio :value="1">手机号+短信验证码</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="请选择默认角色" name="roleId">
|
||||
<role-select v-model:value="form.roleId" />
|
||||
</a-form-item>
|
||||
<div style="margin-bottom: 22px; width: 750px;">
|
||||
<a-divider>微信小程序授权登录</a-divider>
|
||||
</div>
|
||||
<a-form-item label="一键授权登录/注册" name="openWxAuth">
|
||||
<a-radio-group v-model:value="form.openWxAuth">
|
||||
<a-radio :value="1">开启</a-radio>
|
||||
<a-radio :value="0">关闭</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="注册时绑定手机号" name="openWxBindPhone">
|
||||
<a-radio-group v-model:value="form.openWxBindPhone">
|
||||
<a-radio :value="1">强制绑定</a-radio>
|
||||
<a-radio :value="0">不绑定</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<div style="margin-bottom: 22px; width: 750px;">
|
||||
<a-divider>微信公众号授权登录</a-divider>
|
||||
</div>
|
||||
<a-form-item label="一键授权登录/注册" name="openWxofficialAuth">
|
||||
<a-radio-group v-model:value="form.openWxofficialAuth">
|
||||
<a-radio :value="1">开启</a-radio>
|
||||
<a-radio :value="0">关闭</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="注册时绑定手机号" name="openBindPhone">
|
||||
<a-space direction="vertical">
|
||||
<a-radio-group v-model:value="form.openWxofficialBindPhone"
|
||||
style="margin-top: 5px">
|
||||
<a-radio :value="1">强制绑定</a-radio>
|
||||
<a-radio :value="0">不绑定</a-radio>
|
||||
</a-radio-group>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="登录超时时间" name="tokenExpireTime">
|
||||
<a-space direction="vertical">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="30"
|
||||
placeholder="请输入登录超时时间"
|
||||
v-model:value="form.tokenExpireTime"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
style="margin-top: 10px"
|
||||
@click="save"
|
||||
>
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Setting } from '@/api/system/setting/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addSetting, updateSetting } from "@/api/system/setting";
|
||||
import { ItemType } from "ele-admin-pro/es/ele-image-upload/types";
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
import RoleSelect from './role-select.vue';
|
||||
import { FILE_SERVER } from "@/config/setting";
|
||||
import { Role } from "@/api/system/role/model";
|
||||
|
||||
const props = defineProps<{
|
||||
value?: string;
|
||||
// 修改回显的数据
|
||||
data?: Setting | null;
|
||||
}>();
|
||||
|
||||
// 保存字段信息(设定好key和描述,content里的字段是随意加的会自动转为json保存到数据库)
|
||||
const settingId = ref(undefined);
|
||||
const settingKey = ref('register');
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 编辑器内容,双向绑定
|
||||
const logo = ref<any>([]);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Setting>({
|
||||
settingKey: '',
|
||||
type: 1,
|
||||
roleId: undefined,
|
||||
openWxAuth: 1,
|
||||
openWxBindPhone: 1,
|
||||
openWxofficialAuth: 1,
|
||||
openWxofficialBindPhone: 1,
|
||||
tokenExpireTime: 86400,
|
||||
comments: '',
|
||||
tenantId: localStorage.getItem('tenantId')
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择默认注册方式',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
roleId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择默认角色',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
openWxAuth: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入系统名称',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
openWxBindPhone: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入系统名称',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
openWxofficialAuth: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入系统名称',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
openWxofficialBindPhone: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入系统名称',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const onUpload = (d: ItemType) => {
|
||||
uploadFile(<File>d.file)
|
||||
.then((result) => {
|
||||
form.logo = result.path;
|
||||
message.success('上传成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
form.logo = undefined
|
||||
}
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
content: JSON.stringify(form)
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateSetting : addSetting;
|
||||
saveOrUpdate(appForm)
|
||||
.then((msg) => {
|
||||
message.success('保存成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if(data?.settingId){
|
||||
isUpdate.value = true
|
||||
// 表单赋值
|
||||
if(data.content){
|
||||
const jsonData = JSON.parse(data.content);
|
||||
assignFields(jsonData);
|
||||
}
|
||||
// 其他必要参数
|
||||
form.settingId = data.settingId
|
||||
form.settingKey = data.settingKey
|
||||
} else {
|
||||
// 新增
|
||||
isUpdate.value = false
|
||||
resetFields();
|
||||
form.settingKey = settingKey.value
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
64
src/views/system/setting/components/role-select.vue
Normal file
64
src/views/system/setting/components/role-select.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<!-- 角色选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
allow-clear
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@blur="onBlur"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in data"
|
||||
:key="item.roleId"
|
||||
:value="item.roleId"
|
||||
>
|
||||
{{ item.roleName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { listRoles } from '@/api/system/role';
|
||||
import type { Role } from '@/api/system/role/model';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
value?: Role[];
|
||||
//
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择角色'
|
||||
}
|
||||
);
|
||||
|
||||
// 角色数据
|
||||
const data = ref<Role[]>([]);
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
|
||||
/* 获取角色数据 */
|
||||
listRoles()
|
||||
.then((list) => {
|
||||
data.value = list;
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
246
src/views/system/setting/components/sms.vue
Normal file
246
src/views/system/setting/components/sms.vue
Normal file
@@ -0,0 +1,246 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 3, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 9, sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item label="短信平台" name="type">
|
||||
<a-radio-group v-model:value="form.type">
|
||||
<a-radio :value="1">阿里云</a-radio>
|
||||
<a-radio :value="2">腾讯云</a-radio>
|
||||
<a-radio :value="3">七牛元</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="AccessKeyId" name="accessKeyId">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入AccessKeyId"
|
||||
v-model:value="form.accessKeyId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="AccessKeySecret" name="accessKeySecret">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入AccessKeySecret"
|
||||
v-model:value="form.accessKeySecret"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="短信签名" name="sign">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入短信签名"
|
||||
v-model:value="form.sign"
|
||||
/>
|
||||
</a-form-item>
|
||||
<div style="margin-bottom: 22px; width: 750px;">
|
||||
<a-divider>短信验证码 (通知用户)</a-divider>
|
||||
</div>
|
||||
<a-form-item label="是否开启" name="isOpenNotice">
|
||||
<a-radio-group v-model:value="form.isNoticeUser">
|
||||
<a-radio value="1">开启</a-radio>
|
||||
<a-radio value="0">关闭</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="模板内容" name="isContent">
|
||||
验证码${code},您正在进行身份验证,打死不要告诉别人哦!
|
||||
</a-form-item>
|
||||
<a-form-item label="模板ID" name="userTemplateId">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入模板ID"
|
||||
v-model:value="form.userTemplateId"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<div style="margin-bottom: 22px; width: 750px;">
|
||||
<a-divider>新付款订单 (通知商家)</a-divider>
|
||||
</div>
|
||||
|
||||
<a-form-item label="是否开启" name="isNoticeMerchant">
|
||||
<a-radio-group v-model:value="form.isNoticeMerchant">
|
||||
<a-radio value="1">开启</a-radio>
|
||||
<a-radio value="0">关闭</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="模板内容" name="isContent">
|
||||
验证码${code},您正在进行身份验证,打死不要告诉别人哦!
|
||||
</a-form-item>
|
||||
<a-form-item label="模板ID" name="merchantTemplateId">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入模板ID"
|
||||
v-model:value="form.merchantTemplateId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="接收手机号" name="mobile">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入接收手机号"
|
||||
v-model:value="form.merchantMobiles"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
style="margin-top: 10px"
|
||||
@click="save"
|
||||
>
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Setting } from '@/api/system/setting/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addSetting, listSetting, updateSetting } from "@/api/system/setting";
|
||||
import { ItemType } from "ele-admin-pro/es/ele-image-upload/types";
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
import { FILE_SERVER } from "@/config/setting";
|
||||
|
||||
const props = defineProps<{
|
||||
// 当前选项卡
|
||||
value?: string;
|
||||
// 修改回显的数据
|
||||
data?: Setting | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', value): void;
|
||||
}>();
|
||||
|
||||
// 保存字段信息(设定好key和描述,content里的字段是随意加的会自动转为json保存到数据库)
|
||||
const settingId = ref<number>();
|
||||
const settingKey = ref('');
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 编辑器内容,双向绑定
|
||||
const logo = ref<any>([]);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Setting>({
|
||||
settingKey: '',
|
||||
type: 1,
|
||||
accessKeyId: '',
|
||||
accessKeySecret: '',
|
||||
sign: '',
|
||||
isNoticeUser: '1',
|
||||
userTemplateId: '',
|
||||
merchantTemplateId: '',
|
||||
isNoticeMerchant: '1',
|
||||
merchantMobiles: '',
|
||||
tenantId: localStorage.getItem('tenantId')
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
accessKeyId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入accessKeyId',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
accessKeySecret: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入accessKeySecret',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sign: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入短信签名',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const onUpload = (d: ItemType) => {
|
||||
uploadFile(<File>d.file)
|
||||
.then((result) => {
|
||||
form.logo = result.path;
|
||||
message.success('上传成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
form.logo = undefined
|
||||
}
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
content: JSON.stringify(form)
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateSetting : addSetting;
|
||||
saveOrUpdate(appForm)
|
||||
.then((msg) => {
|
||||
message.success('保存成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if(data?.settingId){
|
||||
isUpdate.value = true
|
||||
// 表单赋值
|
||||
if(data.content){
|
||||
const jsonData = JSON.parse(data.content);
|
||||
assignFields(jsonData);
|
||||
}
|
||||
// 其他必要参数
|
||||
form.settingId = data.settingId
|
||||
form.settingKey = data.settingKey
|
||||
} else {
|
||||
// 新增
|
||||
isUpdate.value = false
|
||||
resetFields();
|
||||
form.settingKey = props.value
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
293
src/views/system/setting/components/upload.vue
Normal file
293
src/views/system/setting/components/upload.vue
Normal file
@@ -0,0 +1,293 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 3, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 9, sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item label="默认上传方式" name="uploadMethod">
|
||||
<a-radio-group v-model:value="form.uploadMethod">
|
||||
<a-radio-button value="oss">阿里云</a-radio-button>
|
||||
<a-radio-button value="cos">腾讯云</a-radio-button>
|
||||
<a-radio-button value="kodo">七牛云</a-radio-button>
|
||||
<a-radio-button value="file">本地</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<template v-if="form.uploadMethod !== 'file'">
|
||||
<a-form-item
|
||||
label="存储空间名称"
|
||||
name="bucketName"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.bucketName"
|
||||
placeholder="存储空间名称"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="Region域名"
|
||||
name="endpoint"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.bucketEndpoint"
|
||||
placeholder="https://oss-cn-shenzhen.aliyuncs.com"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="accessKeyId"
|
||||
name="accessKeyId"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.accessKeyId"
|
||||
placeholder="accessKeyId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="accessKeySecret"
|
||||
name="accessKeySecret"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.accessKeySecret"
|
||||
placeholder="accessKeySecret"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="空间域名"
|
||||
name="bucketDomain"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="form.bucketDomain"
|
||||
placeholder="https://oss-gxwebsoft.oss-cn-shenzhen.aliyuncs.com"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 私有云 -->
|
||||
<template v-if="form.uploadMethod === 'file'">
|
||||
<a-form-item
|
||||
label="域名"
|
||||
name="fileUrl"
|
||||
>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
v-model:value="form.fileUrl"
|
||||
placeholder="请输入文件服务器域名"
|
||||
style="width: calc(100% - 50px)"
|
||||
/>
|
||||
<a-tooltip title="复制">
|
||||
<a-button @click="onCopyText(`https://file.wsdns.cn`)">
|
||||
<template #icon><CopyOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 阿里云 -->
|
||||
<template v-if="form.uploadMethod === 'oss'">
|
||||
<a-form-item label="去申请">
|
||||
<a href="https://oss.console.aliyun.com" target="_blank">https://oss.console.aliyun.com</a>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 腾讯云 -->
|
||||
<template v-if="form.uploadMethod === 'cos'">
|
||||
<a-form-item label="去申请">
|
||||
<a href="https://cloud.tencent.com/product/cos" target="_blank">https://cloud.tencent.com/product/cos</a>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 七牛云 -->
|
||||
<template v-if="form.uploadMethod === 'kodo'">
|
||||
<a-form-item label="去申请">
|
||||
<a href="https://www.qiniu.com/products/kodo" target="_blank">https://www.qiniu.com/products/kodo</a>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item label="操作">
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
@click="save"
|
||||
>
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from "vue";
|
||||
import { copyText } from "@/utils/common";
|
||||
import { message } from "ant-design-vue";
|
||||
import { CopyOutlined } from '@ant-design/icons-vue';
|
||||
import { Setting } from "@/api/system/setting/model";
|
||||
import { useThemeStore } from "@/store/modules/theme";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { UploadOutlined } from '@ant-design/icons-vue';
|
||||
import { FormInstance } from "ant-design-vue/es/form";
|
||||
import useFormData from "@/utils/use-form-data";
|
||||
import { addSetting, updateSetting } from "@/api/system/setting";
|
||||
import { ItemType } from "ele-admin-pro/es/ele-image-upload/types";
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
import Upload from "@/components/UploadCert/index.vue";
|
||||
import { FILE_SERVER, TOKEN_STORE_NAME } from "@/config/setting";
|
||||
|
||||
const props = defineProps<{
|
||||
value?: string;
|
||||
// 修改回显的数据
|
||||
data?: Setting | null;
|
||||
}>();
|
||||
|
||||
// 保存字段信息(设定好key和描述,content里的字段是随意加的会自动转为json保存到数据库)
|
||||
const settingId = ref(undefined);
|
||||
const settingKey = ref("upload");
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 编辑器内容,双向绑定
|
||||
const logo = ref<any>([]);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
// token
|
||||
const token = localStorage.getItem(TOKEN_STORE_NAME);
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Setting>({
|
||||
uploadMethod: 'file',
|
||||
fileUrl: "https://file.wsdns.cn",
|
||||
bucketName: '',
|
||||
bucketEndpoint: '',
|
||||
accessKeyId: '',
|
||||
accessKeySecret: '',
|
||||
bucketDomain: '',
|
||||
tenantId: localStorage.getItem('tenantId')
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
uploadMethod: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请设置上传方式",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
bucketName: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请填写存储空间名称",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
accessKeyId: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请填写accessKeyId",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
accessKeySecret: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请填写accessKeySecret",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
bucketDomain: [
|
||||
{
|
||||
required: true,
|
||||
type: "string",
|
||||
message: "请填写存储空间域名",
|
||||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const onCopyText = (text) => {
|
||||
copyText(text);
|
||||
};
|
||||
|
||||
const onApiclientKey = (e) => {
|
||||
const response = e.file.response
|
||||
const parse = JSON.parse(response);
|
||||
console.log(parse);
|
||||
form.apiclientKey = e.file.response
|
||||
}
|
||||
|
||||
const onUpload = (d: ItemType) => {
|
||||
uploadFile(<File>d.file)
|
||||
.then((result) => {
|
||||
form.logo = result.path;
|
||||
message.success("上传成功");
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
form.logo = undefined;
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
console.log(form);
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
content: JSON.stringify(form)
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateSetting : addSetting;
|
||||
saveOrUpdate(appForm)
|
||||
.then((msg) => {
|
||||
message.success("保存成功");
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if (data?.settingId) {
|
||||
isUpdate.value = true;
|
||||
// 表单赋值
|
||||
if (data.content) {
|
||||
const jsonData = JSON.parse(data.content);
|
||||
assignFields(jsonData);
|
||||
}
|
||||
// 其他必要参数
|
||||
form.settingId = data.settingId;
|
||||
form.settingKey = data.settingKey;
|
||||
} else {
|
||||
// 新增
|
||||
isUpdate.value = false;
|
||||
resetFields();
|
||||
form.settingKey = props.value;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.small {
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 14px !important;
|
||||
}
|
||||
</style>
|
||||
176
src/views/system/setting/components/wx-official.vue
Normal file
176
src/views/system/setting/components/wx-official.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 3, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 9, sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item label="开发者ID(AppID)" name="appId">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
placeholder="请输入开发者ID"
|
||||
v-model:value="form.appId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="开发者秘钥(AppSecret)" name="appSecret">
|
||||
<a-input-password
|
||||
:maxlength="100"
|
||||
placeholder="请输入开发者秘钥AppSecret"
|
||||
v-model:value="form.appSecret"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="原始ID" name="originalId">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入原始ID"
|
||||
v-model:value="form.originalId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="微信号" name="wxOfficialAccount">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入微信号"
|
||||
v-model:value="form.wxOfficialAccount"
|
||||
/>
|
||||
</a-form-item>
|
||||
<div style="margin-bottom: 22px; width: 750px">
|
||||
<a-divider>网页授权域名</a-divider>
|
||||
</div>
|
||||
<a-form-item label="网页授权域名" name="authorize">
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
:value="`https://server.gxwebsoft.com`"
|
||||
placeholder="请输入网页授权域名"
|
||||
style="width: calc(100% - 50px)"
|
||||
/>
|
||||
<a-tooltip title="复制">
|
||||
<a-button @click="onCopyText(`https://server.gxwebsoft.com`)">
|
||||
<template #icon><CopyOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-input-group>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
style="margin-top: 10px"
|
||||
@click="save"
|
||||
>
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Setting } from '@/api/system/setting/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addSetting, updateSetting } from '@/api/system/setting';
|
||||
import { copyText } from '@/utils/common';
|
||||
import { CopyOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
value?: string;
|
||||
// 修改回显的数据
|
||||
data?: Setting | null;
|
||||
}>();
|
||||
|
||||
// const emit = defineEmits<{
|
||||
// (e: 'done', value): void;
|
||||
// }>();
|
||||
|
||||
// 保存字段信息(设定好key和描述,content里的字段是随意加的会自动转为json保存到数据库)
|
||||
// const settingId = ref(null);
|
||||
// const settingKey = ref('');
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Setting>({
|
||||
appId: '',
|
||||
appSecret: '',
|
||||
wxOfficialAccount: '',
|
||||
originalId: '',
|
||||
tenantId: localStorage.getItem('tenantId')
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
chatKey: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入KEY',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
content: JSON.stringify(form)
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateSetting : addSetting;
|
||||
saveOrUpdate(appForm)
|
||||
.then(() => {
|
||||
message.success('保存成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const onCopyText = (text) => {
|
||||
copyText(text);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if (data?.settingId) {
|
||||
isUpdate.value = true;
|
||||
// 表单赋值
|
||||
if (data.content) {
|
||||
const jsonData = JSON.parse(data.content);
|
||||
assignFields(jsonData);
|
||||
}
|
||||
// 其他必要参数
|
||||
form.settingId = data.settingId;
|
||||
form.settingKey = data.settingKey;
|
||||
} else {
|
||||
// 新增
|
||||
isUpdate.value = false;
|
||||
resetFields();
|
||||
form.settingKey = props.value;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
210
src/views/system/setting/components/wx-work.vue
Normal file
210
src/views/system/setting/components/wx-work.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 3, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { md: 9, sm: 19, xs: 24 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item label="SuiteID" name="suiteId">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
placeholder="请输入SuiteID"
|
||||
v-model:value="form.suiteId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="Secret" name="secret">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
placeholder="请输入Secret"
|
||||
v-model:value="form.secret"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="CorpId" name="corpId">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
placeholder="请输入CorpId"
|
||||
v-model:value="form.corpId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="Token" name="token">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
placeholder="请输入Token"
|
||||
v-model:value="form.token"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="EncodingAESKey" name="encodingAESKey">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
placeholder="请输入EncodingAESKey"
|
||||
v-model:value="form.encodingAESKey"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- <a-form-item label="开发者秘钥(AppSecret)" name="appSecret">-->
|
||||
<!-- <a-input-password-->
|
||||
<!-- :maxlength="100"-->
|
||||
<!-- placeholder="请输入开发者秘钥AppSecret"-->
|
||||
<!-- v-model:value="form.appSecret"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="原始ID" name="originalId">-->
|
||||
<!-- <a-input-->
|
||||
<!-- allow-clear-->
|
||||
<!-- :maxlength="20"-->
|
||||
<!-- placeholder="请输入原始ID"-->
|
||||
<!-- v-model:value="form.originalId"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="微信号" name="wxOfficialAccount">-->
|
||||
<!-- <a-input-->
|
||||
<!-- allow-clear-->
|
||||
<!-- :maxlength="20"-->
|
||||
<!-- placeholder="请输入微信号"-->
|
||||
<!-- v-model:value="form.wxOfficialAccount"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <div style="margin-bottom: 22px; width: 750px">-->
|
||||
<!-- <a-divider>网页授权域名</a-divider>-->
|
||||
<!-- </div>-->
|
||||
<a-form-item label="指令回调地址" name="authorize">
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
:value="`https://admin.gxwebsoft.com/api/open/wx-work`"
|
||||
placeholder="请输入网页授权域名"
|
||||
style="width: calc(100% - 50px)"
|
||||
/>
|
||||
<a-tooltip title="复制">
|
||||
<a-button @click="onCopyText(`https://admin.gxwebsoft.com/api/open/wx-work`)">
|
||||
<template #icon><CopyOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-input-group>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
style="margin-top: 10px"
|
||||
@click="save"
|
||||
>
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Setting } from '@/api/system/setting/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addSetting, updateSetting } from '@/api/system/setting';
|
||||
import { copyText } from '@/utils/common';
|
||||
import { CopyOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
value?: string;
|
||||
// 修改回显的数据
|
||||
data?: Setting | null;
|
||||
}>();
|
||||
|
||||
// const emit = defineEmits<{
|
||||
// (e: 'done', value): void;
|
||||
// }>();
|
||||
|
||||
// 保存字段信息(设定好key和描述,content里的字段是随意加的会自动转为json保存到数据库)
|
||||
// const settingId = ref(null);
|
||||
// const settingKey = ref('');
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Setting>({
|
||||
suiteId: '',
|
||||
secret: '',
|
||||
corpId: '',
|
||||
token: '',
|
||||
encodingAESKey: '',
|
||||
tenantId: localStorage.getItem('tenantId')
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
chatKey: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入KEY',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const appForm = {
|
||||
...form,
|
||||
content: JSON.stringify(form)
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateSetting : addSetting;
|
||||
saveOrUpdate(appForm)
|
||||
.then(() => {
|
||||
message.success('保存成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const onCopyText = (text) => {
|
||||
copyText(text);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if (data?.settingId) {
|
||||
isUpdate.value = true;
|
||||
// 表单赋值
|
||||
if (data.content) {
|
||||
const jsonData = JSON.parse(data.content);
|
||||
assignFields(jsonData);
|
||||
}
|
||||
// 其他必要参数
|
||||
form.settingId = data.settingId;
|
||||
form.settingKey = data.settingKey;
|
||||
} else {
|
||||
// 新增
|
||||
isUpdate.value = false;
|
||||
resetFields();
|
||||
form.settingKey = props.value;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
89
src/views/system/setting/index.vue
Normal file
89
src/views/system/setting/index.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div class="ele-body ele-body-card">
|
||||
<a-card :bordered="false" class="user-info-tabs" style="min-height: 700px">
|
||||
<a-tabs v-model:active-key="active" type="card">
|
||||
<a-tab-pane tab="基本设置" key="setting">
|
||||
<Basic v-model:value="active" :data="data" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="上传设置" key="upload">
|
||||
<Upload v-model:value="active" :data="data" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="短信设置" key="sms">
|
||||
<Sms v-model:value="active" :data="data" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="支付设置" key="payment">
|
||||
<Payment :value="active" :data="data" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="注册设置" key="register">
|
||||
<Register :value="active" :data="data" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="企业微信" key="wx-work">
|
||||
<WxWork :value="active" :data="data" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="微信公众号" key="wx-official">
|
||||
<WxOfficial :value="active" :data="data" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="微信小程序" key="mp-weixin">
|
||||
<MpWeixin :value="active" :data="data" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="chatGPT" key="chatGPT">
|
||||
<ChatGPT :value="active" :data="data" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="打印设置" key="printer">
|
||||
<Printer :value="active" :data="data" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="更新缓存" key="clear">
|
||||
<Clear :value="active" :data="data" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
// import { listSetting } from '@/api/system/setting';
|
||||
import { Setting } from '@/api/system/setting/model';
|
||||
import Basic from './components/basic.vue';
|
||||
import Upload from './components/upload.vue';
|
||||
import Register from './components/register.vue';
|
||||
import WxWork from './components/wx-work.vue';
|
||||
import WxOfficial from './components/wx-official.vue';
|
||||
import MpWeixin from './components/mp-weixin.vue';
|
||||
import Payment from './components/payment.vue';
|
||||
import Sms from './components/sms.vue';
|
||||
import ChatGPT from './components/chatGPT.vue';
|
||||
import Printer from './components/printer.vue';
|
||||
import Clear from './components/clear.vue';
|
||||
import { listSetting } from '@/api/system/setting';
|
||||
|
||||
// tab页选中
|
||||
const active = ref('setting');
|
||||
|
||||
const data = ref<Setting>();
|
||||
|
||||
const reload = () => {
|
||||
listSetting({ settingKey: active.value }).then((res) => {
|
||||
if (res.length > 0) {
|
||||
data.value = res[0];
|
||||
} else {
|
||||
data.value = {};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
reload();
|
||||
|
||||
watch(
|
||||
() => active.value,
|
||||
(value) => {
|
||||
reload();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Setting'
|
||||
};
|
||||
</script>
|
||||
118
src/views/system/tenant/components/field.vue
Normal file
118
src/views/system/tenant/components/field.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
width="500px"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="`修改价格`"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form layout="horizontal">
|
||||
<a-form-item>
|
||||
<a-input-number :min="0" style="width: 200px" v-model:value="content" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import { updateOrder } from '@/api/order';
|
||||
import { Order } from '@/api/order/model';
|
||||
import { createOrderNo } from "@/utils/common";
|
||||
// import { reloadPageTab } from '@/utils/page-tab-util';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
data?: Order | null;
|
||||
// 修改回显的数据
|
||||
field?: string | null;
|
||||
orderId?: number | 0;
|
||||
content?: number | 0;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
const content = ref<number>(0);
|
||||
const placeholder = ref('请输入订单金额');
|
||||
// 用户信息
|
||||
const form = reactive<Order>({
|
||||
orderId: 0,
|
||||
comments: '',
|
||||
payPrice: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const { resetFields, validate } = useForm(form);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
// 判断更新字段
|
||||
form.orderId = props.orderId;
|
||||
if (props.field === 'payPrice') {
|
||||
form.payPrice = Number(content.value);
|
||||
form.totalPrice = Number(content.value);
|
||||
form.orderNo = createOrderNo();
|
||||
}
|
||||
updateOrder(form)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.orderId) {
|
||||
loading.value = false;
|
||||
content.value = props.content;
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
|
||||
if (props.field == 'tenantCode') {
|
||||
placeholder.value = '请输入要绑定的主体编号';
|
||||
content.value = undefined;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="less">
|
||||
.tab-pane {
|
||||
min-height: 300px;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user