1、调整ui显示 <br/>

2、调整水印 <br/>
3、调整了默认租户的显示排序及显示,可以更方便进入默认租户 <br/>
4、屏蔽了国际化选项 <br/>
5、按需求增加用户身份证号,并校验唯一性 <br/>
6、按需求增加用户是否营销人员,并校验营销号唯一性 <br/>
7、按需求增加,经办人员录入时,录入的营销人员信息位系统提供选择,防止误录,demo见private-ebank-new-operate-drawer.vue <br/>

以上,代码工具和demo表结构暂时没改,初步测试大概率无误,先不改了
This commit is contained in:
xiaocp2009 2025-07-17 16:11:59 +08:00
parent 2759175a68
commit 06d95e439c
30 changed files with 461 additions and 93 deletions

View File

@ -4,7 +4,7 @@ VITE_SERVICE_BASE_URL=http://localhost:8080
VITE_APP_BASE_API=/dev-api
# watermark
VITE_WATERMARK=N
VITE_WATERMARK=Y
# 是否开启 SSE 功能
VITE_APP_SSE=Y
# 是否开启 websocket 功能

View File

@ -0,0 +1,11 @@
--------用户信息------------
用户登陆时,通过userInfo查询用户信息后存储在useAppStore中,
src/store/modules/auth/index.ts
src/views/mps/private-ebank-new/index.vue
--------watermark------------
src/App.vue
--------watermark------------
录入时,增加了营销人员列表
src/views/mps/private-ebank-new/modules/private-ebank-new-operate-drawer.vue

View File

