v3.8.2 版本前端代码

This commit is contained in:
JEECG
2025-07-30 18:25:58 +08:00
parent e6edde963a
commit 219869f4c0
84 changed files with 3587 additions and 1964 deletions

View File

@ -0,0 +1,197 @@
<template>
<PageWrapper contentFullHeight>
<a-card :bordered="false" title="版本管理">
<!--编辑模式-->
<a-spin v-if="active" :spinning="confirmLoading">
<a-form ref="formRef" :model="model" :labelCol="labelCol" :wrapperCol="wrapperCol" :rules="validatorRules">
<a-row>
<a-col :span="24">
<a-form-item label="版本" name="appVersion">
<a-input v-model:value="model.appVersion" placeholder="请输入版本" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="APP安装apk" name="downloadUrl">
<a-input placeholder="设置APP安装apk" v-model:value="model.downloadUrl">
<template #addonAfter>
<Icon icon="ant-design:upload-outlined" style="cursor: pointer" @click="showUploadModal('apk')" />
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="APP热更新文件" name="wgtUrl">
<a-input placeholder="设置APP热更新文件" v-model:value="model.wgtUrl">
<template #addonAfter>
<Icon icon="ant-design:upload-outlined" style="cursor: pointer" @click="showUploadModal('wgt')" />
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="更新内容">
<a-textarea :rows="4" v-model:value="model.updateNote" placeholder="请输入更新内容" />
</a-form-item>
</a-col>
</a-row>
</a-form>
<JUploadModal :value="modalValue" :bizPath="filePath" :maxCount="1" @register="registerModel" @change="uploadBack" />
</a-spin>
<!--详情模式-->
<Description v-else class="desc" :column="1" :data="model" :schema="schema" />
<!--底部按钮-->
<div class="anty-form-btn" v-if="hasPermission('app:edit:version')">
<a-button v-if="active" @click="handleSubmit" type="primary" preIcon="ant-design:save-outlined">保存</a-button>
<a-button v-else @click="active = true" type="primary" preIcon="ant-design:edit-outlined">开启编辑模式</a-button>
</div>
</a-card>
</PageWrapper>
</template>
<script lang="ts" setup name="portalapp-sysAppVersion">
import { useMessage } from '/@/hooks/web/useMessage';
import { usePermission } from '@/hooks/web/usePermission';
import { JUploadModal } from '@/components/Form/src/jeecg/components/JUpload';
import { useModal } from '@/components/Modal';
import { reactive, ref, toRaw, unref, onMounted } from 'vue';
import { PageWrapper } from '@/components/Page';
import { queryAppVersion, saveAppVersion } from './appVersion.api';
import { Description, DescItem } from '/@/components/Description/index';
const { hasPermission } = usePermission();
const { createMessage } = useMessage();
const [registerModel, { openModal }] = useModal();
const confirmLoading = ref(false);
const active = ref(false);
const formRef = ref<any>(null);
const appKey = 'E0CC280';
const filePath = 'appVersion';
const uploadType = ref('');
const modalValue = ref('');
const labelCol = {
xs: { span: 24 },
sm: { span: 5 },
};
const wrapperCol = {
xs: { span: 24 },
sm: { span: 16 },
};
const model = reactive({
id: 'E0CC280',
appVersion: '',
versionNum: 0,
updateNote: '',
downloadUrl: '',
wgtUrl: '',
});
/**
* 初始化表单数据
* @param record
*/
async function initFormData() {
const appVersion = await queryAppVersion({ key: appKey });
if (appVersion) {
Object.assign(model, appVersion);
}
}
/**
* 提交保存版本信息
*/
function handleSubmit() {
const form = unref(formRef);
form.validate().then(async () => {
let obj = toRaw(model);
if (obj.appVersion.indexOf('.') != -1) {
obj.versionNum = Number(obj.appVersion.replace(/\./g, ''));
}
obj.id = appKey;
confirmLoading.value = true;
await saveAppVersion(obj);
createMessage.success('保存成功');
confirmLoading.value = false;
active.value = false;
});
}
/**
* 显示设置弹窗
* @param type
*/
function showUploadModal(type) {
uploadType.value = type;
modalValue.value = type == 'apk' ? model.downloadUrl : model.wgtUrl;
openModal(true, {
maxCount: 1,
bizPath: filePath,
});
}
/**
*上传返回
*/
function uploadBack(value) {
if (unref(uploadType) == 'apk') {
model.downloadUrl = value;
} else {
model.wgtUrl = value;
}
}
//表单校验规则
const validatorRules = {
appVersion: [{ required: true, message: '版本不能为空', trigger: 'blur' }],
downloadUrl: [{ required: true, message: 'APP安装apk不能为空', trigger: 'change' }],
wgtUrl: [{ required: true, message: 'APP热更新文件不能为空', trigger: 'change' }],
};
// 显示字段
const schema: DescItem[] = [
{
field: 'appVersion',
label: '版本',
},
{
field: 'downloadUrl',
label: 'APP安装apk',
},
{
field: 'wgtUrl',
label: 'APP热更新文件',
},
{
field: 'updateNote',
label: '更新内容',
},
];
onMounted(() => {
initFormData();
});
</script>
<style scoped>
.anty-form-btn {
width: 100%;
text-align: center;
}
.anty-form-btn button {
margin: 20px;
}
.approveDiv span {
margin: 0 20px;
}
.desc {
width: 80%;
margin: 0 auto;
}
:deep(.ant-descriptions-item-label) {
width: 30% !important;
min-width: 150px !important;
}
:deep(.ant-descriptions-item-content) {
padding: 16px !important;
width: 60% !important;
}
</style>

