This commit is contained in:
孟帅
2025-03-22 20:23:27 +08:00
parent 5301bedff2
commit 62af998991
98 changed files with 1925 additions and 2860 deletions

View File

@@ -0,0 +1,144 @@
<template>
<n-input-group>
<n-select
v-bind="$props"
:style="{ width: '68%' }"
v-model:value="memberId"
:options="memberOption"
:render-label="renderLabel"
:filter="handleReceiverFilter"
@update:value="handleChangeMemberId"
clearable
filterable
/>
<n-select
:consistent-menu-width="false"
:style="{ width: '32%', 'min-width': '90px' }"
:options="selectOptions"
v-model:value="optVal"
@update:value="handleUpdateOptions"
/>
</n-input-group>
</template>
<script setup lang="ts">
import { h, onMounted, ref, watch } from 'vue';
import { GetMemberOption } from '@/api/org/user';
import { useUserStore } from '@/store/modules/user';
import { NText, SelectRenderLabel } from 'naive-ui';
import { basicProps } from './props';
const props = defineProps({
...basicProps,
});
const emit = defineEmits(['update:value']);
const userStore = useUserStore();
const memberId = ref(null);
const optVal = ref('-1');
const memberOption = ref([]);
const selectOptions = [
{
label: '查全部',
value: '-1',
},
{
label: '查本人',
value: '1',
},
{
label: '查下级',
value: '2',
},
];
const renderLabel: SelectRenderLabel = (option: { username: ''; label: '' }) => {
return h(
'div',
{
style: {
display: 'flex',
alignItems: 'center',
},
},
[
h(
'div',
{
style: {
padding: '4px 0',
display: 'flex',
},
},
[
h(
'div',
{
style: {
marginRight: '4px',
},
},
[option.username as string]
),
h(
NText,
{ depth: 3, tag: 'div' },
{
default: () => option.label as string,
}
),
]
),
]
);
};
function handleReceiverFilter(pattern: string, option: object): boolean {
const isPatternInLabel = option.label.includes(pattern);
const isPatternInUsername = option.username.includes(pattern);
const isValueEqual = option.value.toString() === pattern;
return isPatternInLabel || isPatternInUsername || isValueEqual;
}
function handleChangeMemberId(v: string) {
memberId.value = v;
handleUpdateValue();
}
function handleUpdateOptions(value: string) {
optVal.value = value;
handleUpdateValue();
}
function handleUpdateValue() {
if (memberId.value) {
emit('update:value', [memberId.value, optVal.value]);
} else {
emit('update:value', null);
}
}
function resetFields() {
memberId.value = null;
optVal.value = '-1';
}
watch(
() => props.value,
(v) => {
if (!v) {
resetFields();
}
},
{ deep: true }
);
onMounted(() => {
GetMemberOption().then((res) => {
if (res) {
memberOption.value = res;
}
});
});
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,13 @@
import { NSelect } from 'naive-ui';
export const basicProps = {
...NSelect.props,
defaultValue: {
type: [Array],
default: null,
},
value: {
type: [Array],
default: null,
},
};

View File

