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

@ -16,6 +16,7 @@
"electron:build-all": "npm run electron:build-web && npm run electron:build-app",
"electron:build-web": "cross-env VITE_GLOB_RUN_PLATFORM=electron NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 vite build --mode prod_electron && cross-env VITE_GLOB_RUN_PLATFORM=electron esno ./build/script/postBuild.ts && esno ./build/script/copyChat.ts",
"electron:build-app": "esno ./electron/script/buildBefore.ts && electron-builder && esno ./electron/script/buildAfter.ts",
"electron:install": "cross-env ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ node node_modules/electron/install.js",
},
"devDependencies": {
"electron": "35.1.4",
@ -23,9 +24,9 @@
"vite-plugin-electron": "^0.29.0",
},
}
```
> 提示:在执行`pnpm install`后如果Electron安装失败可以尝试运行`npm run electron:install`进行安装
# Electron桌面通知示例和代码位置

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,4 +1,43 @@
import {ipcMain} from 'electron'
import {openInBrowser} from "../utils";
import { Tray, ipcMain, BrowserWindow, app, Notification } from 'electron';
import type { NotificationConstructorOptions, IpcMainInvokeEvent } from 'electron';
import { openInBrowser } from '../utils';
import { omit } from 'lodash-es';
ipcMain.on('open-in-browser', (event, url) => openInBrowser(url));
ipcMain.on('open-in-browser', (event: IpcMainInvokeEvent, url: string) => openInBrowser(url));
//
ipcMain.on('notify-flash', (event: IpcMainInvokeEvent, count: number = 0) => {
const win = BrowserWindow.getAllWindows()[0];
if (!win) return;
if (win.isFocused()) return;
if (process.platform === 'win32') {
// windows
win.flashFrame(true);
} else if (process.platform === 'darwin') {
// Mac
if (app.dock) {
app.dock.bounce('informational');
// ()
if (count > 0) {
app.dock.setBadge(count.toString());
} else {
app.dock.setBadge('');
}
}
}
});
// ()
ipcMain.on('notify-with-path', (event: IpcMainInvokeEvent, options: NotificationConstructorOptions & { path: string }) => {
const win = BrowserWindow.getAllWindows()[0];
if (!win) return;
if (win.isFocused()) return;
const notification = new Notification({
...omit(options, 'path'),
});
notification.on('click', () => {
if (win.isMinimized()) win.restore();
win.show();
win.focus();
// win.webContents.send('navigate-to', options.path);
});
notification.show();
});

View File

@ -1,9 +1,9 @@
import './ipc';
import { app, BrowserWindow, Menu } from 'electron';
import { app, BrowserWindow, Menu, ipcMain } from 'electron';
import { isDev } from './env';
import { createMainWindow, createIndexWindow } from './utils/window';
import { getAppInfo} from "./utils";
import { getAppInfo } from './utils';
import { ElectronEnum } from '../src/enums/jeecgEnum';
import './ipc';
//
Menu.setApplicationMenu(null);
@ -12,6 +12,14 @@ let mainWindow: BrowserWindow | null = null;
function main() {
mainWindow = createMainWindow();
// update-begin--author:liaozhiyang---date:20250725---forJHHB-13
mainWindow.on('focus', () => {
//
if (process.platform === 'win32') {
mainWindow!.flashFrame(false);
}
});
// update-end--author:liaozhiyang---date:20250725---forJHHB-13
return mainWindow;
}

View File

@ -1,5 +1,20 @@
import {contextBridge, ipcRenderer} from 'electron'
import { contextBridge, ipcRenderer } from 'electron';
import { ElectronEnum } from '../../src/enums/jeecgEnum';
contextBridge.exposeInMainWorld('_ELECTRON_PRELOAD_UTILS_', {
contextBridge.exposeInMainWorld(ElectronEnum.ELECTRON_API, {
openInBrowser: (url: string) => ipcRenderer.send('open-in-browser', url),
//
sendNotification: (title: string, body: string, path: string) => {
ipcRenderer.send('notify-with-path', { title, body, path });
},
//
onNavigate: (cb: (path: string) => void) => {
ipcRenderer.on('navigate-to', (_, path) => cb(path));
},
//
sendNotifyFlash: () => ipcRenderer.send('notify-flash'),
//
trayFlash: () => ipcRenderer.send('tray-flash'),
//
trayFlashStop: () => ipcRenderer.send('tray-flash-stop'),
});

View File

@ -1,11 +1,18 @@
// tray =
import path from 'path';
import {Tray, Menu, app, dialog, nativeImage, BrowserWindow, Notification} from 'electron';
import { Tray, Menu, app, dialog, nativeImage, BrowserWindow, Notification, ipcMain } from 'electron';
import type { IpcMainInvokeEvent } from 'electron';
import {_PATHS} from '../paths';
import {$env, isDev} from '../env';
const TrayIcons = {
normal: nativeImage.createFromPath(path.join(_PATHS.publicRoot, 'logo.png')),
// update-begin--author:liaozhiyang---date:20250725---forJHHB-13
normal: nativeImage.createFromPath(
process.platform === 'win32'
? path.join(_PATHS.publicRoot, 'logo.png')
: path.join(_PATHS.electronRoot, './icons/mac/tray-icon.png').replace(/[\\/]dist[\\/]/, '/')
),
// update-end--author:liaozhiyang---date:20250725---forJHHB-13
empty: nativeImage.createEmpty(),
};
@ -60,7 +67,21 @@ export function useTray(tray: Tray, win: BrowserWindow) {
}
tray.setImage(TrayIcons.normal);
}
ipcMain.on('tray-flash', (event: IpcMainInvokeEvent) => {
// Windows
if (process.platform === 'win32') {
startBlink();
}
});
ipcMain.on('tray-flash-stop', (event: IpcMainInvokeEvent) => {
// Windows
if (process.platform === 'win32') {
stopBlink();
}
});
win.on('focus', () => {
stopBlink();
});
//
function sendDesktopNotice() {
//
@ -75,9 +96,8 @@ export function useTray(tray: Tray, win: BrowserWindow) {
}
const ins = new Notification({
title: '通知标题',
subtitle: '通知副标题',
body: '通知内容第一行\n通知内容第二行',
icon: TrayIcons.normal.resize({width: 32, height: 32}),
// icon: TrayIcons.normal.resize({width: 32, height: 32}),
});
ins.on('click', () => {

View File

@ -1,5 +1,5 @@
import type {BrowserWindowConstructorOptions} from 'electron';
import {BrowserWindow, dialog} from 'electron';
import {app, BrowserWindow, dialog} from 'electron';
import path from 'path';
import {_PATHS} from '../paths';
import {$env, isDev} from '../env';
@ -19,7 +19,13 @@ export function createBrowserWindow(options?: BrowserWindowConstructorOptions) {
icon: isDev ? _PATHS.appIcon : void 0,
...options,
});
// update-begin--author:liaozhiyang---date:20250725---forJHHB-13
if (process.platform === 'darwin') { // macOS
if (app.dock) {
app.dock.setIcon(path.join(_PATHS.electronRoot, './icons/mac/dock.png').replace(/[\\/]dist[\\/]/, '/'));
}
}
// update-end--author:liaozhiyang---date:20250725---forJHHB-13
//
win.webContents.setWindowOpenHandler(({url}) => {
const win = createBrowserWindow();

View File

@ -1,13 +1,14 @@
import { resultSuccess, resultError, getRequestToken, requestParams,baseUrl} from '../_util';
import { MockMethod } from 'vite-plugin-mock';
import { createFakeUserList } from './user';
import { PageEnum } from '/@/enums/pageEnum';
// single
const dashboardRoute = {
path: '/dashboard',
name: 'Dashboard',
component: 'LAYOUT',
redirect: '/dashboard/analysis',
redirect: PageEnum.BASE_HOME,
meta: {
title: 'routes.dashboard.dashboard',
hideChildrenInMenu: true,

View File

@ -1,5 +1,6 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultError, resultSuccess, getRequestToken, requestParams, baseUrl } from '../_util';
import { PageEnum } from '/@/enums/pageEnum';
export function createFakeUserList() {
return [
{
@ -10,7 +11,7 @@ export function createFakeUserList() {
desc: 'manager',
password: '123456',
token: 'fakeToken1',
homePath: '/dashboard/analysis',
homePath: PageEnum.BASE_HOME,
roles: [
{
roleName: 'Super Admin',
@ -26,7 +27,7 @@ export function createFakeUserList() {
avatar: 'https://q1.qlogo.cn/g?b=qq&nk=339449197&s=640',
desc: 'tester',
token: 'fakeToken2',
homePath: '/dashboard/workbench',
homePath: PageEnum.BASE_HOME,
roles: [
{
roleName: 'Tester',

View File

@ -10,7 +10,7 @@
"pinstall": "pnpm install",
"clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
"dev": "vite",
"build": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 vite build && esno ./build/script/postBuild.ts",
"build": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 vite build && esno ./build/script/postBuild.ts && esno ./build/script/copyChat.ts",
"build:report": "pnpm clean:cache && cross-env REPORT=true npm run build",
"preview": "npm run build && vite preview",
"reinstall": "rimraf pnpm-lock.yaml && rimraf yarn.lock && rimraf package.lock.json && rimraf node_modules && npm run install",
@ -79,8 +79,8 @@
"vue-types": "^5.1.3",
"vuedraggable": "^4.1.0",
"vxe-table": "4.13.31",
"vxe-table-plugin-antd": "4.0.8",
"vxe-pc-ui": "4.6.12",
"vxe-table-plugin-antd": "4.0.8",
"xe-utils": "3.5.26",
"xss": "^1.0.15"
},

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@ enum Api {
getTableList = '/sys/user/queryUserComponentData',
getCategoryData = '/sys/category/loadAllData',
refreshDragCache = '/drag/page/refreshCache',
refreshDefaultIndexCache = '/sys/sysRoleIndex/cleanDefaultIndexCache',
}
/**
@ -154,3 +155,8 @@ export const uploadMyFile = (url, data) => {
* @param params
*/
export const refreshDragCache = () => defHttp.get({ url: Api.refreshDragCache }, { isTransformResponse: false });
/**
* 刷新默认首页缓存
* @param params
*/
export const refreshHomeCache = () => defHttp.get({ url: Api.refreshDefaultIndexCache }, { isTransformResponse: false });

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -7,6 +7,7 @@
<a-menu :class="[`${prefixCls}-menu`]" :selectedKeys="selectedKeys">
<template v-for="item in dropMenuList" :key="`${item.event}`">
<a-menu-item
v-if="!item.hide"
v-bind="getAttr(item.event)"
@click="handleClickMenu(item)"
:disabled="item.disabled"

View File

@ -5,5 +5,7 @@ export interface DropMenu {
event: string | number;
text: string;
disabled?: boolean;
//
hide?: boolean;
divider?: boolean;
}

View File

@ -11,6 +11,7 @@
:allDefaultValues="defaultValueRef"
:formModel="formModel"
:formName="getBindValue.name"
:source="getBindValue.source"
:setFormModel="setFormModel"
:validateFields="validateFields"
:clearValidate="clearValidate"
@ -126,7 +127,15 @@
};
});
const getBindValue = computed(() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable));
const getBindValue = computed(() => {
const bindValue = { ...attrs, ...props, ...unref(getProps) } as Recordable;
// update-begin--author:liaozhiyang---date:20250630---forissues/8484labelinput
if (bindValue.name === undefined && bindValue.source === 'table-query') {
bindValue.name = 'top-query-form';
}
// update-end--author:liaozhiyang---date:20250630---forissues/8484labelinput
return bindValue;
});
const getSchema = computed((): FormSchema[] => {
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
@ -302,6 +311,22 @@
}
}
/**
* 获取 componentProps处理可能是函数的情况
* @param schema
*/
function getSchemaComponentProps(schema: FormSchema) {
if (typeof schema.componentProps === 'function') {
return schema.componentProps({
schema,
tableAction: props.tableAction,
formActionType,
formModel,
})
}
return schema.componentProps
}
const formActionType: Partial<FormActionType> = {
getFieldsValue,
setFieldsValue,
@ -318,6 +343,7 @@
validate,
submit: handleSubmit,
scrollToField: scrollToField,
getSchemaComponentProps,
};
onMounted(() => {

View File

@ -66,6 +66,7 @@ import JEllipsis from './jeecg/components/JEllipsis.vue';
import JSelectUserByDept from './jeecg/components/JSelectUserByDept.vue';
import JSelectUserByDepartment from './jeecg/components/JSelectUserByDepartment.vue';
import JLinkTableCard from './jeecg/components/JLinkTableCard/JLinkTableCard.vue';
import JUpload from './jeecg/components/JUpload/JUpload.vue';
import JSearchSelect from './jeecg/components/JSearchSelect.vue';
import JAddInput from './jeecg/components/JAddInput.vue';

View File

@ -190,7 +190,7 @@
} finally {
loading.value = false;
//--@updateBy-begin----author:liusq---date:20210914------for:multiplevalue------
unref(attrs).mode == 'multiple' && !Array.isArray(unref(state)) && setState([]);
['multiple', 'tags'].includes(unref(attrs).mode) && !Array.isArray(unref(state)) && setState([]);
//--@updateBy-end----author:liusq---date:20210914------for:multiplevalue------
//update-begin---author:wangshuai ---date:20230505 forvalue------------
@ -202,7 +202,7 @@
function initValue() {
let value = props.value;
// update-begin--author:liaozhiyang---date:20250407---forissues/8037
if (unref(attrs).mode == 'multiple') {
if (['multiple', 'tags'].includes(unref(attrs).mode)) {
if (value && typeof value === 'string' && value != 'null' && value != 'undefined') {
state.value = value.split(',');
} else if (isNumber(value)) {

View File

@ -66,6 +66,10 @@
default: '',
},
// update-end--author:liaozhiyang---date:20240625---forTV360X-1511blur
source: {
type: String,
default: '',
},
},
setup(props, { slots }) {
const { t } = useI18n();
@ -506,7 +510,7 @@
<div style="display:flex">
{/* author: sunjianlei for: 【VUEN-744】此处加上 width: 100%; 因为要防止组件宽度超出 FormItem */}
{/* update-begin--author:liaozhiyang---date:20240510---for【TV360X-719】表单校验不通过项滚动到可视区内 */}
<Middleware formName={props.formName} fieldName={field}>{getContent()}</Middleware>
<Middleware formName={props.formName} fieldName={field} source={props.source}>{getContent()}</Middleware>
{/* update-end--author:liaozhiyang---date:20240510---for【TV360X-719】表单校验不通过项滚动到可视区内 */}
{showSuffix && <span class="suffix">{getSuffix}</span>}
</div>

View File

@ -8,8 +8,8 @@
import { ref } from 'vue';
// update-begin--author:liaozhiyang---date:20240625---forTV360X-1511blur
const formItemId = ref(null);
const props = defineProps(['formName', 'fieldName']);
if (props.formName && props.fieldName) {
const props = defineProps(['formName', 'fieldName', 'source']);
if (props.formName && props.fieldName && props.source !== 'table-query') {
formItemId.value = `${props.formName}_${props.fieldName}`;
}
// update-end--author:liaozhiyang---date:20240625---forTV360X-1511blur

View File

@ -137,7 +137,7 @@ export function useForm(props?: Props): UseFormReturnType {
let values = form.validate(nameList).then((values) => {
for (let key in values) {
if (values[key] instanceof Array) {
let valueType = getValueTypeBySchema(form.getSchemaByField(key)!);
let valueType = getValueTypeBySchema(form.getSchemaByField(key)!, form);
if (valueType === 'string') {
values[key] = values[key].join(',');
}

View File

@ -595,6 +595,9 @@
flex: 1;
border-right: 1px solid #e8e8e8;
.search-box {
:deep(.ant-input-affix-wrapper) {
border-color: #d9d9d9 !important;
}
margin: 0 16px 16px 16px;
}
:deep(.ant-breadcrumb) {

View File

@ -24,7 +24,7 @@
v-bind="getBindValue"
:useSearchForm="true"
:formConfig="formConfig"
:api="getUserList"
:api="hasCustomApi ? customListApi : getUserList"
:searchInfo="searchInfo"
:rowSelection="rowSelection"
:indexColumnProps="indexColumnProps"
@ -54,7 +54,7 @@
</div>
</template>
<script lang="ts">
import { defineComponent, unref, ref, watch } from 'vue';
import { defineComponent, unref, ref, watch, computed } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { getUserList } from '/@/api/common/api';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
@ -85,6 +85,11 @@
default: [],
},
//update-end---author:wangshuai ---date:20230703 forQQYUN-56855------------
// table使
customListApi: {type: Function},
// 使 JInput
customApiJInput: {type: Boolean, default: true},
},
emits: ['register', 'getSelectResult', 'close'],
setup(props, { emit, refs }) {
@ -93,6 +98,8 @@
const tableRef = ref();
const maxHeight = ref(600);
const hasCustomApi = computed(() => typeof props.customListApi === 'function');
//
const [register, { closeModal }] = useModalInner(() => {
if (window.innerWidth < 900) {
@ -156,12 +163,12 @@
{
label: '账号',
field: 'username',
component: 'JInput',
component: (hasCustomApi.value && !props.customApiJInput) ? 'Input' : 'JInput',
},
{
label: '姓名',
field: 'realname',
component: 'JInput',
component: (hasCustomApi.value && !props.customApiJInput) ? 'Input' : 'JInput',
},
],
};
@ -300,6 +307,7 @@
handleCancel,
maxHeight,
beforeFetch,
hasCustomApi,
};
},
});

View File

@ -41,6 +41,7 @@ export interface FormActionType {
validateFields: (nameList?: NamePath[], options?: ValidateOptions) => Promise<any>;
validate: (nameList?: NamePath[]) => Promise<any>;
scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>;
getSchemaComponentProps: (schema: FormSchema) => Recordable
}
export type RegisterFn = (formInstance: FormActionType) => void;

View File

@ -143,6 +143,11 @@
function scaleFunc(num: number) {
if (imgState.imgScale <= 0.2 && num < 0) return;
imgState.imgScale += num;
// update-begin--author:liaozhiyang---date:20250722---forQQYUN-13162
if (imgState.imgScale < 0.2) {
imgState.imgScale = 0.2;
}
// update-end--author:liaozhiyang---date:20250722---forQQYUN-13162
}
//

View File

@ -4,6 +4,7 @@
:class="{ 'table-search-area-hidden': !getBindValues.formConfig?.schemas?.length }"
submitOnReset
v-bind="getFormProps"
source="table-query"
v-if="getBindValues.useSearchForm"
:tableAction="tableAction"
@register="registerForm"
@ -214,7 +215,7 @@
onChange && isFunction(onChange) && onChange.call(undefined, ...args);
}
const { getViewColumns, getColumns, setCacheColumnsByField, setColumns, getColumnsRef, getCacheColumns } = useColumns(
const { getViewColumns, getColumns, getRefColumns, setCacheColumnsByField, setColumns, getColumnsRef, getCacheColumns } = useColumns(
getProps,
getPaginationInfo,
// update-begin--author:sunjianlei---date:220230630---forQQYUN-5571
@ -367,6 +368,9 @@
getRowSelection,
getPaginationRef: getPagination,
getColumns,
// update-begin--author:liaozhiyang---date:20250722---forissues/8529setColumns
getColumnsRef: () => getColumnsRef,
// update-end--author:liaozhiyang---date:20250722---forissues/8529setColumns
getCacheColumns,
emit,
updateTableData,

View File

@ -49,6 +49,7 @@
},
setup(props) {
const table = useTableContext();
const getColumnsRef = table.getColumnsRef();
const tableFooter = ref<any>(null);
const getDataSource = computed((): Recordable[] => {
const { summaryFunc, summaryData } = props;
@ -71,7 +72,10 @@
const getColumns = computed(() => {
const dataSource = unref(getDataSource);
let columns: BasicColumn[] = cloneDeep(table.getColumns());
// update-begin--author:liaozhiyang---date:20250729---forissues/8502
const allColumns = unref(getColumnsRef);
let columns: BasicColumn[] = cloneDeep(table.getColumns({ ignoreAuth: true, ignoreIfShow: true }));
// update-end--author:liaozhiyang---date:20250729---forissues/8502
// update-begin--author:liaozhiyang---date:220230804---forissues/638
columns = columns.filter((item) => !item.defaultHidden);
// update-begin--author:liaozhiyang---date:220230804---forissues/638

View File

@ -148,7 +148,27 @@
const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []);
const option = options.find((item) => `${item.value}` === `${value}`);
// update-begin---author:liaozhiyang---date:2025-07-28---for:QQYUN-13251apiSelect ---
if (['tags', 'multiple'].includes(editComponentProps?.mode)) {
const result = options
.filter((item) => {
let v = value;
if (isString(value)) {
v = value.split(',');
} else if (isNumber(value)) {
v = [value];
}
if (v.includes(item.value)) {
return true;
}
})
.map((item) => item.label);
if (result.length) {
return result.join(',');
}
return value;
}
// update-end---author:liaozhiyang---date:2025-07-28---for:QQYUN-13251apiSelect ---
return option?.label ?? value;
});

View File

@ -151,6 +151,9 @@
// nextTick(() => popoverVisible.value = false);
// update-end--author:sunjianlei---date:20221101---for:
const defaultRowSelection = omit(table.getRowSelection(), 'selectedRowKeys');
// update-begin--author:liaozhiyang---date:20250722---forissues/8529setColumns
const getColumnsRef = table.getColumnsRef();
// update-end--author:liaozhiyang---date:20250722---forissues/8529setColumns
let inited = false;
const cachePlainOptions = ref<Options[]>([]);
@ -219,7 +222,7 @@
checkSelect.value = !!values.rowSelection;
});
// update-begin--author:liaozhiyang---date:20240724---forissues/6908BasicColumnFormSchema
watch(localeStore, () => {
watch([localeStore, getColumnsRef], () => {
const columns = getColumns();
plainOptions.value = columns;
plainSortOptions.value = columns;
@ -230,7 +233,7 @@
function getColumns() {
const ret: Options[] = [];
// update-begin--author:liaozhiyang---date:20250403---forissues/7996
let t = table.getColumns({ ignoreIndex: true, ignoreAction: true });
let t = table.getColumns({ ignoreIndex: true, ignoreAction: true, ignoreAuth: true, ignoreIfShow: true });
if (!t.length) {
t = table.getCacheColumns();
}
@ -249,7 +252,7 @@
const columns = getColumns();
const checkList = table
.getColumns({ ignoreAction: true, ignoreIndex: true })
.getColumns({ ignoreAction: true, ignoreIndex: true, ignoreAuth: true, ignoreIfShow: true })
.map((item) => {
if (item.defaultHidden) {
return '';
@ -341,7 +344,7 @@
// state.checkedList = [...state.defaultCheckList];
// update-begin--author:liaozhiyang---date:20231103---forissues/825tabel[]
state.checkedList = table
.getColumns({ ignoreAction: true })
.getColumns({ ignoreAction: true, ignoreAuth: true, ignoreIfShow: true })
.map((item) => {
return item.dataIndex || item.title;
})

View File

@ -285,7 +285,7 @@ export function useColumns(
// update-end--author:sunjianlei---date:20220523---for: VUEN-1089vben
function getColumns(opt?: GetColumnsParams) {
const { ignoreIndex, ignoreAction, sort } = opt || {};
const { ignoreIndex, ignoreAction, ignoreAuth, ignoreIfShow, sort } = opt || {};
let columns = toRaw(unref(getColumnsRef));
if (ignoreIndex) {
columns = columns.filter((item) => item.flag !== INDEX_COLUMN_FLAG);
@ -297,7 +297,27 @@ export function useColumns(
//
columns = columns.filter((item) => item.key !== CUS_SEL_COLUMN_KEY);
// update-enb--author:sunjianlei---date:220230630---forQQYUN-5571
// update-begin--author:liaozhiyang---date:20250729---forissues/8502
if (ignoreAuth) {
columns = columns.filter((item) => {
if (item.auth) {
return hasPermission(item.auth);
}
return true;
});
}
if (ignoreIfShow) {
columns = columns.filter((item) => {
if (isBoolean(item.ifShow)) {
return item.ifShow;
}
if (isFunction(item.ifShow)) {
return item.ifShow(item);
}
return true;
});
}
// update-end--author:liaozhiyang---date:20250729---forissues/8502
if (sort) {
columns = sortFixedColumn(columns);
}

View File

@ -1,4 +1,4 @@
import type { VNodeChild } from 'vue';
import type { VNodeChild, ComputedRef } from 'vue';
import type { PaginationProps } from './pagination';
import type { FormProps } from '/@/components/Form';
import type { TableRowSelection as ITableRowSelection } from 'ant-design-vue/lib/table/interface';
@ -80,6 +80,10 @@ export interface FetchParams {
export interface GetColumnsParams {
ignoreIndex?: boolean;
ignoreAction?: boolean;
// update-begin--author:liaozhiyang---date:20250729---forissues/8502
ignoreAuth?: boolean;
ignoreIfShow?: boolean | ((column: BasicColumn) => boolean);
// update-end--author:liaozhiyang---date:20250729---forissues/8502
sort?: boolean;
}
@ -116,6 +120,7 @@ export interface TableActionType {
setShowPagination: (show: boolean) => Promise<void>;
getShowPagination: () => boolean;
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void;
getColumnsRef: () => ComputedRef<BasicColumn[]>;
}
export interface FetchSetting {

View File

@ -151,6 +151,28 @@
toolbar,
menubar: false,
plugins,
//
paste_data_images: true, //
paste_as_text: false, //
paste_retain_style_properties: 'all', //
paste_webkit_styles: 'all', // webkit
paste_merge_formats: true, //
paste_block_drop: true, //
paste_preprocess: (plugin, args) => {
//
//console.log(':', args.content);
},
paste_postprocess: (plugin, args) => {
//
//console.log(':', args.node);
},
//
valid_elements: '*[*]',
extended_valid_elements: '*[*]',
valid_children: '+body[style]',
allow_conditional_comments: true,
allow_html_in_named_anchor: true,
language_url: publicPath + 'resource/tinymce/langs/' + langName.value + '.js',
language: langName.value,
branding: false,

View File

@ -10,10 +10,10 @@ export const plugins = [
export const toolbar =
'fullscreen code preview | undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent lineheight|subscript superscript blockquote| numlist bullist checklist | forecolor backcolor casechange permanentpen formatpainter removeformat | pagebreak | charmap emoticons | insertfile image media pageembed link anchor codesample insertdatetime hr| a11ycheck ltr rtl';
export const simplePlugins = 'lists image link fullscreen';
export const simplePlugins = 'lists image link fullscreen paste';
export const simpleToolbar = [
'undo redo styles bold italic alignleft aligncenter alignright alignjustify bullist numlist outdent indent lists image link fullscreen',
'undo redo styles forecolor fontsize bold italic alignleft aligncenter alignright alignjustify bullist numlist outdent indent lists image link fullscreen',
];
export const menubar = 'file edit insert view format table';

View File

@ -317,3 +317,9 @@ html[data-theme='dark'] {
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.table-action-item {
&.color-red {
color: red !important;
}
}

View File

@ -1,10 +1,10 @@
import type {App} from "vue";
import {router} from "@/router";
import {useGlobSetting} from "@/hooks/setting";
import { ElectronEnum } from '/@/enums/jeecgEnum'
const glob = useGlobSetting();
const _PRELOAD_UTILS = '_ELECTRON_PRELOAD_UTILS_';
const _PRELOAD_UTILS = ElectronEnum.ELECTRON_API;
export const $electron = {
// Electron
@ -38,8 +38,16 @@ export function setupElectron(_: App) {
return;
}
hookWindowOpen();
// update-begin--author:liaozhiyang---date:20250725---forJHHB-13
hookNavigate();
// update-end--author:liaozhiyang---date:20250725---forJHHB-13
}
function hookNavigate() {
// @ts-ignore
window[ElectronEnum.ELECTRON_API].onNavigate((path) => {
router.push({ path });
});
}
function hookWindowOpen() {
//
const originFunc = window.open;

View File

@ -21,3 +21,7 @@ export enum JDragConfigEnum {
//
DRAG_CACHE_PREFIX = 'drag-cache:',
}
// electron
export enum ElectronEnum {
ELECTRON_API = '_ELECTRON_PRELOAD_UTILS_',
}

View File

@ -35,7 +35,7 @@
// components
import { Dropdown, Menu } from 'ant-design-vue';
import { defineComponent, computed, ref } from 'vue';
import { defineComponent, computed, ref, nextTick } from 'vue';
import { SITE_URL } from '/@/settings/siteSetting';
@ -57,9 +57,9 @@
import { removeAuthCache, setAuthCache } from '/src/utils/auth';
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import { getRefPromise } from '/@/utils/index';
import { refreshDragCache } from "@/api/common/api";
import { refreshDragCache, refreshHomeCache } from "@/api/common/api";
type MenuEvent = 'logout' | 'doc' | 'lock' | 'cache' | 'depart';
type MenuEvent = 'logout' | 'doc' | 'lock' | 'cache' | 'depart' | 'defaultHomePage' | 'password' | 'account';
const { createMessage } = useMessage();
export default defineComponent({
name: 'UserDropdown',
@ -82,6 +82,7 @@
const userStore = useUserStore();
const go = useGo();
const passwordVisible = ref(false);
const homeSelectVisible = ref(false);
const lockActionVisible = ref(false);
const lockActionRef = ref(null);
@ -123,9 +124,8 @@
//
async function clearCache() {
const result = await refreshCache();
//TODO jimibi
// const dragRes = await refreshDragCache();
// console.log('dragRes', dragRes);
const dragRes = await refreshDragCache();
console.log('dragRes', dragRes);
if (result.success) {
const res = await queryAllDictItems();
removeAuthCache(DB_DICT_DATA_KEY);
@ -255,4 +255,13 @@
}
}
}
// update-begin--author:liaozhiyang---date:20250702---forQQYUN-13013
html[lang="en"] {
.@{prefix-cls} {
&-dropdown-overlay {
width: 175px;
}
}
}
// update-end--author:liaozhiyang---date:20250702---forQQYUN-13013
</style>

View File

@ -28,6 +28,7 @@
import { useTabDropdown } from '../useTabDropdown';
import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
import { useLocaleStore } from '/@/store/modules/locale';
import { PageEnum } from '/@/enums/pageEnum';
export default defineComponent({
name: 'TabContent',
@ -61,7 +62,7 @@
const prefixIconType = computed(() => {
if (props.tabItem.meta.icon) {
return props.tabItem.meta.icon;
} else if (props.tabItem.path === '/dashboard/analysis') {
} else if (props.tabItem.path === PageEnum.BASE_HOME) {
// home TODO
return 'ant-design:home-outlined';
} else {

View File

@ -0,0 +1,65 @@
import { ref } from 'vue';
import { getMenus } from '/@/router/menus';
export const useHideHomeDesign = (currentRoute) => {
let menus: any = [];
//
const isHideHomeDesign = ref(true);
const getHideHomeDesign = (isCurItem, path) => {
if (/^\/portal-view\/[^/]+$/.test(path) && isCurItem) {
if (['/portal-view/system', '/portal-view/template'].includes(path)) {
// (,,)
getIsHasPortalDesignList();
} else if (['/portal-view/default'].includes(path)) {
//
isHideHomeDesign.value = true;
} else {
//
isHideHomeDesign.value = false;
}
} else {
//
isHideHomeDesign.value = true;
}
};
const getMenusContainPath = async (ptah) => {
if (!menus.length) {
menus = await getMenus();
}
const result = getMatchingRouterName(menus, ptah);
return !!result;
};
const getIsHasPortalDesignList = async () => {
if (['/portal-view/system', '/portal-view/template'].includes(currentRoute.value.path)) {
// portalDesignList
getMenusContainPath('/super/eoa/portalapp/portalDesignList').then((result) => {
isHideHomeDesign.value = !result;
});
}
};
getIsHasPortalDesignList();
return {
getHideHomeDesign,
isHideHomeDesign,
};
};
/*
* 20250701
* liaozhiyang
* 通过path匹配菜单中的项
* */
function getMatchingRouterName(menus, path) {
for (let i = 0, len = menus.length; i < len; i++) {
const item = menus[i];
if (item.path === path && !item.redirect && !item.paramPath) {
return item;
} else if (item.children?.length) {
const result = getMatchingRouterName(item.children, path);
if (result) {
return result;
}
}
}
return null;
}

View File

@ -8,6 +8,7 @@ import { useMultipleTabStore } from '/@/store/modules/multipleTab';
import { RouteLocationNormalized, useRouter } from 'vue-router';
import { useTabs } from '/@/hooks/web/useTabs';
import { useI18n } from '/@/hooks/web/useI18n';
import { useHideHomeDesign } from './useHideHomeDesign';
export function useTabDropdown(tabContentProps: TabContentProps, getIsTabs: ComputedRef<boolean>) {
const state = reactive({
@ -23,6 +24,10 @@ export function useTabDropdown(tabContentProps: TabContentProps, getIsTabs: Comp
const getTargetTab = computed((): RouteLocationNormalized => {
return unref(getIsTabs) ? tabContentProps.tabItem : unref(currentRoute);
});
// update-begin--author:liaozhiyang---date:20250701---forQQYUN-12994
//
const { getHideHomeDesign, isHideHomeDesign } = useHideHomeDesign(currentRoute);
// update-end--author:liaozhiyang---date:20250701---forQQYUN-12994
/**
* @description: drop-down list
@ -65,6 +70,10 @@ export function useTabDropdown(tabContentProps: TabContentProps, getIsTabs: Comp
// Close right
const closeRightDisabled = index === tabStore.getTabList.length - 1 && tabStore.getLastDragEndIndex >= 0;
// update-end--author:liaozhiyang---date:20240605---forTV360X-732使
// update-begin--author:liaozhiyang---date:20250701---forQQYUN-12994
//
getHideHomeDesign(isCurItem, path);
// update-end--author:liaozhiyang---date:20250701---forQQYUN-12994
const dropMenuList: DropMenu[] = [
{
icon: 'jam:refresh-reverse',
@ -76,7 +85,8 @@ export function useTabDropdown(tabContentProps: TabContentProps, getIsTabs: Comp
icon: 'ant-design:setting-outlined',
event: MenuEventEnum.HOME_DESIGN,
text: t('layout.multipleTab.homeDesign'),
disabled: path !== '/PortalView',
disabled: !/^\/portal-view\/[^/]+$/.test(path),
hide: isHideHomeDesign.value,
divider: true,
},
// {

View File

@ -8,6 +8,7 @@ export default {
dropdownItemSwitchDepart: 'Switch Department',
dropdownItemRefreshCache: 'Clean cache',
dropdownItemSwitchAccount: 'Account Setting',
dropdownItemSwitchDefaultHomePage: 'Switch Home Page',
tooltipErrorLog: 'Error log',
tooltipLock: 'Lock screen',

View File

@ -8,6 +8,7 @@ export default {
dropdownItemSwitchDepart: '切换部门',
dropdownItemRefreshCache: '刷新缓存',
dropdownItemSwitchAccount: '账户设置',
dropdownItemSwitchDefaultHomePage: '切换首页',
// tooltip
tooltipErrorLog: '错误日志',
@ -34,7 +35,7 @@ export default {
closeRight: '关闭右侧',
closeOther: '关闭其它',
closeAll: '关闭全部',
homeDesign: '设计模式',
homeDesign: '门户设计',
},
setting: {
// content mode

View File

@ -1,5 +1,5 @@
import type { AppRouteModule } from '/@/router/types';
import { PageEnum } from '/@/enums/pageEnum';
import { LAYOUT } from '/@/router/constant';
import { t } from '/@/hooks/web/useI18n';
@ -7,7 +7,7 @@ const dashboard: AppRouteModule = {
path: '/dashboard',
name: 'Dashboard',
component: LAYOUT,
redirect: '/dashboard/analysis',
redirect: PageEnum.BASE_HOME,
meta: {
orderNo: 10,
icon: 'ion:grid-outline',

View File

@ -67,7 +67,9 @@ export const defIndexApi = {
async update(url: string, component: string, isRoute: boolean) {
let apiUrl = '/sys/sysRoleIndex/updateDefIndex'
apiUrl += '?url=' + url
apiUrl += '&component=' + component
//update-begin-author:liusq---date:2025-07-04--for: ,encodeURIComponent{{ window._CONFIG['domianURL'] }}/**
apiUrl += '&component=' + encodeURIComponent(component)
//update-end-author:liusq---date:2025-07-04--for: ,encodeURIComponent{{ window._CONFIG['domianURL'] }}/**
apiUrl += '&isRoute=' + isRoute
return await defHttp.put({url: apiUrl});
},

View File

@ -232,6 +232,15 @@ export const useMultipleTabStore = defineStore({
curTab.fullPath = fullPath || curTab.fullPath;
this.tabList.splice(updateIndex, 1, curTab);
} else {
// update-begin--author:liaozhiyang---date:20250709---forQQYUN-13058(query)
// pathquery
const findIndex = this.tabList.findIndex((tab) => tab.path === path);
const isTabExist = findIndex !== -1;
if (isTabExist) {
this.tabList.splice(findIndex, 1, route);
return;
}
// update-end--author:liaozhiyang---date:20250709---forQQYUN-13058(query)
// Add tab
// 0
const dynamicLevel = meta?.dynamicLevel ?? -1;

View File

@ -1,6 +1,6 @@
import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router';
import type { App, Plugin } from 'vue';
import type { FormSchema } from "@/components/Form";
import type { FormSchema, FormActionType } from "@/components/Form";
import { unref } from 'vue';
import { isObject, isFunction, isString } from '/@/utils/is';
@ -110,11 +110,12 @@ export function getValueType(props, field) {
/**
* 获取表单字段值数据类型
* @param schema
* @param formAction
*/
export function getValueTypeBySchema(schema: FormSchema) {
export function getValueTypeBySchema(schema: FormSchema, formAction: FormActionType) {
let valueType = 'string';
if (schema) {
const componentProps = schema.componentProps as Recordable;
const componentProps = formAction.getSchemaComponentProps(schema);
valueType = componentProps?.valueType ? componentProps?.valueType : valueType;
}
return valueType;

View File

@ -65,7 +65,7 @@
<a @click="handleDetail(record)">详情</a>
</a-menu-item>
<a-menu-item>
<Popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)">
<Popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)" placement="left">
<a>删除</a>
</Popconfirm>
</a-menu-item>

View File

@ -1,5 +1,7 @@
<template>
<a-spin :spinning="confirmLoading">
<JFormContainer :disabled="disabled">
<template #detail>
<a-form class="antd-modal-form" ref="formRef" :model="formState" :rules="validatorRules">
<a-row>
<a-col :span="24">
@ -220,11 +222,13 @@
</a-col>
</a-row>
</a-form>
</template>
</JFormContainer>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, nextTick } from 'vue';
import { ref, reactive, nextTick, computed } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import dayjs from 'dayjs';
@ -255,7 +259,15 @@
import JCodeEditor from '/@/components/Form/src/jeecg/components/JCodeEditor.vue';
import JAddInput from '/@/components/Form/src/jeecg/components/JAddInput.vue';
import { getValueType } from '/@/utils';
import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue';
const props = defineProps({
formDisabled: { type: Boolean, default: false },
});
//
const disabled = computed(()=>{
return props.formDisabled;
});
const emit = defineEmits(['register', 'ok']);
//update-begin---author:wangshuai ---date:20220616 for--------------
const formState = reactive<Record<string, any>>({

View File

@ -9,7 +9,7 @@
@cancel="handleCancel"
cancelText="关闭"
>
<OneNativeForm ref="realForm" @ok="submitCallback" :disabled="disableSubmit"></OneNativeForm>
<OneNativeForm ref="realForm" @ok="submitCallback" :formDisabled="disableSubmit"></OneNativeForm>
</BasicModal>
</template>
@ -61,5 +61,9 @@
});
</script>
<style lang="less" scoped>
<style lang="less">
/**隐藏样式-modal确定按钮 */
.jee-hidden {
display: none !important;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit" :width="500" :minHeight="20" :maxHeight="20">
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit" :width="500" :minHeight="20" :maxHeight="100">
<BasicForm @register="registerForm" />
</BasicModal>
</template>
@ -16,7 +16,7 @@
const isUpdate = ref(true);
//
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
labelWidth: 150,
// labelWidth: 150,
schemas: ticketFormSchema,
showActionButtonGroup: false,
});

View File

@ -227,6 +227,7 @@ export const ticketFormSchema: FormSchema[] = [
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD',
getPopupContainer:()=>document.body,
},
},
{

View File

@ -1,28 +1,83 @@
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="查看详情" :minHeight="600" :showCancelBtn="false" :showOkBtn="false" :height="88" :destroyOnClose="true">
<BasicModal
v-bind="$attrs"
@register="registerModal"
title="查看详情"
:width="800"
:minHeight="600"
:showCancelBtn="false"
:showOkBtn="false"
:height="88"
:destroyOnClose="true"
@visible-change="handleVisibleChange"
>
<div class="print-btn" @click="onPrinter">
<Icon icon="ant-design:printer-filled" />
<span class="print-text">打印</span>
</div>
<a-card class="daily-article">
<a-card-meta :title="content.titile" :description="'发布人:' + content.sender + ' 发布时间: ' + content.sendTime"> </a-card-meta>
<a-card-meta :title="content.titile">
<template #description>
<div class="article-desc">
<span>发布人{{ content.sender }}</span>
<span>发布时间{{ content.sendTime }}</span>
<span v-if="content.visitsNum">
<a-tooltip placement="top" title="访问次数" :autoAdjustOverflow="true">
<eye-outlined class="item-icon" /> {{ content.visitsNum }}
</a-tooltip>
</span>
</div>
</template>
</a-card-meta>
<a-divider />
<div v-html="content.msgContent" class="article-content"></div>
<div>
<a-button v-if="hasHref" @click="jumpToHandlePage">前往办理<ArrowRightOutlined /></a-button>
</div>
</a-card>
<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 { ArrowRightOutlined } from '@ant-design/icons-vue';
import { useRouter } from 'vue-router'
import xss from 'xss'
import { options } from './XssWhiteList'
const router = useRouter()
import { ArrowRightOutlined, PaperClipOutlined, DownloadOutlined, EyeOutlined } from '@ant-design/icons-vue';
import { addVisitsNum } from '@/views/system/notice/notice.api';
import { useRouter } from 'vue-router';
import xss from 'xss';
import { options } from './XssWhiteList';
import { ref, unref } from 'vue';
import { getFileAccessHttpUrl } from '@/utils/common/compUtils';
import { useGlobSetting } from '@/hooks/setting';
import { encryptByBase64 } from '@/utils/cipher';
const router = useRouter();
const glob = useGlobSetting();
const isUpdate = ref(true);
const content = ref({});
const content = ref<any>({});
const noticeFiles = ref([]);
const emit = defineEmits(['close', 'register']);
//
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
isUpdate.value = !!data?.isUpdate;
@ -35,12 +90,28 @@
//update-end---author:wangshuai---date:2023-11-15---for:QQYUN-70493.6.0 ---
}
//update-end-author:taoyan date:2022-7-14 for: VUEN-1702 sql
//update-begin-author:liusq---date:2025-06-17--for: [QQYUN-12521]访
if (!data.record?.busId) {
await addVisitsNum({ id: data.record.id });
}
//update-end-author:liusq---date:2025-06-17--for: [QQYUN-12521]访
content.value = data.record;
console.log('data---------->>>', data);
if (data.record?.files && data.record?.files.length > 0) {
noticeFiles.value = data.record.files.split(',').map((item) => {
return {
fileName: item.split('/').pop(),
filePath: item,
};
});
}
showHrefButton();
}
});
const hasHref = ref(false)
const hasHref = ref(false);
//
function showHrefButton() {
if (content.value.busId) {
@ -49,44 +120,221 @@
}
//
function jumpToHandlePage() {
let temp:any = content.value
let temp: any = content.value;
if (temp.busId) {
//busId ID
let jsonStr = temp.msgAbstract;
let query = {};
try {
if (jsonStr) {
let temp = JSON.parse(jsonStr)
let temp = JSON.parse(jsonStr);
if (temp) {
Object.keys(temp).map(k=>{
query[k] = temp[k]
Object.keys(temp).map((k) => {
query[k] = temp[k];
});
}
}
} catch (e) {
console.log('参数解析异常', e)
console.log('参数解析异常', e);
}
console.log('query', query, jsonStr)
console.log('busId', temp.busId)
console.log('query', query, jsonStr);
console.log('busId', temp.busId);
if (Object.keys(query).length > 0) {
// taskId taskDefKey procInsId
router.push({ path: '/task/handle/' + temp.busId, query: query })
router.push({ path: '/task/handle/' + temp.busId, query: query });
} else {
router.push({ path: '/task/handle/' + temp.busId })
router.push({ path: '/task/handle/' + temp.busId });
}
}
closeModal();
}
//
function onPrinter() {
//
const printContent = document.querySelector('.daily-article');
if (!printContent) return;
// iframe
const printFrame = document.createElement('iframe');
printFrame.style.position = 'absolute';
printFrame.style.width = '0';
printFrame.style.height = '0';
printFrame.style.border = 'none';
printFrame.style.left = '-9999px';
printFrame.onload = function () {
const frameDoc = printFrame.contentDocument || printFrame.contentWindow?.document;
if (!frameDoc) return;
// iframe
const clone = printContent.cloneNode(true);
frameDoc.body.appendChild(clone);
//
const style = frameDoc.createElement('style');
style.innerHTML = `
body {
margin: 0;
padding: 15px;
font-family: Arial, sans-serif;
}
img {
max-width: 100%;
height: auto;
}
@page {
size: auto;
margin: 15mm;
}
@media print {
body {
padding: 0;
}
}
`;
frameDoc.head.appendChild(style);
//
const images = frameDoc.getElementsByTagName('img');
let imagesToLoad = images.length;
const printWhenReady = () => {
if (imagesToLoad === 0) {
setTimeout(() => {
printFrame.contentWindow?.focus();
printFrame.contentWindow?.print();
document.body.removeChild(printFrame);
}, 300);
}
};
if (imagesToLoad === 0) {
printWhenReady();
} else {
Array.from(images).forEach((img) => {
img.onload = () => {
imagesToLoad--;
printWhenReady();
};
//
if (img.complete && img.naturalWidth !== 0) {
imagesToLoad--;
printWhenReady();
}
});
}
};
document.body.appendChild(printFrame);
}
/**
* 下载文件
* @param filePath
*/
function handleDownloadFile(filePath) {
window.open(getFileAccessHttpUrl(filePath), '_blank');
}
/**
* 预览文件
* @param filePath
*/
function handleViewFile(filePath) {
if (filePath) {
console.log('glob.onlineUrl', glob.viewUrl);
let url = encodeURIComponent(encryptByBase64(filePath));
let previewUrl = `${glob.viewUrl}?url=` + url;
window.open(previewUrl, '_blank');
}
}
function handleVisibleChange(visible: boolean) {
if (!visible) {
emit('close');
}
}
</script>
<style scoped lang="less">
.daily-article {
:deep(.ant-card-meta-detail) {
display: flex !important;
justify-content: center !important;
align-items: center !important;
flex-direction: column !important;
}
:deep(.ant-card-meta-detail .ant-card-meta-title) {
font-size: 22px !important;
}
}
.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%;
height: 100%;
min-height: 600px;
}
.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;
}
}
}
}
.article-desc {
display: flex;
align-items: center;
span:not(:first-child) {
margin-left: 5px;
}
}
/* 确保打印内容中的图片有最大宽度限制 */
.article-content img {
max-width: 100%;
height: auto;
}
</style>

View File

@ -33,9 +33,20 @@
import { useAppStore } from '/@/store/modules/app';
import { useMessageHref } from '/@/views/system/message/components/useSysMessage';
const appStore = useAppStore();
const {goPage} = useMessageHref()
const router = useRouter();
const { currentRoute } = useRouter();
const { goPage } = useMessageHref();
// update-begin--author:liaozhiyang---date:20250709---forQQYUN-13058url
const querystring = currentRoute.value.query;
const findItem: any = searchFormSchema.find((item: any) => item.field === 'msgCategory');
if (findItem) {
if (querystring?.msgCategory) {
findItem.componentProps.defaultValue = querystring.msgCategory
} else {
findItem.componentProps.defaultValue = null
}
}
// update-end--author:liaozhiyang---date:20250709---forQQYUN-13058url
const { prefixCls, tableContext } = useListPage({
designScope: 'mynews-list',
tableProps: {
@ -48,6 +59,14 @@
fieldMapToTime: [['sendTime', ['sendTimeBegin', 'sendTimeEnd'], 'YYYY-MM-DD']],
//update-end---author:wangshuai---date:2024-06-11---for:TV360X-545---
},
beforeFetch: (params) => {
// update-begin--author:liaozhiyang---date:20250709---forQQYUN-13058url
if (querystring?.msgCategory) {
params.msgCategory = querystring.msgCategory;
}
return params;
// update-end--author:liaozhiyang---date:20250709---forQQYUN-13058url
},
},
});
const [registerTable, { reload }] = tableContext;

View File

@ -81,4 +81,16 @@ export const searchFormSchema: FormSchema[] = [
},
colProps: { span: 6 },
},
{
field: 'msgCategory',
label: '消息类型',
component: 'Select',
componentProps: {
options: [
{ label: '通知公告', value: '1' },
{ label: '系统消息', value: '2' },
],
},
colProps: { span: 6 },
},
];

View File

@ -34,7 +34,7 @@
></chat>
</div>
</template>
<Spin v-else :spinning="true"></Spin>
<Loading :loading="loading" tip="加载中,请稍后"></Loading>
</div>
</template>
@ -48,6 +48,7 @@
import { defHttp } from '/@/utils/http/axios';
import { useRouter } from 'vue-router';
import { useAppInject } from "@/hooks/web/useAppInject";
import Loading from '@/components/Loading/src/Loading.vue';
const router = useRouter();
const userId = useUserStore().getUserInfo?.id;
@ -67,6 +68,8 @@
const chatActiveKey = ref<number>(0);
//
const presetQuestion = ref<string>('');
//
const loading = ref<any>(true);
const handleToggle = () => {
expand.value = !expand.value;
@ -179,10 +182,13 @@
})
.catch(() => {
priming();
}).finally(()=>{
loading.value = false
});
}
onMounted(() => {
loading.value = true;
let params: any = router.currentRoute.value.params;
if (params.appId) {
appId.value = params.appId;

View File

@ -84,6 +84,8 @@
}
//
function sendCodeApi() {
return getCaptcha({ mobile: formData.mobile, smsmode: SmsEnum.FORGET_PASSWORD });
//update-begin---author:wangshuai---date:2025-07-15---for:issues/8567---
return getCaptcha({ mobile: formData.mobile, smsmode: SmsEnum.LOGIN });
//update-end---author:wangshuai---date:2025-07-15---for:issues/8567---
}
</script>

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',
}
/**
@ -67,3 +68,14 @@ 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 = "";
}
}
@ -215,6 +218,25 @@
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();
initThirdAppConfigData({ tenantId: tenantId, thirdType: 'dingtalk' });
@ -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 = "";
}
}
@ -160,6 +163,25 @@
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();
initThirdAppConfigData({ tenantId: tenantId, thirdType: 'wechat_enterprise' });
@ -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-9005IP15---
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---forTV360X-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---forTV360X-1632
dynamicRules() {
return [
{
validator: (_, value) => {
return new Promise<void>((resolve, reject) => {
if (value.length > 100) {
reject('最长100个字符');
}
resolve();
});
},
},
];
},
// update-end--author:liaozhiyang---date:20240701---forTV360X-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---forTV360X-1627
labelKey: 'realname',
// update-end--author:liaozhiyang---date:20240701---forTV360X-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();
});
};
/**
* 用户离职(新增代理人和用户状态变更操作)

View File

@ -42,6 +42,10 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
}
// ----- [end] JEECG -----
console.log('[init] Start Port: ', VITE_PORT);
console.log('[init] Vite Proxy Config: ', VITE_PROXY);
return {
base: isQiankunMicro ? VITE_GLOB_QIANKUN_MICRO_APP_ENTRY : VITE_PUBLIC_PATH,
root,