View File

@ -0,0 +1,20 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
//查询app版本
queryAppVersion = '/sys/version/app3version',
//保存app版本
saveAppVersion = '/sys/version/saveVersion',
}
/**
* 查询APP版本
* @param params
*/
export const queryAppVersion = (params) => defHttp.get({ url: Api.queryAppVersion, params });
/**
* 保存APP版本
* @param params
*/
export const saveAppVersion = (params) => {
return defHttp.post({ url: Api.saveAppVersion, params });
};

View File

@ -10,6 +10,7 @@ enum Api {
wechatEnterpriseToLocal = '/sys/thirdApp/sync/wechatEnterprise/departAndUser/toLocal',
getThirdUserBindByWechat = '/sys/thirdApp/getThirdUserBindByWechat',
deleteThirdAccount = '/sys/thirdApp/deleteThirdAccount',
deleteThirdAppConfig = '/sys/thirdApp/deleteThirdAppConfig',
}
/**
@ -66,4 +67,15 @@ export const getThirdUserBindByWechat = () => {
*/
export const deleteThirdAccount = (params) => {
return defHttp.delete({ url: Api.deleteThirdAccount, params }, { isTransformResponse:false, joinParamsToUrl: true });
};
};
/**
* 根据配置表的id删除第三方配置
* @param params
* @param handleSuccess
*/
export const deleteThirdAppConfig = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteThirdAppConfig, params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};

View File

@ -17,7 +17,7 @@
<a-collapse-panel key="2">
<template #header>
<div style="width: 100%; justify-content: space-between; display: flex">
<div style="font-size: 16px"> 2.对接信息录入</div>
<div style="font-size: 16px"> 2.对接信息录入及解绑</div>
</div>
</template>
<div class="base-desc">完成步骤1后填入Agentld AppKeyAppSecret后 可对接应用与同步通讯录</div>
@ -47,6 +47,7 @@
</div>
<div style="margin-top: 20px; width: 100%; text-align: right">
<a-button @click="dingEditClick">编辑</a-button>
<a-button v-if="appConfigData.id" @click="cancelBindClick" danger style="margin-left: 10px">取消绑定</a-button>
</div>
</a-collapse-panel>
</a-collapse>
@ -76,7 +77,7 @@
<script lang="ts">
import { defineComponent, h, inject, onMounted, reactive, ref, watch } from 'vue';
import { getThirdConfigByTenantId, syncDingTalkDepartUserToLocal } from './ThirdApp.api';
import { getThirdConfigByTenantId, syncDingTalkDepartUserToLocal, deleteThirdAppConfig } from './ThirdApp.api';
import { useModal } from '/@/components/Modal';
import ThirdAppConfigModal from './ThirdAppConfigModal.vue';
import { Modal } from 'ant-design-vue';
@ -122,6 +123,8 @@
let values = await getThirdConfigByTenantId(params);
if (values) {
appConfigData.value = values;
} else {
appConfigData.value = "";
}
}
@ -214,6 +217,25 @@
function handleIconClick(){
window.open("https://help.qiaoqiaoyun.com/expand/dingdingsyn.html","_target")
}
/**
* 取消绑定
*/
function cancelBindClick() {
if(!appConfigData.value.id){
createMessage.warning("请先绑定钉钉应用!");
return;
}
Modal.confirm({
title: '取消绑定',
content: '是否要解除当前组织的钉钉应用配置绑定?',
okText: '确认',
cancelText: '取消',
onOk: () => {
deleteThirdAppConfig({ id: appConfigData.value.id }, handleSuccess);
},
});
}
onMounted(() => {
let tenantId = getTenantId();
@ -229,6 +251,7 @@
syncDingTalk,
btnLoading,
handleIconClick,
cancelBindClick,
};
},
});

View File

@ -17,7 +17,7 @@
<a-collapse-panel key="2">
<template #header>
<div style="width: 100%; justify-content: space-between; display: flex">
<div style="font-size: 16px"> 2.对接信息录入</div>
<div style="font-size: 16px"> 2.对接信息录入及解绑</div>
</div>
</template>
<div class="flex-flow">
@ -40,6 +40,7 @@
</div>
<div style="margin-top: 20px; width: 100%; text-align: right">
<a-button @click="weEnterpriseEditClick">编辑</a-button>
<a-button v-if="appConfigData.id" @click="cancelBindClick" danger style="margin-left: 10px">取消绑定</a-button>
</div>
</a-collapse-panel>
</a-collapse>
@ -61,7 +62,7 @@
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import { getThirdConfigByTenantId } from './ThirdApp.api';
import { getThirdConfigByTenantId, deleteThirdAppConfig } from './ThirdApp.api';
import ThirdAppConfigModal from './ThirdAppConfigModal.vue';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
@ -97,6 +98,8 @@
let values = await getThirdConfigByTenantId(params);
if (values) {
appConfigData.value = values;
} else {
appConfigData.value = "";
}
}
@ -159,6 +162,25 @@
function seeBindWeChat() {
openBindModal(true,{ izBind: true })
}
/**
* 取消绑定
*/
function cancelBindClick() {
if(!appConfigData.value.id){
createMessage.warning("请先绑定企业微信应用!");
return;
}
Modal.confirm({
title: '取消绑定',
content: '是否要解除当前组织的企业微信应用配置绑定?',
okText: '确认',
cancelText: '取消',
onOk: () => {
deleteThirdAppConfig({ id: appConfigData.value.id }, handleSuccess);
},
});
}
onMounted(() => {
let tenantId = getTenantId();
@ -175,6 +197,7 @@
thirdUserByWechat,
handleBindSuccess,
seeBindWeChat,
cancelBindClick,
};
},
});