@@ -66,6 +66,13 @@
v-bind="getComponentProps(schema)"
/>
</template>
<template v-else-if="schema.component === 'ComplexMemberPicker'">
<ComplexMemberPicker
:class="{ isFull: schema.isFull !== false && getProps.isFull }"
v-model:value="formModel[schema.field]"
v-bind="getComponentProps(schema)"
/>
</template>
<!--动态渲染表单组件-->
<component
v-else
@@ -147,18 +154,16 @@
import { createPlaceholderMessage } from './helper';
import { useFormEvents } from './hooks/useFormEvents';
import { useFormValues } from './hooks/useFormValues';
import ComplexMemberPicker from '../../ComplexMemberPicker/index.vue';
import { basicProps } from './props';
import { DownOutlined, UpOutlined, QuestionCircleOutlined } from '@vicons/antd';
import type { Ref } from 'vue';
import type { GridProps } from 'naive-ui/lib/grid';
import type { FormSchema, FormProps, FormActionType } from './types/form';
import {isArray, isBoolean, isFunction} from '@/utils/is';
import { isArray, isBoolean, isFunction } from '@/utils/is';
import { deepMerge } from '@/utils';
import { usePermission } from '@/hooks/web/usePermission';
import {ActionItem} from "@/components/Table";
import { ActionItem } from '@/components/Table';
export default defineComponent({
name: 'BasicForm',
@@ -237,7 +242,7 @@
});
const getBindValue = computed(
() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable)
() => ({ ...attrs, ...props, ...unref(getProps) }) as Recordable
);
const getSchema = computed((): FormSchema[] => {

View File

@@ -25,4 +25,5 @@ export type ComponentType =
| 'NIconPicker'
| 'NRender'
| 'NSlider'
| 'NRate';
| 'NRate'
| 'ComplexMemberPicker';

View File

@@ -34,7 +34,7 @@ export const RedirectRoute: AppRouteRecordRaw = {
children: [
{
path: '/redirect/:path(.*)',
name: RedirectName,
name: `${RedirectName}Son`,
component: () => import('@/views/redirect/index.vue'),
meta: {
title: RedirectName,

View File

@@ -183,7 +183,6 @@ export const renderPopoverMemberSumma = (member: MemberSumma | null | undefined)
h(
NButton,
{
strong: true,
size: 'small',
text: true,
iconPlacement: 'right',

View File

@@ -146,6 +146,10 @@ export function isNullOrUnDef(val: unknown): val is null | undefined {
return isUnDef(val) || isNull(val);
}
export function isEmpty(value: any): boolean {
return value === undefined || value === null || value === '';
}
// 判断字串符是否以字母开头
export function isLetterBegin(str) {
return /^[A-z]/.test(str);

View File

@@ -81,7 +81,7 @@ export default () => {
clearTimeout(timer);
timer = setTimeout(() => {
createSocket();
}, 2000);
}, 1000 * 10);
};
const init = () => {

View File

@@ -106,7 +106,7 @@ export const columns = [
{
title: '文件名称',
key: 'name',
width: 120,
width: 150,
},
{
title: '文件',
@@ -157,7 +157,7 @@ export const columns = [
{
title: '扩展类型',
key: 'mimeType',
width: 120,
width: 200,
},
{
title: '上传时间',

View File

@@ -21,7 +21,7 @@
ref="actionRef"
:actionColumn="actionColumn"
@update:checked-row-keys="onCheckedRow"
:scroll-x="1280"
:scroll-x="scrollX"
:resizeHeightOffset="-20000"
>
<template #tableTitle>
@@ -77,7 +77,7 @@
</template>
<script lang="ts" setup>
import { h, onMounted, reactive, ref } from 'vue';
import { computed, h, onMounted, reactive, ref } from 'vue';
import { useDialog, useMessage } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, useForm } from '@/components/Form/index';
@@ -93,6 +93,7 @@
import FileUpload from '@/components/FileChooser/src/Upload.vue';
import MultipartUpload from '@/components/Upload/multipartUpload.vue';
import { Attachment } from '@/components/FileChooser/src/model';
import { adaTableScrollX } from '@/utils/hotgo';
const message = useMessage();
const actionRef = ref();
@@ -106,7 +107,7 @@
const multipartUploadRef = ref();
const actionColumn = reactive({
width: 120,
width: 132,
title: '操作',
key: 'action',
fixed: 'right',
@@ -128,6 +129,10 @@
},
});
const scrollX = computed(() => {
return adaTableScrollX(columns, actionColumn.width);
});
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 100,

View File

@@ -1,9 +1,7 @@
import { h } from 'vue';
import { NAvatar, NAvatarGroup, NTooltip } from 'naive-ui';
import { renderOptionTag } from '@/utils';
import { useDictStore } from '@/store/modules/dict';
const dict = useDictStore();
export const columns = [
{
title: 'ID',
@@ -106,7 +104,3 @@ export const columns = [
width: 180,
},
];
export function loadOptions() {
dict.loadOptions(['sys_normal_disable']);
}

View File

@@ -0,0 +1,215 @@
<template>
<div>
<n-drawer v-model:show="showModal" :width="dialogWidth">
<n-drawer-content closable>
<template #header>
{{
formValue.id > 0
? '编辑' + dict.getLabel('noticeTypeOptions', formValue.type) + ' #' + formValue.id
: '发送' + dict.getLabel('noticeTypeOptions', formValue.type)
}}
</template>
<n-scrollbar style="max-height: 87vh" class="pr-5">
<n-spin :show="loading" description="请稍候...">
<n-alert :show-icon="false" type="info">
消息发送成功后如果接收人在线会立即收到一条消息通知编辑已发送的消息不会再次通知
</n-alert>
<n-form
:model="formValue"
:rules="rules"
ref="formRef"
:label-placement="settingStore.isMobile ? 'top' : 'left'"
:label-width="80"
class="py-4"
>
<n-form-item label="消息标题" path="title">
<n-input placeholder="请输入消息标题" v-model:value="formValue.title" />
</n-form-item>
<n-form-item label="接收人" path="receiver" v-if="formValue.type === 3">
<n-select
multiple
:options="options"
:render-label="renderLabel"
:render-tag="renderMultipleSelectTag"
v-model:value="formValue.receiver"
filterable
/>
</n-form-item>
<n-form-item label="消息内容" path="content">
<template v-if="formValue.type === 1">
<n-input
type="textarea"
:autosize="{ minRows: 3, maxRows: 10 }"
placeholder="请输入通知内容"
v-model:value="formValue.content"
/>
</template>
<template v-else>
<Editor style="height: 320px" v-model:value="formValue.content" />
</template>
</n-form-item>
<n-grid x-gap="24" :cols="2">
<n-gi>
<n-form-item label="标签" path="tag">
<n-select
clearable
placeholder="可以不填"
:render-tag="renderTag"
v-model:value="formValue.tag"
:options="dict.getOptionUnRef('noticeTagOptions')"
/>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="排序" path="sort">
<n-input-number style="width: 100%" v-model:value="formValue.sort" clearable />
</n-form-item>
</n-gi>
</n-grid>
<n-form-item label="状态" path="status">
<n-radio-group v-model:value="formValue.status" name="status">
<n-radio-button
v-for="status in statusOptions"
:key="status.value"
:value="status.value"
:label="status.label"
/>
</n-radio-group>
</n-form-item>
<n-form-item label="备注" path="remark">
<n-input
type="textarea"
placeholder="请输入备注,没有可以不填"
v-model:value="formValue.remark"
/>
</n-form-item>
</n-form>
</n-spin>
</n-scrollbar>
<template #footer>
<n-space>
<n-button @click="closeForm"> 取消 </n-button>
<n-button type="primary" :loading="formBtnLoading" @click="confirmForm">
立即发送
</n-button>
</n-space>
</template>
</n-drawer-content>
</n-drawer>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useDictStore } from '@/store/modules/dict';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
import { useMessage } from 'naive-ui';
import { personOption, renderLabel, renderMultipleSelectTag } from '@/enums/systemMessageEnum';
import Editor from '@/components/Editor/editor.vue';
import { statusOptions } from '@/enums/optionsiEnum';
import { GetMemberOption } from '@/api/org/user';
import { MaxSort, EditLetter, EditNotice, EditNotify } from '@/api/apply/notice';
import { renderTag } from '@/utils';
import { adaModalWidth } from '@/utils/hotgo';
import { State, newState, rules } from './model';
const emit = defineEmits(['reloadTable']);
const message = useMessage();
const settingStore = useProjectSettingStore();
const dict = useDictStore();
const loading = ref(false);
const showModal = ref(false);
const formValue = ref<State>(newState(null));
const formRef = ref<any>({});
const formBtnLoading = ref(false);
const options = ref<personOption[]>();
const dialogWidth = ref(adaModalWidth(840));
function confirmForm(e) {
e.preventDefault();
formBtnLoading.value = true;
formRef.value.validate((errors) => {
if (!errors) {
switch (formValue.value.type) {
case 1:
EditNotify(formValue.value).then((_res) => {
confirmComplete();
});
break;
case 2:
EditNotice(formValue.value).then((_res) => {
confirmComplete();
});
break;
case 3:
EditLetter(formValue.value).then((_res) => {
confirmComplete();
});
break;
default:
message.error('公告类型不支持');
}
} else {
message.error('请填写完整信息');
}
formBtnLoading.value = false;
});
}
function confirmComplete() {
message.success('操作成功');
setTimeout(() => {
closeForm();
emit('reloadTable');
});
}
function getMemberOption() {
GetMemberOption().then((res) => {
options.value = res;
});
}
// 关闭抽屉
function closeForm() {
showModal.value = false;
loading.value = false;
}
// 打开抽屉
function openModal(state: State, type: number) {
showModal.value = true;
dialogWidth.value = adaModalWidth(840);
getMemberOption();
// 新增
if (!state || state.id < 1) {
formValue.value = newState(state);
formValue.value.type = type;
loading.value = true;
MaxSort()
.then((res) => {
formValue.value.sort = res.sort;
})
.finally(() => {
loading.value = false;
});
return;
}
// 编辑
formValue.value = newState(state);
}
defineExpose({
openModal,
});
</script>
<style lang="less"></style>

View File

@@ -26,7 +26,7 @@
ref="actionRef"
:actionColumn="actionColumn"
@update:checked-row-keys="onCheckedRow"
:scroll-x="1280"
:scroll-x="scrollX"
:resizeHeightOffset="-20000"
>
<template #tableTitle>
@@ -88,109 +88,8 @@
</n-button>
</template>
</BasicTable>
<n-modal
v-model:show="showModal"
:show-icon="false"
:block-scroll="false"
:mask-closable="false"
preset="dialog"
:title="
formParams.id > 0
? '编辑' + dict.getLabel('noticeTypeOptions', formParams.type) + ' #' + formParams.id
: '发送' + dict.getLabel('noticeTypeOptions', formParams.type)
"
:style="{
width: dialogWidth,
}"
>
<n-alert :show-icon="false" type="info">
消息发送成功后如果接收人在线会立即收到一条消息通知,编辑已发送的消息不会再次通知
</n-alert>
<n-form
:model="formParams"
:rules="rules"
ref="formRef"
label-placement="left"
:label-width="80"
class="py-4"
>
<n-form-item label="消息标题" path="title">
<n-input placeholder="请输入消息标题" v-model:value="formParams.title" />
</n-form-item>
<n-form-item label="接收人" path="receiver" v-if="formParams.type === 3">
<n-select
multiple
:options="options"
:render-label="renderLabel"
:render-tag="renderMultipleSelectTag"
v-model:value="formParams.receiver"
filterable
/>
</n-form-item>
<n-form-item label="消息内容" path="content">
<template v-if="formParams.type === 1">
<n-input
type="textarea"
:autosize="{ minRows: 3, maxRows: 30 }"
placeholder="请输入通知内容"
v-model:value="formParams.content"
/>
</template>
<template v-else>
<Editor style="height: 450px" v-model:value="formParams.content" />
</template>
</n-form-item>
<n-grid x-gap="24" :cols="2">
<n-gi>
<n-form-item label="标签" path="tag">
<n-select
clearable
placeholder="可以不填"
:render-tag="renderTag"
v-model:value="formParams.tag"
:options="dict.getOptionUnRef('noticeTagOptions')"
/>
</n-form-item>
</n-gi>
<n-gi>
<n-form-item label="排序" path="sort">
<n-input-number style="width: 100%" v-model:value="formParams.sort" clearable />
</n-form-item>
</n-gi>
</n-grid>
<n-form-item label="状态" path="status">
<n-radio-group v-model:value="formParams.status" name="status">
<n-radio-button
v-for="status in statusOptions"
:key="status.value"
:value="status.value"
:label="status.label"
/>
</n-radio-group>
</n-form-item>
<n-form-item label="备注" path="remark">
<n-input
type="textarea"
placeholder="请输入备注没有可以不填"
v-model:value="formParams.remark"
/>
</n-form-item>
</n-form>
<template #action>
<n-space>
<n-button @click="() => (showModal = false)">取消</n-button>
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">立即发送</n-button>
</n-space>
</template>
</n-modal>
</n-card>
<Edit ref="editRef" @reload-table="reloadTable" />
</div>
</template>
@@ -198,117 +97,25 @@
import { computed, h, onMounted, reactive, ref } from 'vue';
import { useDialog, useMessage } from 'naive-ui';
import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
import {
Delete,
EditNotify,
EditLetter,
EditNotice,
List,
MaxSort,
Status,
} from '@/api/apply/notice';
import { columns, loadOptions } from './columns';
import { BasicForm, useForm } from '@/components/Form/index';
import { Delete, List, Status } from '@/api/apply/notice';
import { BellOutlined, DeleteOutlined, NotificationOutlined, SendOutlined } from '@vicons/antd';
import { statusOptions } from '@/enums/optionsiEnum';
import { personOption, renderLabel, renderMultipleSelectTag } from '@/enums/systemMessageEnum';
import { adaModalWidth } from '@/utils/hotgo';
import { renderTag } from '@/utils';
import Editor from '@/components/Editor/editor.vue';
import { cloneDeep } from 'lodash-es';
import { GetMemberOption } from '@/api/org/user';
import { adaTableScrollX } from '@/utils/hotgo';
import { usePermission } from '@/hooks/web/usePermission';
import { useDictStore } from '@/store/modules/dict';
const dict = useDictStore();
const rules = {
title: {
required: true,
trigger: ['blur', 'input'],
message: '请输入消息标题',
},
};
const schemas: FormSchema[] = [
{
field: 'type',
component: 'NSelect',
label: '消息类型',
defaultValue: null,
componentProps: {
placeholder: '请选择消息类型',
options: dict.getOption('noticeTypeOptions'),
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'title',
component: 'NInput',
label: '消息标题',
componentProps: {
placeholder: '请输入消息标题',
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules: [{ message: '请输入消息标题', trigger: ['blur'] }],
},
{
field: 'content',
component: 'NInput',
label: '消息内容',
componentProps: {
placeholder: '请输入消息内容关键词',
showButton: false,
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'status',
component: 'NSelect',
label: '状态',
defaultValue: null,
componentProps: {
placeholder: '请选择类型',
options: dict.getOption('sys_normal_disable'),
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
];
import { columns } from './columns';
import { schemas, loadOptions } from './model';
import Edit from './edit.vue';
const { hasPermission } = usePermission();
const message = useMessage();
const dict = useDictStore();
const actionRef = ref();
const dialog = useDialog();
const showModal = ref(false);
const formBtnLoading = ref(false);
const searchFormRef = ref<any>({});
const formRef = ref<any>({});
const editRef = ref();
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
const options = ref<personOption[]>();
const dialogWidth = computed(() => {
return adaModalWidth();
});
const resetFormParams = {
id: 0,
title: '',
type: 1,
tag: 0,
content: '',
receiver: null,
remark: '',
sort: 0,
status: 1,
};
let formParams = ref<any>(cloneDeep(resetFormParams));
const actionColumn = reactive({
width: 200,
@@ -351,6 +158,10 @@
},
});
const scrollX = computed(() => {
return adaTableScrollX(columns, actionColumn.width);
});
const [register, {}] = useForm({
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
labelWidth: 80,
@@ -358,12 +169,7 @@
});
function addTable(type) {
showModal.value = true;
formParams.value = cloneDeep(resetFormParams);
formParams.value.type = type;
MaxSort().then((res) => {
formParams.value.sort = res.sort;
});
editRef.value.openModal(null, type);
}
const loadDataTable = async (res) => {
@@ -379,52 +185,8 @@
actionRef.value.reload();
}
function confirmForm(e) {
e.preventDefault();
formBtnLoading.value = true;
formRef.value.validate((errors) => {
if (!errors) {
switch (formParams.value.type) {
case 1:
EditNotify(formParams.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
showModal.value = false;
reloadTable();
});
});
break;
case 2:
EditNotice(formParams.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
showModal.value = false;
reloadTable();
});
});
break;
case 3:
EditLetter(formParams.value).then((_res) => {
message.success('操作成功');
setTimeout(() => {
showModal.value = false;
reloadTable();
});
});
break;
default:
message.error('公告类型不支持');
}
} else {
message.error('请填写完整信息');
}
formBtnLoading.value = false;
});
}
function handleEdit(record: Recordable) {
showModal.value = true;
formParams.value = cloneDeep(record);
editRef.value.openModal(record, record.type);
}
function handleDelete(record: Recordable) {
@@ -474,13 +236,8 @@
});
}
async function getMemberOption() {
options.value = await GetMemberOption();
}
onMounted(async () => {
onMounted(() => {
loadOptions();
await getMemberOption();
});
</script>

View File

@@ -0,0 +1,113 @@
import { cloneDeep } from 'lodash-es';
import { FormSchema } from '@/components/Form';
import { useDictStore } from '@/store/modules/dict';
const dict = useDictStore();
export class State {
id: number;
title: string;
type: number;
tag: number = 1;
content: string;
receiver: number[];
remark: string;
sort: number;
status: number = 1;
createdBy: number;
updatedBy: number;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
readCount: number;
receiverGroup: Receiver[];
constructor(state?: Partial<State>) {
if (state) {
Object.assign(this, state);
}
}
}
export class Receiver {
name: string;
src: string;
}
export function newState(state: State | Record<string, any> | null): State {
if (state !== null) {
if (state instanceof State) {
return cloneDeep(state);
}
return new State(state);
}
return new State();
}
// 表单验证规则
export const rules = {
title: {
required: true,
trigger: ['blur', 'input'],
message: '请输入消息标题',
},
};
// 表格搜索表单
export const schemas: FormSchema[] = [
{
field: 'type',
component: 'NSelect',
label: '消息类型',
defaultValue: null,
componentProps: {
placeholder: '请选择消息类型',
options: dict.getOption('noticeTypeOptions'),
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'title',
component: 'NInput',
label: '消息标题',
componentProps: {
placeholder: '请输入消息标题',
onUpdateValue: (e: any) => {
console.log(e);
},
},
rules: [{ message: '请输入消息标题', trigger: ['blur'] }],
},
{
field: 'content',
component: 'NInput',
label: '消息内容',
componentProps: {
placeholder: '请输入消息内容关键词',
showButton: false,
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
{
field: 'status',
component: 'NSelect',
label: '状态',
defaultValue: null,
componentProps: {
placeholder: '请选择类型',
options: dict.getOption('sys_normal_disable'),
onUpdateValue: (e: any) => {
console.log(e);
},
},
},
];
// 加载字典数据选项
export function loadOptions() {
dict.loadOptions(['sys_normal_disable']);
}

View File

@@ -1,5 +1,6 @@
import { h } from 'vue';
import { NTag } from 'naive-ui';
import { renderPopoverMemberSumma } from '@/utils';
const msgMap = {
1: '处理中',
@@ -29,20 +30,12 @@ export const columns = [
width: 100,
},
{
title: '用户名',
key: 'memberUser',
render(row) {
return row.memberUser;
},
title: '申请人',
key: 'memberId',
width: 100,
},
{
title: '姓名',
key: 'memberName',
render(row) {
return row.memberName;
return renderPopoverMemberSumma(row.memberBySumma);
},
width: 100,
},
{
title: '提现金额',

View File

@@ -118,7 +118,7 @@
</n-carousel>
</n-form-item>
<n-form-item label="提现状态" path="status">
<n-form-item label="变更提现状态" path="status">
<n-radio-group v-model:value="paymentParams.status" name="status">
<n-radio-button
v-for="status in statusOptions"
@@ -187,16 +187,15 @@
const schemas: FormSchema[] = [
{
field: 'memberId',
component: 'NInput',
label: '管理员ID',
field: 'complexMemberId',
component: 'ComplexMemberPicker',
label: '申请人',
componentProps: {
placeholder: '请输入管理员ID',
onUpdateValue: (e: any) => {
placeholder: '请选择申请人',
onInput: (e: any) => {
console.log(e);
},
},
rules: [{ message: '请输入管理员ID', trigger: ['blur'] }],
},
{
field: 'ip',

View File

@@ -2,18 +2,18 @@ import { ref } from 'vue';
import { FormSchema } from '@/components/Form';
import { defRangeShortcuts } from '@/utils/dateUtil';
import { useDictStore } from '@/store/modules/dict';
import { renderOptionTag } from '@/utils';
import { renderOptionTag, renderPopoverMemberSumma } from '@/utils';
const dict = useDictStore();
export const schemas = ref<FormSchema[]>([
{
field: 'memberId',
component: 'NInput',
label: '管理员ID',
field: 'complexMemberId',
component: 'ComplexMemberPicker',
label: '选择用户',
componentProps: {
placeholder: '请输入管理员ID',
onUpdateValue: (e: any) => {
placeholder: '请选择用户',
onInput: (e: any) => {
console.log(e);
},
},
@@ -21,10 +21,10 @@ export const schemas = ref<FormSchema[]>([
{
field: 'creditGroup',
component: 'NSelect',
label: '组别',
label: '变动组别',
defaultValue: null,
componentProps: {
placeholder: '请选择变动组别',
placeholder: '请选择变动组别',
options: dict.getOption('creditGroup'),
onUpdateValue: (e: any) => {
console.log(e);
@@ -86,9 +86,12 @@ export const columns = [
width: 100,
},
{
title: '管理员ID',
title: '用户',
key: 'memberId',
width: 100,
render(row) {
return renderPopoverMemberSumma(row.memberBySumma);
},
},
{
title: '变动类型',
@@ -99,7 +102,7 @@ export const columns = [
width: 150,
},
{
title: '组别',
title: '变动组别',
key: 'creditGroup',
render(row) {
return renderOptionTag('creditGroup', row.creditGroup);

View File

@@ -58,7 +58,6 @@
}
const emit = defineEmits(['reloadTable', 'updateShowModal']);
const props = withDefaults(defineProps<Props>(), {
showModal: false,
formParams: () => {

View File

@@ -3,7 +3,7 @@ import { cloneDeep } from 'lodash-es';
import { FormSchema } from '@/components/Form';
import { defRangeShortcuts } from '@/utils/dateUtil';
import { useDictStore } from '@/store/modules/dict';
import { renderOptionTag } from '@/utils';
import { MemberSumma, renderOptionTag, renderPopoverMemberSumma } from '@/utils';
export interface State {
id: number;
@@ -20,6 +20,7 @@ export interface State {
refundReason: string;
rejectRefundReason: string;
payLogPayType: string;
memberBySumma?: null | MemberSumma;
}
export const defaultState = {
@@ -51,12 +52,12 @@ export const rules = {};
export const schemas = ref<FormSchema[]>([
{
field: 'memberId',
component: 'NInput',
label: '管理员ID',
field: 'complexMemberId',
component: 'ComplexMemberPicker',
label: '下单用户',
componentProps: {
placeholder: '请输入管理员ID',
onUpdateValue: (e: any) => {
placeholder: '请选择下单用户',
onInput: (e: any) => {
console.log(e);
},
},
@@ -99,25 +100,18 @@ export const schemas = ref<FormSchema[]>([
]);
export const columns = [
{
title: '订单ID',
key: 'id',
width: 100,
},
{
title: '管理员ID',
key: 'memberId',
width: 100,
},
{
title: '业务订单号',
key: 'orderSn',
width: 260,
width: 220,
},
{
title: '商户订单号',
key: 'payLogOutTradeNo',
width: 260,
title: '下单用户',
key: 'memberId',
width: 100,
render(row: State) {
return renderPopoverMemberSumma(row.memberBySumma);
},
},
{
title: '支付方式',
@@ -143,6 +137,11 @@ export const columns = [
},
width: 150,
},
{
title: '商户订单号',
key: 'payLogOutTradeNo',
width: 220,
},
{
title: '创建时间',
key: 'createdAt',

View File

@@ -1,7 +1,5 @@
import { h } from 'vue';
import { NTag, NEllipsis, NSpace } from 'naive-ui';
import { timestampToTime } from '@/utils/dateUtil';
import { renderHtmlTooltip } from '@/utils';
import Column from './components/Column.vue';
export const columns = [
{
@@ -14,49 +12,10 @@ export const columns = [
key: 'name',
width: 180,
render(row) {
const operator =
row.memberId === 0 ? row.memberName : row.memberName + '(' + row.memberId + ')';
return h(
NEllipsis,
{
style: {
maxWidth: '180px',
},
},
{
default: () =>
h(
NSpace,
{ vertical: true },
{
default: () => [
h('div', {
innerHTML: '<div><p>' + operator + '</p></div>',
}),
h('div', {
innerHTML: '<div><p>IP' + row.ip + '</p></div>',
}),
row.cityLabel != ''
? h(
NTag,
{
style: {
marginRight: '6px',
},
type: 'primary',
bordered: false,
},
{
default: () => row.cityLabel,
}
)
: null,
],
}
),
}
);
return h(Column, {
state: row,
column: 'visitor',
});
},
},
{
@@ -64,43 +23,10 @@ export const columns = [
key: 'name',
width: 260,
render(row) {
return h(
NEllipsis,
{
style: {
maxWidth: '260px',
},
},
{
default: () =>
h(
NSpace,
{ vertical: true },
{
default: () => [
h(
NTag,
{
style: {
marginRight: '6px',
},
bordered: false,
},
{
default: () => row.method,
}
),
h('div', {
innerHTML: '<div><p>接口:' + row.url + '</p></div>',
}),
h('div', {
innerHTML: '<div><p>名称:' + row.tags + ' / ' + row.summary + '</p></div>',
}),
],
}
),
}
);
return h(Column, {
state: row,
column: 'request',
});
},
},
{
@@ -108,39 +34,10 @@ export const columns = [
key: 'name',
width: 260,
render(row) {
return h(
NEllipsis,
{
style: {
maxWidth: '260px',
},
},
{
default: () =>
h(
NSpace,
{ vertical: true },
{
default: () => [
renderHtmlTooltip(
'<div style="width: 240px"><p>状态码:' +
row.errorMsg +
'(' +
row.errorCode +
')' +
'</p></div>'
),
h('div', {
innerHTML: '<div><p>处理耗时:' + row.takeUpTime + 'ms</p></div>',
}),
h('div', {
innerHTML: '<div><p>响应时间:' + timestampToTime(row.timestamp) + '</p></div>',
}),
],
}
),
}
);
return h(Column, {
state: row,
column: 'response',
});
},
},
{

View File

@@ -0,0 +1,77 @@
<template>
<div class="box">
<template v-if="column === 'visitor'">
<div class="flex flex-col">
<div class="flex flex-row mb-1">
<div class="text">
{{
state.memberId === 0
? state.memberName
: state.memberName + '(' + state.memberId + ')'
}}
</div>
</div>
<div class="flex flex-row">
<div>访问IP</div>
<div class="text">{{ state.ip }}</div>
</div>
</div>
<div class="text">
{{ state.cityLabel !== '' ? state.cityLabel : '局域网' }}
</div>
</template>
<template v-if="column === 'request'">
<div class="flex flex-col">
<div class="flex flex-row">
<div class="text mr-1 mb-1">
<n-button :type="state.method === 'GET' ? 'tertiary' : 'primary'" size="tiny">
{{ state.method }}
</n-button>
</div>
<div class="text">{{ state.url }}</div>
</div>
<div class="flex flex-row">
<div class="text">{{ state.tags }}</div>
</div>
</div>
<div>
{{ state.summary }}
</div>
</template>
<template v-if="column === 'response'">
<div class="flex flex-col">
<div class="flex flex-row">
<div class="text mr-1 mb-1">
<n-button v-if="state.errorCode === 0" type="tertiary" size="tiny">
{{ state.errorMsg }}
</n-button>
<n-button v-else type="error" size="tiny">
{{ state.errorCode }} {{ state.errorMsg }}
</n-button>
</div>
</div>
<div class="flex flex-row">
<div class="text">处理耗时{{ state.takeUpTime }}ms</div>
</div>
<div class="flex flex-row">
<div class="text">响应时间{{ timestampToTime(state.timestamp) }}</div>
</div>
</div>
</template>
</div>
</template>
<script lang="ts" setup>
import { basicProps } from './props';
import { timestampToTime } from '@/utils/dateUtil';
const props = defineProps(basicProps);
</script>
<style lang="less" scoped>
.text {
display: inline;
}
</style>

View File

@@ -0,0 +1,12 @@
import { State } from '@/views/log/log/model';
export const basicProps = {
state: {
type: State,
default: null,
},
column: {
type: String,
default: '',
},
};

View File

@@ -53,7 +53,6 @@
const dialog = useDialog();
const batchDeleteDisabled = ref(true);
const checkedIds = ref([]);
const router = useRouter();
const message = useMessage();
const actionRef = ref();

View File

@@ -48,16 +48,15 @@ export const schemas = ref<FormSchema[]>([
},
},
{
field: 'memberId',
component: 'NInput',
field: 'complexMemberId',
component: 'ComplexMemberPicker',
label: '操作人',
componentProps: {
placeholder: '请输入操作人ID',
placeholder: '请选择操作人',
onInput: (e: any) => {
console.log(e);
},
},
rules: [{ trigger: ['blur'] }],
},
{
field: 'url',

View File

@@ -129,18 +129,12 @@
<template #trigger>
<n-icon :component="QuestionCircleOutlined" :size="18" :depth="3" />
</template>
请填写API路径地址可同时作用于server端接口鉴权和web端细粒度权限一次添加多个权限用,分割
请填写API路径地址可同时作用于server端接口鉴权和web端细粒度权限
</n-tooltip>
分配权限
</template>
</n-form-item>
</n-gi>
<!-- <n-gi>-->
<!-- <n-form-item label="权限名称" path="permissionName">-->
<!-- <n-input placeholder="权限名称" v-model:value="formParams.permissionName" />-->
<!-- <template #feedback>分配权限存在多个时权限名称只绑定到第一个权限</template>-->
<!-- </n-form-item>-->
<!-- </n-gi>-->
</n-grid>
<n-grid cols="2 300:1 600:2">

View File

@@ -8,7 +8,6 @@
ref="formRef"
label-placement="top"
>
<n-divider title-placement="left">基础设置</n-divider>
<n-form-item label="默认驱动" path="uploadDrive">
<n-select
placeholder="默认驱动"
@@ -16,46 +15,16 @@
v-model:value="formValue.uploadDrive"
/>
</n-form-item>
<n-form-item label="图片大小限制" path="uploadImageSize">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.uploadImageSize"
>
<template #suffix> MB</template>
</n-input-number>
</n-form-item>
<n-form-item label="文件大小限制" path="uploadFileSize">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.uploadFileSize"
>
<template #suffix> MB</template>
</n-input-number>
</n-form-item>
<n-form-item label="图片类型限制" path="uploadImageType">
<n-input v-model:value="formValue.uploadImageType" placeholder="" />
</n-form-item>
<n-form-item label="文件类型限制" path="uploadFileType">
<n-input v-model:value="formValue.uploadFileType" placeholder="" />
</n-form-item>
<n-tabs type="card" size="small" v-model:value="tabName">
<n-tab-pane name="local">
<template #tab> 本地存储</template>
<n-divider title-placement="left">本地存储</n-divider>
<n-tab-pane name="local" tab="本地存储">
<n-form-item label="本地存储路径" path="uploadLocalPath">
<n-input v-model:value="formValue.uploadLocalPath" placeholder="" />
<template #feedback>填对外访问的相对路径</template>
</n-form-item>
</n-tab-pane>
<n-tab-pane name="oss">
<template #tab> 阿里云OSS存储</template>
<n-divider title-placement="left">阿里云OSS存储</n-divider>
<n-tab-pane name="oss" tab="阿里云OSS">
<n-form-item label="AccessKey ID" path="uploadOssSecretId">
<n-input
type="password"
@@ -102,9 +71,7 @@
</n-form-item>
</n-tab-pane>
<n-tab-pane name="cos">
<template #tab> 腾讯云COS存储</template>
<n-divider title-placement="left">腾讯云COS存储</n-divider>
<n-tab-pane name="cos" tab="腾讯云COS">
<n-form-item label="APPID" path="uploadCosSecretId">
<n-input v-model:value="formValue.uploadCosSecretId" />
<template #feedback>
@@ -138,9 +105,7 @@
</n-form-item>
</n-tab-pane>
<n-tab-pane name="qiniu">
<template #tab> 七牛云对象存储</template>
<n-divider title-placement="left">七牛云对象存储</n-divider>
<n-tab-pane name="qiniu" tab="七牛云对象存储">
<n-form-item label="AccessKey" path="uploadQiNiuAccessKey">
<n-input
type="password"
@@ -183,9 +148,7 @@
</n-form-item>
</n-tab-pane>
<n-tab-pane name="ucloud">
<template #tab> ucloud对象存储</template>
<n-divider title-placement="left">ucloud对象存储</n-divider>
<n-tab-pane name="ucloud" tab="UC对象存储">
<n-form-item label="公钥" path="uploadUCloudPublicKey">
<n-input
type="password"
@@ -237,9 +200,7 @@
</n-form-item>
</n-tab-pane>
<n-tab-pane name="minio">
<template #tab> minio对象存储</template>
<n-divider title-placement="left">minio对象存储</n-divider>
<n-tab-pane name="minio" tab="MinIO">
<n-form-item label="AccessKey ID" path="uploadMinioAccessKey">
<n-input
type="password"
@@ -295,6 +256,45 @@
</n-form-item>
</n-tab-pane>
</n-tabs>
<n-grid x-gap="24" :cols="4" class="mt-4">
<n-gi>
<n-form-item label="图片大小限制" path="uploadImageSize">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.uploadImageSize"
>
<template #suffix> MB</template>
</n-input-number>
</n-form-item>
</n-gi>
<n-gi :span="3">
<n-form-item label="图片类型限制" path="uploadImageType">
<n-input v-model:value="formValue.uploadImageType" placeholder="" />
</n-form-item>
</n-gi>
</n-grid>
<n-grid x-gap="24" :cols="4">
<n-gi>
<n-form-item label="文件大小限制" path="uploadFileSize">
<n-input-number
:show-button="false"
placeholder="请输入"
v-model:value="formValue.uploadFileSize"
>
<template #suffix> MB</template>
</n-input-number>
</n-form-item>
</n-gi>
<n-gi :span="3">
<n-form-item label="文件类型限制" path="uploadFileType">
<n-input v-model:value="formValue.uploadFileType" placeholder="" />
</n-form-item>
</n-gi>
</n-grid>
<div>
<n-space>
<n-button type="primary" @click="formSubmit">保存更新</n-button>