优化功能
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
This commit is contained in:
@@ -3,9 +3,13 @@ import { baseRequestClient, requestClient } from '#/api/request';
|
|||||||
export namespace AuthApi {
|
export namespace AuthApi {
|
||||||
/** 登录接口参数 */
|
/** 登录接口参数 */
|
||||||
export interface LoginParams {
|
export interface LoginParams {
|
||||||
|
login_method?: 'phone' | 'login_account' | 'job_number'; // 登录方式类型
|
||||||
password?: string;
|
password?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
|
phone?: string; // 手机号(当login_method为phone时)
|
||||||
code?: string;
|
code?: string;
|
||||||
|
login_account?: string; // 登录账号(当login_method为login_account时)
|
||||||
|
job_number?: string; // 工号(当login_method为job_number时)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 登录接口返回值 */
|
/** 登录接口返回值 */
|
||||||
@@ -17,25 +21,62 @@ export namespace AuthApi {
|
|||||||
data: string;
|
data: string;
|
||||||
status: number;
|
status: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 发送验证码返回结果 */
|
||||||
|
export interface SendCodeResult {
|
||||||
|
code?: string; // 开发环境返回验证码
|
||||||
|
accounts?: Array<{
|
||||||
|
id: number;
|
||||||
|
login_account: string;
|
||||||
|
job_number: string;
|
||||||
|
}>; // 如果手机号绑定多个账号,返回账号列表
|
||||||
|
need_select?: boolean; // 是否需要选择账号/工号
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录
|
* 登录
|
||||||
*/
|
*/
|
||||||
export async function loginApi(data: AuthApi.LoginParams) {
|
export async function loginApi(data: AuthApi.LoginParams) {
|
||||||
return requestClient.post<AuthApi.LoginResult>('login', {
|
const params: any = {
|
||||||
phone: data.username,
|
login_method: data.login_method || 'phone', // 添加默认值
|
||||||
password: data.password,
|
password: data.password,
|
||||||
code: data.code,
|
code: data.code,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (data.login_method === 'phone') {
|
||||||
|
params.phone = data.phone || data.username;
|
||||||
|
} else if (data.login_method === 'login_account') {
|
||||||
|
params.login_account = data.login_account;
|
||||||
|
} else if (data.login_method === 'job_number') {
|
||||||
|
params.job_number = data.job_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestClient.post<AuthApi.LoginResult>('login', params);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 发送验证码
|
* 发送验证码
|
||||||
*/
|
*/
|
||||||
export async function sendVerificationCode(username: string) {
|
export async function sendVerificationCode(params: {
|
||||||
return requestClient.post('send-verification-code', {
|
login_method?: 'phone' | 'login_account' | 'job_number';
|
||||||
username,
|
username?: string;
|
||||||
});
|
phone?: string;
|
||||||
|
login_account?: string;
|
||||||
|
job_number?: string;
|
||||||
|
}) {
|
||||||
|
const requestParams: any = {
|
||||||
|
login_method: params.login_method || 'phone',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.login_method === 'phone') {
|
||||||
|
requestParams.phone = params.phone || params.username;
|
||||||
|
} else if (params.login_method === 'login_account') {
|
||||||
|
requestParams.login_account = params.login_account;
|
||||||
|
} else if (params.login_method === 'job_number') {
|
||||||
|
requestParams.job_number = params.job_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestClient.post<AuthApi.SendCodeResult>('send-verification-code', requestParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { BasicOption } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { computed, markRaw, ref, useTemplateRef } from 'vue';
|
import { computed, ref, useTemplateRef } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationCodeLogin, type VbenFormSchema } from '@vben/common-ui';
|
import { AuthenticationCodeLogin, type VbenFormSchema } from '@vben/common-ui';
|
||||||
import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
|
import { AuthenticationLogin, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import { sendVerificationCode } from '#/api';
|
import { sendVerificationCode } from '#/api';
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
import {message} from "ant-design-vue";
|
import { message } from 'ant-design-vue';
|
||||||
import {useRouter} from "vue-router";
|
|
||||||
|
|
||||||
defineOptions({ name: 'Login' });
|
defineOptions({ name: 'Login' });
|
||||||
|
|
||||||
@@ -20,58 +19,52 @@ const authStore = useAuthStore();
|
|||||||
const CODE_LENGTH = 6;
|
const CODE_LENGTH = 6;
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const MOCK_USER_OPTIONS: BasicOption[] = [
|
const loginMethod = ref<'phone' | 'login_account' | 'job_number'>('phone');
|
||||||
{
|
const needSelectAccount = ref(false);
|
||||||
label: 'Super',
|
const accountList = ref<Array<{ id: number; login_account: string; job_number: string }>>([]);
|
||||||
value: 'vben',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Admin',
|
|
||||||
value: 'admin',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'User',
|
|
||||||
value: 'jack',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
return [
|
const schema: VbenFormSchema[] = [
|
||||||
/* {
|
// 1. Radio选择组(最上面)
|
||||||
component: 'VbenSelect',
|
|
||||||
componentProps: {
|
|
||||||
options: MOCK_USER_OPTIONS,
|
|
||||||
placeholder: $t('authentication.selectAccount'),
|
|
||||||
},
|
|
||||||
fieldName: 'selectAccount',
|
|
||||||
label: $t('authentication.selectAccount'),
|
|
||||||
rules: z
|
|
||||||
.string()
|
|
||||||
.min(1, { message: $t('authentication.selectAccount') })
|
|
||||||
.optional()
|
|
||||||
.default('vben'),
|
|
||||||
},*/
|
|
||||||
{
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
optionType: 'button',
|
||||||
|
options: [
|
||||||
|
{ label: '手机号登录', value: 'phone' },
|
||||||
|
{ label: '登录账号登录', value: 'login_account' },
|
||||||
|
{ label: '工号登录', value: 'job_number' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fieldName: 'login_method',
|
||||||
|
label: '登录方式',
|
||||||
|
rules: z.string().min(1, { message: '请选择登录方式' }),
|
||||||
|
defaultValue: 'phone',
|
||||||
|
dependencies: {
|
||||||
|
trigger(values, form) {
|
||||||
|
loginMethod.value = values.login_method;
|
||||||
|
// 切换登录方式时清空其他字段
|
||||||
|
if (values.login_method === 'phone') {
|
||||||
|
form.setValues({ login_account: '', job_number: '' });
|
||||||
|
} else if (values.login_method === 'login_account') {
|
||||||
|
form.setValues({ username: '', job_number: '' });
|
||||||
|
} else if (values.login_method === 'job_number') {
|
||||||
|
form.setValues({ username: '', login_account: '' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
triggerFields: ['login_method'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 2. 根据选择动态显示输入框
|
||||||
|
const loginMethodValue = loginMethod.value;
|
||||||
|
if (loginMethodValue === 'phone') {
|
||||||
|
schema.push({
|
||||||
component: 'VbenInput',
|
component: 'VbenInput',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: $t('authentication.usernameTip'),
|
placeholder: $t('authentication.usernameTip'),
|
||||||
},
|
},
|
||||||
dependencies: {
|
|
||||||
trigger(values, form) {
|
|
||||||
if (values.selectAccount) {
|
|
||||||
const findUser = MOCK_USER_OPTIONS.find(
|
|
||||||
(item) => item.value === values.selectAccount,
|
|
||||||
);
|
|
||||||
if (findUser) {
|
|
||||||
form.setValues({
|
|
||||||
password: 'qiqi991012',
|
|
||||||
username: '15100000000',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
triggerFields: ['selectAccount'],
|
|
||||||
},
|
|
||||||
fieldName: 'username',
|
fieldName: 'username',
|
||||||
label: $t('authentication.username'),
|
label: $t('authentication.username'),
|
||||||
rules: z
|
rules: z
|
||||||
@@ -80,72 +73,188 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
.refine((v) => /^\d{11}$/.test(v), {
|
.refine((v) => /^\d{11}$/.test(v), {
|
||||||
message: $t('authentication.mobileErrortip'),
|
message: $t('authentication.mobileErrortip'),
|
||||||
}),
|
}),
|
||||||
},
|
});
|
||||||
{
|
} else if (loginMethodValue === 'login_account') {
|
||||||
component: 'VbenInputPassword',
|
schema.push({
|
||||||
|
component: 'VbenInput',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: $t('authentication.password'),
|
placeholder: '请输入登录账号',
|
||||||
},
|
},
|
||||||
fieldName: 'password',
|
fieldName: 'login_account',
|
||||||
label: $t('authentication.password'),
|
label: '登录账号',
|
||||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
rules: z.string().min(1, { message: '请输入登录账号' }),
|
||||||
/* .regex(
|
});
|
||||||
/^(?=.*[A-Z0-9])(?=.*[!@#$%^&*])[A-Z0-9!@#$%^&*]{5,18}$/i,
|
} else if (loginMethodValue === 'job_number') {
|
||||||
'密码由5-18位数字、字母、特殊字符组成。',
|
schema.push({
|
||||||
)*/
|
component: 'VbenInput',
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'VbenPinInput',
|
|
||||||
componentProps: {
|
componentProps: {
|
||||||
codeLength: CODE_LENGTH,
|
placeholder: '请输入工号',
|
||||||
createText: (countdown: number) => {
|
},
|
||||||
return countdown > 0
|
fieldName: 'job_number',
|
||||||
? $t('authentication.sendText', [countdown])
|
label: '工号',
|
||||||
: $t('authentication.sendCode');
|
rules: z.string().min(1, { message: '请输入工号' }),
|
||||||
},
|
});
|
||||||
placeholder: $t('authentication.code'),
|
}
|
||||||
handleSendCode: async () => {
|
|
||||||
// 模拟发送验证码
|
// 3. 密码输入框(常驻)
|
||||||
// Simulate sending verification code
|
schema.push({
|
||||||
loading.value = true;
|
component: 'VbenInputPassword',
|
||||||
const formApi = loginRef.value?.getFormApi();
|
componentProps: {
|
||||||
if (!formApi) {
|
placeholder: $t('authentication.password'),
|
||||||
loading.value = false;
|
},
|
||||||
throw new Error('formApi is not ready');
|
fieldName: 'password',
|
||||||
}
|
label: $t('authentication.password'),
|
||||||
await formApi.validateField('username');
|
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||||
const isPhoneReady = await formApi.isFieldValid('username');
|
});
|
||||||
if (!isPhoneReady) {
|
|
||||||
loading.value = false;
|
// 4. 验证码输入框(常驻)
|
||||||
throw new Error('Phone number is not Ready');
|
schema.push({
|
||||||
}
|
component: 'VbenPinInput',
|
||||||
const password = await formApi.isFieldValid('password');
|
componentProps: {
|
||||||
if (!password) {
|
codeLength: CODE_LENGTH,
|
||||||
loading.value = false;
|
createText: (countdown: number) => {
|
||||||
throw new Error('密码不符合要求 number is not Ready');
|
return countdown > 0
|
||||||
}
|
? $t('authentication.sendText', [countdown])
|
||||||
const { username } = await formApi.getValues();
|
: $t('authentication.sendCode');
|
||||||
// 验证username的手机号格式
|
},
|
||||||
await sendVerificationCode(username);
|
placeholder: $t('authentication.code'),
|
||||||
|
handleSendCode: async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const formApi = loginRef.value?.getFormApi();
|
||||||
|
if (!formApi) {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
message.success('发送成功,请注意查收');
|
throw new Error('formApi is not ready');
|
||||||
},
|
}
|
||||||
|
|
||||||
|
const values = await formApi.getValues();
|
||||||
|
const loginMethodValue = values.login_method || 'phone';
|
||||||
|
|
||||||
|
try {
|
||||||
|
let result;
|
||||||
|
if (loginMethodValue === 'phone') {
|
||||||
|
// 手机号登录:验证手机号
|
||||||
|
await formApi.validateField('username');
|
||||||
|
const isPhoneReady = await formApi.isFieldValid('username');
|
||||||
|
if (!isPhoneReady) {
|
||||||
|
loading.value = false;
|
||||||
|
throw new Error('手机号格式不正确');
|
||||||
|
}
|
||||||
|
const password = await formApi.isFieldValid('password');
|
||||||
|
if (!password) {
|
||||||
|
loading.value = false;
|
||||||
|
throw new Error('密码不符合要求');
|
||||||
|
}
|
||||||
|
result = await sendVerificationCode({
|
||||||
|
login_method: 'phone',
|
||||||
|
username: values.username
|
||||||
|
});
|
||||||
|
} else if (loginMethodValue === 'login_account') {
|
||||||
|
// 登录账号登录:验证登录账号
|
||||||
|
await formApi.validateField('login_account');
|
||||||
|
const isAccountReady = await formApi.isFieldValid('login_account');
|
||||||
|
if (!isAccountReady) {
|
||||||
|
loading.value = false;
|
||||||
|
throw new Error('登录账号不能为空');
|
||||||
|
}
|
||||||
|
const password = await formApi.isFieldValid('password');
|
||||||
|
if (!password) {
|
||||||
|
loading.value = false;
|
||||||
|
throw new Error('密码不符合要求');
|
||||||
|
}
|
||||||
|
result = await sendVerificationCode({
|
||||||
|
login_method: 'login_account',
|
||||||
|
login_account: values.login_account
|
||||||
|
});
|
||||||
|
} else if (loginMethodValue === 'job_number') {
|
||||||
|
// 工号登录:验证工号
|
||||||
|
await formApi.validateField('job_number');
|
||||||
|
const isJobNumberReady = await formApi.isFieldValid('job_number');
|
||||||
|
if (!isJobNumberReady) {
|
||||||
|
loading.value = false;
|
||||||
|
throw new Error('工号不能为空');
|
||||||
|
}
|
||||||
|
const password = await formApi.isFieldValid('password');
|
||||||
|
if (!password) {
|
||||||
|
loading.value = false;
|
||||||
|
throw new Error('密码不符合要求');
|
||||||
|
}
|
||||||
|
result = await sendVerificationCode({
|
||||||
|
login_method: 'job_number',
|
||||||
|
job_number: values.job_number
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
if (result.need_select && result.accounts && result.accounts.length > 1) {
|
||||||
|
needSelectAccount.value = true;
|
||||||
|
accountList.value = result.accounts;
|
||||||
|
message.warning('该手机号绑定了多个账号,请选择登录账号或工号');
|
||||||
|
} else {
|
||||||
|
needSelectAccount.value = false;
|
||||||
|
message.success('发送成功,请注意查收');
|
||||||
|
}
|
||||||
|
// 开发环境显示验证码
|
||||||
|
if (result.code) {
|
||||||
|
message.info(`验证码:${result.code}`, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message || '发送验证码失败');
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
fieldName: 'code',
|
|
||||||
label: $t('authentication.code'),
|
|
||||||
rules: z.string().length(CODE_LENGTH, {
|
|
||||||
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
// {
|
fieldName: 'code',
|
||||||
// component: markRaw(SliderCaptcha),
|
label: $t('authentication.code'),
|
||||||
// fieldName: 'captcha',
|
rules: z.string().length(CODE_LENGTH, {
|
||||||
// rules: z.boolean().refine((value) => value, {
|
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
||||||
// message: $t('authentication.verifyRequiredTip'),
|
}),
|
||||||
// }),
|
});
|
||||||
// },
|
|
||||||
];
|
return schema;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 自定义提交处理
|
||||||
|
async function handleLogin(values: Recordable<any>) {
|
||||||
|
const loginMethodValue = values.login_method || 'phone';
|
||||||
|
|
||||||
|
// 验证必填字段
|
||||||
|
if (loginMethodValue === 'phone') {
|
||||||
|
if (!values.username || !/^\d{11}$/.test(values.username)) {
|
||||||
|
message.error('请输入正确的手机号');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (loginMethodValue === 'login_account') {
|
||||||
|
if (!values.login_account || values.login_account.trim() === '') {
|
||||||
|
message.error('请输入登录账号');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (loginMethodValue === 'job_number') {
|
||||||
|
if (!values.job_number || values.job_number.trim() === '') {
|
||||||
|
message.error('请输入工号');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginData: any = {
|
||||||
|
login_method: loginMethodValue,
|
||||||
|
password: values.password,
|
||||||
|
code: values.code,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据登录方式组装参数
|
||||||
|
if (loginMethodValue === 'phone') {
|
||||||
|
loginData.phone = values.username;
|
||||||
|
} else if (loginMethodValue === 'login_account') {
|
||||||
|
loginData.login_account = values.login_account;
|
||||||
|
} else if (loginMethodValue === 'job_number') {
|
||||||
|
loginData.job_number = values.job_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
await authStore.authLogin(loginData);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -159,6 +268,6 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
:show-register="false"
|
:show-register="false"
|
||||||
:show-remember-me="false"
|
:show-remember-me="false"
|
||||||
:show-third-party-login="false"
|
:show-third-party-login="false"
|
||||||
@submit="authStore.authLogin"
|
@submit="handleLogin"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -358,3 +358,11 @@ export async function addDoctorMyDiseaseApi(disease_id: number) {
|
|||||||
export async function deleteDoctorMyDiseaseApi(id: number) {
|
export async function deleteDoctorMyDiseaseApi(id: number) {
|
||||||
return requestClient.post<any>(`${prefix}delete-my-disease`, { id });
|
return requestClient.post<any>(`${prefix}delete-my-disease`, { id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新医生签名
|
||||||
|
* @param data 签名数据,包含 sign_image 字段
|
||||||
|
*/
|
||||||
|
export async function updateSignatureApi(data: { sign_image: string }) {
|
||||||
|
return requestClient.post<any>(`${prefix}update-signature`, data);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
/**
|
||||||
|
* 签名查看和更新模态框组件
|
||||||
|
*
|
||||||
|
* @description 用于查看和更新医生签名
|
||||||
|
* @author 系统
|
||||||
|
* @date 2024
|
||||||
|
*/
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button, Image, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import type { VbenFormProps } from '#/adapter/form';
|
||||||
|
import { updateSignatureApi } from '../api';
|
||||||
|
|
||||||
|
// ==================== 表单配置 ====================
|
||||||
|
|
||||||
|
const formProps: VbenFormProps = {
|
||||||
|
wrapperClass: 'grid-cols-12',
|
||||||
|
commonConfig: {
|
||||||
|
formItemClass: 'col-span-12',
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layout: 'horizontal',
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: 'Avatar',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请上传签名图片',
|
||||||
|
},
|
||||||
|
fieldName: 'sign_image',
|
||||||
|
formItemClass: 'col-span-12',
|
||||||
|
label: '签名图片',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
showDefaultActions: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm(formProps);
|
||||||
|
|
||||||
|
// ==================== 模态框配置 ====================
|
||||||
|
|
||||||
|
const currentSignature = ref<string>('');
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
fullscreenButton: false,
|
||||||
|
draggable: true,
|
||||||
|
onCancel() {
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
const valid = await formApi.validate();
|
||||||
|
if (valid.valid) {
|
||||||
|
const values = await formApi.getValues();
|
||||||
|
modalApi.setState({ loading: true, confirmLoading: true });
|
||||||
|
|
||||||
|
await updateSignatureApi({ sign_image: values.sign_image });
|
||||||
|
|
||||||
|
message.success('更新签名成功');
|
||||||
|
|
||||||
|
// 调用回调刷新数据
|
||||||
|
const data = modalApi.getData<{ onSuccess?: () => void }>();
|
||||||
|
if (data?.onSuccess) {
|
||||||
|
data.onSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
modalApi.close();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error?.message || '更新签名失败');
|
||||||
|
} finally {
|
||||||
|
modalApi.setState({ loading: false, confirmLoading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onOpenChange(isOpen: boolean) {
|
||||||
|
if (isOpen) {
|
||||||
|
const data = modalApi.getData<{ signature?: string }>();
|
||||||
|
if (data?.signature) {
|
||||||
|
currentSignature.value = data.signature;
|
||||||
|
// 设置表单初始值
|
||||||
|
formApi.setValues({ sign_image: data.signature });
|
||||||
|
} else {
|
||||||
|
currentSignature.value = '';
|
||||||
|
formApi.resetFields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 计算属性 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 签名图片URL
|
||||||
|
* 处理base64格式或URL格式
|
||||||
|
*/
|
||||||
|
const signatureImageUrl = computed(() => {
|
||||||
|
if (!currentSignature.value) return '';
|
||||||
|
|
||||||
|
// 如果是base64格式
|
||||||
|
if (currentSignature.value.startsWith('data:')) {
|
||||||
|
return currentSignature.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是URL格式
|
||||||
|
if (currentSignature.value.startsWith('http://') || currentSignature.value.startsWith('https://')) {
|
||||||
|
return currentSignature.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他情况,尝试作为base64处理
|
||||||
|
return `data:image/jpeg;base64,${currentSignature.value}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否有签名
|
||||||
|
*/
|
||||||
|
const hasSignature = computed(() => {
|
||||||
|
return !!currentSignature.value;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal title="查看和更新签名" class="w-[600px]">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- 当前签名查看区域 -->
|
||||||
|
<div v-if="hasSignature" class="space-y-2">
|
||||||
|
<div class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
当前签名:
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800">
|
||||||
|
<Image
|
||||||
|
:src="signatureImageUrl"
|
||||||
|
alt="医生签名"
|
||||||
|
:preview="true"
|
||||||
|
style="max-height: 160px; max-width: 100%; object-fit: contain;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 无签名提示 -->
|
||||||
|
<div v-else class="rounded-lg border border-dashed border-gray-300 bg-gray-50 p-8 text-center dark:border-gray-600 dark:bg-gray-800">
|
||||||
|
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
暂无签名,请上传签名图片
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 更新签名表单区域 -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
更新签名:
|
||||||
|
</div>
|
||||||
|
<Form />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 自定义样式 */
|
||||||
|
</style>
|
||||||
@@ -15,6 +15,8 @@ import { useUserStore } from '@vben/stores';
|
|||||||
import EditCommonPrescriptionModal from './components/EditCommonPrescriptionModal.vue';
|
import EditCommonPrescriptionModal from './components/EditCommonPrescriptionModal.vue';
|
||||||
// 常用方新增弹窗组件
|
// 常用方新增弹窗组件
|
||||||
import AddCommonPrescriptionModal from './components/AddCommonPrescriptionModal.vue';
|
import AddCommonPrescriptionModal from './components/AddCommonPrescriptionModal.vue';
|
||||||
|
// 签名查看和更新弹窗组件
|
||||||
|
import SignatureModal from './components/SignatureModal.vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -201,6 +203,12 @@ const [AddCommonPrescriptionModals, AddCommonPrescriptionModalApi] =
|
|||||||
connectedComponent: AddCommonPrescriptionModal,
|
connectedComponent: AddCommonPrescriptionModal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ==================== 签名查看和更新弹窗 ====================
|
||||||
|
|
||||||
|
const [SignatureModals, SignatureModalApi] = useVbenModal({
|
||||||
|
connectedComponent: SignatureModal,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开新增常用方弹窗
|
* 打开新增常用方弹窗
|
||||||
*/
|
*/
|
||||||
@@ -296,6 +304,21 @@ const createDoctorOrder = () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开签名查看和更新弹窗
|
||||||
|
*/
|
||||||
|
const handleViewSignature = () => {
|
||||||
|
const signature = data.value?.identity_info?.sign_image || '';
|
||||||
|
SignatureModalApi.setData({
|
||||||
|
signature,
|
||||||
|
onSuccess: () => {
|
||||||
|
// 刷新医生信息
|
||||||
|
getMyInfo();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
SignatureModalApi.open();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -321,11 +344,21 @@ const createDoctorOrder = () => {
|
|||||||
<Card v-if="data && activeTabBar === 1" title="个人资料">
|
<Card v-if="data && activeTabBar === 1" title="个人资料">
|
||||||
<!-- 头部信息 -->
|
<!-- 头部信息 -->
|
||||||
<div class="mb-8 flex flex-col gap-6 md:flex-row md:gap-8">
|
<div class="mb-8 flex flex-col gap-6 md:flex-row md:gap-8">
|
||||||
<img
|
<div class="relative">
|
||||||
:src="data?.avatar"
|
<img
|
||||||
alt="医生头像"
|
:src="data?.avatar"
|
||||||
class="h-24 w-24 rounded-full border-4 border-white object-cover shadow-lg md:h-32 md:w-32"
|
alt="医生头像"
|
||||||
/>
|
class="h-24 w-24 rounded-full border-4 border-white object-cover shadow-lg md:h-32 md:w-32"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
class="absolute bottom-0 right-0"
|
||||||
|
@click="handleViewSignature"
|
||||||
|
>
|
||||||
|
查看签名
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="mb-3 flex items-center gap-4">
|
<div class="mb-3 flex items-center gap-4">
|
||||||
@@ -743,6 +776,8 @@ const createDoctorOrder = () => {
|
|||||||
<EditCommonPrescriptionModals />
|
<EditCommonPrescriptionModals />
|
||||||
<!-- 常用方新增弹窗 -->
|
<!-- 常用方新增弹窗 -->
|
||||||
<AddCommonPrescriptionModals />
|
<AddCommonPrescriptionModals />
|
||||||
|
<!-- 签名查看和更新弹窗 -->
|
||||||
|
<SignatureModals />
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -80,6 +80,14 @@ export async function getQuickMenuApi() {
|
|||||||
return requestClient.get<any>(`${prefix}get-quick-menu`);
|
return requestClient.get<any>(`${prefix}get-quick-menu`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成登录账号
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
export async function generateLoginAccount(id: number) {
|
||||||
|
return requestClient.post<any>(`${prefix}generate-login-account`, { id });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编辑账户绑定银行卡
|
* 编辑账户绑定银行卡
|
||||||
* @param data
|
* @param data
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ interface RowType {
|
|||||||
code: string;
|
code: string;
|
||||||
platform_id: string;
|
platform_id: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
|
login_account: string;
|
||||||
desc: string;
|
desc: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
@@ -44,6 +45,13 @@ export const gridOptions: VxeGridProps<RowType> = {
|
|||||||
{ field: 'platform.name', title: '所属平台' },
|
{ field: 'platform.name', title: '所属平台' },
|
||||||
{ field: 'supplier.name', title: '所属供应商' },
|
{ field: 'supplier.name', title: '所属供应商' },
|
||||||
{ field: 'phone', title: '手机号码' },
|
{ field: 'phone', title: '手机号码' },
|
||||||
|
{
|
||||||
|
field: 'login_account',
|
||||||
|
align: 'left',
|
||||||
|
title: '登录账号',
|
||||||
|
slots: { default: 'login_account' },
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
{ field: 'email', title: '邮箱' },
|
{ field: 'email', title: '邮箱' },
|
||||||
{ field: 'desc', title: '备注' },
|
{ field: 'desc', title: '备注' },
|
||||||
{ field: 'created_at', title: '注册时间' },
|
{ field: 'created_at', title: '注册时间' },
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Button, Image, message } from 'ant-design-vue';
|
|||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { TableAction } from '#/components/table-action';
|
import { TableAction } from '#/components/table-action';
|
||||||
|
|
||||||
import {deleteAdmin, resetPassword} from './api';
|
import { deleteAdmin, resetPassword, generateLoginAccount } from './api';
|
||||||
import FormModalDemo from './components/modal.vue';
|
import FormModalDemo from './components/modal.vue';
|
||||||
import { formOptions } from './config/search';
|
import { formOptions } from './config/search';
|
||||||
import { gridOptions } from './config/table';
|
import { gridOptions } from './config/table';
|
||||||
@@ -69,6 +69,40 @@ const resetPasswordApi = (id: number) => {
|
|||||||
gridApi.reload();
|
gridApi.reload();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generateAccountApi = async (row: any) => {
|
||||||
|
try {
|
||||||
|
const result = await generateLoginAccount(row.id);
|
||||||
|
if (result.login_account) {
|
||||||
|
message.success('生成账号成功');
|
||||||
|
gridApi.reload();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message || '生成账号失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyToClipboard = async (text: string) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
message.success('复制成功');
|
||||||
|
} catch (error) {
|
||||||
|
// 降级方案:使用传统方法
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = text;
|
||||||
|
textArea.style.position = 'fixed';
|
||||||
|
textArea.style.left = '-999999px';
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
message.success('复制成功');
|
||||||
|
} catch (err) {
|
||||||
|
message.error('复制失败');
|
||||||
|
}
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -110,6 +144,16 @@ const resetPasswordApi = (id: number) => {
|
|||||||
<template #avatar="{ row }">
|
<template #avatar="{ row }">
|
||||||
<Image :src="row.avatar" height="30" width="30" />
|
<Image :src="row.avatar" height="30" width="30" />
|
||||||
</template>
|
</template>
|
||||||
|
<template #login_account="{ row }">
|
||||||
|
<span v-if="row.login_account">
|
||||||
|
<Button type="link" size="small" @click="copyToClipboard(row.login_account)">
|
||||||
|
{{ row.login_account }}
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
<Button v-else type="link" size="small" @click="generateAccountApi(row)">
|
||||||
|
生成账号
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
<template #toolbar-tools></template>
|
<template #toolbar-tools></template>
|
||||||
<template #action="{ row }">
|
<template #action="{ row }">
|
||||||
<TableAction
|
<TableAction
|
||||||
|
|||||||
@@ -233,6 +233,7 @@ export const modalFormProps: VbenFormProps = {
|
|||||||
label: '中药采购价比例',
|
label: '中药采购价比例',
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
suffix: () => '%',
|
suffix: () => '%',
|
||||||
|
defaultValue: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'InputNumber',
|
component: 'InputNumber',
|
||||||
@@ -244,6 +245,7 @@ export const modalFormProps: VbenFormProps = {
|
|||||||
label: '中药销售比例',
|
label: '中药销售比例',
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
suffix: () => '%',
|
suffix: () => '%',
|
||||||
|
defaultValue: 100,
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// component: 'VbenInput',
|
// component: 'VbenInput',
|
||||||
|
|||||||
Reference in New Issue
Block a user