View File

@ -0,0 +1,60 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="首页配置" @ok="handleSubmit" :width="600">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from '../home.data';
import { saveOrUpdate } from '../home.api';
// Emits声明
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(false);
//表单配置
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
labelWidth: 100,
baseRowStyle: { marginTop: '10px' },
schemas: formSchema,
showActionButtonGroup: false,
});
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
//重置表单
await resetFields();
setModalProps({ confirmLoading: false });
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)) {
//表单赋值
if (data.values.relationType == 'USER') {
data.values.userCode = data.values.roleCode;
}
await setFieldsValue({
...data.values,
});
}
});
//表单提交事件
async function handleSubmit() {
try {
let values = await validate();
setModalProps({ confirmLoading: true });
//提交表单
if(values.relationType == 'USER'){
values.roleCode = values.userCode;
}
await saveOrUpdate(values, isUpdate.value);
//关闭弹窗
closeModal();
//刷新列表
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,55 @@
import { defHttp } from '/@/utils/http/axios';
import { Modal } from 'ant-design-vue';
enum Api {
list = '/sys/sysRoleIndex/list',
save = '/sys/sysRoleIndex/add',
edit = '/sys/sysRoleIndex/edit',
deleteIndex = '/sys/sysRoleIndex/delete',
deleteBatch = '/sys/sysRoleIndex/deleteBatch',
queryIndexByCode = '/sys/sysRoleIndex/queryByCode',
}
/**
* 系统角色列表
* @param params
*/
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
* 删除角色
*/
export const deleteIndex = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteIndex, params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};
/**
* 批量删除角色
* @param params
*/
export const batchDelete = (params, handleSuccess) => {
Modal.confirm({
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
},
});
};
/**
* 保存或者更新首页配置
* @param params
*/
export const saveOrUpdate = (params, isUpdate) => {
const url = isUpdate ? Api.edit : Api.save;
return defHttp.post({ url: url, params });
};
/**
* 查询首页配置
* @param params
*/
export const queryIndexByCode = (params) => defHttp.get({ url: Api.queryIndexByCode, params }, { isTransformResponse: false });

View File

@ -0,0 +1,129 @@
import { FormSchema } from '/@/components/Table';
//列配置
export const columns = [
{
title: '关联类型(用户/角色)',
dataIndex: 'relationType_dictText',
width: 80,
slots: { customRender: 'relationType' },
},
{
title: '用户/角色编码',
dataIndex: 'roleCode',
width: 80,
slots: { customRender: 'roleCode' },
},
{
title: '首页路由',
dataIndex: 'url',
width: 100,
},
{
title: '组件地址',
dataIndex: 'component',
width: 100,
},
{
title: '是否开启',
dataIndex: 'status',
slots: { customRender: 'status' },
width: 60,
},
];
//查询配置
export const searchFormSchema: FormSchema[] = [
{
field: 'relationType',
label: '关联类型',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'relation_type',
},
},
{
field: 'route',
label: '是否路由菜单',
helpMessage: '非路由菜单设置成首页,需开启',
component: 'Switch',
show: false,
},
];
export const formSchema: FormSchema[] = [
{
field: 'id',
label: '',
component: 'Input',
show: false,
},
{
field: 'relationType',
label: '关联类型',
component: 'JDictSelectTag',
required: true,
defaultValue: 'ROLE',
componentProps: {
dictCode: 'relation_type',
type: 'radioButton',
},
},
{
label: '角色编码',
field: 'roleCode',
component: 'JSelectRole',
required: true,
componentProps: {
rowKey: 'roleCode',
isRadioSelection: true,
},
ifShow: ({ values }) => values.relationType == 'ROLE',
},
{
label: '用户编码',
field: 'userCode',
component: 'JSelectUser',
required: true,
componentProps: {
isRadioSelection: true,
},
ifShow: ({ values }) => values.relationType == 'USER',
},
{
label: '首页路由',
field: 'url',
component: 'Input',
required: true,
},
{
label: '组件地址',
field: 'component',
component: 'Input',
componentProps: {
placeholder: '请输入前端组件',
},
required: true,
},
{
label: '优先级',
field: 'priority',
component: 'InputNumber',
},
{
field: 'route',
label: '是否路由菜单',
helpMessage: '非路由菜单设置成首页,需开启',
component: 'Switch',
defaultValue: true,
show: false,
},
{
label: '是否开启',
field: 'status',
component: 'JSwitch',
defaultValue: '1',
componentProps: {
options: ['1', '0'],
},
},
];

View File