@ -26,7 +26,7 @@ const naiveDateLocale = computed(() => {
});
const watermarkProps = computed<WatermarkProps>(() => {
const appTitle = import.meta.env.VITE_APP_TITLE || 'RuoYi-Vue-Plus';
const appTitle = import.meta.env.VITE_APP_TITLE || '全员营销计价';
const content =
themeStore.watermark.enableUserName && userInfo.user?.userName
? `${userInfo.user?.nickName}@${appTitle} ${userInfo.user?.userName}`
@ -36,9 +36,9 @@ const watermarkProps = computed<WatermarkProps>(() => {
cross: true,
fullscreen: true,
fontSize: 14,
fontColor: themeStore.darkMode ? 'rgba(200, 200, 200, 0.03)' : 'rgba(200, 200, 200, 0.2)',
fontColor: themeStore.darkMode ? 'rgb(67,61,61)' : 'rgba(28,25,25,0.2)',
lineHeight: 14,
width: 200,
width: 260,
height: 300,
xOffset: 12,
yOffset: 60,

View File

@ -84,7 +84,8 @@ const toGitee = () => {
</NScrollbar>
<template #footer>
<div class="flex items-center justify-end">
<NButton type="primary" size="small" @click="toGitee">前往 Gitee</NButton>
<!-- <NButton type="primary" size="small" @click="toGitee">前往 Gitee</NButton>-->
<NButton type="primary" size="small" >跳转</NButton>
</div>
</template>
</NCard>

View File

@ -48,12 +48,12 @@ const tenantId = ref<CommonType.IdType>(authStore.userInfo?.user?.tenantId || '0
<GlobalSearch v-if="themeStore.header.globalSearch.visible && !appStore.isMobile" />
<MessageButton />
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
<LangSwitch
<!-- <LangSwitch
v-if="themeStore.header.multilingual.visible"
:lang="appStore.locale"
:lang-options="appStore.localeOptions"
@change-lang="appStore.changeLocale"
/>
/>-->
<ThemeSchemaSwitch
:theme-schema="themeStore.themeScheme"
:is-dark="themeStore.darkMode"

View File

@ -1073,6 +1073,7 @@ const local: App.I18n.Schema = {
title: 'User List',
userName: 'Username',
nickName: 'Nickname',
idCard: 'IdCardNo',
deptName: 'Department',
phonenumber: 'Phone Number',
status: 'Status',
@ -1094,6 +1095,10 @@ const local: App.I18n.Schema = {
required: 'Please enter Nickname',
invalid: 'Nickname cannot be empty'
},
idCard: {
required: 'Please enter IdCardNo',
invalid: 'IdCardNo cannot be empty'
},
deptId: {
required: 'Please select Department',
invalid: 'Department cannot be empty'

View File

@ -1069,8 +1069,11 @@ const local: App.I18n.Schema = {
},
user: {
title: '用户列表',
userName: '用户名称',
nickName: '用户昵称',
userName: '登录名',
nickName: '用户姓名',
idCard: '身份证号',
userCategory: '用户种类',
mktNo: '营销编号',
deptName: '部门',
phonenumber: '手机号码',
status: '状态',
@ -1085,12 +1088,20 @@ const local: App.I18n.Schema = {
remark: '备注',
form: {
userName: {
required: '请输入用户名称',
invalid: '用户名称不能为空'
required: '请输入登录名',
invalid: '登录名不能为空'
},
nickName: {
required: '请输入用户昵称',
invalid: '用户昵称不能为空'
required: '请输入用户姓名',
invalid: '用户姓名不能为空'
},
idCard: {
required: '请输入身份证号',
invalid: '身份证号不能为空'
},
mktNo: {
required: '请输入营销编号',
invalid: '营销编号不能为空'
},
deptId: {
required: '请选择部门',

View File

@ -42,6 +42,14 @@ export function fetchGetUserSelect() {
});
}
/** 获取营销用户选择框列表 */
export function fetchGetMuUserSelect() {
return request<Api.System.User[]>({
url: '/system/user/muList',
method: 'get'
});
}
/** 修改用户状态 */
export function fetchUpdateUserStatus(data: Api.System.UserOperateParams) {
return request<boolean>({

View File

@ -58,8 +58,8 @@ export const themeSettings: App.Theme.ThemeSetting = {
},
watermark: {
visible: import.meta.env.VITE_WATERMARK === 'Y',
text: 'RuoYi-Vue-Plus',
enableUserName: false
text: '全员营销计价',
enableUserName: true
},
table: {
bordered: true,

View File

@ -7,31 +7,31 @@ namespace Mps {
/** private ebank new */
type PrivateEbankNew = Common.CommonRecord<{
/** 数据编号 */
dataId: CommonType.IdType;
dataId: CommonType.IdType;
/** 营销人员营销号 */
userId: CommonType.IdType;
userId: CommonType.IdType;
/** 客户身份证号 */
custId: CommonType.IdType;
custId: CommonType.IdType;
/** 客户姓名 */
custName: string;
custName: string;
/** 客户账号/卡号 */
custAcctNo: string;
custAcctNo: string;
/** 客户联系电话 */
custPhoneNo: string;
custPhoneNo: string;
/** 核对标志 */
checkFlag: string;
checkFlag: string;
/** 核对时间 */
checkTime: string;
checkTime: string;
/** 核对人员 */
checkUser: string;
checkUser: string;
/** 核对方式 */
checkType: string;
checkType: string;
/** 核对结果 */
checkMsg: string;
checkMsg: string;
/** 租户编号 */
tenantId: CommonType.IdType;
tenantId: CommonType.IdType;
/** 删除标志 */
delFlag: string;
delFlag: string;
}>;
/** private ebank new search params */
@ -53,6 +53,7 @@ namespace Mps {
Pick<
Api.Mps.PrivateEbankNew,
| 'dataId'
| 'operatorId'
| 'userId'
| 'custId'
| 'custName'

View File

@ -100,10 +100,16 @@ declare namespace Api {
deptName: string;
/** 用户账号 */
userName: string;
/** 用户昵称 */
/** 用户姓名 */
nickName: string;
/** 身份证号 */
idCard: string;
/** 用户类型sys_user系统用户 */
userType: string;
/** 用户种类(0-营销 其他-其他) */
userCategory: string;
/** 营销编号 */
mktNo: string;
/** 用户邮箱 */
email: string;
/** 手机号码 */
@ -139,6 +145,9 @@ declare namespace Api {
| 'deptId'
| 'userName'
| 'nickName'
| 'idCard'
| 'userCategory'
| 'mktNo'
| 'email'
| 'phonenumber'
| 'sex'

View File

@ -59,14 +59,14 @@ const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
class="text-20px lt-sm:text-18px"
@switch="themeStore.toggleThemeScheme"
/>
<LangSwitch
<!-- <LangSwitch
v-if="themeStore.header.multilingual.visible"
:lang="appStore.locale"
:lang-options="appStore.localeOptions"
:show-tooltip="false"
class="text-20px lt-sm:text-18px"
@change-lang="appStore.changeLocale"
/>
/>-->
</div>
</header>
<main class="pt-24px">

View File

@ -29,7 +29,8 @@ const tenantEnabled = ref<boolean>(false);
const tenantOption = ref<SelectOption[]>([]);
const model: Api.Auth.PwdLoginForm = reactive({
tenantId: '000000',
/*tenantId: '000000',*/
tenantId: '069291',//
username: 'admin',
password: 'admin123'
});
@ -165,9 +166,9 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
<NSpace vertical :size="16" class="mb-8px">
<div class="mx-6px mb-10px flex-y-center justify-between">
<NCheckbox v-model:checked="remberMe" size="large">{{ $t('page.login.pwdLogin.rememberMe') }}</NCheckbox>
<NA type="primary" class="text-18px" @click="toggleLoginModule('reset-pwd')">
<!-- <NA type="primary" class="text-18px" @click="toggleLoginModule('reset-pwd')">
{{ $t('page.login.pwdLogin.forgetPassword') }}
</NA>
</NA>-->
</div>
<NButton type="primary" size="large" block :loading="authStore.loginLoading" @click="handleSubmit">
{{ $t('common.login') }}
@ -179,10 +180,13 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
</NForm>
<NDivider>
<div class="color-#858585">{{ $t('page.login.pwdLogin.otherAccountLogin') }}</div>
<!-- <div class="color-#858585">{{ $t('page.login.pwdLogin.otherAccountLogin') }}</div>-->
评估是否增加注册功能
</NDivider>
<div class="w-full flex-y-center gap-16px">
<!-- <div class="w-full flex-y-center gap-16px">
<NButton class="flex-1" @click="handleSocialLogin('gitee')">
<template #icon>
<icon-simple-icons:gitee class="color-#c71d23" />
@ -195,14 +199,14 @@ async function handleSocialLogin(type: Api.System.SocialSource) {
</template>
<span class="ml-6px">GitHub</span>
</NButton>
</div>
</div>-->
<div class="mt-32px w-full text-center text-18px text-#858585">
<!-- <div class="mt-32px w-full text-center text-18px text-#858585">
您还没有账户
<NA type="primary" class="text-18px" @click="toggleLoginModule('register')">
{{ $t('page.login.common.register') }}
</NA>
</div>
</div>-->
</div>
</template>

View File

@ -55,7 +55,7 @@ const statisticData = computed<StatisticData[]>(() => [
})
}}
</h3>
<p class="text-#999 leading-30px">{{ $t('page.home.weatherDesc') }}</p>
<!-- <p class="text-#999 leading-30px">{{ $t('page.home.weatherDesc') }}</p>-->
</div>
</div>
</NGi>

View File

@ -9,15 +9,17 @@ import {$t} from '@/locales';
import ButtonIcon from '@/components/custom/button-icon.vue';
import PrivateEbankNewOperateDrawer from './modules/private-ebank-new-operate-drawer.vue';
import PrivateEbankNewSearch from './modules/private-ebank-new-search.vue';
import {computed, ref, watch} from "vue";
import {computed, onMounted, ref, watch} from "vue";
import {useBoolean} from "~/packages/hooks";
import PrivateEbankImportModal from './modules/private-ebank-import-modal.vue';
import {useAuthStore} from "@/store/modules/auth";
defineOptions({
name: 'PrivateEbankNewList'
});
const appStore = useAppStore();
const { userInfo } = useAuthStore();
const {download} = useDownload();
const {hasAuth} = useAuth();
const {bool: importVisible, setTrue: openImportModal} = useBoolean();
@ -68,6 +70,14 @@ const {
ellipsis: true,
resizable: true
},
{
key: 'operatorId',
title: '经办人员营销号',
align: 'center',
minWidth: 120,
ellipsis: true,
resizable: true
},
{
key: 'userId',
title: '营销人员营销号',
@ -238,6 +248,13 @@ watch(columns, (newColumns) => {
scrollX.value = calculateTotalWidth();
}, {deep: true});
//
onMounted(() => {
console.log(JSON.stringify(userInfo));
console.log("是否营销:" + userInfo.user.userCategory);
console.log("营销编号:" + userInfo.user.mktNo);
});
const {drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted} =
useTableOperate(data, getData);

View File

@ -1,9 +1,11 @@
<script setup lang="ts">
import {computed, reactive, watch} from 'vue';
import {computed, onMounted, reactive, ref, watch} from 'vue';
import {fetchCreatePrivateEbankNew, fetchUpdatePrivateEbankNew} from '@/service/api/mps/private-ebank-new';
import {useFormRules, useNaiveForm} from '@/hooks/common/form';
import {useDict} from '@/hooks/business/dict';
import {$t} from '@/locales';
import {fetchGetMuUserSelect, fetchGetUserSelect} from '@/service/api/system';
import {useLoading} from "~/packages/hooks";
defineOptions({
name: 'PrivateEbankNewOperateDrawer'
@ -48,6 +50,7 @@ const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model {
return {
operatorId: '',
userId: '',
custId: '',
custName: '',
@ -62,6 +65,7 @@ function createDefaultModel(): Model {
type RuleKey = Extract<
keyof Model,
| 'operatorId'
| 'userId'
| 'custId'
| 'custName'
@ -70,6 +74,7 @@ type RuleKey = Extract<
>;
const rules: Record<RuleKey, App.Global.FormRule> = {
operatorId: createRequiredRule('经办人员营销号不能为空'),
userId: createRequiredRule('营销人员营销号不能为空'),
custId: createRequiredRule('客户身份证号不能为空'),
custName: createRequiredRule('客户姓名不能为空'),
@ -95,16 +100,16 @@ function closeDrawer() {
async function handleSubmit() {
await validate();
const {dataId, userId, custId, custName, custAcctNo, custPhoneNo, checkFlag, checkTime, checkUser, checkType, checkMsg} = model;
const {dataId, operatorId, userId, custId, custName, custAcctNo, custPhoneNo, checkFlag, checkTime, checkUser, checkType, checkMsg} = model;
// request
if (props.operateType === 'add') {
const {error} = await fetchCreatePrivateEbankNew({userId, custId, custName, custAcctNo, custPhoneNo, checkFlag, checkUser, checkType, checkMsg});
const {error} = await fetchCreatePrivateEbankNew({operatorId, userId, custId, custName, custAcctNo, custPhoneNo, checkFlag, checkUser, checkType, checkMsg});
if (error) return;
}
if (props.operateType === 'edit') {
const {error} = await fetchUpdatePrivateEbankNew({dataId, userId, custId, custName, custAcctNo, custPhoneNo, checkFlag, checkTime, checkUser, checkType, checkMsg});
const {error} = await fetchUpdatePrivateEbankNew({dataId, operatorId, userId, custId, custName, custAcctNo, custPhoneNo, checkFlag, checkTime, checkUser, checkType, checkMsg});
if (error) return;
}
@ -113,6 +118,26 @@ async function handleSubmit() {
emit('submitted');
}
/*------------------------处理营销人员列表start-------------------------------*/
const { startLoading: startUserLoading, endLoading: endUserLoading } = useLoading();
const operatorIdOptions = ref<CommonType.Option<CommonType.IdType>[]>([]);
onMounted(() => {
getoperatorIdOptions()
});
async function getoperatorIdOptions() {
startUserLoading();
const { error, data } = await fetchGetMuUserSelect();
if (!error) {
operatorIdOptions.value = data.map(item => ({
label: item.nickName,
value: item.mktNo
}));
}
endUserLoading();
}
watch(visible, () => {
if (visible.value) {
handleUpdateModelWhenEdit();
@ -222,19 +247,20 @@ watch(visible, () => {
<NDivider class="section-divider">营销信息</NDivider>
<n-grid :cols="24" :x-gap="24" responsive="screen">
<n-grid-item :span="12">
<NFormItem label="营销人员营销号:" path="userId">
<NInput
v-model:value="model.userId"
placeholder="请输入营销人员营销号"
clearable
<NFormItem label="经办人员营销号:" path="operatorId">
<NSelect
v-model:value="model.operatorId"
filterable
placeholder="选择经办人员营销号"
:options="operatorIdOptions"
/>
</NFormItem>
</n-grid-item>
<n-grid-item :span="12">
<NFormItem label="客户身份证号:" path="custId">
<NFormItem label="营销人员营销号:" path="userId">
<NInput
v-model:value="model.custId"
placeholder="请输入客户身份证号"
v-model:value="model.userId"
placeholder="请输入营销人员营销号"
clearable
/>
</NFormItem>
@ -266,6 +292,15 @@ watch(visible, () => {
/>
</NFormItem>
</n-grid-item>
<n-grid-item :span="12">
<NFormItem label="客户身份证号:" path="custId">
<NInput
v-model:value="model.custId"
placeholder="请输入客户身份证号"
clearable
/>
</NFormItem>
</n-grid-item>
</n-grid>
</div>

View File

@ -63,14 +63,20 @@ const { columns, data, getData, getDataByPage, loading, mobilePagination, search
},
{
key: 'userName',
title: '用户名称',
title: '登录名',
align: 'center',
minWidth: 120,
ellipsis: true
},
{
key: 'nickName',
title: '用户昵称',
title: '用户姓名',
align: 'center',
minWidth: 120,
ellipsis: true
},{
key: 'idCard',
title: '身份证号',
align: 'center',
minWidth: 120,
ellipsis: true
@ -149,11 +155,11 @@ watch(visible, () => {
<div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto">
<NForm :model="searchParams" label-placement="left" :label-width="80">
<NGrid responsive="screen" item-responsive>
<NFormItemGi span="24 s:12 m:8" label="用户名称" path="userName" class="pr-24px">
<NInput v-model:value="searchParams.userName" placeholder="请输入用户名称" />
<NFormItemGi span="24 s:12 m:8" label="登录名" path="userName" class="pr-24px">
<NInput v-model:value="searchParams.userName" placeholder="请输入登录名" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:8" label="用户昵称" path="nickName" class="pr-24px">
<NInput v-model:value="searchParams.nickName" placeholder="请输入用户昵称" />
<NFormItemGi span="24 s:12 m:8" label="用户姓名" path="nickName" class="pr-24px">
<NInput v-model:value="searchParams.nickName" placeholder="请输入用户姓名" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:8" label="手机号码" path="phonenumber" class="pr-24px">
<NInput v-model:value="searchParams.phonenumber" placeholder="请输入手机号码" />

View File

@ -1,5 +1,5 @@
<script setup lang="tsx">
import { computed, ref } from 'vue';
import {computed, ref, watch} from 'vue';
import { NButton, NDivider } from 'naive-ui';
import { useBoolean, useLoading } from '@sa/hooks';
import { jsonClone } from '@sa/utils';
@ -65,41 +65,54 @@ const {
key: 'index',
title: $t('common.index'),
align: 'center',
width: 64
width: 64,
resizable: true
},
{
key: 'userName',
title: $t('page.system.user.userName'),
align: 'center',
minWidth: 120,
ellipsis: true
ellipsis: true,
resizable: true
},
{
key: 'nickName',
title: $t('page.system.user.nickName'),
align: 'center',
minWidth: 120,
ellipsis: true
ellipsis: true,
resizable: true
},{
key: 'idCard',
title: $t('page.system.user.idCard'),
align: 'center',
minWidth: 120,
ellipsis: true,
resizable: true
},
{
key: 'deptName',
title: $t('page.system.user.deptName'),
align: 'center',
minWidth: 120,
ellipsis: true
ellipsis: true,
resizable: true
},
{
key: 'phonenumber',
title: $t('page.system.user.phonenumber'),
align: 'center',
minWidth: 120,
ellipsis: true
ellipsis: true,
resizable: true
},
{
key: 'status',
title: $t('page.system.user.status'),
align: 'center',
minWidth: 80,
resizable: true,
render(row) {
return (
<StatusSwitch
@ -115,12 +128,14 @@ const {
key: 'createTime',
title: $t('page.system.user.createTime'),
align: 'center',
minWidth: 120
minWidth: 120,
ellipsis: true,
resizable: true
},
{
key: 'operate',
title: $t('common.operate'),
align: 'center',
fixed: 'right',
width: 150,
render: row => {
if (row.userId === 1) return null;
@ -182,6 +197,34 @@ const {
]
});
const scrollX = ref(0);
//
const calculateTotalWidth = () => {
let totalWidth = 0;
const visibleColumns = columns.value;
for (let i = 0; i < visibleColumns.length; i++) {
const column = visibleColumns[i];
//
// column.width
let width = column.width;
// 使minWidth
if (!width) {
width = column.minWidth || 120;
}
//
width = typeof width === 'string' ? parseInt(width) : width;
totalWidth += width;
}
// 50px
return totalWidth + 50;
};
//
watch(columns, (newColumns) => {
scrollX.value = calculateTotalWidth();
}, {deep: true});
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
useTableOperate(data, getData);
@ -341,7 +384,7 @@ function handleResetSearch() {
:data="data"
size="small"
:flex-height="!appStore.isMobile"
:scroll-x="962"
:scroll-x="scrollX"
:loading="loading"
remote
:row-key="row => row.userId"

View File

@ -4,6 +4,7 @@ import { useLoading } from '@sa/hooks';
import { fetchCreateUser, fetchGetUserInfo, fetchUpdateUser } from '@/service/api/system';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
import {useDict} from "@/hooks/business/dict";
defineOptions({
name: 'UserOperateDrawer'
@ -32,6 +33,8 @@ const visible = defineModel<boolean>('visible', {
default: false
});
const {options: mpsUserCategory} = useDict('mps_user_category');
const { loading, startLoading, endLoading } = useLoading();
const { loading: deptLoading, startLoading: startDeptLoading, endLoading: endDeptLoading } = useLoading();
const { formRef, validate, restoreValidation } = useNaiveForm();
@ -54,6 +57,9 @@ function createDefaultModel(): Model {
deptId: null,
userName: '',
nickName: '',
idCard: '',
userCategory: '1',//
mktNo: '',
email: '',
phonenumber: '',
sex: '0',
@ -65,11 +71,13 @@ function createDefaultModel(): Model {
};
}
type RuleKey = Extract<keyof Model, 'userName' | 'nickName' | 'password' | 'status' | 'phonenumber'>;
type RuleKey = Extract<keyof Model, 'userName' | 'nickName' | 'idCard'| 'mktNo'| 'password' | 'status' | 'phonenumber'>;
const rules: Record<RuleKey, App.Global.FormRule[]> = {
userName: [createRequiredRule($t('page.system.user.form.userName.required'))],
nickName: [createRequiredRule($t('page.system.user.form.nickName.required'))],
idCard: [createRequiredRule($t('page.system.user.form.idCard.required'))],
mktNo: [createRequiredRule($t('page.system.user.form.mktNo.required'))],
password: [{ ...patternRules.pwd, required: props.operateType === 'add' }],
phonenumber: [patternRules.phone],
status: [createRequiredRule($t('page.system.user.form.status.required'))]
@ -108,7 +116,7 @@ function closeDrawer() {
async function handleSubmit() {
await validate();
const { userId, deptId, userName, nickName, email, phonenumber, sex, password, status, roleIds, postIds, remark } =
const { userId, deptId, userName, nickName,idCard,userCategory,mktNo, email, phonenumber, sex, password, status, roleIds, postIds, remark } =
model;
// request
@ -118,6 +126,9 @@ async function handleSubmit() {
userName,
password,
nickName,
idCard,
userCategory,
mktNo,
email,
phonenumber,
sex,
@ -135,6 +146,9 @@ async function handleSubmit() {
deptId,
userName,
nickName,
idCard,
userCategory,
mktNo,
email,
phonenumber,
sex,
@ -167,6 +181,9 @@ watch(visible, () => {
<NFormItem :label="$t('page.system.user.nickName')" path="nickName">
<NInput v-model:value="model.nickName" :placeholder="$t('page.system.user.form.nickName.required')" />
</NFormItem>
<NFormItem :label="$t('page.system.user.idCard')" path="idCard">
<NInput v-model:value="model.idCard" :placeholder="$t('page.system.user.form.idCard.required')" />
</NFormItem>
<NFormItem :label="$t('page.system.user.deptName')" path="deptId">
<NTreeSelect
v-model:value="model.deptId"
@ -179,6 +196,21 @@ watch(visible, () => {
:placeholder="$t('page.system.user.form.deptId.required')"
/>
</NFormItem>
<NFormItem label="用户类别" path="userCategory">
<NSelect
v-model:value="model.userCategory"
placeholder="请选择用户类别"
:options="mpsUserCategory"
filterable
/>
</NFormItem>
<NFormItem v-if="model.userCategory === '0'" label="营销编号" path="mktNo">
<NInput v-model:value="model.mktNo" :placeholder="$t('page.system.user.form.mktNo.required')" />
</NFormItem>
<NFormItem :label="$t('page.system.user.phonenumber')" path="phonenumber">
<NInput v-model:value="model.phonenumber" :placeholder="$t('page.system.user.form.phonenumber.required')" />
</NFormItem>

View File

@ -104,7 +104,7 @@ public class AuthController {
Long userId = LoginHelper.getUserId();
scheduledExecutorService.schedule(() -> {
SseMessageDto dto = new SseMessageDto();
dto.setMessage("欢迎登录RuoYi-Vue-Plus后台管理系统");
dto.setMessage("这是系统消息,预留,按需使用");
dto.setUserIds(List.of(userId));
SseMessageUtils.publishMessage(dto);
}, 5, TimeUnit.SECONDS);

View File

@ -65,6 +65,19 @@ public class SysUserController extends BaseController {
return userService.selectPageUserList(user, pageQuery);
}
/**
* 获取营销用户列表(暂不设置权限)
*
* TODO // 其实与查询列表 list重复,是否需要考虑精简为部门内营销人员
*
* 暂时查询所有营销人员
*/
@GetMapping("/muList")
public R<List<SysUserVo>> muList() {
return R.ok(userService.selectMuUserList());
}
/**
* 导出用户列表
*/
@ -160,11 +173,20 @@ public class SysUserController extends BaseController {
deptService.checkDeptDataScope(user.getDeptId());
if (!userService.checkUserNameUnique(user)) {
return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
} else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
return R.fail("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
} else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
else if (StringUtils.isNotEmpty(user.getIdCard()) && !userService.checkIdCardUnique(user)) {
return R.fail("新增用户'" + user.getUserName() + "'失败,身份证号已存在");
}else if (StringUtils.isNotEmpty(user.getMktNo()) && !userService.checkMktNoUnique(user)) {
return R.fail("新增用户'" + user.getUserName() + "'失败,营销编号已存在");
}
// 不再校验手机号和邮箱
/*else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
} else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}*/
if (TenantHelper.isEnable()) {
if (!tenantService.checkAccountBalance(TenantHelper.getTenantId())) {
return R.fail("当前租户下用户名额不足,请联系管理员");
@ -172,8 +194,6 @@ public class SysUserController extends BaseController {
}
user.setPassword(BCrypt.hashpw(user.getPassword()));
System.out.println("user1:" + JSONUtil.toJsonStr(user));
return toAjax(userService.insertUser(user));
}
@ -189,11 +209,19 @@ public class SysUserController extends BaseController {
deptService.checkDeptDataScope(user.getDeptId());
if (!userService.checkUserNameUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
} else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
}
else if (StringUtils.isNotEmpty(user.getIdCard()) && !userService.checkIdCardUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,身份证号已存在");
}else if (StringUtils.isNotEmpty(user.getMktNo()) && !userService.checkMktNoUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,营销编号已存在");
}
// 不再校验手机号和邮箱
/*else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
} else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
}*/
return toAjax(userService.updateUser(user));
}

View File

@ -33,12 +33,12 @@ public class SysUser extends TenantEntity {
private Long deptId;
/**
* 用户账号
* 登录名
*/
private String userName;
/**
* 用户昵称
* 用户姓名
*/
private String nickName;
@ -47,6 +47,21 @@ public class SysUser extends TenantEntity {
*/
private String userType;
/**
* 用户种类(0-营销 其他-其他)
*/
private String userCategory;
/**
* 营销编号
*/
private String mktNo;
/**
* 身份证号
*/
private String idCard;
/**
* 用户邮箱
*/

View File

@ -45,9 +45,9 @@ public class SysUserBo extends BaseEntity {
/**
* 用户昵称
*/
@Xss(message = "用户昵称不能包含脚本字符")
@NotBlank(message = "用户昵称不能为空")
@Size(min = 0, max = 30, message = "用户昵称长度不能超过{max}个字符")
@Xss(message = "用户姓名不能包含脚本字符")
@NotBlank(message = "用户姓名不能为空")
@Size(min = 0, max = 30, message = "用户姓名长度不能超过{max}个字符")
private String nickName;
/**
@ -55,6 +55,27 @@ public class SysUserBo extends BaseEntity {
*/
private String userType;
/**
* 用户种类(0-营销 其他-其他)
*/
@NotBlank(message = "用户种类不能为空")
private String userCategory;
/**
* 营销编号
*/
@NotBlank(message = "营销编号不能为空")
@Size(min = 9, max = 9, message = "营销编号长度必须为9位")
private String mktNo;
/**
* 身份证号
*/
@Xss(message = "身份证号不能包含脚本字符")
@NotBlank(message = "身份证号不能为空")
@Size(min = 15, max = 18, message = "身份证号长度必须在 15 - 18 之间")
private String idCard;
/**
* 用户邮箱
*/

View File

@ -32,17 +32,23 @@ public class SysUserExportVo implements Serializable {
private Long userId;
/**
* 用户账号
* 登录名
*/
@ExcelProperty(value = "登录名")
@ExcelProperty(value = "登录名")
private String userName;
/**
* 用户昵称
* 用户姓名
*/
@ExcelProperty(value = "用户")
@ExcelProperty(value = "用户")
private String nickName;
/**
* 身份证号
*/
@ExcelProperty(value = "身份证号")
private String idCard;
/**
* 用户邮箱
*/
@ -62,6 +68,19 @@ public class SysUserExportVo implements Serializable {
@ExcelDictFormat(dictType = "sys_user_sex")
private String sex;
/**
* 用户种类(0-营销 其他-其他)
*/
@ExcelProperty(value = "用户种类", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "mps_user_category")
private String userCategory;
/**
* 营销编号
*/
@ExcelProperty(value = "营销编号")
private String mktNo;
/**
* 帐号状态0正常 1停用
*/

View File

@ -36,17 +36,23 @@ public class SysUserImportVo implements Serializable {
private Long deptId;
/**
* 用户账号
* 登录名
*/
@ExcelProperty(value = "登录名")
@ExcelProperty(value = "登录名")
private String userName;
/**
* 用户昵称
* 用户姓名
*/
@ExcelProperty(value = "用户")
@ExcelProperty(value = "用户")
private String nickName;
/**
* 身份证号
*/
@ExcelProperty(value = "身份证号")
private String idCard;
/**
* 用户邮箱
*/
@ -59,6 +65,19 @@ public class SysUserImportVo implements Serializable {
@ExcelProperty(value = "手机号码")
private String phonenumber;
/**
* 用户种类(0-营销 其他-其他)
*/
@ExcelProperty(value = "用户种类", converter = ExcelDictConvert.class)
@ExcelDictFormat(dictType = "mps_user_category")
private String userCategory;
/**
* 营销编号
*/
@ExcelProperty(value = "营销编号")
private String mktNo;
/**
* 用户性别
*/

View File

@ -49,7 +49,7 @@ public class SysUserVo implements Serializable {
private String userName;
/**
* 用户昵称
* 用户姓名
*/
private String nickName;
@ -58,6 +58,21 @@ public class SysUserVo implements Serializable {
*/
private String userType;
/**
* 用户种类(0-营销 其他-其他)
*/
private String userCategory;
/**
* 营销编号
*/
private String mktNo;
/**
* 身份证号
*/
private String idCard;
/**
* 用户邮箱
*/

View File

@ -50,6 +50,11 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
return this.selectVoList(queryWrapper);
}
default List<SysUserVo> selectMuUserList(Wrapper<SysUser> queryWrapper) {
return this.selectVoList(queryWrapper);
}
/**
* 根据条件分页查询用户列表
*

View File

@ -84,6 +84,12 @@ public interface ISysUserService {
*/
List<SysUserVo> selectUserByIds(List<Long> userIds, Long deptId);
/**
* 查询所有营销人员信息
* @return
*/
List<SysUserVo> selectMuUserList();
/**
* 根据用户ID查询用户所属角色组
*
@ -124,6 +130,20 @@ public interface ISysUserService {
*/
boolean checkEmailUnique(SysUserBo user);
/**
* 校验身份证号是否唯一
*
* @param user 用户信息
*/
boolean checkIdCardUnique(SysUserBo user);
/**
* 校验营销编号是否唯一
*
* @param user 用户信息
*/
boolean checkMktNoUnique(SysUserBo user);
/**
* 校验用户是否允许操作
*

View File

@ -91,6 +91,10 @@ public class SysTenantServiceImpl implements ISysTenantService {
@Override
public List<SysTenantVo> queryList(SysTenantBo bo) {
LambdaQueryWrapper<SysTenant> lqw = buildQueryWrapper(bo);
//暂时剔除掉管理组,不作为租户显示
lqw.ne(SysTenant::getTenantId, "000000");
return baseMapper.selectVoList(lqw);
}

View File

@ -62,6 +62,18 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
return TableDataInfo.build(page);
}
/**
* 查询所有营销人员信息
*
*/
@Override
public List<SysUserVo> selectMuUserList() {
return baseMapper.selectMuUserList(new LambdaQueryWrapper<SysUser>()
//.select(SysUser::getUserId, SysUser::getMktNo, SysUser::getNickName)
.select(SysUser::getMktNo, SysUser::getNickName)
.eq(SysUser::getUserCategory, "0"));
}
/**
* 根据条件分页查询用户列表
*
@ -93,6 +105,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
LambdaQueryWrapper<SysUser> wrapper = Wrappers.lambdaQuery();
wrapper.eq(SysUser::getDelFlag, SystemConstants.NORMAL)
.eq(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId())
.eq(ObjectUtil.isNotNull(user.getMktNo()), SysUser::getMktNo, user.getMktNo())
.in(StringUtils.isNotBlank(user.getUserIds()), SysUser::getUserId, StringUtils.splitTo(user.getUserIds(), Convert::toLong))
.like(StringUtils.isNotBlank(user.getUserName()), SysUser::getUserName, user.getUserName())
.like(StringUtils.isNotBlank(user.getNickName()), SysUser::getNickName, user.getNickName())
@ -275,6 +288,32 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
return !exist;
}
/**
* 校验身份证号是否唯一
*
* @param user 用户信息
*/
@Override
public boolean checkIdCardUnique(SysUserBo user) {
boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getIdCard, user.getIdCard())
.ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
return !exist;
}
/**
* 校验营销编号是否唯一
*
* @param user 用户信息
*/
@Override
public boolean checkMktNoUnique(SysUserBo user) {
boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getMktNo, user.getMktNo())
.ne(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId()));
return !exist;
}
/**
* 校验用户是否允许操作
*