重新整理仓库
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/passport/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>
|
||||
148
src/views/system/access-key/index.vue
Normal file
148
src/views/system/access-key/index.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<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';
|
||||
|
||||
// 表格实例
|
||||
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<any>(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?: any) => {
|
||||
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>
|
||||
39
src/views/system/admin/components/org-select.vue
Normal file
39
src/views/system/admin/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>
|
||||
71
src/views/system/admin/components/role-select.vue
Normal file
71
src/views/system/admin/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>
|
||||
42
src/views/system/admin/components/search.vue
Normal file
42
src/views/system/admin/components/search.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<!-- 搜索表单 -->
|
||||
<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-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
45
src/views/system/admin/components/sex-select.vue
Normal file
45
src/views/system/admin/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>
|
||||
299
src/views/system/admin/components/user-edit.vue
Normal file
299
src/views/system/admin/components/user-edit.vue
Normal file
@@ -0,0 +1,299 @@
|
||||
<!-- 管理员编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="500"
|
||||
: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: 4, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 17, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="姓名" name="realName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
:disabled="isUpdate"
|
||||
placeholder="请输入真实姓名"
|
||||
v-model:value="form.realName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="11"
|
||||
:disabled="isUpdate"
|
||||
placeholder="请输入手机号"
|
||||
v-model:value="form.phone"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="角色" name="roles">
|
||||
<role-select v-model:value="form.roles" />
|
||||
</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="性别" name="sex">
|
||||
<DictSelect
|
||||
dict-code="sex"
|
||||
:placeholder="`请选择性别`"
|
||||
v-model:value="form.sexName"
|
||||
@done="chooseSex"
|
||||
/>
|
||||
</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="所属机构" name="type">
|
||||
<org-select
|
||||
:data="organizationList"
|
||||
placeholder="请选择所属机构"
|
||||
v-model:value="form.organizationId"
|
||||
/>
|
||||
</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 { 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 RoleSelect from './role-select.vue';
|
||||
import { addUser, updateUser, checkExistence } from '@/api/system/user';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
import OrgSelect from './org-select.vue';
|
||||
// import { getDictionaryOptions } from '@/utils/common';
|
||||
import { Organization } from '@/api/system/organization/model';
|
||||
import { Grade } from '@/api/user/grade/model';
|
||||
import {TEMPLATE_ID} from "@/config/setting";
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 获取字典数据
|
||||
// const userTypeData = getDictionaryOptions('userType');
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
// 全部机构
|
||||
organizationList: Organization[];
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<User>({
|
||||
type: undefined,
|
||||
userId: undefined,
|
||||
username: '',
|
||||
nickname: '',
|
||||
realName: '',
|
||||
companyName: '',
|
||||
sex: undefined,
|
||||
sexName: undefined,
|
||||
roles: [],
|
||||
email: '',
|
||||
phone: '',
|
||||
mobile: '',
|
||||
password: '',
|
||||
introduction: '',
|
||||
organizationId: undefined,
|
||||
birthday: '',
|
||||
idCard: '',
|
||||
comments: '',
|
||||
gradeName: '',
|
||||
isAdmin: true,
|
||||
gradeId: undefined,
|
||||
templateId: TEMPLATE_ID
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
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'
|
||||
// }
|
||||
// ],
|
||||
roles: [
|
||||
{
|
||||
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: [
|
||||
{
|
||||
required: true,
|
||||
pattern: phoneReg,
|
||||
message: '手机号格式不正确',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseGradeId = (data: Grade) => {
|
||||
form.gradeName = data.name;
|
||||
form.gradeId = data.gradeId;
|
||||
};
|
||||
|
||||
const chooseSex = (data: any) => {
|
||||
form.sex = data.key;
|
||||
form.sexName = data.label;
|
||||
};
|
||||
|
||||
const updateIsAdmin = (value: boolean) => {
|
||||
form.isAdmin = value;
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const saveOrUpdate = isUpdate.value ? updateUser : addUser;
|
||||
form.username = form.phone;
|
||||
form.nickname = form.realName;
|
||||
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,
|
||||
password: ''
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
88
src/views/system/admin/components/user-import.vue
Normal file
88
src/views/system/admin/components/user-import.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<!-- 用户导入弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="520"
|
||||
:footer="null"
|
||||
title="导入用户"
|
||||
:visible="visible"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<a-spin :spinning="loading">
|
||||
<a-upload-dragger
|
||||
accept=".xls,.xlsx"
|
||||
:show-upload-list="false"
|
||||
:customRequest="doUpload"
|
||||
style="padding: 24px 0; margin-bottom: 16px"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<cloud-upload-outlined />
|
||||
</p>
|
||||
<p class="ant-upload-hint">将文件拖到此处,或点击上传</p>
|
||||
</a-upload-dragger>
|
||||
</a-spin>
|
||||
<div class="ele-text-center">
|
||||
<span>只能上传xls、xlsx文件,</span>
|
||||
<a
|
||||
href="https://cdn.eleadmin.com/20200610/用户导入模板.xlsx"
|
||||
download="用户导入模板.xlsx"
|
||||
>
|
||||
下载模板
|
||||
</a>
|
||||
</div>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { CloudUploadOutlined } from '@ant-design/icons-vue';
|
||||
import { importUsers } from '@/api/system/user';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
// 是否打开弹窗
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
// 导入请求状态
|
||||
const loading = ref(false);
|
||||
|
||||
/* 上传 */
|
||||
const doUpload = ({ file }) => {
|
||||
if (
|
||||
![
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
].includes(file.type)
|
||||
) {
|
||||
message.error('只能选择 excel 文件');
|
||||
return false;
|
||||
}
|
||||
if (file.size / 1024 / 1024 > 10) {
|
||||
message.error('大小不能超过 10MB');
|
||||
return false;
|
||||
}
|
||||
loading.value = true;
|
||||
importUsers(file)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
/* 更新 visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
</script>
|
||||
143
src/views/system/admin/components/user-info.vue
Normal file
143
src/views/system/admin/components/user-info.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<a-drawer
|
||||
:width="680"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="'基本信息'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
:footer="null"
|
||||
>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 6 }, sm: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 19 }, sm: { span: 24 } }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="账号">
|
||||
<span class="ele-text">{{ user.username }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="昵称">
|
||||
<span class="ele-text">{{ user.nickname }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="性别">
|
||||
<span class="ele-text">{{ user.sexName }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号">
|
||||
<span class="ele-text">{{ user.phone }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="角色">
|
||||
<a-tag v-for="item in user.roles" :key="item.roleId" color="blue">
|
||||
{{ item.roleName }}
|
||||
</a-tag>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-badge
|
||||
v-if="typeof user.status === 'number'"
|
||||
:status="(['processing', 'error'][user.status] as any)"
|
||||
:text="['正常', '冻结'][user.status]"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="地址">
|
||||
<span class="ele-text">{{ user.address }}</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="可用余额">
|
||||
<span class="ele-text-success">¥{{ formatNumber(user.balance) }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="可用积分">
|
||||
<span class="ele-text">{{ user.points }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="实际消费">
|
||||
<span class="ele-text">{{ user.payMoney }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="机构/部门">
|
||||
<span class="ele-text">{{ user.organizationName }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="头像">
|
||||
<a-image :src="user.avatar" :width="36" />
|
||||
</a-form-item>
|
||||
<a-form-item label="生日">
|
||||
<span class="ele-text">{{ user.birthday }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="创建时间">
|
||||
<span class="ele-text">{{ user.createTime }}</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { formatNumber } from 'ele-admin-pro/es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 用户信息
|
||||
const user = reactive<User>({
|
||||
username: '',
|
||||
nickname: '',
|
||||
sexName: '',
|
||||
phone: '',
|
||||
avatar: '',
|
||||
balance: undefined,
|
||||
points: 0,
|
||||
payMoney: 0,
|
||||
birthday: '',
|
||||
address: '',
|
||||
roles: [],
|
||||
createTime: undefined,
|
||||
status: undefined
|
||||
});
|
||||
|
||||
// 请求状态
|
||||
const loading = ref(true);
|
||||
|
||||
const { resetFields } = useForm(user);
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
loading.value = false;
|
||||
assignObject(user, props.data);
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
111
src/views/system/admin/components/user-search.vue
Normal file
111
src/views/system/admin/components/user-search.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<!-- 搜索表单 -->
|
||||
<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-select v-model:value="form.sex" placeholder="请选择" allow-clear>
|
||||
<a-select-option value="1">男</a-select-option>
|
||||
<a-select-option value="2">女</a-select-option>
|
||||
</a-select>
|
||||
</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 { UserParam } from '@/api/system/user/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 默认搜索条件
|
||||
where?: UserParam;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: UserParam): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields } = useFormData<UserParam>({
|
||||
username: '',
|
||||
nickname: '',
|
||||
sex: undefined,
|
||||
...props.where
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', form);
|
||||
};
|
||||
|
||||
/* 重置 */
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
search();
|
||||
};
|
||||
</script>
|
||||
274
src/views/system/admin/components/userEdit.vue
Normal file
274
src/views/system/admin/components/userEdit.vue
Normal file
@@ -0,0 +1,274 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑' : '添加'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="用户唯一小程序id" name="openId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入用户唯一小程序id"
|
||||
v-model:value="form.openId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="小程序用户秘钥" name="sessionKey">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入小程序用户秘钥"
|
||||
v-model:value="form.sessionKey"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="用户名" name="username">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入用户名"
|
||||
v-model:value="form.username"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="头像地址" name="avatarUrl">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入头像地址"
|
||||
v-model:value="form.avatarUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="1男,2女" name="gender">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入1男,2女"
|
||||
v-model:value="form.gender"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="国家" name="country">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入国家"
|
||||
v-model:value="form.country"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="省份" name="province">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入省份"
|
||||
v-model:value="form.province"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="城市" name="city">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入城市"
|
||||
v-model:value="form.city"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号码" name="phone">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入手机号码"
|
||||
v-model:value="form.phone"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="积分" name="integral">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入积分"
|
||||
v-model:value="form.integral"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="余额" name="money">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入余额"
|
||||
v-model:value="form.money"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="idcard">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.idcard"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="truename">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.truename"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否管理员:1是;2否" name="isAdmin">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否管理员:1是;2否"
|
||||
v-model:value="form.isAdmin"
|
||||
/>
|
||||
</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, uuid } from 'ele-admin-pro';
|
||||
import { addUser, updateUser } from '@/api/system/user';
|
||||
import { User } from '@/api/system/user/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<User>({
|
||||
id: undefined,
|
||||
openId: undefined,
|
||||
sessionKey: undefined,
|
||||
username: undefined,
|
||||
avatarUrl: undefined,
|
||||
gender: undefined,
|
||||
country: undefined,
|
||||
province: undefined,
|
||||
city: undefined,
|
||||
phone: undefined,
|
||||
integral: undefined,
|
||||
money: undefined,
|
||||
createTime: undefined,
|
||||
idcard: undefined,
|
||||
truename: undefined,
|
||||
isAdmin: undefined,
|
||||
tenantId: undefined,
|
||||
userId: undefined,
|
||||
userName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
userName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateUser : addUser;
|
||||
saveOrUpdate(formData)
|
||||
.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) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if(props.data.image){
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
})
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
130
src/views/system/admin/details/index.vue
Normal file
130
src/views/system/admin/details/index.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card title="基本信息" :bordered="false">
|
||||
<a-form
|
||||
class="ele-form-detail"
|
||||
:label-col="
|
||||
styleResponsive ? { md: 2, sm: 4, xs: 6 } : { flex: '90px' }
|
||||
"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 22, sm: 20, xs: 18 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="账号">
|
||||
<div class="ele-text-secondary">{{ form.username }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="昵称">
|
||||
<div class="ele-text-secondary">{{ form.nickname }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="性别">
|
||||
<div class="ele-text-secondary">{{ form.sexName }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号">
|
||||
<div class="ele-text-secondary">{{ form.phone }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="真实姓名">
|
||||
<div class="ele-text-secondary">{{ form.realName }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="别名">
|
||||
<div class="ele-text-secondary">{{ form.alias }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="角色">
|
||||
<a-tag v-for="item in form.roles" :key="item.roleId" color="blue">
|
||||
{{ item.roleName }}
|
||||
</a-tag>
|
||||
</a-form-item>
|
||||
<a-form-item label="创建时间">
|
||||
<div class="ele-text-secondary">{{ form.createTime }}</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-badge
|
||||
v-if="typeof form.status === 'number'"
|
||||
:status="(['processing', 'error'][form.status] as any)"
|
||||
:text="['正常', '冻结'][form.status]"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, unref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { toDateString } from 'ele-admin-pro/es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { setPageTabTitle } from '@/utils/page-tab-util';
|
||||
import { getUser } from '@/api/system/user';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
const ROUTE_PATH = '/system/user/details';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const { currentRoute } = useRouter();
|
||||
|
||||
// 用户信息
|
||||
const { form, assignFields } = useFormData<User>({
|
||||
userId: undefined,
|
||||
alias: '',
|
||||
realName: '',
|
||||
username: '',
|
||||
nickname: '',
|
||||
sexName: '',
|
||||
phone: '',
|
||||
roles: [],
|
||||
createTime: undefined,
|
||||
status: undefined
|
||||
});
|
||||
|
||||
// 请求状态
|
||||
const loading = ref(true);
|
||||
|
||||
/* */
|
||||
const query = () => {
|
||||
const { query } = unref(currentRoute);
|
||||
const id = query.id;
|
||||
if (!id || form.userId === Number(id)) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
getUser(Number(id))
|
||||
.then((data) => {
|
||||
loading.value = false;
|
||||
assignFields({
|
||||
...data,
|
||||
createTime: toDateString(data.createTime)
|
||||
});
|
||||
// 修改页签标题
|
||||
if (unref(currentRoute).path === ROUTE_PATH) {
|
||||
setPageTabTitle(data.nickname + '的信息');
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
currentRoute,
|
||||
(route) => {
|
||||
const { path } = unref(route);
|
||||
if (path !== ROUTE_PATH) {
|
||||
return;
|
||||
}
|
||||
query();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemUserDetails'
|
||||
};
|
||||
</script>
|
||||
434
src/views/system/admin/index.vue
Normal file
434
src/views/system/admin/index.vue
Normal file
@@ -0,0 +1,434 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<template #extra>
|
||||
<Extra/>
|
||||
</template>
|
||||
<a-card :bordered="false">
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="userId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:scroll="{ x: 1300 }"
|
||||
:where="defaultWhere"
|
||||
:customRow="customRow"
|
||||
cache-key="proSystemUserTable"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="openEdit()">
|
||||
<template #icon>
|
||||
<plus-outlined/>
|
||||
</template>
|
||||
<span>新建</span>
|
||||
</a-button>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入关键词"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'avatar'">
|
||||
<a-avatar
|
||||
:size="30"
|
||||
:src="`${record.avatar}`"
|
||||
style="margin-right: 4px"
|
||||
>
|
||||
<template #icon>
|
||||
<UserOutlined/>
|
||||
</template>
|
||||
</a-avatar>
|
||||
</template>
|
||||
<template v-if="column.key === 'nickname'">
|
||||
<span>{{ record.nickname }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'mobile'">
|
||||
<span v-if="hasRole('superAdmin')">{{ record.phone }}</span>
|
||||
<span v-else>{{ record.mobile }}</span>
|
||||
</template>
|
||||
<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-if="column.key === 'platform'">
|
||||
<WechatOutlined v-if="record.platform === 'MP-WEIXIN'"/>
|
||||
<Html5Outlined v-if="record.platform === 'H5'"/>
|
||||
<ChromeOutlined v-if="record.platform === 'WEB'"/>
|
||||
</template>
|
||||
<template v-if="column.key === 'balance'">
|
||||
<span class="ele-text-success">
|
||||
¥{{ formatNumber(record.balance) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'expendMoney'">
|
||||
<span class="ele-text-warning">
|
||||
¥{{ formatNumber(record.expendMoney) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'isAdmin'">
|
||||
<a-switch
|
||||
:checked="record.isAdmin == 1"
|
||||
@change="updateIsAdmin(record)"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<div>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="resetPsw(record)">重置</a>
|
||||
<a-divider type="vertical"/>
|
||||
<a-popconfirm
|
||||
placement="topRight"
|
||||
title="确定要删除此用户吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<user-edit
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:organization-list="data"
|
||||
@done="reload"
|
||||
/>
|
||||
<!-- 导入弹窗 -->
|
||||
<user-import v-model:visible="showImport" @done="reload"/>
|
||||
<!-- 用户详情 -->
|
||||
<user-info v-model:visible="showInfo" :data="current" @done="reload"/>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {createVNode, ref, reactive} from 'vue';
|
||||
import {message, Modal} from 'ant-design-vue/es';
|
||||
import {
|
||||
PlusOutlined,
|
||||
UserOutlined,
|
||||
Html5Outlined,
|
||||
ChromeOutlined,
|
||||
WechatOutlined,
|
||||
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, formatNumber} from 'ele-admin-pro/es';
|
||||
import UserEdit from './components/user-edit.vue';
|
||||
import UserImport from './components/user-import.vue';
|
||||
import UserInfo from './components/user-info.vue';
|
||||
import {toDateString} from 'ele-admin-pro';
|
||||
import {
|
||||
pageUsers,
|
||||
removeUser,
|
||||
removeUsers,
|
||||
updateUserPassword,
|
||||
updateUser
|
||||
} from '@/api/system/user';
|
||||
import type {User, UserParam} from '@/api/system/user/model';
|
||||
import {toTreeData, uuid} from 'ele-admin-pro';
|
||||
import {listRoles} from '@/api/system/role';
|
||||
import {listOrganizations} from '@/api/system/organization';
|
||||
import {Organization} from '@/api/system/organization/model';
|
||||
import {hasRole} from '@/utils/permission';
|
||||
import {getPageTitle} from "@/utils/common";
|
||||
import Extra from "@/views/system/user/components/Extra.vue";
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
// 树形数据
|
||||
const data = ref<Organization[]>([]);
|
||||
// 树展开的key
|
||||
const expandedRowKeys = ref<number[]>([]);
|
||||
// 树选中的key
|
||||
const selectedRowKeys = ref<number[]>([]);
|
||||
// 表格选中数据
|
||||
const selection = ref<User[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<User | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示用户详情
|
||||
const showInfo = ref(false);
|
||||
// 是否显示用户导入弹窗
|
||||
const showImport = ref(false);
|
||||
const userType = ref<number>();
|
||||
const searchText = ref('');
|
||||
|
||||
// 加载角色
|
||||
const roles = ref<any[]>([]);
|
||||
const filters = () => {
|
||||
listRoles().then((result) => {
|
||||
result.map((d) => {
|
||||
roles.value.push({
|
||||
text: d.roleName,
|
||||
value: d.roleId
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
filters();
|
||||
// 加载机构
|
||||
listOrganizations()
|
||||
.then((list) => {
|
||||
loading.value = false;
|
||||
const eks: number[] = [];
|
||||
list.forEach((d) => {
|
||||
d.key = d.organizationId;
|
||||
d.value = d.organizationId;
|
||||
d.title = d.organizationName;
|
||||
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];
|
||||
} else {
|
||||
selectedRowKeys.value = [];
|
||||
// current.value = null;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'userId',
|
||||
width: 90,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '真实姓名',
|
||||
dataIndex: 'realName',
|
||||
align: 'center',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'mobile',
|
||||
align: 'center',
|
||||
key: 'mobile',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'sexName',
|
||||
align: 'center',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '所属部门',
|
||||
dataIndex: 'organizationName',
|
||||
key: 'organizationName',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'roles',
|
||||
key: 'roles',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '注册时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
align: 'center',
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true,
|
||||
customRender: ({text}) => toDateString(text, 'yyyy-MM-dd')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 默认搜索条件
|
||||
const defaultWhere = reactive({
|
||||
username: '',
|
||||
nickname: ''
|
||||
});
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
where = {};
|
||||
where.roleId = filters.roles;
|
||||
where.keywords = searchText.value;
|
||||
where.isAdmin = 1;
|
||||
return pageUsers({page, limit, ...where, ...orders});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: UserParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({where});
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: User) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开用户详情弹窗 */
|
||||
const openInfo = (row?: User) => {
|
||||
current.value = row ?? null;
|
||||
showInfo.value = true;
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openImport = () => {
|
||||
showImport.value = true;
|
||||
};
|
||||
|
||||
const handleTabs = (e) => {
|
||||
userType.value = Number(e.target.value);
|
||||
reload();
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
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 removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的用户吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeUsers(selection.value.map((d) => d.userId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 重置用户密码 */
|
||||
const resetPsw = (row: User) => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要重置此用户的密码吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
const password = uuid(8);
|
||||
updateUserPassword(row.userId, password)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg + ',新密码:' + password);
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 修改用户状态 */
|
||||
const updateIsAdmin = (row: User) => {
|
||||
row.isAdmin = row.isAdmin ? 0 : 1;
|
||||
updateUser(row)
|
||||
.then((msg) => {
|
||||
message.success(msg);
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: User) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemAdmin'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.user-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
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/passport/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>
|
||||
@@ -0,0 +1,230 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑聊天消息表' : '添加聊天消息表'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="用户ID" name="userId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入用户ID"
|
||||
v-model:value="form.userId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="好友ID" name="friendId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入好友ID"
|
||||
v-model:value="form.friendId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="消息类型" name="type">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入消息类型"
|
||||
v-model:value="form.type"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="消息内容" name="content">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入消息内容"
|
||||
v-model:value="form.content"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="未读消息" name="unRead">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入未读消息"
|
||||
v-model:value="form.unRead"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态, 0未读, 1已读" name="status">
|
||||
<a-radio-group v-model:value="form.status">
|
||||
<a-radio :value="0">显示</a-radio>
|
||||
<a-radio :value="1">隐藏</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否删除, 0否, 1是" name="deleted">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否删除, 0否, 1是"
|
||||
v-model:value="form.deleted"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="修改时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入修改时间"
|
||||
v-model:value="form.updateTime"
|
||||
/>
|
||||
</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, uuid } from 'ele-admin-pro';
|
||||
import {
|
||||
addChatConversation,
|
||||
updateChatConversation
|
||||
} from '@/api/system/chatConversation';
|
||||
import { ChatConversation } from '@/api/system/chatConversation/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: ChatConversation | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<ChatConversation>({
|
||||
id: undefined,
|
||||
userId: undefined,
|
||||
friendId: undefined,
|
||||
type: undefined,
|
||||
content: undefined,
|
||||
unRead: undefined,
|
||||
status: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
chatConversationId: undefined,
|
||||
chatConversationName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
chatConversationName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写聊天消息表名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value
|
||||
? updateChatConversation
|
||||
: addChatConversation;
|
||||
saveOrUpdate(formData)
|
||||
.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) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if (props.data.image) {
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
});
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
42
src/views/system/chatConversation/components/search.vue
Normal file
42
src/views/system/chatConversation/components/search.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<!-- 搜索表单 -->
|
||||
<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-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
263
src/views/system/chatConversation/index.vue
Normal file
263
src/views/system/chatConversation/index.vue
Normal file
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="chatConversationId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image :src="record.image" :width="50" />
|
||||
</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>
|
||||
<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>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ChatConversationEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, 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 { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import ChatConversationEdit from './components/chatConversationEdit.vue';
|
||||
import { pageChatConversation, removeChatConversation, removeBatchChatConversation } from '@/api/system/chatConversation';
|
||||
import type { ChatConversation, ChatConversationParam } from '@/api/system/chatConversation/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<ChatConversation[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ChatConversation | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pageChatConversation({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '自增ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '用户ID',
|
||||
dataIndex: 'userId',
|
||||
key: 'userId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '好友ID',
|
||||
dataIndex: 'friendId',
|
||||
key: 'friendId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '消息类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '消息内容',
|
||||
dataIndex: 'content',
|
||||
key: 'content',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '未读消息',
|
||||
dataIndex: 'unRead',
|
||||
key: 'unRead',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '状态, 0未读, 1已读',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '是否删除, 0否, 1是',
|
||||
dataIndex: 'deleted',
|
||||
key: 'deleted',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '注册时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ChatConversationParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ChatConversation) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ChatConversation) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeChatConversation(row.chatConversationId)
|
||||
.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);
|
||||
removeBatchChatConversation(selection.value.map((d) => d.chatConversationId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ChatConversation) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ChatConversation'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
259
src/views/system/chatMessage/components/chatMessageEdit.vue
Normal file
259
src/views/system/chatMessage/components/chatMessageEdit.vue
Normal file
@@ -0,0 +1,259 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑聊天消息表' : '添加聊天消息表'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="发送人ID" name="formUserId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入发送人ID"
|
||||
v-model:value="form.formUserId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="接收人ID" name="toUserId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入接收人ID"
|
||||
v-model:value="form.toUserId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="消息类型" name="type">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入消息类型"
|
||||
v-model:value="form.type"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="消息内容" name="content">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入消息内容"
|
||||
v-model:value="form.content"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="屏蔽接收方" name="sideTo">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入屏蔽接收方"
|
||||
v-model:value="form.sideTo"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="屏蔽发送方" name="sideFrom">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入屏蔽发送方"
|
||||
v-model:value="form.sideFrom"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否撤回" name="withdraw">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否撤回"
|
||||
v-model:value="form.withdraw"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="文件信息" name="fileInfo">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入文件信息"
|
||||
v-model:value="form.fileInfo"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="存在联系方式" name="hasContact">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入存在联系方式"
|
||||
v-model:value="form.hasContact"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态, 0未读, 1已读" name="status">
|
||||
<a-radio-group v-model:value="form.status">
|
||||
<a-radio :value="0">显示</a-radio>
|
||||
<a-radio :value="1">隐藏</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否删除, 0否, 1是" name="deleted">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否删除, 0否, 1是"
|
||||
v-model:value="form.deleted"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="修改时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入修改时间"
|
||||
v-model:value="form.updateTime"
|
||||
/>
|
||||
</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, uuid } from 'ele-admin-pro';
|
||||
import { addChatMessage, updateChatMessage } from '@/api/system/chatMessage';
|
||||
import { ChatMessage } from '@/api/system/chatMessage/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: ChatMessage | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<ChatMessage>({
|
||||
id: undefined,
|
||||
formUserId: undefined,
|
||||
toUserId: undefined,
|
||||
type: undefined,
|
||||
content: undefined,
|
||||
sideTo: undefined,
|
||||
sideFrom: undefined,
|
||||
withdraw: undefined,
|
||||
fileInfo: undefined,
|
||||
hasContact: undefined,
|
||||
status: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
chatMessageId: undefined,
|
||||
chatMessageName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
chatMessageName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写聊天消息表名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value
|
||||
? updateChatMessage
|
||||
: addChatMessage;
|
||||
saveOrUpdate(formData)
|
||||
.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) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if (props.data.image) {
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
});
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
42
src/views/system/chatMessage/components/search.vue
Normal file
42
src/views/system/chatMessage/components/search.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<!-- 搜索表单 -->
|
||||
<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-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
287
src/views/system/chatMessage/index.vue
Normal file
287
src/views/system/chatMessage/index.vue
Normal file
@@ -0,0 +1,287 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="chatMessageId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image :src="record.image" :width="50" />
|
||||
</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>
|
||||
<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>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ChatMessageEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, 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 { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import ChatMessageEdit from './components/chatMessageEdit.vue';
|
||||
import { pageChatMessage, removeChatMessage, removeBatchChatMessage } from '@/api/system/chatMessage';
|
||||
import type { ChatMessage, ChatMessageParam } from '@/api/system/chatMessage/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<ChatMessage[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ChatMessage | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pageChatMessage({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '自增ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '发送人ID',
|
||||
dataIndex: 'formUserId',
|
||||
key: 'formUserId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '接收人ID',
|
||||
dataIndex: 'toUserId',
|
||||
key: 'toUserId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '消息类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '消息内容',
|
||||
dataIndex: 'content',
|
||||
key: 'content',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '屏蔽接收方',
|
||||
dataIndex: 'sideTo',
|
||||
key: 'sideTo',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '屏蔽发送方',
|
||||
dataIndex: 'sideFrom',
|
||||
key: 'sideFrom',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '是否撤回',
|
||||
dataIndex: 'withdraw',
|
||||
key: 'withdraw',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '文件信息',
|
||||
dataIndex: 'fileInfo',
|
||||
key: 'fileInfo',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '存在联系方式',
|
||||
dataIndex: 'hasContact',
|
||||
key: 'hasContact',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '状态, 0未读, 1已读',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '是否删除, 0否, 1是',
|
||||
dataIndex: 'deleted',
|
||||
key: 'deleted',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '注册时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ChatMessageParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ChatMessage) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ChatMessage) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeChatMessage(row.chatMessageId)
|
||||
.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);
|
||||
removeBatchChatMessage(selection.value.map((d) => d.chatMessageId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ChatMessage) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ChatMessage'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
23
src/views/system/demo/index.vue
Normal file
23
src/views/system/demo/index.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const top = ref<number>(10);
|
||||
const bottom = ref<number>(10);
|
||||
|
||||
const change = (affixed: boolean) => {
|
||||
console.log(affixed);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card title="测试页面" style="height: 2000px">
|
||||
收到佛山市
|
||||
<a-affix :offset-top="120" @change="change">
|
||||
<a-button>120px to affix top</a-button>
|
||||
</a-affix>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
176
src/views/system/dict/components/dict-data-edit.vue
Normal file
176
src/views/system/dict/components/dict-data-edit.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<!-- 字典项编辑弹窗 -->
|
||||
<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-divider style="margin-bottom: 20px" />
|
||||
<a-form-item label="预留字段" v-bind="validateInfos.component">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="100"
|
||||
placeholder="预设字段:组件路径(选填)"
|
||||
v-model:value="form.component"
|
||||
/>
|
||||
</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: '',
|
||||
path: '',
|
||||
component: '',
|
||||
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>
|
||||
195
src/views/system/dict/index.vue
Normal file
195
src/views/system/dict/index.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<template #extra>
|
||||
<CmsWebsiteSearch />
|
||||
</template>
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-split-layout
|
||||
width="366px"
|
||||
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'">
|
||||
<a-space>
|
||||
<a-tooltip>
|
||||
<template #title>
|
||||
{{ record.dictId }}
|
||||
</template>
|
||||
{{ record.dictName }}
|
||||
</a-tooltip>
|
||||
<span class="ele-text-placeholder">
|
||||
{{ record.dictCode }}
|
||||
</span>
|
||||
</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" />
|
||||
</a-page-header>
|
||||
</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';
|
||||
import {getPageTitle} from "@/utils/common";
|
||||
import CmsWebsiteSearch from "@/views/cms/cmsWebsite/components/search.vue";
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
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>
|
||||
215
src/views/system/dict/list.vue
Normal file
215
src/views/system/dict/list.vue
Normal file
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<template #extra>
|
||||
<CmsWebsiteSearch />
|
||||
</template>
|
||||
<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" />
|
||||
</a-page-header>
|
||||
</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';
|
||||
import {getPageTitle} from "@/utils/common";
|
||||
import CmsWebsiteSearch from "@/views/cms/cmsWebsite/components/search.vue";
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 50,
|
||||
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>
|
||||
181
src/views/system/dictionary/components/dict-data-edit.vue
Normal file
181
src/views/system/dictionary/components/dict-data-edit.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<!-- 字典项编辑弹窗 -->
|
||||
<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-divider style="margin-bottom: 20px" />
|
||||
<a-form-item label="预留字段" v-bind="validateInfos.component">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="100"
|
||||
placeholder="预设字段:组件路径(选填)"
|
||||
v-model:value="form.component"
|
||||
/>
|
||||
</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: '',
|
||||
path: '',
|
||||
component: '',
|
||||
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>
|
||||
203
src/views/system/dictionary/index.vue
Normal file
203
src/views/system/dictionary/index.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-split-layout
|
||||
width="366px"
|
||||
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>
|
||||
<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: 'ID',
|
||||
dataIndex: 'dictId',
|
||||
key: 'dictId',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
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>
|
||||
193
src/views/system/domain/components/domainEdit.vue
Normal file
193
src/views/system/domain/components/domainEdit.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑域名' : '授权域名'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="域名" name="domain">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入域名"
|
||||
v-model:value="form.domain"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="主机记录" name="hostName">
|
||||
<a-tag>{{ form.hostName }}</a-tag>
|
||||
</a-form-item>
|
||||
<a-form-item label="记录值" name="hostValue">
|
||||
<a-tag>{{ form.hostValue }}</a-tag>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-radio-group v-model:value="form.status">
|
||||
<a-radio :value="1">已通过</a-radio>
|
||||
<a-radio :value="0">驳回</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="驳回原因" name="comments" v-if="form.status == 0">
|
||||
<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, uuid } from 'ele-admin-pro';
|
||||
import { addDomain, updateDomain } from '@/api/system/domain';
|
||||
import { Domain } from '@/api/system/domain/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Domain | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<Domain>({
|
||||
id: undefined,
|
||||
domain: undefined,
|
||||
hostName: undefined,
|
||||
hostValue: undefined,
|
||||
sortNumber: undefined,
|
||||
userId: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
status: 0,
|
||||
comments: ''
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
domainName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写授权域名名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateDomain : addDomain;
|
||||
saveOrUpdate(formData)
|
||||
.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) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if (props.data.image) {
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
});
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
53
src/views/system/domain/components/search.vue
Normal file
53
src/views/system/domain/components/search.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="where.keywords"
|
||||
placeholder="租户ID|域名"
|
||||
@search="search"
|
||||
@pressEnter="search"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { watch } from 'vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { DomainParam } from '@/api/system/domain/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: DomainParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', where);
|
||||
};
|
||||
|
||||
// 表单数据
|
||||
const { where } = useSearch<DomainParam>({
|
||||
keywords: ''
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
250
src/views/system/domain/index.vue
Normal file
250
src/views/system/domain/index.vue
Normal file
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="domainId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'domain'">
|
||||
<text
|
||||
@click="
|
||||
openSpmUrl(
|
||||
`https://${record.domain}`,
|
||||
record,
|
||||
record.tenantId
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ record.domain }}
|
||||
</text>
|
||||
</template>
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image :src="record.image" :width="50" />
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 1" color="green">已通过</a-tag>
|
||||
<a-tag v-if="record.status === 0" color="red">待审核</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>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<DomainEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, 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 { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import DomainEdit from './components/domainEdit.vue';
|
||||
import {
|
||||
pageDomain,
|
||||
removeDomain,
|
||||
removeBatchDomain
|
||||
} from '@/api/system/domain';
|
||||
import type { Domain, DomainParam } from '@/api/system/domain/model';
|
||||
import { openSpmUrl } from '@/utils/common';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<Domain[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<Domain | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pageDomain({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '租户ID',
|
||||
dataIndex: 'tenantId',
|
||||
key: 'tenantId',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '域名',
|
||||
dataIndex: 'domain',
|
||||
key: 'domain',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '租户名称',
|
||||
dataIndex: 'tenantName',
|
||||
key: 'tenantName',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: DomainParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Domain) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: Domain) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeDomain(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 = message.loading('请求中..', 0);
|
||||
removeBatchDomain(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Domain) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Domain'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
17
src/views/system/exception/403/index.vue
Normal file
17
src/views/system/exception/403/index.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div style="padding-top: 80px">
|
||||
<a-result status="403" title="403" sub-title="抱歉, 你无权访问该页面.">
|
||||
<template #extra>
|
||||
<router-link to="/">
|
||||
<a-button type="primary">返回首页</a-button>
|
||||
</router-link>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Exception403'
|
||||
};
|
||||
</script>
|
||||
70
src/views/system/exception/404/index.vue
Normal file
70
src/views/system/exception/404/index.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false">
|
||||
<div style="max-width: 960px; margin: 0 auto">
|
||||
<a-result
|
||||
status="error"
|
||||
title="404"
|
||||
sub-title="您访问的页面走丢了"
|
||||
>
|
||||
<!-- <div>无访问原因如下:</div>-->
|
||||
<!-- <div class="error-tips-item">-->
|
||||
<!-- <close-circle-outlined class="ele-text-danger" />-->
|
||||
<!-- <div>您的账户已被冻结</div>-->
|
||||
<!-- <a>立即解冻></a>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="error-tips-item">-->
|
||||
<!-- <close-circle-outlined class="ele-text-danger" />-->
|
||||
<!-- <div>您的账户还不具备申请资格</div>-->
|
||||
<!-- <a>立即咨询></a>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="error-tips-item">-->
|
||||
<!-- <close-circle-outlined class="ele-text-danger" />-->
|
||||
<!-- <div>您的站点已过期</div>-->
|
||||
<!-- <a>立即续费></a>-->
|
||||
<!-- </div>-->
|
||||
<template #extra>
|
||||
<a-space size="middle">
|
||||
<a-button type="primary" @click="openUrl('/')">返回首页</a-button>
|
||||
<!-- <a-button @click="openNew('https://www.gxwebsoft.com')">技术支持</a-button>-->
|
||||
</a-space>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { CloseCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { openNew, openUrl } from '@/utils/common';
|
||||
|
||||
console.log('>>>>>');
|
||||
|
||||
const reload = () => {
|
||||
return false;
|
||||
};
|
||||
reload();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ResultFail'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.error-tips-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 16px;
|
||||
|
||||
& > div {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
17
src/views/system/exception/500/index.vue
Normal file
17
src/views/system/exception/500/index.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div style="padding-top: 80px">
|
||||
<a-result status="500" title="500" sub-title="抱歉, 服务器出错了.">
|
||||
<template #extra>
|
||||
<router-link to="/">
|
||||
<a-button type="primary">返回首页</a-button>
|
||||
</router-link>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Exception500'
|
||||
};
|
||||
</script>
|
||||
237
src/views/system/field/components/edit.vue
Normal file
237
src/views/system/field/components/edit.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="600"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="isUpdate ? '编辑参数' : '添加参数'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="{ md: { span: 3 }, sm: { span: 4 }, xs: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 21 }, sm: { span: 22 }, xs: { span: 24 } }"
|
||||
>
|
||||
<a-form-item label="参数" name="comments">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="100"
|
||||
placeholder="网站名称"
|
||||
v-model:value="form.comments"
|
||||
@pressEnter="save"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="调用" name="name">
|
||||
<SelectWebsiteField
|
||||
:placeholder="`可配置参数`"
|
||||
class="input-item"
|
||||
v-model:value="form.name"
|
||||
@done="chooseData"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="配置值" name="value">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="网站名称"
|
||||
v-model:value="form.value"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="css样式" name="style">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="css样式"
|
||||
v-model:value="form.style"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="图片">
|
||||
<SelectFile
|
||||
:placeholder="`请选择图片`"
|
||||
:limit="1"
|
||||
:data="images"
|
||||
@done="chooseImage"
|
||||
@del="onDeleteItem"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" name="sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="99999"
|
||||
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 { FormInstance } from 'ant-design-vue/es/form';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import {
|
||||
addCmsWebsiteField,
|
||||
updateCmsWebsiteField
|
||||
} from '@/api/cms/cmsWebsiteField';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { removeSiteInfoCache } from '@/api/cms/cmsWebsite';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
import { uuid } from 'ele-admin-pro';
|
||||
import { CmsWebsiteField } from '@/api/cms/cmsWebsiteField/model';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
websiteId: number | null | undefined;
|
||||
// 修改回显的数据
|
||||
data?: CmsWebsiteField | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
const images = ref<ItemType[]>([]);
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
const { form, resetFields, assignFields } = useFormData<CmsWebsiteField>({
|
||||
id: undefined,
|
||||
type: 0,
|
||||
name: undefined,
|
||||
value: undefined,
|
||||
modifyRange: undefined,
|
||||
defaultValue: undefined,
|
||||
comments: '',
|
||||
style: '',
|
||||
status: 0,
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入参数名称(英语)'
|
||||
}
|
||||
],
|
||||
// comments: [
|
||||
// {
|
||||
// required: true,
|
||||
// type: 'string',
|
||||
// message: '请输入描述'
|
||||
// }
|
||||
// ],
|
||||
value: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写参数值'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.value = data.downloadUrl;
|
||||
form.type = 1;
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.type = 0;
|
||||
};
|
||||
|
||||
const chooseData = (data: CmsWebsiteField) => {
|
||||
assignFields(data);
|
||||
form.value = data.defaultValue;
|
||||
if (data.type == 1) {
|
||||
images.value.push({
|
||||
uid: `${data.id}`,
|
||||
url: data.defaultValue,
|
||||
status: 'done'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const data = {
|
||||
...form,
|
||||
// name: form.name?.toUpperCase(),
|
||||
websiteId: props.websiteId
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value
|
||||
? updateCmsWebsiteField
|
||||
: addCmsWebsiteField;
|
||||
saveOrUpdate(data)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
// 清除缓存
|
||||
removeSiteInfoCache('SiteInfo:' + localStorage.getItem('TenantId'));
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignFields(props.data);
|
||||
form.comments = props.data.comments;
|
||||
if (form.type == 1) {
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.value,
|
||||
status: 'done'
|
||||
});
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
13
src/views/system/field/components/search.vue
Normal file
13
src/views/system/field/components/search.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<a-button @click="add">添加参数</a-button>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const emit = defineEmits<{
|
||||
(e: 'add'): void;
|
||||
}>();
|
||||
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
</script>
|
||||
230
src/views/system/field/index.vue
Normal file
230
src/views/system/field/index.vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<a-card :bordered="false">
|
||||
<div class="website-field">
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="websiteId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
:need-page="false"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<Search @add="openEdit" />
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<div
|
||||
class="ele-text-heading"
|
||||
@click="copyText(`config.${record.name}`)"
|
||||
>{{ record.name }}</div
|
||||
>
|
||||
<!-- <span class="ele-text-placeholder">{{ record.comments }}</span>-->
|
||||
</template>
|
||||
<template v-if="column.key === 'value'">
|
||||
<a-image
|
||||
v-if="record.type === 1"
|
||||
:src="record.value"
|
||||
:width="120"
|
||||
/>
|
||||
<div v-else>{{ record.value }}</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'comments'">
|
||||
<a-popover>
|
||||
<template #content>
|
||||
{{ record.comments }}
|
||||
</template>
|
||||
{{ record.comments }}
|
||||
</a-popover>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a @click="copyText('{{ config.' + record.name + ' }}')">调用</a>
|
||||
<a-divider type="vertical" />
|
||||
<a @click="openEdit(record)">编辑</a>
|
||||
<template v-if="record.deleted == 0">
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
<template v-if="record.deleted == 1">
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要放回原处吗?"
|
||||
@confirm="recovery(record)"
|
||||
>
|
||||
<a class="ele-text-danger">恢复</a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
<!-- 编辑弹窗 -->
|
||||
<Edit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</a-card>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import type { DatasourceFunction } from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import Edit from './components/edit.vue';
|
||||
import {
|
||||
CmsWebsiteField,
|
||||
CmsWebsiteFieldParam
|
||||
} from '@/api/cms/cmsWebsiteField/model';
|
||||
import {
|
||||
listCmsWebsiteField,
|
||||
removeCmsWebsiteField,
|
||||
undeleteWebsiteField,
|
||||
updateCmsWebsiteField
|
||||
} from '@/api/cms/cmsWebsiteField';
|
||||
import { copyText, getPageTitle } from '@/utils/common';
|
||||
|
||||
const props = defineProps<{
|
||||
websiteId: any;
|
||||
data: CmsWebsiteField;
|
||||
}>();
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
const selection = ref<any[]>();
|
||||
// 当前编辑数据
|
||||
const current = ref<CmsWebsiteField | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
// 搜索条件
|
||||
return listCmsWebsiteField({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<any[]>([
|
||||
{
|
||||
title: '参数名',
|
||||
dataIndex: 'comments',
|
||||
key: 'comments',
|
||||
width: 250,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '配置值',
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sortNumber',
|
||||
width: 120,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
const moveUp = (row?: CmsWebsiteField) => {
|
||||
updateCmsWebsiteField({
|
||||
id: row?.id,
|
||||
sortNumber: Number(row?.sortNumber) + 1
|
||||
}).then((msg) => {
|
||||
message.success(msg);
|
||||
reload();
|
||||
});
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: CmsWebsiteField) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: CmsWebsiteFieldParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: CmsWebsiteField) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeCmsWebsiteField(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
// 从回收站放回原处
|
||||
const recovery = (row: CmsWebsiteField) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
undeleteWebsiteField(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: CmsWebsiteField) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.websiteId,
|
||||
(websiteId) => {
|
||||
if (websiteId) {
|
||||
reload();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'CmsWebsiteFieldIndex'
|
||||
};
|
||||
</script>
|
||||
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>
|
||||
244
src/views/system/file/index.vue
Normal file
244
src/views/system/file/index.vue
Normal file
@@ -0,0 +1,244 @@
|
||||
<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 @click="openNew(getUrl(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, openNew } from '@/utils/common';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
width: 80,
|
||||
hideInTable: true
|
||||
},
|
||||
{
|
||||
title: '文件名称',
|
||||
dataIndex: 'name',
|
||||
key: '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>
|
||||
191
src/views/system/grade/components/grade-edit.vue
Normal file
191
src/views/system/grade/components/grade-edit.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="600"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑等级' : '添加等级'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="等级名称" name="name">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入等级名称"
|
||||
v-model:value="form.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="等级权重" name="weight">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入等级权重"
|
||||
v-model:value="form.weight"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="升级条件" name="upgrade">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入升级条件"
|
||||
v-model:value="form.upgrade"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="会员权益" name="equity">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入会员权益"
|
||||
v-model:value="form.equity"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="comments">
|
||||
<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 { addGrade, updateGrade } from '@/api/system/user-grade';
|
||||
import { Grade } from '@/api/user/grade/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Grade | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<Grade>({
|
||||
gradeId: undefined,
|
||||
name: '',
|
||||
weight: undefined,
|
||||
upgrade: undefined,
|
||||
equity: '',
|
||||
status: undefined,
|
||||
comments: undefined,
|
||||
sortNumber: undefined,
|
||||
userId: undefined,
|
||||
createTime: '',
|
||||
updateTime: ''
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入等级名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
weight: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入等级权重',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
upgrade: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入升级条件',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateGrade : addGrade;
|
||||
saveOrUpdate(formData)
|
||||
.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) {
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
42
src/views/system/grade/components/search.vue
Normal file
42
src/views/system/grade/components/search.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<!-- 搜索表单 -->
|
||||
<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-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
296
src/views/system/grade/index.vue
Normal file
296
src/views/system/grade/index.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="gradeId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
v-model:selection="selection"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'gradeName'">
|
||||
<a-avatar
|
||||
:size="30"
|
||||
:src="`${record.gradeAvatar}`"
|
||||
style="margin-right: 4px"
|
||||
:srcset="`https://file.wsdns.cn/${record.gradeAvatar}`"
|
||||
>
|
||||
<template #icon>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
</a-avatar>
|
||||
<a-tooltip title="查看详情">
|
||||
<a href="#" @click="openInfo(record)">{{ record.gradeName }}</a>
|
||||
</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="red">禁用</a-tag>
|
||||
</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="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>
|
||||
<!-- 编辑弹窗 -->
|
||||
<GradeEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import {
|
||||
ExclamationCircleOutlined,
|
||||
UserOutlined
|
||||
} 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 GradeEdit from './components/grade-edit.vue';
|
||||
import {
|
||||
pageGrade,
|
||||
removeGrade,
|
||||
removeBatchGrade
|
||||
} from '@/api/system/user-grade';
|
||||
import { timeAgo } from 'ele-admin-pro';
|
||||
import type { Grade, GradeParam } from '@/api/user/grade/model';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const userStore = useUserStore();
|
||||
// 当前用户信息
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<Grade[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<Grade | null>(null);
|
||||
// 是否显示资产详情
|
||||
const showInfo = ref(false);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.progress = filters.progress;
|
||||
where.gradeSource = filters.gradeSource;
|
||||
where.gradeType = filters.gradeType;
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pageGrade({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'gradeId',
|
||||
width: 80,
|
||||
hideInTable: true
|
||||
},
|
||||
{
|
||||
title: '等级名称',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: '等级权重',
|
||||
dataIndex: 'weight'
|
||||
},
|
||||
{
|
||||
title: '升级条件',
|
||||
dataIndex: 'upgrade'
|
||||
},
|
||||
{
|
||||
title: '等级权益',
|
||||
dataIndex: 'equity'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: GradeParam) => {
|
||||
console.log(where);
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Grade) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 打开用户详情弹窗 */
|
||||
const openInfo = (row?: Grade) => {
|
||||
current.value = row ?? null;
|
||||
showInfo.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: Grade) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeGrade(row.gradeId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量转移 */
|
||||
const batchMove = (userId) => {
|
||||
console.log(userId, '批量转移0000');
|
||||
console.log(selection.value);
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchGrade(
|
||||
selection.value.map((d) => {
|
||||
if (loginUser.value.userId === d.userId) {
|
||||
return d.gradeId;
|
||||
}
|
||||
})
|
||||
)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Grade) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Grade'
|
||||
};
|
||||
</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>
|
||||
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: 80,
|
||||
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>
|
||||
216
src/views/system/menu/components/clone.vue
Normal file
216
src/views/system/menu/components/clone.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="550"
|
||||
: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: 17, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="操作说明" name="readme">
|
||||
<a-alert
|
||||
description="本功能可以实现一键复制A系统的菜单到B系统的菜单,复制成功后会作为顶级目录出现在B系统的菜单功能上(注意:还需要手动给角色勾选权限才能使用)。菜单ID可以在A系统的菜单管理->修改菜单->是否展示的后面获取。"
|
||||
type="warning"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="菜单ID" name="menuId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入菜单ID"
|
||||
v-model:value="form.menuId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="租户ID" name="tenantId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入租户ID"
|
||||
v-model:value="form.tenantId"
|
||||
/>
|
||||
</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 tenantName = ref('请选择租户');
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Menu>({
|
||||
title: '',
|
||||
icon: '',
|
||||
path: '',
|
||||
component: '',
|
||||
tenantId: undefined,
|
||||
tenantName: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
tenantId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择要复制的租户ID',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
menuId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择要复制的菜单ID',
|
||||
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;
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
message.success(msg);
|
||||
})
|
||||
.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 onPromoter = (e) => {
|
||||
tenantName.value = e.tenantName;
|
||||
form.tenantId = e.tenantId;
|
||||
form.tenantName = e.tenantName;
|
||||
};
|
||||
|
||||
// 查询租户列表
|
||||
// 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>
|
||||
175
src/views/system/menu/components/delete.vue
Normal file
175
src/views/system/menu/components/delete.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="550"
|
||||
: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: 17, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="操作说明" name="readme">
|
||||
<a-alert
|
||||
description="批量删除顶级菜单目录下的所有子菜单(菜单ID可以在菜单管理->修改菜单->是否展示的后面获取)"
|
||||
type="warning"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="菜单ID" name="menuId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入菜单ID"
|
||||
v-model:value="form.menuId"
|
||||
/>
|
||||
</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 { deleteParentMenu } 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,
|
||||
tenantName: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
menuId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请顶级菜单ID,将删除栏目下的所有子菜单',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
deleteParentMenu(form.menuId).then((msg) => {
|
||||
message.success(msg);
|
||||
emit('done');
|
||||
loading.value = false;
|
||||
updateVisible(false);
|
||||
});
|
||||
})
|
||||
.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
|
||||
});
|
||||
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>
|
||||
474
src/views/system/menu/components/menu-edit.vue
Normal file
474
src/views/system/menu/components/menu-edit.vue
Normal file
@@ -0,0 +1,474 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<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
|
||||
show-search
|
||||
tree-node-filter-prop="title"
|
||||
:tree-data="menuList"
|
||||
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 ? '请输入内链地址' : '请输入组件路径'
|
||||
"
|
||||
/>
|
||||
<!-- <div class="ele-text-placeholder" v-if="form.modulesUrl">-->
|
||||
<!-- {{ `${form.modulesUrl}${form.component}?token=TOKEN` }}-->
|
||||
<!-- </div>-->
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="modulesUrl"
|
||||
v-if="form.menuType == 1 && form.openType === 0"
|
||||
>
|
||||
<template #label>
|
||||
<a-tooltip title="适用于分布式部署">
|
||||
<question-circle-outlined
|
||||
style="vertical-align: -2px; margin-right: 4px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<span>节点域名</span>
|
||||
</template>
|
||||
<a-input
|
||||
allow-clear
|
||||
v-model:value="form.modulesUrl"
|
||||
placeholder="请输入模块URL"
|
||||
@change="onClose"
|
||||
/>
|
||||
</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)
|
||||
"
|
||||
@pressEnter="save"
|
||||
/>
|
||||
</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="是否展示">
|
||||
<div class="flex justify-between">
|
||||
<div class="left">
|
||||
<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>
|
||||
</div>
|
||||
<a-tag>菜单ID:{{ form.menuId }}</a-tag>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="选择节点"
|
||||
name="modulesUrl"
|
||||
v-if="form.menuType == 1 && form.openType === 0"
|
||||
>
|
||||
<SelectModules
|
||||
:placeholder="`请选择模块所在的服务器`"
|
||||
v-model:value="form.modules"
|
||||
:disabled="isUpdate"
|
||||
@done="chooseModules"
|
||||
/>
|
||||
</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,
|
||||
modules: undefined,
|
||||
modulesUrl: undefined,
|
||||
title: '',
|
||||
menuType: 0,
|
||||
openType: 0,
|
||||
icon: '',
|
||||
path: '',
|
||||
component: '',
|
||||
authority: '',
|
||||
plugUrl: '',
|
||||
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 chooseModules = (e) => {
|
||||
form.modulesUrl = e.modulesUrl;
|
||||
form.modules = e.modules;
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
if (form.modulesUrl == '') {
|
||||
form.modules = '';
|
||||
}
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
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 = () => {
|
||||
form.sortNumber = undefined;
|
||||
if (form.menuType === 0) {
|
||||
form.authority = '';
|
||||
form.openType = 0;
|
||||
form.component = '';
|
||||
} else if (form.menuType === 1) {
|
||||
if (form.openType === 2) {
|
||||
form.authority = '';
|
||||
}
|
||||
} else if (form.menuType === 2) {
|
||||
form.sortNumber = 0;
|
||||
} 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>
|
||||
393
src/views/system/menu/index.vue
Normal file
393
src/views/system/menu/index.vue
Normal file
@@ -0,0 +1,393 @@
|
||||
<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"
|
||||
:customRow="customRow"
|
||||
@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="removeBatch">
|
||||
批量删除
|
||||
</a-button>
|
||||
<a-button type="dashed" class="ele-btn-icon" @click="cloneMenu">
|
||||
一键克隆
|
||||
</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'">
|
||||
<div v-if="record.modulesUrl">
|
||||
<a-tooltip
|
||||
:title="`${
|
||||
record.modulesUrl
|
||||
? record.modulesUrl + record.path + '?token=' + token
|
||||
: ''
|
||||
}`"
|
||||
>
|
||||
<a
|
||||
class="ele-text-primary"
|
||||
@click="
|
||||
openUrl(`${record.modulesUrl}${record.path}?token=${token}`)
|
||||
"
|
||||
>{{ record.path }}</a
|
||||
>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<span v-else>{{ record.path }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'component'">
|
||||
<div v-if="record.modulesUrl">
|
||||
<a-tooltip
|
||||
:title="`${
|
||||
record.modulesUrl ? record.modulesUrl + record.path : ''
|
||||
}`"
|
||||
>
|
||||
<span class="ele-text-primary">{{ record.component }}</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div v-else>{{ record.component }}</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'authority'">
|
||||
{{ record.authority }}
|
||||
</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"
|
||||
/>
|
||||
<Delete v-model:visible="showRemoveBatch" @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 Delete from './components/delete.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, openUrl } from '@/utils/common';
|
||||
import { TOKEN_STORE_NAME } from '@/config/setting';
|
||||
|
||||
// 表格实例
|
||||
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',
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '路由地址',
|
||||
dataIndex: 'path',
|
||||
key: 'path',
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '组件路径',
|
||||
dataIndex: 'component',
|
||||
key: 'component',
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '权限标识',
|
||||
dataIndex: 'authority',
|
||||
key: 'authority',
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: 'API节点',
|
||||
dataIndex: 'modules'
|
||||
},
|
||||
{
|
||||
title: '菜单编号',
|
||||
dataIndex: 'menuId',
|
||||
hideInTable: true
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sortNumber',
|
||||
showSorterTooltip: false,
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '可见',
|
||||
dataIndex: 'hide',
|
||||
showSorterTooltip: false,
|
||||
customRender: ({ text }) => ['是', '否'][text],
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
key: 'menuType',
|
||||
showSorterTooltip: false,
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true,
|
||||
hideInTable: true,
|
||||
customRender: ({ text }) => toDateString(text)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 140,
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<Menu | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
const showPlug = ref(false);
|
||||
const showClone = ref(false);
|
||||
const showRemoveBatch = ref(false);
|
||||
const token = ref(localStorage.getItem(TOKEN_STORE_NAME));
|
||||
|
||||
// 上级菜单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 removeBatch = () => {
|
||||
showRemoveBatch.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 customRow = (record: Menu) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/* 点击展开图标时触发 */
|
||||
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>
|
||||
171
src/views/system/modules/components/modules-edit.vue
Normal file
171
src/views/system/modules/components/modules-edit.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<!-- 节点编辑弹窗 -->
|
||||
<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="modules">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
placeholder="shop"
|
||||
v-model:value="form.modules"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="节点URL" name="modulesUrl">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="200"
|
||||
placeholder="https://shop.wsdns.cn"
|
||||
v-model:value="form.modulesUrl"
|
||||
/>
|
||||
</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, RuleObject } from 'ant-design-vue/es/form';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addModules, updateModules } from '@/api/system/modules';
|
||||
import type { Modules } from '@/api/system/modules/model';
|
||||
import { isChinese, urlReg } from 'ele-admin-pro';
|
||||
|
||||
// 是否开启响应式布局
|
||||
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?: Modules | null;
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Modules>({
|
||||
id: undefined,
|
||||
modules: '',
|
||||
modulesUrl: '',
|
||||
comments: undefined
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
modules: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
trigger: 'blur',
|
||||
validator: async (_rule: RuleObject, value: string) => {
|
||||
if (!value) {
|
||||
return Promise.reject('请输入节点ID');
|
||||
} else if (isChinese(value)) {
|
||||
return Promise.reject('仅支持英文字母');
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
modulesUrl: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入正确的URL',
|
||||
pattern: urlReg,
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
if (isChinese(String(form.modules))) {
|
||||
return message.error('节点名称请使用英文字母');
|
||||
}
|
||||
const saveOrUpdate = isUpdate.value ? updateModules : addModules;
|
||||
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/modules/components/modules-search.vue
Normal file
106
src/views/system/modules/components/modules-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.modules"
|
||||
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.modulesUrl"
|
||||
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 { Modules, ModulesParam } from '@/api/system/modules/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: ModulesParam): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields } = useFormData<Modules>({
|
||||
modules: '',
|
||||
modulesUrl: '',
|
||||
comments: ''
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', form);
|
||||
};
|
||||
|
||||
/* 重置 */
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
search();
|
||||
};
|
||||
</script>
|
||||
160
src/views/system/modules/index.vue
Normal file
160
src/views/system/modules/index.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false">
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
:scroll="{ x: 800 }"
|
||||
cache-key="proSystemModulesTable"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="openEdit()">
|
||||
<template #icon>
|
||||
<plus-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-popconfirm
|
||||
placement="topRight"
|
||||
title="确定要删除此模块吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<modules-edit v-model:visible="showEdit" :data="current" @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 { 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 ModulesEdit from './components/modules-edit.vue';
|
||||
import { pageModules, removeModules } from '@/api/system/modules';
|
||||
import type { Modules, ModulesParam } from '@/api/system/modules/model';
|
||||
import { Menu } from '@/api/system/menu/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: 'modules',
|
||||
sorter: true,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: 'URL',
|
||||
dataIndex: 'modulesUrl',
|
||||
sorter: true,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'comments',
|
||||
sorter: true,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<Modules[]>([]);
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<Modules | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
return pageModules({ ...where, ...orders, limit, page });
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ModulesParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Modules) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Menu) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: Modules) => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeModules(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemModules'
|
||||
};
|
||||
</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: 80,
|
||||
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>
|
||||
175
src/views/system/order/components/order-edit.vue
Normal file
175
src/views/system/order/components/order-edit.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<!-- 订单编辑弹窗 -->
|
||||
<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="companyId">
|
||||
<SelectCompany v-model:value="form.tenantName" @done="onCompany" />
|
||||
</a-form-item>
|
||||
<a-form-item label="支付金额" name="money">
|
||||
<a-input-number
|
||||
allow-clear
|
||||
max="1000000"
|
||||
style="width: 200px"
|
||||
placeholder="请输入订单金额"
|
||||
v-model:value="form.money"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="到期时间" name="days">
|
||||
<a-date-picker
|
||||
v-model:value="form.expirationTime"
|
||||
show-time
|
||||
placeholder="到期时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</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 { urlReg } from 'ele-admin-pro';
|
||||
import { Order } from '@/api/system/order/model';
|
||||
import { addOrder, updateOrder } from '@/api/system/order';
|
||||
import { Company } from '@/api/system/company/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?: Order | null;
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Order>({
|
||||
orderId: undefined,
|
||||
money: undefined,
|
||||
companyName: '',
|
||||
companyId: 0,
|
||||
tenantId: undefined,
|
||||
tenantName: '',
|
||||
comments: undefined
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
companyId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择客户',
|
||||
pattern: urlReg,
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
money: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入订单金额',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const onCompany = (item: Company) => {
|
||||
console.log(item);
|
||||
form.companyId = item.companyId;
|
||||
form.tenantName = item.tenantName;
|
||||
form.tenantId = item.tenantId;
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const saveOrUpdate = isUpdate.value ? updateOrder : addOrder;
|
||||
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>
|
||||
453
src/views/system/order/components/orderEdit.vue
Normal file
453
src/views/system/order/components/orderEdit.vue
Normal file
@@ -0,0 +1,453 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑订单' : '添加订单'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="订单编号" name="orderNo">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入订单编号"
|
||||
v-model:value="form.orderNo"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="订单类型,0产品 1插件" name="type">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入订单类型,0产品 1插件"
|
||||
v-model:value="form.type"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="下单渠道,0网站 1小程序 2其他" name="channel">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入下单渠道,0网站 1小程序 2其他"
|
||||
v-model:value="form.channel"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="微信支付订单号" name="transactionId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入微信支付订单号"
|
||||
v-model:value="form.transactionId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="微信退款订单号" name="refundOrder">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入微信退款订单号"
|
||||
v-model:value="form.refundOrder"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="使用的优惠券id" name="couponId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入使用的优惠券id"
|
||||
v-model:value="form.couponId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="真实姓名" name="realName">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入真实姓名"
|
||||
v-model:value="form.realName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号码" name="phone">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入手机号码"
|
||||
v-model:value="form.phone"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="订单总额" name="totalPrice">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入订单总额"
|
||||
v-model:value="form.totalPrice"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="减少的金额,使用VIP会员折扣、优惠券抵扣、优惠券折扣后减去的价格" name="reducePrice">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入减少的金额,使用VIP会员折扣、优惠券抵扣、优惠券折扣后减去的价格"
|
||||
v-model:value="form.reducePrice"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="实际付款" name="payPrice">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入实际付款"
|
||||
v-model:value="form.payPrice"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="用于统计" name="price">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入用于统计"
|
||||
v-model:value="form.price"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="价钱,用于积分赠送" name="money">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入价钱,用于积分赠送"
|
||||
v-model:value="form.money"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="退款金额" name="refundMoney">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入退款金额"
|
||||
v-model:value="form.refundMoney"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="购买数量" name="totalNum">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入购买数量"
|
||||
v-model:value="form.totalNum"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="0余额支付, 1微信支付,102微信Native,2会员卡支付,3支付宝,4现金,5POS机" name="payType">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入0余额支付, 1微信支付,102微信Native,2会员卡支付,3支付宝,4现金,5POS机"
|
||||
v-model:value="form.payType"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="0未付款,1已付款" name="payStatus">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入0未付款,1已付款"
|
||||
v-model:value="form.payStatus"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="0未完成,1已完成,2已取消,3取消中,4退款申请中,5退款被拒绝,6退款成功,7客户端申请退款" name="orderStatus">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入0未完成,1已完成,2已取消,3取消中,4退款申请中,5退款被拒绝,6退款成功,7客户端申请退款"
|
||||
v-model:value="form.orderStatus"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="优惠类型:0无、1抵扣优惠券、2折扣优惠券、3、VIP月卡、4VIP年卡,5VIP次卡、6VIP会员卡、7IC月卡、8IC年卡、9IC次卡、10IC会员卡、11免费订单、12VIP充值卡、13IC充值卡、14VIP季卡、15IC季卡" name="couponType">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入优惠类型:0无、1抵扣优惠券、2折扣优惠券、3、VIP月卡、4VIP年卡,5VIP次卡、6VIP会员卡、7IC月卡、8IC年卡、9IC次卡、10IC会员卡、11免费订单、12VIP充值卡、13IC充值卡、14VIP季卡、15IC季卡"
|
||||
v-model:value="form.couponType"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="优惠说明" name="couponDesc">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入优惠说明"
|
||||
v-model:value="form.couponDesc"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="二维码地址,保存订单号,支付成功后才生成" name="qrcode">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入二维码地址,保存订单号,支付成功后才生成"
|
||||
v-model:value="form.qrcode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="预约详情开始时间数组" name="startTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入预约详情开始时间数组"
|
||||
v-model:value="form.startTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否已开具发票:0未开发票,1已开发票,2不能开具发票" name="isInvoice">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否已开具发票:0未开发票,1已开发票,2不能开具发票"
|
||||
v-model:value="form.isInvoice"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="发票流水号" name="invoiceNo">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入发票流水号"
|
||||
v-model:value="form.invoiceNo"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="支付时间" name="payTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入支付时间"
|
||||
v-model:value="form.payTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="退款时间" name="refundTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入退款时间"
|
||||
v-model:value="form.refundTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="申请退款时间" name="refundApplyTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入申请退款时间"
|
||||
v-model:value="form.refundApplyTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="过期时间" name="expirationTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入过期时间"
|
||||
v-model:value="form.expirationTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="对账情况:0=未对账;1=已对账;3=已对账,金额对不上;4=未查询到该订单" name="checkBill">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入对账情况:0=未对账;1=已对账;3=已对账,金额对不上;4=未查询到该订单"
|
||||
v-model:value="form.checkBill"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="订单是否已结算(0未结算 1已结算)" name="isSettled">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入订单是否已结算(0未结算 1已结算)"
|
||||
v-model:value="form.isSettled"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="系统版本号 0当前版本 value=其他版本" name="version">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入系统版本号 0当前版本 value=其他版本"
|
||||
v-model:value="form.version"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="用户id" name="userId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入用户id"
|
||||
v-model:value="form.userId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="comments">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入描述"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" name="sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否删除, 0否, 1是" name="deleted">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否删除, 0否, 1是"
|
||||
v-model:value="form.deleted"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="修改时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入修改时间"
|
||||
v-model:value="form.updateTime"
|
||||
/>
|
||||
</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, uuid } from 'ele-admin-pro';
|
||||
import { addOrder, updateOrder } from '@/api/system/order';
|
||||
import { Order } from '@/api/system/order/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Order | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<Order>({
|
||||
orderId: undefined,
|
||||
orderNo: undefined,
|
||||
type: undefined,
|
||||
channel: undefined,
|
||||
transactionId: undefined,
|
||||
refundOrder: undefined,
|
||||
couponId: undefined,
|
||||
realName: undefined,
|
||||
phone: undefined,
|
||||
totalPrice: undefined,
|
||||
reducePrice: undefined,
|
||||
payPrice: undefined,
|
||||
price: undefined,
|
||||
money: undefined,
|
||||
refundMoney: undefined,
|
||||
totalNum: undefined,
|
||||
payType: undefined,
|
||||
payStatus: undefined,
|
||||
orderStatus: undefined,
|
||||
couponType: undefined,
|
||||
couponDesc: undefined,
|
||||
qrcode: undefined,
|
||||
startTime: undefined,
|
||||
isInvoice: undefined,
|
||||
invoiceNo: undefined,
|
||||
payTime: undefined,
|
||||
refundTime: undefined,
|
||||
refundApplyTime: undefined,
|
||||
expirationTime: undefined,
|
||||
checkBill: undefined,
|
||||
isSettled: undefined,
|
||||
version: undefined,
|
||||
userId: undefined,
|
||||
comments: undefined,
|
||||
sortNumber: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
updateTime: undefined,
|
||||
createTime: undefined,
|
||||
orderId: undefined,
|
||||
orderName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
orderName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写订单名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateOrder : addOrder;
|
||||
saveOrUpdate(formData)
|
||||
.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) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if(props.data.image){
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
})
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
104
src/views/system/order/components/search.vue
Normal file
104
src/views/system/order/components/search.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<a-space style="flex-wrap: wrap">
|
||||
<!-- <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"
|
||||
v-if="selection?.length > 0"
|
||||
@click="removeBatch"
|
||||
>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
<span>批量删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
<a-space :size="10" style="flex-wrap: wrap; margin-right: 20px">
|
||||
<a-input-search
|
||||
allow-clear
|
||||
placeholder="请输入关键词"
|
||||
v-model:value="searchText"
|
||||
@pressEnter="search"
|
||||
@search="search"
|
||||
/>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { ref, watch } from 'vue';
|
||||
import { CompanyParam } from '@/api/system/company/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: CompanyParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'update', status?: number): void;
|
||||
(e: 'import'): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { where, resetFields } = useSearch<CompanyParam>({
|
||||
companyId: undefined,
|
||||
companyName: undefined,
|
||||
keywords: '',
|
||||
authentication: undefined,
|
||||
version: undefined,
|
||||
province: '',
|
||||
city: '',
|
||||
region: ''
|
||||
});
|
||||
const tenantId = ref<number>();
|
||||
if (localStorage.getItem('TenantId')) {
|
||||
tenantId.value = Number(localStorage.getItem('TenantId'));
|
||||
}
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref('');
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', {
|
||||
...where
|
||||
});
|
||||
};
|
||||
|
||||
/* 重置 */
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
search();
|
||||
};
|
||||
|
||||
// 批量删除
|
||||
const removeBatch = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
193
src/views/system/order/index.vue
Normal file
193
src/views/system/order/index.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false">
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="orderId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
:scroll="{ x: 800 }"
|
||||
cache-key="proSystemOrderTable"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'money'">
|
||||
<span class="ele-text-warning">
|
||||
¥{{ formatNumber(record.money) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'type'">
|
||||
<a-tag v-if="record.type === 0">续费订单</a-tag>
|
||||
<a-tag v-if="record.type === 1" color="purple">普通订单</a-tag>
|
||||
</template>
|
||||
<template v-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>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<order-edit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { EleProTable, formatNumber } 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 OrderEdit from './components/order-edit.vue';
|
||||
import { pageOrder, removeOrder } from '@/api/system/order';
|
||||
import type { Order, OrderParam } from '@/api/system/order/model';
|
||||
import { Menu } from '@/api/system/menu/model';
|
||||
import Search from '@/views/system/order/components/search.vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { removeBatchCompany } from '@/api/system/company';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '订单号',
|
||||
dataIndex: 'orderId',
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '订单类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
align: 'center',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '租户名称',
|
||||
dataIndex: 'tenantName',
|
||||
key: 'tenantName',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '订单金额(元)',
|
||||
dataIndex: 'money',
|
||||
key: 'money',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'comments',
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<Order[]>([]);
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<Order | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
return pageOrder({ ...where, ...orders, limit, page });
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: OrderParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Order) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Menu) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: Order) => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeOrder(row.orderId)
|
||||
.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);
|
||||
removeBatchCompany(selection.value.map((d) => d.orderId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemOrder'
|
||||
};
|
||||
</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: 100,
|
||||
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>
|
||||
509
src/views/system/organization/components/org-user-edit.vue
Normal file
509
src/views/system/organization/components/org-user-edit.vue
Normal file
@@ -0,0 +1,509 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="1280"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '添加成员' : '添加成员'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px 16px' }">
|
||||
<a-row :gutter="14">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive ? { lg: 12, md: 24, sm: 24, xs: 24 } : { span: 12 }
|
||||
"
|
||||
>
|
||||
<!-- 未选择的成员数据表格 -->
|
||||
<ele-pro-table
|
||||
bordered
|
||||
size="small"
|
||||
:toolkit="[]"
|
||||
:columns="columns"
|
||||
row-key="userId"
|
||||
sub-title="未选成员:"
|
||||
empty-text="已全部选择"
|
||||
tools-theme="default"
|
||||
:show-size-changer="false"
|
||||
:datasource="unChooseClass"
|
||||
:scroll="{ x: 400 }"
|
||||
>
|
||||
<template #toolkit>
|
||||
<a-button type="dashed" class="ele-btn-icon" @click="addAll">
|
||||
全部添加
|
||||
</a-button>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-button size="small" type="dashed" @click="addItem(record)">
|
||||
添加
|
||||
</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive ? { lg: 12, md: 24, sm: 24, xs: 24 } : { span: 12 }
|
||||
"
|
||||
>
|
||||
<!-- 已选择的成员数据表格 -->
|
||||
<ele-pro-table
|
||||
bordered
|
||||
size="small"
|
||||
:toolkit="[]"
|
||||
:columns="columns"
|
||||
row-key="userId"
|
||||
sub-title="已选成员:"
|
||||
emptyText="未选择成员"
|
||||
tools-theme="default"
|
||||
:show-size-changer="false"
|
||||
:datasource="chooseUsers"
|
||||
:scroll="{ x: 400 }"
|
||||
>
|
||||
<template #toolkit>
|
||||
<a-button
|
||||
danger
|
||||
type="dashed"
|
||||
class="ele-btn-icon"
|
||||
@click="removeAll"
|
||||
>
|
||||
全部移除
|
||||
</a-button>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-button
|
||||
danger
|
||||
size="small"
|
||||
type="dashed"
|
||||
@click="removeItem(record)"
|
||||
>
|
||||
移除
|
||||
</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
<!-- <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="roles">-->
|
||||
<!-- <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, computed, 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 type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import OrgSelect from './org-select.vue';
|
||||
import RoleSelect from './role-select.vue';
|
||||
import SexSelect from './sex-select.vue';
|
||||
import {
|
||||
addUser,
|
||||
updateUser,
|
||||
checkExistence,
|
||||
pageUsers
|
||||
} 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;
|
||||
}>();
|
||||
|
||||
interface MockData {
|
||||
key: string;
|
||||
title: string;
|
||||
description: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
const targetKeys = ref<string[]>([]);
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
const users = ref<User[]>([]);
|
||||
const mockData: MockData[] = [];
|
||||
const disabled = ref<boolean>(false);
|
||||
|
||||
// 已选择的成员数据
|
||||
const chooseUsers = ref<User[]>([]);
|
||||
|
||||
// 未选择的成员数据
|
||||
const unChooseClass = computed(() =>
|
||||
users.value.filter((d) => chooseUsers.value.indexOf(d) === -1)
|
||||
);
|
||||
|
||||
/* 获取全部成员 */
|
||||
const query = () => {
|
||||
pageUsers({})
|
||||
.then((data) => {
|
||||
if (data?.list) {
|
||||
users.value = data?.list;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
query();
|
||||
|
||||
/* 添加 */
|
||||
const addItem = (row: User) => {
|
||||
chooseUsers.value = [...chooseUsers.value, row];
|
||||
};
|
||||
|
||||
/* 移除 */
|
||||
const removeItem = (row: User) => {
|
||||
const index = chooseUsers.value.indexOf(row);
|
||||
chooseUsers.value = chooseUsers.value.filter((_d, i) => i !== index);
|
||||
};
|
||||
|
||||
/* 添加全部 */
|
||||
const addAll = () => {
|
||||
chooseUsers.value = [...users.value];
|
||||
};
|
||||
|
||||
/* 移除所有 */
|
||||
const removeAll = () => {
|
||||
chooseUsers.value = [];
|
||||
};
|
||||
|
||||
// 表单数据
|
||||
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'
|
||||
// }
|
||||
// ],
|
||||
roles: [
|
||||
{
|
||||
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: [
|
||||
{
|
||||
required: true,
|
||||
pattern: phoneReg,
|
||||
message: '手机号格式不正确',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '用户ID',
|
||||
dataIndex: 'userId',
|
||||
width: 120,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'mobile',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'realName',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'sexName',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
showSorterTooltip: false,
|
||||
hideInTable: true,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
const handleChange = (
|
||||
nextTargetKeys: string[],
|
||||
direction: string,
|
||||
moveKeys: string[]
|
||||
) => {
|
||||
console.log('targetKeys: ', nextTargetKeys);
|
||||
console.log('direction: ', direction);
|
||||
console.log('moveKeys: ', moveKeys);
|
||||
};
|
||||
|
||||
const handleSelectChange = (
|
||||
sourceSelectedKeys: string[],
|
||||
targetSelectedKeys: string[]
|
||||
) => {
|
||||
console.log('sourceSelectedKeys: ', sourceSelectedKeys);
|
||||
console.log('targetSelectedKeys: ', targetSelectedKeys);
|
||||
};
|
||||
const handleScroll = (direction: string, e: Event) => {
|
||||
console.log('direction:', direction);
|
||||
console.log('target:', e.target);
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
pageUsers({}).then((res) => {
|
||||
if (res?.list) {
|
||||
users.value = res?.list;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
reload();
|
||||
|
||||
/* 保存编辑 */
|
||||
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>
|
||||
201
src/views/system/organization/components/org-user-list.vue
Normal file
201
src/views/system/organization/components/org-user-list.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<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 }"
|
||||
bordered
|
||||
>
|
||||
<template #toolbar>
|
||||
<org-user-search
|
||||
@search="reload"
|
||||
:organizationId="organizationId"
|
||||
@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 class="text-red-500" @click="remove(record)">移除</a>
|
||||
</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,
|
||||
updateUser,
|
||||
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[]>([
|
||||
{
|
||||
title: '用户ID',
|
||||
dataIndex: 'userId',
|
||||
width: 120,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'mobile',
|
||||
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: '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) => {
|
||||
updateUser({ ...row, organizationId: 0 }).then(() => {
|
||||
reload();
|
||||
});
|
||||
};
|
||||
|
||||
/* 修改用户状态 */
|
||||
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>
|
||||
31
src/views/system/organization/components/org-user-search.vue
Normal file
31
src/views/system/organization/components/org-user-search.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space style="flex-wrap: wrap">
|
||||
<SelectUser
|
||||
:placeholder="`添加成员`"
|
||||
:organizationId="0"
|
||||
@done="updateOrganizationId"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { updateUser } from '@/api/system/user';
|
||||
import { User } from '@/api/system/user/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 部门 id
|
||||
organizationId?: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const updateOrganizationId = (date: User) => {
|
||||
updateUser({ ...date, organizationId: props.organizationId }).then(() => {
|
||||
emit('search');
|
||||
});
|
||||
};
|
||||
</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>
|
||||
214
src/views/system/organization/index.vue
Normal file
214
src/views/system/organization/index.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<template #extra>
|
||||
<Extra/>
|
||||
</template>
|
||||
<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)"
|
||||
:show-line="true"
|
||||
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"
|
||||
@done="query"
|
||||
/>
|
||||
</template>
|
||||
</ele-split-layout>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<org-edit
|
||||
v-model:visible="showEdit"
|
||||
:data="editData"
|
||||
:organization-list="data"
|
||||
:organization-id="current?.organizationId"
|
||||
@done="query"
|
||||
/>
|
||||
</a-page-header>
|
||||
</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';
|
||||
import {getPageTitle} from "@/utils/common";
|
||||
import Extra from "@/views/system/user/components/Extra.vue";
|
||||
|
||||
// 加载状态
|
||||
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) => {
|
||||
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>
|
||||
369
src/views/system/payment/components/paymentEdit.vue
Normal file
369
src/views/system/payment/components/paymentEdit.vue
Normal file
@@ -0,0 +1,369 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑支付方式' : '添加支付方式'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="支付方式" name="name">
|
||||
<PayMethod
|
||||
dict-code="payMethod"
|
||||
v-model:value="form.name"
|
||||
:placeholder="`选择支付方式`"
|
||||
@change="onPayMethod"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!--微信支付-->
|
||||
<template v-if="form.code == '1'">
|
||||
<a-form-item label="微信商户号类型" name="wechatType">
|
||||
<a-radio-group v-model:value="form.wechatType">
|
||||
<a-radio :value="0">
|
||||
<text>普通商户</text>
|
||||
</a-radio>
|
||||
<a-radio :value="1">子商户 (服务商模式)</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="应用ID" name="appId" extra="微信小程序或者微信公众号的APPID,需要在哪个客户端支付就填写哪个,APP支付需要填写开放平台的应用APPID">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入应用ID"
|
||||
v-model:value="form.appId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="商户号" name="mchId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入商户号"
|
||||
v-model:value="form.mchId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="设置APIv3密钥" name="apiKey">
|
||||
<a-input-password
|
||||
allow-clear
|
||||
placeholder="请输入设置APIv3密钥"
|
||||
v-model:value="form.apiKey"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="证书文件 (KEY)" name="apiclientKey" extra="请上传 apiclient_key.pem 文件">
|
||||
<Upload accept=".crt,.pem" v-model:value="form.apiclientKey" />
|
||||
{{ form.apiclientKey }}
|
||||
</a-form-item>
|
||||
<a-form-item label="证书文件 (CERT)" name="apiclientCert" extra="请上传 apiclient_cert.pem 文件">
|
||||
<Upload accept=".crt,.pem" v-model:value="form.apiclientCert" />
|
||||
{{ form.apiclientCert }}
|
||||
</a-form-item>
|
||||
<a-form-item label="商户证书序列号" name="merchantSerialNumber" extra="填写API证书里的证书序列号">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入商户证书序列号"
|
||||
v-model:value="form.merchantSerialNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="公钥文件 (CERT)" name="apiclientCert" extra="请上传 公钥文件">
|
||||
<Upload accept=".crt,.pem" v-model:value="form.pubKey" />
|
||||
{{ form.pubKey }}
|
||||
</a-form-item>
|
||||
<a-form-item label="公钥ID" name="pubKeyId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输公钥ID"
|
||||
v-model:value="form.pubKeyId"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item label="支付通知" name="notifyUrl" extra="推送支付结果(携带租户ID的POST请求)">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入支付结果通知地址"
|
||||
v-model:value="form.notifyUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="图标" name="image">
|
||||
<SelectFile
|
||||
:placeholder="`请选择图片`"
|
||||
:limit="1"
|
||||
:data="images"
|
||||
@done="chooseImage"
|
||||
@del="onDeleteItem"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="comments">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入描述"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" name="sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否启用" name="status">
|
||||
<a-switch v-model:checked="form.status" />
|
||||
</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, uuid } from 'ele-admin-pro';
|
||||
import { addPayment, updatePayment } from '@/api/system/payment';
|
||||
import { Payment } from '@/api/system/payment/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
import Upload from "@/components/UploadCert/index.vue";
|
||||
import { uploadFile } from "@/api/system/file";
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
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 maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<Payment>({
|
||||
id: 0,
|
||||
name: undefined,
|
||||
type: 1,
|
||||
code: '',
|
||||
image: '',
|
||||
wechatType: 0,
|
||||
appId: '',
|
||||
mchId: '',
|
||||
apiKey: '',
|
||||
pubKey: '',
|
||||
pubKeyId: '',
|
||||
apiclientCert: '',
|
||||
apiclientKey: '',
|
||||
merchantSerialNumber: '',
|
||||
notifyUrl: 'https://modules.gxwebsoft.com/api/shop/wx-pay/notify',
|
||||
comments: '',
|
||||
sortNumber: 0,
|
||||
status: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: ''
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写支付方式名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
appId: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写应用ID',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
mchId: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写商户号',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
wechatType: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请选择商户类型',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
apiKey: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写APIv3密钥',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
// apiclientCert: [
|
||||
// {
|
||||
// required: true,
|
||||
// type: 'string',
|
||||
// message: '请上传证书文件(CERT)',
|
||||
// trigger: 'blur'
|
||||
// }
|
||||
// ],
|
||||
// pubKeyId: [
|
||||
// {
|
||||
// required: true,
|
||||
// type: 'string',
|
||||
// message: '请填写公钥ID',
|
||||
// trigger: 'blur'
|
||||
// }
|
||||
// ],
|
||||
apiclientKey: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请上传证书文件(KEY)',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
notifyUrl: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写支付通知地址',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
merchantSerialNumber: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写商户证书序列号',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.thumbnail,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.thumbnail;
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
};
|
||||
|
||||
const onPayMethod = (value: string, item: any) => {
|
||||
form.name = item.label
|
||||
form.code = item.value
|
||||
form.type = item.value
|
||||
}
|
||||
|
||||
const onUpload = (d: ItemType) => {
|
||||
uploadFile(<File>d.file)
|
||||
.then((result) => {
|
||||
form.image = result.path;
|
||||
message.success("上传成功");
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
console.log(form);
|
||||
const saveOrUpdate = isUpdate.value ? updatePayment : addPayment;
|
||||
saveOrUpdate(formData)
|
||||
.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) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if(props.data.image){
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
})
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
42
src/views/system/payment/components/search.vue
Normal file
42
src/views/system/payment/components/search.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<!-- 搜索表单 -->
|
||||
<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-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
252
src/views/system/payment/index.vue
Normal file
252
src/views/system/payment/index.vue
Normal file
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<a-space class="ele-cell">
|
||||
<a-avatar :src="record.image" size="small" shape="square" />
|
||||
<span class="ele-text-secondary">{{ record.name }}</span>
|
||||
</a-space>
|
||||
</template>
|
||||
<template v-if="column.key === 'code'">
|
||||
<span class="ele-text-secondary">{{ record.code }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-switch
|
||||
v-model:checked="record.status"
|
||||
@change="updateStatus(record)"
|
||||
/>
|
||||
</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>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<PaymentEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import {
|
||||
ExclamationCircleOutlined,
|
||||
WechatOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import PaymentEdit from './components/paymentEdit.vue';
|
||||
import {
|
||||
pagePayment,
|
||||
removePayment,
|
||||
removeBatchPayment,
|
||||
updatePayment
|
||||
} from '@/api/system/payment';
|
||||
import type { Payment, PaymentParam } from '@/api/system/payment/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<Payment[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<Payment | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pagePayment({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '支付方式',
|
||||
dataIndex: 'name',
|
||||
key: 'name'
|
||||
},
|
||||
// {
|
||||
// title: '标识',
|
||||
// dataIndex: 'code',
|
||||
// key: 'code',
|
||||
// width: 280,
|
||||
// align: 'center'
|
||||
// },
|
||||
// {
|
||||
// title: '商户号类型',
|
||||
// dataIndex: 'wechatType',
|
||||
// key: 'wechatType',
|
||||
// align: 'center',
|
||||
// width: 180,
|
||||
// customRender: ({ text }) => ['普通商户', '子商户'][text]
|
||||
// },
|
||||
{
|
||||
title: '是否启用',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 180,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: PaymentParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Payment) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
const updateStatus = (item: Payment) => {
|
||||
updatePayment(item)
|
||||
.then((msg) => {
|
||||
message.success(msg);
|
||||
})
|
||||
.catch((msg) => {
|
||||
message.error(msg);
|
||||
});
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: Payment) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removePayment(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 = message.loading('请求中..', 0);
|
||||
removeBatchPayment(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Payment) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Payment'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
1012
src/views/system/plug/components/companyEdit.vue
Normal file
1012
src/views/system/plug/components/companyEdit.vue
Normal file
File diff suppressed because it is too large
Load Diff
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/oa/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>
|
||||
194
src/views/system/plug/components/plug.vue
Normal file
194
src/views/system/plug/components/plug.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<a-card
|
||||
style="background-color: transparent"
|
||||
:bordered="false"
|
||||
:body-style="{ padding: '0px' }"
|
||||
>
|
||||
<a-alert
|
||||
message="欢迎使用"
|
||||
description="请选择需要安装的插件"
|
||||
type="success"
|
||||
show-icon
|
||||
closable
|
||||
/>
|
||||
<div style="margin: 30px auto; display: flex; justify-content: center">
|
||||
<a-space style="flex-wrap: wrap">
|
||||
<industry-select
|
||||
v-model:value="industry"
|
||||
valueField="label"
|
||||
size="large"
|
||||
placeholder="按行业筛选"
|
||||
class="ele-fluid"
|
||||
@change="onIndustry"
|
||||
/>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
size="large"
|
||||
style="width: 360px"
|
||||
placeholder="请输入搜索关键词"
|
||||
v-model:value="where.keywords"
|
||||
@pressEnter="reload"
|
||||
@search="reload"
|
||||
/>
|
||||
<a-button size="large" @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-tabs v-model:active-key="where.sceneType" @change="onTabs">
|
||||
<a-tab-pane tab="热门推荐" key="recommend" />
|
||||
<a-tab-pane tab="免费热榜" key="free" />
|
||||
<a-tab-pane tab="付费热榜" key="pay" />
|
||||
<a-tab-pane tab="最新上架" key="new" />
|
||||
<a-tab-pane tab="我的收藏" key="collect" />
|
||||
</a-tabs>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 6, md: 6, sm: 24, xs: 24 }
|
||||
: { span: 12 }
|
||||
"
|
||||
class="gutter-row"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:span="6"
|
||||
>
|
||||
<a-card hoverable>
|
||||
<div class="gutter-box">
|
||||
<div class="plug-item">
|
||||
<a-image
|
||||
:height="80"
|
||||
:width="80"
|
||||
:preview="false"
|
||||
:src="item.companyLogo"
|
||||
@click="openUrl('/system/plug/detail/' + item.companyId)"
|
||||
fallback="https://file.wsdns.cn/20230218/550e610d43334dd2a7f66d5b20bd58eb.svg"
|
||||
/>
|
||||
<div class="info">
|
||||
<a
|
||||
class="name ele-text-heading"
|
||||
@click="openUrl('/system/plug/detail/' + item.companyId)"
|
||||
>{{ item.tenantName }}</a
|
||||
>
|
||||
<a-rate class="rate" v-model:value="rate" disabled allow-half />
|
||||
<div class="company ele-text-placeholder">
|
||||
<a-typography-text
|
||||
type="secondary"
|
||||
:ellipsis="{ rows: 1, expandable: true, symbol: '...' }"
|
||||
>
|
||||
{{ item.companyName }}
|
||||
</a-typography-text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plug-desc ele-text-secondary">
|
||||
<a-typography-paragraph
|
||||
type="secondary"
|
||||
:ellipsis="{ rows: 2, expandable: true, symbol: '显示' }"
|
||||
:content="item.comments"
|
||||
/>
|
||||
</div>
|
||||
<div class="plug-bottom">
|
||||
<div class="downloads ele-text-placeholder"
|
||||
>安装 {{ item.clicks }}
|
||||
</div>
|
||||
<a-button type="primary" disabled v-if="planId === item.tenantId"
|
||||
>已安装
|
||||
</a-button>
|
||||
<a-button v-else type="primary" @click="onClone(item)"
|
||||
>安装
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="plug-page" v-if="list.length">
|
||||
<a-pagination
|
||||
v-model:current="where.page"
|
||||
v-model:pageSize="where.limit"
|
||||
size="large"
|
||||
:total="total"
|
||||
@change="reload"
|
||||
/>
|
||||
</div>
|
||||
<a-empty v-else />
|
||||
</a-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { openUrl } from '@/utils/common';
|
||||
import { onClone } from '@/utils/plug-uitl';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { Company, CompanyParam } from '@/api/system/company/model';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { pagePlug } from "@/api/system/plug";
|
||||
|
||||
const props = defineProps<{
|
||||
// 修改回显的数据
|
||||
use: boolean;
|
||||
}>();
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const searchText = ref('');
|
||||
const rate = ref(3.5);
|
||||
const list = ref<Company[]>([]);
|
||||
const industry = ref<any>();
|
||||
const total = ref<any>(0);
|
||||
const planId = ref<number>(Number(localStorage.getItem('PlanId')));
|
||||
|
||||
// 查询条件
|
||||
const { where, resetFields } = useSearch<CompanyParam>({
|
||||
keywords: undefined,
|
||||
industryParent: '',
|
||||
industryChild: '',
|
||||
recommend: undefined,
|
||||
authoritative: 1,
|
||||
sceneType: 'recommend',
|
||||
limit: 20,
|
||||
page: 1
|
||||
});
|
||||
|
||||
const onIndustry = (item: any) => {
|
||||
where.industryChild = item[1];
|
||||
};
|
||||
|
||||
const onTabs = (index) => {
|
||||
if (index == 'recommend') {
|
||||
where.recommend = true;
|
||||
}
|
||||
if (index == 'free') {
|
||||
where.recommend = false;
|
||||
}
|
||||
if (index == 'pay') {
|
||||
where.recommend = false;
|
||||
}
|
||||
if (index == 'new') {
|
||||
where.sceneType = 'new';
|
||||
}
|
||||
if (index == 'collect') {
|
||||
where.sceneType = 'collect';
|
||||
}
|
||||
reload();
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
reload();
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
where.sort = 'buys';
|
||||
where.order = 'desc';
|
||||
pagePlug({}).then((data) => {
|
||||
total.value = data?.count;
|
||||
if (data?.list) {
|
||||
list.value = data?.list;
|
||||
}
|
||||
});
|
||||
};
|
||||
reload();
|
||||
</script>
|
||||
104
src/views/system/plug/components/search.vue
Normal file
104
src/views/system/plug/components/search.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<!-- <a-space style="flex-wrap: wrap">-->
|
||||
<!-- <a-button-->
|
||||
<!-- type="text"-->
|
||||
<!-- @click="openUrl(`/website/plug`)"-->
|
||||
<!-- >企业官网-->
|
||||
<!-- </a-button>-->
|
||||
<!-- <a-button-->
|
||||
<!-- type="text"-->
|
||||
<!-- @click="openUrl('/website/plug')"-->
|
||||
<!-- >项目管理-->
|
||||
<!-- </a-button-->
|
||||
<!-- >-->
|
||||
<!-- <a-button-->
|
||||
<!-- type="text"-->
|
||||
<!-- @click="openUrl('/website/plug')"-->
|
||||
<!-- >河马商店-->
|
||||
<!-- </a-button-->
|
||||
<!-- >-->
|
||||
<!-- </a-space>-->
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<!-- <a-button type="primary" class="ele-btn-icon">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <PlusOutlined/>-->
|
||||
<!-- </template>-->
|
||||
<!-- <span>创建应用</span>-->
|
||||
<!-- </a-button>-->
|
||||
<a-input-search
|
||||
allow-clear
|
||||
placeholder="请输入关键词"
|
||||
v-model:value="where.keywords"
|
||||
@pressEnter="search"
|
||||
@search="search"
|
||||
style="width: 240px"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {PlusOutlined} from '@ant-design/icons-vue';
|
||||
import type {CompanyParam} from '@/api/system/company/model';
|
||||
import {watch} from 'vue';
|
||||
import useSearch from "@/utils/use-search";
|
||||
import {openSpmUrl} from "@/utils/common";
|
||||
import {CmsNavigation} from "@/api/cms/cmsNavigation/model";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
navigationList?: CmsNavigation[];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: CompanyParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const {where, resetFields} = useSearch<CompanyParam>({
|
||||
companyId: undefined,
|
||||
userId: undefined,
|
||||
phone: undefined,
|
||||
tenantId: undefined,
|
||||
version: undefined,
|
||||
type: undefined,
|
||||
official: undefined,
|
||||
keywords: undefined
|
||||
});
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
openSpmUrl(`https://websoft.top/passport/login`)
|
||||
};
|
||||
|
||||
// 按分类查询
|
||||
const onCategoryId = (id: number) => {
|
||||
where.categoryId = id;
|
||||
emit('search', where);
|
||||
};
|
||||
|
||||
const search = () => {
|
||||
emit('search', {
|
||||
...where
|
||||
});
|
||||
};
|
||||
|
||||
/* 重置 */
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
search();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {
|
||||
}
|
||||
);
|
||||
</script>
|
||||
239
src/views/system/plug/components/tenant.vue
Normal file
239
src/views/system/plug/components/tenant.vue
Normal file
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<div class="alert py-3">
|
||||
<a-alert
|
||||
description="通过安装扩展程序来满足您的业务需求,该操作将复制菜单和权限到您的系统。"
|
||||
type="warning"
|
||||
show-icon
|
||||
closable
|
||||
/>
|
||||
</div>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 6, md: 6, sm: 24, xs: 24 }
|
||||
: { span: 12 }
|
||||
"
|
||||
class="gutter-row"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:span="6"
|
||||
>
|
||||
<a-card hoverable>
|
||||
<div class="gutter-box">
|
||||
<div class="plug-item">
|
||||
<a-image
|
||||
:height="72"
|
||||
:width="72"
|
||||
:preview="false"
|
||||
class="app-icon"
|
||||
style="
|
||||
border: 1px solid #e3e3e3;
|
||||
box-shadow: 0 0 1px -1px;
|
||||
border-radius: 12px;
|
||||
"
|
||||
:src="item.companyLogo"
|
||||
@click="openUrl('/system/plug/detail/' + item.companyId)"
|
||||
fallback="https://file.wsdns.cn/20230218/550e610d43334dd2a7f66d5b20bd58eb.svg"
|
||||
/>
|
||||
<div class="info">
|
||||
<a
|
||||
class="name ele-text-heading"
|
||||
@click="openUrl('/system/plug/detail/' + item.companyId)"
|
||||
>{{ item.tenantName }}</a
|
||||
>
|
||||
<a-rate class="rate" v-model:value="rate" disabled allow-half/>
|
||||
<div class="company ele-text-placeholder">
|
||||
<a-typography-text
|
||||
type="secondary"
|
||||
:ellipsis="{ rows: 1, expandable: true, symbol: '...' }"
|
||||
>
|
||||
{{ item.companyName }}
|
||||
</a-typography-text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plug-desc ele-text-secondary">
|
||||
<a-typography-paragraph
|
||||
type="secondary"
|
||||
:ellipsis="{ rows: 2, expandable: true, symbol: '显示' }"
|
||||
:content="item.comments"
|
||||
/>
|
||||
</div>
|
||||
<div class="plug-bottom">
|
||||
<div class="downloads ele-text-placeholder"
|
||||
>安装 {{ item.clicks }}
|
||||
</div>
|
||||
<a-button type="primary" disabled v-if="planId === item.tenantId"
|
||||
>已安装
|
||||
</a-button>
|
||||
<a-button v-else type="primary" @click="onClone(item)"
|
||||
>安装
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="plug-page" v-if="list.length">
|
||||
<a-pagination
|
||||
v-model:current="where.page"
|
||||
v-model:pageSize="where.limit"
|
||||
size="large"
|
||||
:total="total"
|
||||
@change="reload"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {getPageTitle, openUrl} from '@/utils/common';
|
||||
import {onClone} from '@/utils/plug-uitl';
|
||||
import {useThemeStore} from '@/store/modules/theme';
|
||||
import {storeToRefs} from 'pinia';
|
||||
import {ref} from 'vue';
|
||||
import {Company, CompanyParam} from '@/api/system/company/model';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import {pageCompanyAll} from '@/api/system/company';
|
||||
|
||||
const props = defineProps<{
|
||||
// 修改回显的数据
|
||||
use: boolean;
|
||||
}>();
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const {styleResponsive} = storeToRefs(themeStore);
|
||||
|
||||
const searchText = ref('');
|
||||
const rate = ref(3.5);
|
||||
const list = ref<Company[]>([]);
|
||||
const industry = ref<any>();
|
||||
const total = ref<any>(0);
|
||||
const planId = ref<number>(Number(localStorage.getItem('PlanId')));
|
||||
|
||||
// 查询条件
|
||||
const {where, resetFields} = useSearch<CompanyParam>({
|
||||
keywords: undefined,
|
||||
industryParent: '',
|
||||
industryChild: '',
|
||||
recommend: undefined,
|
||||
authoritative: 1,
|
||||
sceneType: 'recommend',
|
||||
limit: 20,
|
||||
page: 1
|
||||
});
|
||||
|
||||
const onIndustry = (item: any) => {
|
||||
where.industryChild = item[1];
|
||||
};
|
||||
|
||||
const onTabs = (index) => {
|
||||
if (index == 'recommend') {
|
||||
where.recommend = true;
|
||||
}
|
||||
if (index == 'free') {
|
||||
where.recommend = false;
|
||||
}
|
||||
if (index == 'pay') {
|
||||
where.recommend = false;
|
||||
}
|
||||
if (index == 'new') {
|
||||
where.sceneType = 'new';
|
||||
}
|
||||
if (index == 'collect') {
|
||||
where.sceneType = 'collect';
|
||||
}
|
||||
reload();
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
reload();
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
where.sort = 'buys';
|
||||
where.order = 'desc';
|
||||
pageCompanyAll(where).then((data) => {
|
||||
total.value = data?.count;
|
||||
if (data?.list) {
|
||||
list.value = data?.list;
|
||||
}
|
||||
});
|
||||
};
|
||||
reload();
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.ele-body-card {
|
||||
background-color: transparent;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.gutter-row {
|
||||
margin: 0 auto 30px auto;
|
||||
|
||||
.gutter-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 200px;
|
||||
|
||||
.plug-item {
|
||||
display: flex;
|
||||
|
||||
.app-icon {
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 14px;
|
||||
margin-left: 12px;
|
||||
max-width: 234px;
|
||||
|
||||
.name {
|
||||
font-size: 20px;
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.rate {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.company {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plug-desc {
|
||||
padding: 10px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.plug-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ele-text-heading {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.plug-page {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
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>
|
||||
261
src/views/system/plug/create/components/plug-edit.vue
Normal file
261
src/views/system/plug/create/components/plug-edit.vue
Normal file
@@ -0,0 +1,261 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="740"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '插件管理' : '发布插件'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-alert
|
||||
:description="`审核通过后,插件将展示在插件市场,可供其他用户安装和使用后,获取销售分成。`"
|
||||
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>
|
||||
308
src/views/system/plug/detail/index.vue
Normal file
308
src/views/system/plug/detail/index.vue
Normal file
@@ -0,0 +1,308 @@
|
||||
<template>
|
||||
<a-page-header title="应用详情" @back="push('/system/plug')">
|
||||
<a-row :gutter="16">
|
||||
<!-- 左侧区域-->
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 18, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 12 }
|
||||
"
|
||||
class="gutter-row"
|
||||
:span="6"
|
||||
>
|
||||
<a-card :bordered="false" style="width: 100%; margin-bottom: 16px">
|
||||
<div class="goods-info">
|
||||
<div class="logo">
|
||||
<a-image
|
||||
:width="72"
|
||||
:height="72"
|
||||
:preview="false"
|
||||
style="
|
||||
border: 1px solid #e3e3e3;
|
||||
box-shadow: 0 0 1px -1px;
|
||||
border-radius: 12px;
|
||||
"
|
||||
:src="form.companyLogo"
|
||||
/>
|
||||
</div>
|
||||
<div class="info">
|
||||
<a-card :bordered="false" :body-style="{ padding: 0 }">
|
||||
<div class="goods-name ele-text-heading">{{
|
||||
form.shortName
|
||||
}}</div>
|
||||
<div class="comments ele-text-secondary">{{
|
||||
form.comments
|
||||
}}</div>
|
||||
</a-card>
|
||||
<a-card :bordered="false" class="buy-card">
|
||||
<div class="price-box">
|
||||
<div class="left">
|
||||
<div
|
||||
><span class="ele-text-secondary">价格:</span
|
||||
><span class="ele-text-danger price">¥50</span>/月</div
|
||||
>
|
||||
<div
|
||||
><span class="ele-text-secondary">续费:</span
|
||||
><span class="ele-text-heading">¥1280</span></div
|
||||
>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="sales ele-text-secondary">浏览 35</div>
|
||||
<div class="sales ele-text-secondary">评价 3</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="goods-item">-->
|
||||
<!-- <div class="title"> 购买时长 </div>-->
|
||||
<!-- <div class="info">-->
|
||||
<!-- <a-radio-group v-model:value="duration">-->
|
||||
<!-- <a-radio-button value="1">1个月</a-radio-button>-->
|
||||
<!-- <a-radio-button value="12">1年</a-radio-button>-->
|
||||
<!-- <a-radio-button value="24">2年</a-radio-button>-->
|
||||
<!-- <a-radio-button value="36">3年</a-radio-button>-->
|
||||
<!-- <a-radio-button value="60">5年</a-radio-button>-->
|
||||
<!-- </a-radio-group>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<div class="goods-item">
|
||||
<div class="title"></div>
|
||||
<div class="info">
|
||||
<a-button type="primary" size="large">立即购买</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
<a-card :body-style="{ padding: '0 16px 30px ' }">
|
||||
<a-tabs v-model:active-key="active">
|
||||
<a-tab-pane tab="应用详情" key="detail">
|
||||
<div>应用详情</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="演示地址" key="domo">
|
||||
<Tenant :use="true" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="常见问题" key="help">
|
||||
<Tenant :use="true" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="更新日志" key="log">
|
||||
<Tenant :use="true" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="用户评论(8)" key="comments">
|
||||
<Tenant :use="true" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<!-- 右侧区域 -->
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 12 }
|
||||
"
|
||||
class="gutter-row"
|
||||
:span="6"
|
||||
>
|
||||
<a-card :bordered="false" class="task-card">
|
||||
<a-list :bordered="false">
|
||||
<a-list-item style="font-weight: 500">
|
||||
服务厂商:{{ form.companyName }}
|
||||
</a-list-item>
|
||||
<a-list-item @click="openUrl('http://www.' + form.domain)">
|
||||
官方网站:{{ form.domain }}
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
联系客服:<QqOutlined :style="{ fontSize: '18px' }" /><span
|
||||
class="ele-text-secondary"
|
||||
>
|
||||
(在线时间:9:00到6:00)</span
|
||||
>
|
||||
</a-list-item>
|
||||
<a-list-item> 电话:0771-5386339 </a-list-item>
|
||||
<a-list-item> 邮箱:{{ form.email }} </a-list-item>
|
||||
<a-list-item>
|
||||
问题处理:<a-button @click="openUrl('/oa/task/add')"
|
||||
>提交工单</a-button
|
||||
></a-list-item
|
||||
>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, unref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { openUrl } from '@/utils/common';
|
||||
import { QqOutlined } from '@ant-design/icons-vue';
|
||||
import { setPageTabTitle } from '@/utils/page-tab-util';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { getCompanyAll } from '@/api/system/company';
|
||||
import { Company } from '@/api/system/company/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
const { push } = useRouter();
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const { currentRoute } = useRouter();
|
||||
const active = ref('detail');
|
||||
const duration = ref<any>('1');
|
||||
|
||||
// 用户信息
|
||||
const { form, assignFields } = useFormData<Company>({
|
||||
companyId: undefined,
|
||||
companyName: undefined,
|
||||
companyLogo: undefined,
|
||||
shortName: undefined,
|
||||
domain: undefined,
|
||||
email: undefined,
|
||||
tenantId: undefined,
|
||||
tenantName: '',
|
||||
comments: '',
|
||||
version: undefined,
|
||||
createTime: undefined
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
taskType: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择工单类型',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
content: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写问题描述',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sortNumber: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入排序号',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 查询租户信息
|
||||
const query = () => {
|
||||
const { params } = unref(currentRoute);
|
||||
const id = params.id;
|
||||
if (id) {
|
||||
getCompanyAll(Number(id)).then((data) => {
|
||||
console.log(data,'getCompany')
|
||||
assignFields({
|
||||
...data
|
||||
});
|
||||
// 修改当前页签标题
|
||||
setPageTabTitle(`${form.tenantName}`);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
currentRoute,
|
||||
() => {
|
||||
query();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemPlugDetail'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.goods-info {
|
||||
max-width: 80%;
|
||||
display: flex;
|
||||
.logo {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: 20px;
|
||||
width: 120px;
|
||||
}
|
||||
.info {
|
||||
.goods-name {
|
||||
font-size: 22px;
|
||||
}
|
||||
.comments {
|
||||
margin: 5px 0;
|
||||
}
|
||||
.buy-card {
|
||||
margin-top: 20px;
|
||||
width: 700px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #f3faee;
|
||||
border-radius: 1px;
|
||||
.price-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.price {
|
||||
font-size: 28px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.goods-item {
|
||||
max-width: 80%;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
.title {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding-right: 20px;
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
.task-card {
|
||||
padding: 2px !important;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.user-content {
|
||||
max-width: 100%;
|
||||
border-radius: 8px !important;
|
||||
background-color: #a2ec71;
|
||||
border: none;
|
||||
}
|
||||
.admin-content {
|
||||
border-radius: 8px !important;
|
||||
border: 3px solid #f1f1f1;
|
||||
}
|
||||
/deep/.markdown-body {
|
||||
background-color: transparent; /* 设置背景透明 */
|
||||
}
|
||||
.files {
|
||||
margin-top: 10px;
|
||||
}
|
||||
#bottom {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.transparent-bg {
|
||||
background-color: transparent; /* 设置背景透明 */
|
||||
}
|
||||
.item-name {
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
20
src/views/system/plug/index.vue
Normal file
20
src/views/system/plug/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<template #extra>
|
||||
<CmsWebsiteSearch />
|
||||
</template>
|
||||
<Tenant :use="false" />
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Tenant from './components/tenant.vue';
|
||||
import {getPageTitle} from "@/utils/common";
|
||||
import CmsWebsiteSearch from "./components/search.vue";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SystemPlug'
|
||||
};
|
||||
</script>
|
||||
253
src/views/system/plug/list/index.vue
Normal file
253
src/views/system/plug/list/index.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<a-page-header
|
||||
:title="title"
|
||||
:sub-title="subTitle"
|
||||
@back="() => $router.go(-1)"
|
||||
>
|
||||
<template #extra>
|
||||
<a-tabs v-model:activeKey="activeKey" @change="onTabs">
|
||||
<a-tab-pane key="free" tab="免费热门" />
|
||||
<a-tab-pane key="pay" tab="付费热门" />
|
||||
<a-tab-pane key="new" tab="最新上架" />
|
||||
<a-tab-pane key="collect" tab="我的收藏" />
|
||||
</a-tabs>
|
||||
</template>
|
||||
<template v-if="list.length > 0">
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 12 }
|
||||
"
|
||||
class="gutter-row"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:span="6"
|
||||
>
|
||||
<a-card class="gutter-box" hoverable>
|
||||
<div class="plug-item">
|
||||
<a-image
|
||||
:height="80"
|
||||
:width="80"
|
||||
:preview="false"
|
||||
:src="item.companyLogo"
|
||||
style="margin-right: 10px"
|
||||
fallback="https://file.wsdns.cn/20230218/550e610d43334dd2a7f66d5b20bd58eb.svg"
|
||||
/>
|
||||
<div class="info">
|
||||
<a
|
||||
class="name ele-text-heading"
|
||||
@click="openUrl('/system/plug/detail/' + item.companyId)"
|
||||
>{{ item.tenantName }}</a
|
||||
>
|
||||
<a-rate
|
||||
class="rate"
|
||||
v-model:value="rate"
|
||||
disabled
|
||||
allow-half
|
||||
/>
|
||||
<div class="company ele-text-placeholder">
|
||||
<a-typography-paragraph
|
||||
type="secondary"
|
||||
:ellipsis="{ rows: 2, expandable: true, symbol: '...' }"
|
||||
>
|
||||
{{ item.companyName }}
|
||||
</a-typography-paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plug-desc ele-text-secondary">
|
||||
<a-typography-paragraph
|
||||
type="secondary"
|
||||
:ellipsis="{ rows: 2, expandable: true, symbol: '显示' }"
|
||||
:content="item.comments"
|
||||
/>
|
||||
</div>
|
||||
<div class="plug-bottom">
|
||||
<div
|
||||
class="downloads ele-text-placeholder"
|
||||
@click="() => openNotification('success', '开发中')"
|
||||
>
|
||||
安装 {{ item.clicks }}</div
|
||||
>
|
||||
<a-button type="primary" disabled v-if="planId === item.tenantId"
|
||||
>已安装</a-button
|
||||
>
|
||||
<a-button v-else type="primary" @click="onClone(item)"
|
||||
>安装</a-button
|
||||
>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, unref, watch } from 'vue';
|
||||
import { pageCompanyAll } from '@/api/system/company';
|
||||
import { Company, CompanyParam } from '@/api/system/company/model';
|
||||
import { openUrl } from '@/utils/common';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { setPageTabTitle } from '@/utils/page-tab-util';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onClone } from '@/utils/plug-uitl';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
const ROUTE_PATH = '/system/plug/list';
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
const rate = ref(3.5);
|
||||
const title = ref('Tenant');
|
||||
const subTitle = ref('租户系统');
|
||||
const { currentRoute } = useRouter();
|
||||
const list = ref<Company[]>([]);
|
||||
const searchText = ref('');
|
||||
const activeKey = ref('free');
|
||||
const planId = ref<number>(Number(localStorage.getItem('PlanId')));
|
||||
|
||||
/**
|
||||
* 通知提醒框
|
||||
*/
|
||||
const openNotification = (type: string, text: string) => {
|
||||
notification[type]({
|
||||
message: '通知提醒框',
|
||||
description: text
|
||||
});
|
||||
};
|
||||
|
||||
// 查询条件
|
||||
const { where, resetFields } = useSearch<CompanyParam>({
|
||||
keywords: undefined,
|
||||
limit: 500,
|
||||
recommend: undefined,
|
||||
authoritative: 1,
|
||||
sort: 'buys',
|
||||
order: 'desc'
|
||||
});
|
||||
|
||||
const onTabs = () => {
|
||||
resetFields();
|
||||
if (activeKey.value == 'new') {
|
||||
where.sort = 'createTime';
|
||||
where.order = 'desc';
|
||||
}
|
||||
if (activeKey.value == 'free') {
|
||||
where.sort = 'buys';
|
||||
where.order = 'desc';
|
||||
}
|
||||
if (activeKey.value == 'pay') {
|
||||
where.sort = 'likes';
|
||||
where.order = 'desc';
|
||||
}
|
||||
if (activeKey.value == 'collect') {
|
||||
where.sceneType = 'collect';
|
||||
where.limit = 0;
|
||||
}
|
||||
reload();
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
const hide = message.loading('加载中...');
|
||||
pageCompanyAll(where)
|
||||
.then((data) => {
|
||||
if (data?.list) {
|
||||
list.value = data.list;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
};
|
||||
|
||||
reload();
|
||||
|
||||
watch(
|
||||
currentRoute,
|
||||
(route) => {
|
||||
const { path } = unref(route);
|
||||
if (path !== ROUTE_PATH) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { query } = unref(currentRoute);
|
||||
const { type } = query;
|
||||
if (type == 'Tenant') {
|
||||
title.value = 'Tenant';
|
||||
subTitle.value = '租户系统';
|
||||
setPageTabTitle('租户系统');
|
||||
} else if (type == 'Vue') {
|
||||
title.value = 'Vue';
|
||||
subTitle.value = 'Vue开发的应用';
|
||||
setPageTabTitle('Vue开发的应用');
|
||||
} else if (type == 'UniApp') {
|
||||
title.value = 'UniApp';
|
||||
subTitle.value = '使用UniApp开发的移动应用';
|
||||
setPageTabTitle('使用UniApp开发的移动应用');
|
||||
} else if (type == 'WebSite') {
|
||||
title.value = 'WebSite';
|
||||
subTitle.value = '网站应用';
|
||||
setPageTabTitle('网站应用');
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as PlugIcons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
name: 'SystemPlug',
|
||||
components: PlugIcons
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ele-body-card {
|
||||
background-color: transparent;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.gutter-row {
|
||||
margin: 15px auto;
|
||||
.gutter-box {
|
||||
.plug-item {
|
||||
display: flex;
|
||||
.info {
|
||||
font-size: 14px;
|
||||
.name {
|
||||
font-size: 20px;
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
font-weight: 500;
|
||||
}
|
||||
.rate {
|
||||
font-size: 13px;
|
||||
}
|
||||
.company {
|
||||
}
|
||||
}
|
||||
}
|
||||
.plug-desc {
|
||||
padding: 10px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.plug-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
285
src/views/system/plug/search/index.vue
Normal file
285
src/views/system/plug/search/index.vue
Normal file
@@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<a-page-header :title="title" @back="() => $router.go(-1)">
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
margin-bottom: 50px;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
"
|
||||
>
|
||||
<a-space style="flex-wrap: wrap">
|
||||
<a-input-search
|
||||
allow-clear
|
||||
size="large"
|
||||
style="width: 500px"
|
||||
placeholder="请输入搜索关键词"
|
||||
v-model:value="searchText"
|
||||
@pressEnter="reload"
|
||||
@search="reload"
|
||||
/>
|
||||
<a-button size="large" @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<div :bordered="false" class="ele-body-card">
|
||||
<ele-split-layout
|
||||
width="266px"
|
||||
:right-style="{ overflow: 'hidden' }"
|
||||
:style="{ minHeight: 'calc(100vh - 15px)' }"
|
||||
>
|
||||
<div class="ele-bg-white">
|
||||
<ele-toolbar theme="default">
|
||||
<div class="toolbar">
|
||||
<span>应用分类</span>
|
||||
</div>
|
||||
</ele-toolbar>
|
||||
<div class="ele-border-split sys-category-list"> </div>
|
||||
</div>
|
||||
<template #content>
|
||||
<div v-if="list.length > 0">
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { lg: 24 } : { span: 12 }"
|
||||
class="gutter-row"
|
||||
:span="6"
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
>
|
||||
<a-card class="gutter-box" hoverable>
|
||||
<div class="plug-item">
|
||||
<a-image
|
||||
:height="80"
|
||||
:width="80"
|
||||
:preview="false"
|
||||
:src="item.companyLogo"
|
||||
@click="
|
||||
openUrl('/system/plug/detail/' + item.companyId)
|
||||
"
|
||||
fallback="https://file.wsdns.cn/20230218/550e610d43334dd2a7f66d5b20bd58eb.svg"
|
||||
/>
|
||||
<div class="info">
|
||||
<a
|
||||
class="name ele-text-heading"
|
||||
@click="
|
||||
openUrl('/system/plug/detail/' + item.companyId)
|
||||
"
|
||||
>{{ item.tenantName }}</a
|
||||
>
|
||||
<a-rate
|
||||
class="rate"
|
||||
v-model:value="rate"
|
||||
disabled
|
||||
allow-half
|
||||
/>
|
||||
<div class="company ele-text-placeholder">
|
||||
<a-typography-paragraph
|
||||
type="secondary"
|
||||
:ellipsis="{
|
||||
rows: 2,
|
||||
expandable: true,
|
||||
symbol: '...'
|
||||
}"
|
||||
>
|
||||
{{ item.companyName }}
|
||||
</a-typography-paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plug-desc ele-text-secondary">
|
||||
<a-typography-paragraph
|
||||
type="secondary"
|
||||
:ellipsis="{ rows: 2, expandable: true, symbol: '显示' }"
|
||||
:content="item.comments"
|
||||
/>
|
||||
</div>
|
||||
<div class="plug-bottom">
|
||||
<div
|
||||
class="downloads ele-text-placeholder"
|
||||
@click="() => openNotification('success', '开发中')"
|
||||
>
|
||||
安装 {{ item.clicks }}</div
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
disabled
|
||||
v-if="planId === item.tenantId"
|
||||
>已安装</a-button
|
||||
>
|
||||
<a-button v-else type="primary" @click="onClone(item)"
|
||||
>安装</a-button
|
||||
>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<a-space
|
||||
v-if="count > 0"
|
||||
style="display: flex; justify-content: center"
|
||||
>
|
||||
<a-pagination
|
||||
v-model:current="current"
|
||||
:total="count"
|
||||
@change="onChange"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
</ele-split-layout>
|
||||
</div>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, unref, watch } from 'vue';
|
||||
import { pageCompanyAll } from '@/api/system/company';
|
||||
import { Company, CompanyParam } from '@/api/system/company/model';
|
||||
import { openUrl } from '@/utils/common';
|
||||
import { notification } from 'ant-design-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onClone } from '@/utils/plug-uitl';
|
||||
const ROUTE_PATH = '/system/plug/search';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
const rate = ref(3.5);
|
||||
const title = ref('搜索');
|
||||
const { currentRoute } = useRouter();
|
||||
const list = ref<Company[]>([]);
|
||||
const searchText = ref('');
|
||||
const current = ref(1);
|
||||
const count = ref(0);
|
||||
const planId = ref<number>(Number(localStorage.getItem('PlanId')));
|
||||
|
||||
/**
|
||||
* 通知提醒框
|
||||
*/
|
||||
const openNotification = (type: string, text: string) => {
|
||||
notification[type]({
|
||||
message: '通知提醒框',
|
||||
description: text
|
||||
});
|
||||
};
|
||||
|
||||
// 查询条件
|
||||
const { where, resetFields } = useSearch<CompanyParam>({
|
||||
keywords: undefined,
|
||||
companyName: undefined,
|
||||
limit: 10,
|
||||
recommend: undefined,
|
||||
authoritative: 1,
|
||||
page: 1,
|
||||
sort: 'buys',
|
||||
order: 'desc'
|
||||
});
|
||||
|
||||
const onChange = (page) => {
|
||||
where.page = page;
|
||||
reload();
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
searchText.value = '';
|
||||
resetFields();
|
||||
reload();
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
pageCompanyAll(where).then((data) => {
|
||||
if (data?.list) {
|
||||
list.value = data.list;
|
||||
count.value = data.count;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
currentRoute,
|
||||
(route) => {
|
||||
const { path } = unref(route);
|
||||
if (path !== ROUTE_PATH) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { query } = unref(currentRoute);
|
||||
const { type, keywords, companyName } = query;
|
||||
if (companyName) {
|
||||
where.companyName = String(companyName);
|
||||
searchText.value = String(companyName);
|
||||
}
|
||||
if (keywords) {
|
||||
searchText.value = String(keywords);
|
||||
}
|
||||
reload();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as PlugIcons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
name: 'SystemPlug',
|
||||
components: PlugIcons
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ele-body-card {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.gutter-row {
|
||||
margin-bottom: 30px;
|
||||
.gutter-box {
|
||||
.plug-item {
|
||||
display: flex;
|
||||
.info {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
.name {
|
||||
font-size: 20px;
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
font-weight: 500;
|
||||
}
|
||||
.rate {
|
||||
font-size: 13px;
|
||||
}
|
||||
.company {
|
||||
}
|
||||
}
|
||||
}
|
||||
.plug-desc {
|
||||
padding: 10px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.plug-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sys-category-list {
|
||||
padding: 12px 6px;
|
||||
height: calc(100vh - 242px);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
overflow: auto;
|
||||
}
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
274
src/views/system/profile/components/field.vue
Normal file
274
src/views/system/profile/components/field.vue
Normal file
@@ -0,0 +1,274 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<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>
|
||||
<template v-if="field === 'city'">
|
||||
<regions-select
|
||||
v-model:value="city"
|
||||
valueField="label"
|
||||
placeholder="请选择省市区"
|
||||
class="ele-fluid"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="field === 'freeDomain'">
|
||||
<a-input
|
||||
v-model:value="form.freeDomain"
|
||||
placeholder="huawei"
|
||||
addon-before="https://"
|
||||
addon-after=".websoft.top"
|
||||
/>
|
||||
</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-if="field === 'appType'">
|
||||
<a-select
|
||||
:options="appType"
|
||||
:value="form.appType"
|
||||
placeholder="请选择应用类型"
|
||||
@change="onAppType"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="field === 'comments'">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入备注"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-input
|
||||
v-model:value="content"
|
||||
@input="onInput"
|
||||
:placeholder="`请输入${field}的内容`"
|
||||
@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';
|
||||
|
||||
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,
|
||||
appType: '',
|
||||
companyLogo: '',
|
||||
domain: '',
|
||||
freeDomain: undefined,
|
||||
phone: '',
|
||||
invoiceHeader: '',
|
||||
startTime: '',
|
||||
expirationTime: '',
|
||||
version: undefined,
|
||||
members: undefined,
|
||||
departments: undefined,
|
||||
country: '',
|
||||
province: '',
|
||||
city: '',
|
||||
region: '',
|
||||
address: '',
|
||||
comments: '',
|
||||
authentication: undefined,
|
||||
requestUrl: '',
|
||||
socketUrl: '',
|
||||
serverUrl: '',
|
||||
modulesUrl: '',
|
||||
status: undefined,
|
||||
userId: undefined,
|
||||
tenantId: 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: '其他组织'
|
||||
}
|
||||
]);
|
||||
|
||||
const appType = ref<SelectProps['options']>([
|
||||
{
|
||||
value: 'web',
|
||||
label: '应用'
|
||||
},
|
||||
{
|
||||
value: 'plug',
|
||||
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 onAppType = (e) => {
|
||||
form.appType = 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>
|
||||
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>
|
||||
232
src/views/system/profile/components/version.vue
Normal file
232
src/views/system/profile/components/version.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
width="80%"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="`商业授权`"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '16px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<div class="version-select">
|
||||
<div class="item">1 </div>
|
||||
<div class="item">2</div>
|
||||
<div class="item">3</div>
|
||||
</div>
|
||||
</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';
|
||||
|
||||
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 activeKey = ref('1');
|
||||
const placeholder = ref('请输入修改内容');
|
||||
// 用户信息
|
||||
const form = reactive<Company>({
|
||||
companyId: undefined,
|
||||
shortName: '',
|
||||
companyName: '',
|
||||
companyType: undefined,
|
||||
appType: '',
|
||||
companyLogo: '',
|
||||
domain: '',
|
||||
phone: '',
|
||||
InvoiceHeader: '',
|
||||
startTime: '',
|
||||
expirationTime: '',
|
||||
version: undefined,
|
||||
members: undefined,
|
||||
departments: undefined,
|
||||
country: '',
|
||||
province: '',
|
||||
city: '',
|
||||
region: '',
|
||||
address: '',
|
||||
comments: '',
|
||||
authentication: undefined,
|
||||
modules: '',
|
||||
requestUrl: '',
|
||||
socketUrl: '',
|
||||
serverUrl: '',
|
||||
modulesUrl: '',
|
||||
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: '其他组织'
|
||||
}
|
||||
]);
|
||||
|
||||
const appType = ref<SelectProps['options']>([
|
||||
{
|
||||
value: 'Tenant',
|
||||
label: 'Tenant'
|
||||
},
|
||||
{
|
||||
value: 'Vue',
|
||||
label: 'Vue'
|
||||
},
|
||||
{
|
||||
value: 'UniApp',
|
||||
label: 'UniApp'
|
||||
},
|
||||
{
|
||||
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 onAppType = (e) => {
|
||||
form.appType = 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">
|
||||
.version-select {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
.item {
|
||||
width: 300px;
|
||||
border: 1px solid #f0f0f0;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
449
src/views/system/profile/index.vue
Normal file
449
src/views/system/profile/index.vue
Normal file
@@ -0,0 +1,449 @@
|
||||
<template>
|
||||
<div class="ele-body ele-body-card">
|
||||
<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">
|
||||
<SelectFile
|
||||
:placeholder="`请选择图片`"
|
||||
:limit="1"
|
||||
:data="logo"
|
||||
@done="chooseFile"
|
||||
@del="onDeleteItem"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="租户ID">
|
||||
<span class="ele-text-heading">{{ form.tenantId }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="应用名称">
|
||||
<a-space class="justify ele-text-heading">
|
||||
<span>{{ form.shortName }}</span>
|
||||
<a @click="onEdit('应用名称', 'shortName', form.shortName)"
|
||||
>修改</a
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="应用描述">
|
||||
<a-space class="justify ele-text-heading">
|
||||
<div style="max-width: 600px">{{ form.comments }}</div>
|
||||
<a @click="onEdit('应用描述', 'comments', form.comments)"
|
||||
>修改</a
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="专属域名">
|
||||
<a-space class="justify">
|
||||
<a class="cursor-pointer" @click="openSpmUrl(`${form.adminUrl}`)">{{ `${form.adminUrl}` }}</a>
|
||||
<a @click="onEdit('专属域名', 'adminUrl', form.adminUrl)">修改</a>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="应用类型">-->
|
||||
<!-- <a-space class="justify">-->
|
||||
<!-- <span>Tenant</span>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="应用状态">-->
|
||||
<!-- <span v-if="form.status == 0" class="ele-text-success">已上线</span>-->
|
||||
<!-- <span v-if="form.status == 1" class="ele-text-warning">开发中</span>-->
|
||||
<!-- <span v-if="form.status == 2" class="ele-text-danger">维护中</span>-->
|
||||
<!-- <span v-if="form.status == 3" class="ele-text-placeholder">已欠费</span>-->
|
||||
<!-- </a-form-item>-->
|
||||
<a-form-item label="应用版本">
|
||||
<a-space class="justify">
|
||||
<a-tag v-if="form.version === 10" class="cursor-pointer" @click="updateVersion(form.version)">基础版</a-tag>
|
||||
<a-tag color="blue" v-if="form.version === 20" class="cursor-pointer" @click="updateVersion(form.version)">专业版</a-tag>
|
||||
<a-tag color="cyan" v-if="form.version === 30">永久授权</a-tag>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="网站主页">-->
|
||||
<!-- <a-space class="justify">-->
|
||||
<!-- <span class="cursor-pointer" @click="openSpmUrl(`https://${form.websiteUrl}`)">{{ `${form.websiteUrl}` }}</span>-->
|
||||
<!-- <a @click="onEdit('专属域名', 'websiteUrl', form.websiteUrl)">修改</a>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </a-form-item>-->
|
||||
<a-form-item label="开通时间">
|
||||
<span class="ele-text-heading">{{ form.createTime }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="到期时间" v-if="form.version != 30">
|
||||
<a-space>
|
||||
<span class="ele-text-heading">{{ form.expirationTime }}</span>
|
||||
<a-tag color="red">已到期</a-tag>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-divider style="padding-bottom: 20px" />
|
||||
<!-- <a-form-item label="主体名称">-->
|
||||
<!-- <a-space class="justify">-->
|
||||
<!-- <div class="ele-text-heading">-->
|
||||
<!-- <span style="padding-right: 12px">{{ form.companyName ? form.companyName : "-" }}</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- <a @click="onEdit('主体名称', 'companyName', form.companyName)">修改</a>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </a-form-item>-->
|
||||
<a-form-item label="主体类型">
|
||||
<a-space class="justify">
|
||||
<a-tag>{{ form.companyType }}</a-tag>
|
||||
<a @click="onEdit('主体类型', 'companyType', form.companyType)">修改</a>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="实名认证">
|
||||
<a-space class="justify">
|
||||
<a-tag v-if="form.authentication == 1" color="green">已认证</a-tag>
|
||||
<a-tag v-else color="orange" class="cursor-pointer" @click="openSpmUrl(`https://websoft.top/user/auth`)">未认证</a-tag>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="所属行业">
|
||||
<a-space class="justify">
|
||||
<span>{{ form.industryParent }}/{{ form.industryChild }}</span>
|
||||
<a @click="onEdit('行业类型', 'industryParent', form.industryParent)"
|
||||
>修改</a
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="用户数量">
|
||||
<a-space class="justify ele-text-heading">
|
||||
<span>{{ form.users }}/{{ form.members }}</span>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="部门数量">-->
|
||||
<!-- <a-space class="justify ele-text-heading">-->
|
||||
<!-- <span>{{ form.departments }}</span>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </a-form-item>-->
|
||||
<a-form-item label="存储空间">
|
||||
<a-space class="justify ele-text-heading">
|
||||
<span>{{ getFileSize(form.storage) }}/{{ getFileSize(form.storageMax) }}</span>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="所属地区">-->
|
||||
<!-- <a-space class="justify">-->
|
||||
<!-- <span-->
|
||||
<!-- >{{ form.province }}{{ form.city }}{{ form.region ? form.region : '-' }}</span-->
|
||||
<!-- >-->
|
||||
<!-- <a @click="onEdit('所属地区', 'city', form.city)">修改</a>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="企业地址">-->
|
||||
<!-- <a-space class="justify">-->
|
||||
<!-- <span>{{ form.address ? form.address : '-' }}</span>-->
|
||||
<!-- <a @click="onEdit('企业地址', 'address', form.address)">修改</a>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="企业域名">-->
|
||||
<!-- <a-space class="justify ele-text-heading">-->
|
||||
<!-- <span>{{ form.domain ? form.domain : '-' }}</span>-->
|
||||
<!-- <a @click="onEdit('企业域名', 'domain', form.domain)">修改</a>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="联系电话">-->
|
||||
<!-- <a-space class="justify ele-text-heading">-->
|
||||
<!-- <span>{{ form.phone }}</span>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="电子邮箱">-->
|
||||
<!-- <a-space class="justify ele-text-heading">-->
|
||||
<!-- <span>{{ form.email }}</span>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </a-form-item>-->
|
||||
<a-divider style="padding-bottom: 20px" />
|
||||
<!-- <a-form-item label="应用类型">-->
|
||||
<!-- <a-space class="justify">-->
|
||||
<!-- <a-tag>{{ form.appType }}</a-tag>-->
|
||||
<!-- <a @click="onEdit('应用类型', 'appType', form.appType)">修改</a>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="专属域名">-->
|
||||
<!-- <a-space class="justify">-->
|
||||
<!-- <span class="cursor-pointer" @click="openSpmUrl(`https://${form.domain}`)">{{ `${form.domain}` }}</span>-->
|
||||
<!-- <a @click="onEdit('专属域名', 'domain', form.domain)">修改</a>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="当前版本">-->
|
||||
<!-- <a-space class="justify ele-text-heading">-->
|
||||
<!-- {{ form.versionName }}-->
|
||||
<!-- <a @click="openUrl('/system/version')">更新</a>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-divider style="padding-bottom: 20px" />-->
|
||||
<a-form-item
|
||||
v-if="form.tenantId"
|
||||
label="注销"
|
||||
>
|
||||
<a-button @click="destruction(form.tenantId)" :disabled="true">注销</a-button>
|
||||
<div class="ele-text-placeholder">注销后,当前应用的数据将会销毁,且不可恢复,请谨慎操作</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
<Field
|
||||
v-model:visible="showEdit"
|
||||
:data="form"
|
||||
:title="title"
|
||||
:field="field"
|
||||
:content="content"
|
||||
@done="query"
|
||||
/>
|
||||
<Version
|
||||
v-model:visible="showVersionForm"
|
||||
:data="form"
|
||||
title="升级套餐"
|
||||
:field="field"
|
||||
:content="content"
|
||||
@done="query"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, createVNode } from "vue";
|
||||
import { Form, message, Modal } from "ant-design-vue";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useThemeStore } from "@/store/modules/theme";
|
||||
import { getCompany, updateCompany, destructionTenant } from "@/api/system/company";
|
||||
import Field from "./components/field.vue";
|
||||
import Version from "./components/version.vue";
|
||||
import { assignObject } from "ele-admin-pro";
|
||||
import { getFileSize, openSpmUrl, openUrl } 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";
|
||||
import { logout } from "@/utils/page-tab-util";
|
||||
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
|
||||
import { FileRecord } from "@/api/system/file/model";
|
||||
import { uuid } from 'ele-admin-pro';
|
||||
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
const userStore = useUserStore();
|
||||
// tab 页选中
|
||||
const active = ref("info");
|
||||
// 是否显示裁剪弹窗
|
||||
const logo = ref<any>([]);
|
||||
const field = ref("");
|
||||
const title = ref("");
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
const content = ref("请输入要修改的内容");
|
||||
const showVersionForm = ref(false);
|
||||
// 登录用户信息
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<Company>({
|
||||
companyId: undefined,
|
||||
shortName: "",
|
||||
companyName: "",
|
||||
companyType: undefined,
|
||||
companyLogo: "",
|
||||
domain: "",
|
||||
freeDomain: undefined,
|
||||
phone: "",
|
||||
email: "",
|
||||
invoiceHeader: "",
|
||||
startTime: "",
|
||||
expirationTime: "",
|
||||
appType: undefined,
|
||||
planId: 0,
|
||||
businessEntity: "",
|
||||
version: undefined,
|
||||
versionName: "",
|
||||
versionCode: "",
|
||||
members: undefined,
|
||||
storage: undefined,
|
||||
storageMax: undefined,
|
||||
users: undefined,
|
||||
departments: undefined,
|
||||
country: "",
|
||||
province: "",
|
||||
city: "",
|
||||
region: "",
|
||||
address: "",
|
||||
comments: "",
|
||||
authentication: undefined,
|
||||
industryParent: undefined,
|
||||
industryChild: undefined,
|
||||
status: undefined,
|
||||
userId: undefined,
|
||||
tenantId: undefined,
|
||||
adminUrl: '',
|
||||
websiteUrl: '',
|
||||
requestUrl: "",
|
||||
socketUrl: "",
|
||||
serverUrl: "",
|
||||
modulesUrl: "",
|
||||
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 chooseFile = (data: FileRecord) => {
|
||||
logo.value.push({
|
||||
uid: data.id,
|
||||
url: data.thumbnail,
|
||||
status: "done"
|
||||
});
|
||||
form.companyLogo = data.path;
|
||||
updateCompany(form).then(() => {
|
||||
message.success("上传成功");
|
||||
});
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
logo.value.splice(index, 1);
|
||||
form.companyLogo = "";
|
||||
};
|
||||
|
||||
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 updateVersion = (version: any) => {
|
||||
if(version === 30){
|
||||
return ;
|
||||
}
|
||||
if(version === 20){
|
||||
showVersionForm.value = true;
|
||||
}
|
||||
if(version === 10){
|
||||
showVersionForm.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const destruction = (tenantId) => {
|
||||
Modal.confirm({
|
||||
title: "确定要销毁吗?",
|
||||
content: "销毁后,当前租户的相关数据将永久删除,且不可恢复,请谨慎操作!",
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading("正在销毁相关数据请等待...", 0);
|
||||
setTimeout(() => {
|
||||
destructionTenant(tenantId).then(() => {
|
||||
logout();
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
const query = () => {
|
||||
logo.value = [];
|
||||
getCompany().then((response) => {
|
||||
if (response.companyLogo) {
|
||||
logo.value.push({
|
||||
uid: `${uuid()}`,
|
||||
url: response.companyLogo,
|
||||
status: "done"
|
||||
});
|
||||
}
|
||||
assignObject(form, response);
|
||||
});
|
||||
};
|
||||
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "SystemProfile"
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.justify {
|
||||
display: flex !important;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.cursor-pointer{
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user