@ -0,0 +1,126 @@
<template>
<div>
<BasicTable @register="registerTable" :rowSelection="rowSelection">
<template #tableTitle>
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate">新增</a-button>
<a-dropdown v-if="selectedRowKeys.length > 0">
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="batchHandleDelete">
<Icon icon="ant-design:delete-outlined" /> 删除
</a-menu-item>
</a-menu>
</template>
<a-button>批量操作<Icon icon="mdi:chevron-down" /></a-button>
</a-dropdown>
</template>
<template #action="{ record }">
<TableAction :actions="getTableAction(record)" />
</template>
<template #status="{ text }">
<a-tag color="pink" v-if="text == 0">禁用</a-tag>
<a-tag color="#87d068" v-if="text == 1">启用</a-tag>
</template>
<template #relationType="{ text, record }">
<span>{{ record.roleCode == 'DEF_INDEX_ALL' ? '--' : text }}</span>
</template>
<template #roleCode="{ text, record }">
<span>{{ record.roleCode == 'DEF_INDEX_ALL' ? '菜单默认首页' : text }}</span>
</template>
</BasicTable>
<!--角色首页配置-->
<HomeConfigModal @register="register" @success="reload" />
</div>
</template>
<script lang="ts" name="home-config" setup>
import { BasicTable, TableAction } from '/@/components/Table';
import { useModal } from '/@/components/Modal';
import HomeConfigModal from './components/HomeConfigModal.vue';
import { columns, searchFormSchema } from './home.data';
import { useListPage } from '/@/hooks/system/useListPage';
import { list, deleteIndex, batchDelete } from './home.api';
//弹窗配置
const [register, { openModal }] = useModal();
// 列表页面公共参数、方法
const { tableContext } = useListPage({
designScope: 'home-config',
tableProps: {
title: '首页配置',
api: list,
columns: columns,
formConfig: {
labelAlign: 'left',
labelWidth: 80,
schemas: searchFormSchema,
baseRowStyle: {
marginLeft: '2px',
},
},
actionColumn: {
width: 80,
},
//自定义默认排序
defSort: {
column: 'id',
order: 'desc',
},
},
});
const [registerTable, { reload, clearSelectedRowKeys }, { rowSelection, selectedRowKeys }] = tableContext;
/**
* 新增事件
*/
async function handleCreate() {
openModal(true, {
isUpdate: false,
});
}
/**
* 编辑事件
*/
async function handleEdit(record) {
openModal(true, {
isUpdate: true,
values: record,
});
}
/**
* 删除事件
*/
async function handleDelete(record) {
await deleteIndex({ id: record.id }, () => {
reload();
});
}
/**
* 批量删除事件
*/
async function batchHandleDelete() {
await batchDelete({ ids: selectedRowKeys.value }, () => {
clearSelectedRowKeys();
reload();
});
}
/**
* 操作栏
*/
function getTableAction(record) {
return [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
},
{
label: '删除',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
},
},
];
}
</script>

View File

@ -344,7 +344,9 @@
return;
}
//update-begin---author:wangshuai---date:2024-04-18---for:【QQYUN-9005】同一个IP1分钟超过5次短信则提示需要验证码---
const result = await getCaptcha({ mobile: phoneFormData.mobile, smsmode: SmsEnum.FORGET_PASSWORD }).catch((res) =>{
//update-begin---author:wangshuai---date:2025-07-15---for:【issues/8567】严重修改密码存在水平越权问题登录应该用登录模板不应该用忘记密码的模板---
const result = await getCaptcha({ mobile: phoneFormData.mobile, smsmode: SmsEnum.LOGIN }).catch((res) =>{
//update-end---author:wangshuai---date:2025-07-15---for:【issues/8567】严重修改密码存在水平越权问题登录应该用登录模板不应该用忘记密码的模板---
if(res.code === ExceptionEnum.PHONE_SMS_FAIL_CODE){
openCaptchaModal(true, {});
}

View File

@ -1,5 +1,5 @@
<template>
<BasicModal @register="registerModal" :title="title" :width="800" v-bind="$attrs" @ok="onSubmit">
<BasicModal @register="registerModal" :title="title" :width="600" v-bind="$attrs" @ok="onSubmit">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
@ -21,6 +21,15 @@
//update-end---author:wangshuai ---date:20221123 for[VUEN-2807]消息模板加一个查看功能--------------z
schemas: formSchemas,
showActionButtonGroup: false,
baseRowStyle: {
marginTop: '10px',
},
labelCol: {
span: 5,
},
wrapperCol: {
span: 17,
},
});
// 注册 modal
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {

View File

@ -86,12 +86,23 @@ export const formSchemas: FormSchema[] = [
label: '模板类型',
field: 'templateType',
component: 'JDictSelectTag',
defaultValue: '1',
componentProps: {
dictCode: 'msgType',
type: 'radio',
placeholder: '请选择模板类型',
},
required: true,
},
{
label: '模板分类',
field: 'templateCategory',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'msgCategory',
placeholder: '请选择模板分类',
}
},
{
label: '是否应用',
field: 'useStatus',

View File

@ -1,20 +1,113 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="查看详情" :showCancelBtn="false" :showOkBtn="false" :maxHeight="500">
<iframe :src="frameSrc" class="detail-iframe" />
<BasicModal v-bind="$attrs" @register="registerModal" :width="800" title="查看详情" :showCancelBtn="false" :showOkBtn="false" :maxHeight="500">
<div class="print-btn" @click="onPrinter">
<Icon icon="ant-design:printer-filled" />
<span class="print-text">打印</span>
</div>
<iframe ref="iframeRef" :src="frameSrc" class="detail-iframe" @load="onIframeLoad"></iframe>
<template v-if="noticeFiles && noticeFiles.length > 0">
<div class="files-title">相关附件</div>
<template v-for="(file, index) in noticeFiles" :key="index">
<div class="files-area">
<div class="files-area-text">
<span>
<paper-clip-outlined />
<a
target="_blank"
rel="noopener noreferrer"
:title="file.fileName"
:href="getFileAccessHttpUrl(file.filePath)"
class="ant-upload-list-item-name"
>{{ file.fileName }}</a
>
</span>
</div>
<div class="files-area-operate">
<download-outlined class="item-icon" @click="handleDownloadFile(file.filePath)" />
<eye-outlined class="item-icon" @click="handleViewFile(file.filePath)" />
</div>
</div>
</template>
</template>
</BasicModal>
</template>
<script lang="ts" setup>
import { BasicModal, useModalInner } from '/@/components/Modal';
import { propTypes } from '/@/utils/propTypes';
import { ref } from 'vue';
import { buildUUID } from '@/utils/uuid';
import { getFileAccessHttpUrl } from '@/utils/common/compUtils';
import { DownloadOutlined, EyeOutlined, PaperClipOutlined } from '@ant-design/icons-vue';
import { encryptByBase64 } from '@/utils/cipher';
import { useGlobSetting } from '@/hooks/setting';
const glob = useGlobSetting();
// 获取props
defineProps({
frameSrc: propTypes.string.def(''),
});
//附件内容
const noticeFiles = ref([]);
//表单赋值
const [registerModal] = useModalInner();
const [registerModal] = useModalInner((data) => {
noticeFiles.value = [];
if (data.record?.files && data.record?.files.length > 0) {
noticeFiles.value = data.record.files.split(',').map((item) => {
return {
fileName: item.split('/').pop(),
filePath: item,
};
});
}
});
// iframe引用
const iframeRef = ref<HTMLIFrameElement>();
// 存储当前打印会话ID
const printSessionId = ref<string>('');
// iframe加载完成后初始化通信
const onIframeLoad = () => {
printSessionId.value = buildUUID(); // 每次加载生成新的会话ID
};
//打印
function onPrinter() {
if (!iframeRef.value) return;
console.log('onPrinter', iframeRef.value);
iframeRef.value?.contentWindow?.postMessage({ printSessionId: printSessionId.value, type: 'action:print' }, '*');
}
/**
* 下载文件
* @param filePath
*/
function handleDownloadFile(filePath) {
window.open(getFileAccessHttpUrl(filePath), '_blank');
}
/**
* 预览文件
* @param filePath
*/
function handleViewFile(filePath) {
if (filePath) {
let url = encodeURIComponent(encryptByBase64(filePath));
let previewUrl = `${glob.viewUrl}?url=` + url;
window.open(previewUrl, '_blank');
}
}
</script>
<style scoped lang="less">
.print-btn {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
color: #a3a3a5;
z-index: 999;
.print-text {
margin-left: 5px;
}
&:hover {
color: #40a9ff;
}
}
.detail-iframe {
border: 0;
width: 100%;
@ -24,4 +117,37 @@
display: block;
// -update-end--author:liaozhiyang---date:20240702---for【TV360X-1685】通知公告查看出现两个滚动条
}
.files-title {
font-size: 16px;
margin: 10px;
font-weight: 600;
color: #333;
}
.files-area {
display: flex;
align-items: center;
justify-content: flex-start;
margin: 6px;
&:hover {
background-color: #f5f5f5;
}
.files-area-text {
display: flex;
.ant-upload-list-item-name {
margin: 0 6px;
color: #56befa;
}
}
.files-area-operate {
display: flex;
margin-left: 10px;
.item-icon {
cursor: pointer;
margin: 0 6px;
&:hover {
color: #56befa;
}
}
}
}
</style>

View File

@ -0,0 +1,116 @@
<template>
<div style="min-height: 400px">
<BasicForm @register="registerForm">
<template #msgTemplate="{ model, field }">
<a-select v-model:value="model[field]" placeholder="请选择消息模版" :options="templateOption" @change="handleChange" />
</template>
<template #msgContent="{ model, field }">
<div v-html="model[field]" class="article-content"></div>
</template>
</BasicForm>
<div class="footer-btn" v-if="!formDisabled">
<a-button @click="submitForm" pre-icon="ant-design:check" type="primary"> </a-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { BasicForm, useForm } from '/@/components/Form/index';
import { getBpmFormSchema } from './notice.data';
import { getTempList, queryById, saveOrUpdate } from './notice.api';
import { computed, ref } from 'vue';
// 定义属性
const props = defineProps({
formData: {
type: Object,
default: () => ({}),
},
});
//表单禁用
const formDisabled = computed(() => {
if (props.formData.disabled === false) {
return false;
}
return true;
});
const templateOption = ref([]);
//表单配置
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
schemas: getBpmFormSchema(props.formData),
showActionButtonGroup: false,
disabled: formDisabled.value,
labelWidth: 100,
baseRowStyle: { marginTop: '10px' },
baseColProps: { xs: 24, sm: 12, md: 12, lg: 12, xl: 12, xxl: 12 },
});
//表单提交
async function submitForm() {
let values = await validate();
if (values.msgType === 'ALL') {
values.userIds = '';
} else {
values.userIds += ',';
}
console.log('表单数据', values);
await saveOrUpdate(values, true);
}
//初始化模板
async function initTemplate() {
const res = await getTempList({ templateCategory: 'notice', pageSize: 100 });
console.log('res', res);
if (res.records && res.records.length > 0) {
templateOption.value = res.records.map((item) => {
return {
label: item.templateName,
value: item.templateCode,
content: item.templateContent,
};
});
}
}
/**
* 模版修改
* @param val
*/
function handleChange(val) {
const content = templateOption.value.find((item: any) => item.value === val)?.content;
if (content) {
setFieldsValue({
msgContent: content,
});
}
}
/**
* 加载数据
*/
async function initFormData() {
let res = await queryById({ id: props.formData.dataId });
if (res.success) {
//重置表单
await resetFields();
const record = res.result;
if (record.userIds) {
record.userIds = record.userIds.substring(0, record.userIds.length - 1);
}
//表单赋值
await setFieldsValue({
...record,
});
}
}
//加载模版
initTemplate();
//加载数据
initFormData();
</script>
<style lang="less" scoped>
.footer-btn {
width: 100%;
text-align: center;
}
.article-content {
max-width: 100%;
max-height: 500px;
overflow-y: auto;
}
</style>

View File

@ -1,6 +1,19 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit" width="900px" destroyOnClose>
<BasicForm @register="registerForm" />
<BasicModal
v-bind="$attrs"
@register="registerModal"
@ok="handleSubmit"
:title="title"
width="900px"
wrapClassName="notice-cls-modal"
:maxHeight="800"
destroyOnClose
>
<BasicForm @register="registerForm">
<template #msgTemplate="{ model, field }">
<a-select v-model:value="model[field]" placeholder="请选择消息模版" :options="templateOption" @change="handleChange" />
</template>
</BasicForm>
</BasicModal>
</template>
<script lang="ts" setup>
@ -8,17 +21,24 @@
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from './notice.data';
import { saveOrUpdate } from './notice.api';
import { getTempList, saveOrUpdate } from './notice.api';
// 声明Emits
const emit = defineEmits(['register', 'success']);
const isUpdate = ref(true);
const record = ref<any>({});
const templateOption = ref([]);
//表单配置
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
schemas: formSchema,
showActionButtonGroup: false,
labelWidth: 100,
baseRowStyle: { marginTop: '10px' },
baseColProps: { xs: 24, sm: 12, md: 12, lg: 12, xl: 12, xxl: 12 },
});
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
//加载模版
await initTemplate();
//重置表单
await resetFields();
setModalProps({ confirmLoading: false });
@ -31,12 +51,13 @@
await setFieldsValue({
...data.record,
});
record.value = data.record;
}
});
//设置标题
const title = computed(() => (!unref(isUpdate) ? '新增' : '编辑'));
//表单提交事件
async function handleSubmit(v) {
async function handleSubmit() {
try {
let values = await validate();
setModalProps({ confirmLoading: true });
@ -48,7 +69,7 @@
values.userIds += ',';
}
//update-end-author:liusq---date:20230404--for: [issue#429]新增通知公告提交指定用户参数有undefined ---
if (isUpdate.value) {
if (isUpdate.value && record.value.sendStatus != '2') {
values.sendStatus = '0';
}
await saveOrUpdate(values, isUpdate.value);
@ -60,4 +81,36 @@
setModalProps({ confirmLoading: false });
}
}
//初始化模板
async function initTemplate() {
const res = await getTempList({ templateCategory: 'notice', pageSize: 100 });
console.log('res', res);
if (res.records && res.records.length > 0) {
templateOption.value = res.records.map((item) => {
return {
label: item.templateName,
value: item.templateCode,
content: item.templateContent,
};
});
}
}
/**
* 模版修改
* @param val
*/
function handleChange(val) {
const content = templateOption.value.find((item: any) => item.value === val)?.content;
if (content) {
setFieldsValue({
msgContent: content,
});
}
}
</script>
<style scoped>
.notice-cls-modal {
top: 20px !important;
}
</style>

View File

@ -34,17 +34,17 @@
import { useModal } from '/@/components/Modal';
import NoticeModal from './NoticeModal.vue';
import DetailModal from './DetailModal.vue';
import { useMethods } from '/@/hooks/system/useMethods';
import { useMessage } from '/@/hooks/web/useMessage';
import { useGlobSetting } from '/@/hooks/setting';
import { getToken } from '/@/utils/auth';
import { columns, searchFormSchema } from './notice.data';
import { getList, deleteNotice, batchDeleteNotice, getExportUrl, getImportUrl, doReleaseData, doReovkeData } from './notice.api';
import { getList, deleteNotice, batchDeleteNotice,editIzTop, getExportUrl, getImportUrl, doReleaseData, doReovkeData } from './notice.api';
import { useListPage } from '/@/hooks/system/useListPage';
const glob = useGlobSetting();
const [registerModal, { openModal }] = useModal();
const [register, { openModal: openDetail }] = useModal();
const iframeUrl = ref('');
const { createMessage, createConfirm } = useMessage();
// 列表页面公共参数、方法
const { prefixCls, onExportXls, onImportXls, tableContext, doRequest } = useListPage({
designScope: 'notice-template',
@ -66,7 +66,8 @@
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
//流程编码
const flowCode = 'dev_sys_announcement_001';
/**
* 新增事件
*/
@ -92,6 +93,12 @@
async function handleDelete(record) {
await deleteNotice({ id: record.id }, reload);
}
/**
* 置顶操作
*/
async function handleTop(record, izTop) {
await editIzTop({ id: record.id, izTop }, reload);
}
/**
* 批量删除事件
@ -118,8 +125,9 @@
*/
function handleDetail(record) {
iframeUrl.value = `${glob.uploadUrl}/sys/annountCement/show/${record.id}?token=${getToken()}`;
openDetail(true);
openDetail(true, { record });
}
/**
* 操作列定义
* @param record
@ -131,6 +139,11 @@
onClick: handleEdit.bind(null, record),
ifShow: record.sendStatus == 0 || record.sendStatus == '2',
},
{
label: '查看',
onClick: handleDetail.bind(null, record),
ifShow: record.sendStatus == 1,
},
];
}
/**
@ -148,7 +161,7 @@
},
{
label: '发布',
ifShow: record.sendStatus == 0,
ifShow: (!record?.izApproval || record.izApproval == '0') && record.sendStatus == 0,
onClick: handleRelease.bind(null, record.id),
},
{
@ -160,8 +173,22 @@
},
},
{
label: '查看',
onClick: handleDetail.bind(null, record),
label: '发布',
ifShow: record.sendStatus == '2',
popConfirm: {
title: '确定要再次发布吗?',
confirm: handleRelease.bind(null, record.id),
},
},
{
label: '置顶',
onClick: handleTop.bind(null, record, 1),
ifShow: record.sendStatus == 1 && record.izTop == 0,
},
{
label: '取消置顶',
onClick: handleTop.bind(null, record, 0),
ifShow: record.sendStatus == 1 && record.izTop == 1,
},
];
}

View File

@ -5,11 +5,17 @@ enum Api {
save = '/sys/annountCement/add',
edit = '/sys/annountCement/edit',
delete = '/sys/annountCement/delete',
queryById = '/sys/annountCement/queryById',
deleteBatch = '/sys/annountCement/deleteBatch',
exportXls = '/sys/annountCement/exportXls',
importExcel = '/sys/annountCement/importExcel',
releaseData = '/sys/annountCement/doReleaseData',
reovkeData = '/sys/annountCement/doReovkeData',
editIzTop = '/sys/annountCement/editIzTop',
addVisitsNum = '/sys/annountCement/addVisitsNumber',
tempList = '/sys/message/sysMessageTemplate/list',
}
/**
@ -21,7 +27,7 @@ export const getExportUrl = Api.exportXls;
*/
export const getImportUrl = Api.importExcel;
/**
* 查询租户列表
* 查询消息列表
* @param params
*/
export const getList = (params) => {
@ -33,7 +39,7 @@ export const getList = (params) => {
* @param params
*/
export const saveOrUpdate = (params, isUpdate) => {
let url = isUpdate ? Api.edit : Api.save;
const url = isUpdate ? Api.edit : Api.save;
return defHttp.post({ url: url, params });
};
@ -46,6 +52,15 @@ export const deleteNotice = (params, handleSuccess) => {
handleSuccess();
});
};
/**
* 置顶编辑
* @param params
*/
export const editIzTop = (params, handleSuccess) => {
return defHttp.post({ url: Api.editIzTop, data: params }).then(() => {
handleSuccess();
});
};
/**
* 批量消息公告
@ -63,3 +78,26 @@ export const doReleaseData = (params) => defHttp.get({ url: Api.releaseData, par
* @param id
*/
export const doReovkeData = (params) => defHttp.get({ url: Api.reovkeData, params });
/**
* 新增访问量
* @param id
*/
export const addVisitsNum = (params) => defHttp.get({ url: Api.addVisitsNum, params }, { successMessageMode: 'none' });
/**
* 根据ID查询数据
* @param id
*/
export const queryById = (params) => defHttp.get({ url: Api.queryById, params }, { isTransformResponse: false });
/**
* 发起流程
* import { startProcess } from '/@/api/common/api';
* @param params
*/
export const startProcess = (params) => defHttp.post({ url: Api.startProcess, params }, { isTransformResponse: false });
/**
* 查询模板列表
* @param params
*/
export const getTempList = (params) => {
return defHttp.get({ url: Api.tempList, params });
};

View File

@ -1,6 +1,7 @@
import { BasicColumn, FormSchema } from '/@/components/Table';
import { rules } from '/@/utils/helper/validator';
import { render } from '/@/utils/common/renderUtils';
import { h } from 'vue';
import { Tinymce } from '@/components/Tinymce';
export const columns: BasicColumn[] = [
{
@ -87,9 +88,24 @@ export const formSchema: FormSchema[] = [
placeholder: '请选择类型',
},
},
{
field: 'izTop',
label: '是否置顶',
defaultValue: '0',
component: 'JSwitch',
componentProps: {
//取值 options
options: ['1', '0'],
//文本option
labelOptions: ['是', '否'],
placeholder: '是否置顶',
checkedChildren: '是',
unCheckedChildren: '否',
},
},
{
field: 'titile',
label: '标题',
label: '通告标题',
component: 'Input',
required: true,
componentProps: {
@ -114,8 +130,15 @@ export const formSchema: FormSchema[] = [
},
{
field: 'msgAbstract',
label: '摘要',
label: '通告摘要',
component: 'InputTextArea',
componentProps: {
allowClear: true,
autoSize: {
minRows: 2,
maxRows: 5,
},
},
required: true,
},
// {
@ -154,9 +177,18 @@ export const formSchema: FormSchema[] = [
},
ifShow: ({ values }) => values.msgType == 'USER',
},
{
field: 'msgClassify',
label: '公告分类',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'notice_type',
placeholder: '请选择公告分类',
},
},
{
field: 'priority',
label: '优先级',
label: '优先级',
defaultValue: 'H',
component: 'JDictSelectTag',
componentProps: {
@ -166,9 +198,211 @@ export const formSchema: FormSchema[] = [
},
},
{
field: 'msgContent',
label: '内容',
field: 'izApproval',
label: '是否审批',
component: 'RadioGroup',
defaultValue: '0',
componentProps: {
options: [
{
label: '是',
value: '1',
},
{
label: '否',
value: '0',
},
],
},
},
{
field: 'msgTemplate',
label: '公告模版',
component: 'Input',
slot: 'msgTemplate',
},
{
field: 'files',
label: '通告附件',
component: 'JUpload',
componentProps: {
//是否显示选择按钮
text: '文件上传',
//最大上传数
maxCount: 20,
//是否显示下载按钮
download: true,
},
},
{
field: 'msgContent',
label: '通告内容',
component: 'Input',
colProps: { span: 24 },
render: render.renderTinymce,
},
];
/**
* 流程表单调用这个方法获取formSchema
* @param param
*/
export function getBpmFormSchema(_formData): FormSchema[] {
// 默认和原始表单保持一致 如果流程中配置了权限数据这里需要单独处理formSchema
return [
{
field: 'id',
label: 'id',
component: 'Input',
show: false,
},
{
field: 'msgCategory',
label: '消息类型',
required: true,
component: 'JDictSelectTag',
defaultValue: '1',
componentProps: {
type: 'radio',
dictCode: 'msg_category',
placeholder: '请选择类型',
},
},
{
field: 'izTop',
label: '是否置顶',
defaultValue: '0',
component: 'JSwitch',
componentProps: {
//取值 options
options: ['1', '0'],
//文本option
labelOptions: ['是', '否'],
placeholder: '是否置顶',
checkedChildren: '是',
unCheckedChildren: '否',
},
},
{
field: 'titile',
label: '通告标题',
component: 'Input',
required: true,
componentProps: {
placeholder: '请输入标题',
},
// update-begin--author:liaozhiyang---date:20240701---for【TV360X-1632】标题过长保存报错长度校验
dynamicRules() {
return [
{
validator: (_, value) => {
return new Promise<void>((resolve, reject) => {
if (value.length > 100) {
reject('最长100个字符');
}
resolve();
});
},
},
];
},
// update-end--author:liaozhiyang---date:20240701---for【TV360X-1632】标题过长保存报错长度校验
},
{
field: 'msgAbstract',
label: '通告摘要',
component: 'InputTextArea',
required: true,
},
{
field: 'msgType',
label: '接收用户',
defaultValue: 'ALL',
component: 'JDictSelectTag',
required: true,
componentProps: {
type: 'radio',
dictCode: 'msg_type',
placeholder: '请选择发布范围',
},
},
{
field: 'userIds',
label: '指定用户',
component: 'JSelectUserByDepartment',
required: true,
componentProps: {
rowKey: 'id',
// update-begin--author:liaozhiyang---date:20240701---for【TV360X-1627】通知公告用户选择组件没翻译
labelKey: 'realname',
// update-end--author:liaozhiyang---date:20240701---for【TV360X-1627】通知公告用户选择组件没翻译
},
ifShow: ({ values }) => values.msgType == 'USER',
},
{
field: 'msgClassify',
label: '公告分类',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'notice_type',
placeholder: '请选择公告分类',
},
},
{
field: 'priority',
label: '优先级别',
defaultValue: 'H',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'priority',
type: 'radio',
placeholder: '请选择优先级',
},
},
{
field: 'msgTemplate',
label: '公告模版',
component: 'Input',
slot: 'msgTemplate',
},
{
field: 'files',
label: '通告附件',
component: 'JUpload',
componentProps: {
//是否显示选择按钮
text: '文件上传',
//最大上传数
maxCount: 2,
//是否显示下载按钮
download: true,
},
},
{
field: 'msgContent',
label: '通告内容',
component: 'Input',
colProps: { span: 24 },
ifShow: ({}) => _formData.disabled == false,
render: ({ model, field }) => {
return h(Tinymce, {
showImageUpload: false,
disabled: _formData.disabled !== false,
height: 300,
value: model[field],
onChange: (value: string) => {
model[field] = value;
},
});
},
},
{
field: 'msgContent',
label: '通告内容',
component: 'Input',
colProps: { span: 24 },
ifShow: ({}) => _formData.disabled !== false,
slot: 'msgContent',
},
];
}

View File

@ -184,10 +184,6 @@
confirm: handleDelete.bind(null, record),
},
},
{
label: '首页配置',
onClick: handleIndexConfig.bind(null, record.roleCode),
},
];
}
</script>

View File

@ -1,6 +1,11 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :width="800" title="用户代理" @ok="handleSubmit" destroyOnClose>
<BasicForm @register="registerForm" />
<template #insertFooter>
<Popconfirm title="确定删除当前配置的代理吗?" @confirm="handleDel">
<a-button v-if="agentData.id"><Icon icon="ant-design:clear-outlined" />删除代理</a-button>
</Popconfirm>
</template>
</BasicModal>
</template>
<script lang="ts" setup>
@ -8,7 +13,8 @@
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formAgentSchema } from './user.data';
import { getUserAgent, saveOrUpdateAgent } from './user.api';
import { deleteAgent, getUserAgent, saveOrUpdateAgent } from './user.api';
import { Popconfirm } from 'ant-design-vue';
// 声明Emits
const emit = defineEmits(['success', 'register']);
//表单配置
@ -16,6 +22,8 @@
schemas: formAgentSchema,
showActionButtonGroup: false,
});
//表单数据
const agentData = ref<any>({});
//表单赋值
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
//重置表单
@ -24,6 +32,8 @@
//查询获取表单数据
const res = await getUserAgent({ userName: data.userName });
data = res.result ? res.result : data;
//代理数据赋值
agentData.value = { ...data };
//表单赋值
await setFieldsValue({ ...data });
});
@ -42,4 +52,20 @@
setModalProps({ confirmLoading: false });
}
}
/**
* 删除代理
*/
async function handleDel() {
const reload = async () => {
await resetFields();
await setFieldsValue({ userName: agentData.value.userName });
//关闭弹窗
closeModal();
emit('success');
};
if (agentData.value.id) {
await deleteAgent({ id: agentData.value.id }, reload);
}
}
</script>

View File

@ -8,6 +8,7 @@ enum Api {
edit = '/sys/user/edit',
agentSave = '/sys/sysUserAgent/add',
agentEdit = '/sys/sysUserAgent/edit',
deleteAgent = '/sys/sysUserAgent/delete',
getUserRole = '/sys/user/queryUserRole',
duplicateCheck = '/sys/duplicate/check',
deleteUser = '/sys/user/delete',
@ -208,6 +209,15 @@ export const saveOrUpdateAgent = (params) => {
let url = params.id ? Api.agentEdit : Api.agentSave;
return defHttp.post({ url: url, params });
};
/**
* 代理删除
* @param params
*/
export const deleteAgent = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteAgent, params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};
/**
* 用户离职(新增代理人和用户状态变更操作)