fix: 一些基本功能

This commit is contained in:
2025-03-07 08:18:45 +08:00
parent 638462295d
commit 4cdb8351d4
453 changed files with 13336 additions and 24197 deletions

View File

@@ -33,11 +33,9 @@ const formSchema = computed((): VbenFormSchema[] => {
componentProps: {
codeLength: CODE_LENGTH,
createText: (countdown: number) => {
const text =
countdown > 0
? $t('authentication.sendText', [countdown])
: $t('authentication.sendCode');
return text;
return countdown > 0
? $t('authentication.sendText', [countdown])
: $t('authentication.sendCode');
},
placeholder: $t('authentication.code'),
},

View File

@@ -1,18 +1,24 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '@vben/common-ui';
import type { BasicOption } from '@vben/types';
import { computed, markRaw } from 'vue';
import { computed, markRaw, ref, useTemplateRef } from 'vue';
import { AuthenticationCodeLogin, type VbenFormSchema } from '@vben/common-ui';
import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { sendVerificationCode } from '#/api';
import { useAuthStore } from '#/store';
import {message} from "ant-design-vue";
defineOptions({ name: 'Login' });
const loginRef =
useTemplateRef<InstanceType<typeof AuthenticationCodeLogin>>('loginRef');
const authStore = useAuthStore();
const CODE_LENGTH = 6;
const loading = ref(false);
const MOCK_USER_OPTIONS: BasicOption[] = [
{
label: 'Super',
@@ -30,7 +36,7 @@ const MOCK_USER_OPTIONS: BasicOption[] = [
const formSchema = computed((): VbenFormSchema[] => {
return [
{
/* {
component: 'VbenSelect',
componentProps: {
options: MOCK_USER_OPTIONS,
@@ -43,7 +49,7 @@ const formSchema = computed((): VbenFormSchema[] => {
.min(1, { message: $t('authentication.selectAccount') })
.optional()
.default('vben'),
},
},*/
{
component: 'VbenInput',
componentProps: {
@@ -57,8 +63,8 @@ const formSchema = computed((): VbenFormSchema[] => {
);
if (findUser) {
form.setValues({
password: '123456',
username: findUser.value,
password: 'qiqi991012',
username: '15100000000',
});
}
}
@@ -67,7 +73,12 @@ const formSchema = computed((): VbenFormSchema[] => {
},
fieldName: 'username',
label: $t('authentication.username'),
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
rules: z
.string()
.min(1, { message: $t('authentication.mobileTip') })
.refine((v) => /^\d{11}$/.test(v), {
message: $t('authentication.mobileErrortip'),
}),
},
{
component: 'VbenInputPassword',
@@ -77,6 +88,53 @@ const formSchema = computed((): VbenFormSchema[] => {
fieldName: 'password',
label: $t('authentication.password'),
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
/* .regex(
/^(?=.*[A-Z0-9])(?=.*[!@#$%^&*])[A-Z0-9!@#$%^&*]{5,18}$/i,
'密码由5-18位数字、字母、特殊字符组成。',
)*/
},
{
component: 'VbenPinInput',
componentProps: {
codeLength: CODE_LENGTH,
createText: (countdown: number) => {
return countdown > 0
? $t('authentication.sendText', [countdown])
: $t('authentication.sendCode');
},
placeholder: $t('authentication.code'),
handleSendCode: async () => {
// 模拟发送验证码
// Simulate sending verification code
loading.value = true;
const formApi = loginRef.value?.getFormApi();
if (!formApi) {
loading.value = false;
throw new Error('formApi is not ready');
}
await formApi.validateField('username');
const isPhoneReady = await formApi.isFieldValid('username');
if (!isPhoneReady) {
loading.value = false;
throw new Error('Phone number is not Ready');
}
const password = await formApi.isFieldValid('password');
if (!password) {
loading.value = false;
throw new Error('密码不符合要求 number is not Ready');
}
const { username } = await formApi.getValues();
// 验证username的手机号格式
await sendVerificationCode(username);
loading.value = false;
message.success('发送成功,请注意查收');
},
},
fieldName: 'code',
label: $t('authentication.code'),
rules: z.string().length(CODE_LENGTH, {
message: $t('authentication.codeTip', [CODE_LENGTH]),
}),
},
{
component: markRaw(SliderCaptcha),
@@ -91,8 +149,15 @@ const formSchema = computed((): VbenFormSchema[] => {
<template>
<AuthenticationLogin
ref="loginRef"
:form-schema="formSchema"
:loading="authStore.loginLoading"
:show-code-login="false"
:show-forget-password="false"
:show-qrcode-login="false"
:show-register="false"
:show-remember-me="false"
:show-third-party-login="false"
@submit="authStore.authLogin"
/>
</template>

View File

@@ -0,0 +1,49 @@
import { requestClient } from '#/api/request';
const prefix = 'express-companies/';
/**
* 分页查询用户列表
* @param data
*/
export async function getExpressCompaniesList(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 分页查询用户列表
* @param data
*/
export async function getExpressCompaniesOption(data: any) {
return requestClient.get<any>(`${prefix}option`, { params: data });
}
/**
* 获取快递公司详情
* @param id
*/
export async function getExpressCompaniesInfo(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增快递公司
* @param data
*/
export async function createExpressCompanies(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑快递公司
* @param data
*/
export async function updateExpressCompanies(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除快递公司
* @param data
*/
export async function deleteExpressCompanies(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,65 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createExpressCompanies, updateExpressCompanies } from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'FormModelDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value
? updateExpressCompanies
: createExpressCompanies;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}快递公司`" class="w-[30%]">
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,69 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入快递公司昵称',
},
fieldName: 'name',
label: '快递公司名称',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入快递公司编码',
},
fieldName: 'code',
label: '快递公司编码',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入快递公司类型',
},
fieldName: 'type',
label: '快递公司类型',
rules: 'required',
},
// {
// component: 'Avatar',
// fieldName: 'logo',
// label: 'LOGO',
// rules: 'required',
// },
{
component: 'Textarea',
componentProps: {
placeholder: '请输入快递公司介绍',
},
fieldName: 'introduce',
label: '快递公司介绍',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,37 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'VbenInput',
componentProps: {
placeholder: '输入名称',
},
defaultValue: '',
fieldName: 'name',
label: '快递公司名称',
},
// {
// component: 'RangePicker',
// componentProps: {
// format: 'YYYY-MM-DD', // 确保格式与你需要的一致
// },
// // defaultValue: [dayjs().startOf('month'), dayjs()],
// fieldName: 'search_time',
// label: '时间范围',
// },
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,67 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getExpressCompaniesList } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'name', align: 'left', title: '快递公司每次' },
{ field: 'code', title: '快递公司编码' },
{ field: 'type', title: '快递公司类型' },
{ field: 'created_at', title: '上传时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getExpressCompaniesList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,155 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteExpressCompanies } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const deleteApi = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteExpressCompanies({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="快递公司管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
// auth: ['超级快递公司', 'sys:user:save'],
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
// auth: ['超级快递公司', 'sys:user:save'],
popConfirm: {
title: '确定删除吗',
confirm: deleteApi.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px">
批量操作
<Icon icon="ant-design:down-outlined" />
</Button>
</template>
</TableAction>
</template>
<template #logo="{ row }">
<Image :src="row.logo" height="30" width="30" />
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['express-companies', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['express-companies', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: deleteApi.bind(null, row.id),
},
},
]"
:drop-down-actions="[
// {
// label: '编辑',
// type: 'link',
// icon: 'ant-design:delete-outlined',
// size: 'small',
// // auth: ['express-companies', 'sys:role:detail'],
// onClick: showModal.bind(null, row, true),
// },
// {
// label: '删除',
// type: 'link',
// icon: 'ant-design:delete-outlined',
// size: 'small',
// // auth: ['express-companies', 'sys:role:detail'],
// popConfirm: {
// title: '确定删除吗',
// confirm: deleteApi.bind(null, row.id),
// },
// },
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,65 @@
import { requestClient } from '#/api/request';
const prefix = 'order/';
/**
* 分页查询用户列表
* @param data
*/
export async function getOrderList(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 分页查询用户列表
* @param data
*/
export async function getOrderStatusOption(data: any) {
return requestClient.get<any>(`${prefix}status-option`, { params: data });
}
/**
* 获取订单详情
* @param id
*/
export async function getOrderInfo(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增订单
* @param data
*/
export async function createOrder(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑订单
* @param data
*/
export async function updateOrder(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除订单
* @param data
*/
export async function deleteOrder(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}
/**
* 订单发货
* @param data
*/
export async function sendOrder(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}ware-send`, data);
}
/**
* 订单物流追踪
* @param data
*/
export async function expressDetailByOrderId(data: Record<string, any>) {
return requestClient.post<any>(`express-detail/detail-by-order`, data);
}

View File

@@ -0,0 +1,275 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Card, Descriptions, Image, Tag, Timeline } from 'ant-design-vue';
import { expressDetailByOrderId } from '../api';
defineOptions({
name: 'DetailModal',
});
const gridApi = ref();
// 订单信息
const data = ref({});
// 快递信息
const expressDetail = ref({});
// 订单发货方式
const deliveryMethod = ref(-1);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
modalApi.close();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
if (values) {
data.value = values;
deliveryMethod.value = data.value.delivery_method;
getExpressDetail();
}
}
},
});
async function getExpressDetail() {
expressDetail.value = await expressDetailByOrderId({
order_id: data.value.id,
});
}
/**
* 获取物流信息标签颜色
* @param status
*/
function getColor(status: any) {
switch (status) {
case '在途': {
return '';
}
case '揽收': {
return 'orange';
}
case '派件': {
return 'blue';
}
case '签收': {
return 'green';
}
default: {
return '';
}
}
}
</script>
<template>
<Modal class="w-[80%]" title="订单详情">
<div class="flex flex-col gap-4">
<h3 class="mt-4">订单信息</h3>
<Descriptions
:column="{ xxl: 2, xl: 2, lg: 2, md: 1, sm: 1, xs: 1 }"
bordered
class="mt-4"
>
<Descriptions.Item label="订单号">
{{ data.order_no }}
</Descriptions.Item>
<Descriptions.Item label="订单类型">
{{ data.order_type === 1 ? '普通订单' : '其他' }}
</Descriptions.Item>
<Descriptions.Item label="总支付价格">
{{ data.total_pay_price }}
</Descriptions.Item>
<Descriptions.Item label="物品总价">
{{ data.items_price }}
</Descriptions.Item>
<Descriptions.Item label="市场价">
{{ data.market_price }}
</Descriptions.Item>
<Descriptions.Item label="运费">
{{ data.trans_expenses }}
</Descriptions.Item>
<Descriptions.Item label="是否免邮">
{{ data.free_ship === 1 ? '是' : '否' }}
</Descriptions.Item>
<Descriptions.Item label="状态">
{{ data.status === 0 ? '待处理' : '已处理' }}
</Descriptions.Item>
<Descriptions.Item label="配送方式">
{{ deliveryMethod === 0 ? '快递到家' : '药店自提' }}
</Descriptions.Item>
<Descriptions.Item label="患者">{{ data.patient }}</Descriptions.Item>
<Descriptions.Item label="医生">
{{ data.doctor.name }}
</Descriptions.Item>
<Descriptions.Item label="处方来源">
{{ data.store.name }}
</Descriptions.Item>
</Descriptions>
<div class="mt-4">
<h3>{{ deliveryMethod === 0 ? '收货人' : '就诊人' }}信息</h3>
<Descriptions
:column="{ xxl: 2, xl: 2, lg: 2, md: 1, sm: 1, xs: 1 }"
bordered
class="mt-4"
>
<Descriptions.Item
:label="deliveryMethod === 0 ? '收货人' : '就诊人'"
>
<span v-if="deliveryMethod === 0">{{ data.address?.name }}</span>
<span v-else>
{{
data.express_name || data.patient?.name || data.cancel_remark
}}
</span>
</Descriptions.Item>
<Descriptions.Item label="联系电话">
{{ data.address?.mobile || data.patient_mobile }}
</Descriptions.Item>
<Descriptions.Item v-if="deliveryMethod === 0" label="配送地址">
{{
`${data.address?.province || ''} ${data.address?.region || ''} ${data.address?.detail_address || ''}`
}}
</Descriptions.Item>
<Descriptions.Item v-else label="自提地址">
{{ `${data.store?.position || ''} ` }}
</Descriptions.Item>
<Descriptions.Item label="发货备注">
{{ data.cancel_remark || '无' }}
</Descriptions.Item>
</Descriptions>
</div>
<div class="mt-4">
<h3>订单商品</h3>
<Card
v-for="(item, index) in data.product_order_items"
:key="index"
class="mb-4 mt-5"
>
<div class="flex items-center space-x-4">
<div
class="mr-6 h-32 w-32 overflow-hidden rounded-lg bg-gray-200 dark:bg-gray-800"
>
<Image :src="item.drug_image" alt="" height="100%" width="100%" />
</div>
<div>
<h4>{{ item.drug_name }}</h4>
<p>规格: {{ item.drug.specification }}</p>
<p>数量: {{ item.number }}</p>
<p>单价: {{ item.price }} </p>
<p>
厂家:
{{
item.drug
? item.drug.source_info != null
? item.drug.source_info.name
: item.drug.source
: '暂无'
}}
</p>
<!-- <p>总价: {{ item.total_price }} </p>-->
</div>
</div>
</Card>
</div>
<div class="mt-4">
<h3>处方信息</h3>
<Descriptions
:column="{ xxl: 2, xl: 2, lg: 2, md: 1, sm: 1, xs: 1 }"
bordered
class="mt-4"
>
<Descriptions.Item label="处方编号">
{{ data.prescription.content.prescription_no }}
</Descriptions.Item>
<Descriptions.Item label="诊断">
{{ data.prescription.content.clinical_diagnose }}
</Descriptions.Item>
<Descriptions.Item label="医嘱">
{{ data.prescription.content.doctor_order }}
</Descriptions.Item>
<Descriptions.Item label="开药医生">
{{ data.prescription.content.doctor.name }}
</Descriptions.Item>
<Descriptions.Item label="科室">
{{ data.prescription.content.doctor.depart.name }}
</Descriptions.Item>
<Descriptions.Item label="职称">
{{ data.prescription.content.doctor.title.name }}
</Descriptions.Item>
</Descriptions>
</div>
<div v-if="deliveryMethod === 0">
<div class="mt-4">
<h3>物流信息</h3>
<Descriptions
:column="{ xxl: 2, xl: 2, lg: 2, md: 1, sm: 1, xs: 1 }"
bordered
class="mt-4"
>
<Descriptions.Item label="物流公司">
{{ expressDetail.express_company_name }}
</Descriptions.Item>
<Descriptions.Item label="运单号">
{{ expressDetail.express_no }}
</Descriptions.Item>
<Descriptions.Item label="最新状态">
{{ expressDetail.state_txt }}
</Descriptions.Item>
</Descriptions>
</div>
<div class="mt-4">
<h3>物流追踪</h3>
<Timeline class="mt-4">
<Timeline.Item
v-for="(detail, index) in expressDetail.detail"
:key="index"
>
<Tag :color="getColor(detail.status)">
{{ detail.status }}
</Tag>
<p>{{ detail.detail_at }}</p>
<p>{{ detail.detail }}</p>
</Timeline.Item>
</Timeline>
</div>
</div>
</div>
</Modal>
</template>
<style scoped>
.ant-descriptions-item-label {
font-weight: bold;
}
.mt-4 {
margin-top: 1rem;
}
.mb-4 {
margin-bottom: 1rem;
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.space-x-4 > * + * {
margin-left: 1rem;
}
</style>

View File

@@ -0,0 +1,64 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { sendOrder } from '#/views/business/order/product-order/api';
import { modalFormProps } from '#/views/business/order/product-order/config/form';
defineOptions({
name: 'FormModelDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const orderNo = ref('');
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = sendOrder;
submitApi(values)
.then(() => {
message.success('发货成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
orderNo.value = values.order_no;
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal :title="`订单:【 ${orderNo} 】发货`" class="w-[30%]">
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,71 @@
import type { VbenFormProps } from '#/adapter/form';
import { getExpressCompaniesOption } from '#/views/business/express/express-company/api';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'order_id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['oreder_id'],
},
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入快递单号',
},
fieldName: 'express_no',
label: '快递单号',
rules: 'required',
},
{
component: 'ApiSelect',
componentProps: {
allowClear: true,
// filterOption: true,
showSearch: true,
filterOption: (input: string, option: any) => {
// 自定义过滤逻辑,确保可以根据 name 进行搜索
return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.code,
}));
},
api: getExpressCompaniesOption,
placeholder: '请选择',
},
fieldName: 'express_company_code',
formItemClass: 'col-span-6',
label: '快递公司',
rules: 'required',
},
{
component: 'Textarea',
componentProps: {
placeholder: '请输入订单备注',
},
fieldName: 'introduce',
label: '发货备注',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,108 @@
import type { VbenFormProps } from '#/adapter/form';
import dayjs from 'dayjs';
import {getOrderStatusOption} from "#/views/business/order/product-order/api";
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'VbenInput',
componentProps: {
placeholder: '输入订单号',
},
defaultValue: '',
fieldName: 'order_no',
label: '订单号',
},
{
component: 'ApiSelect',
componentProps: {
allowClear: true,
filterOption: true,
// showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
}));
},
api: getOrderStatusOption,
placeholder: '请选择',
},
fieldName: 'status',
label: '订单状态',
},
{
component: 'VbenSelect',
componentProps: {
allowClear: true,
filterOption: true,
showSearch: true,
options: [
{
label: '快递到家',
value: 0,
},
{
label: '诊所自提',
value: 1,
},
],
placeholder: '请选择',
},
fieldName: 'delivery_method',
label: '发货方式',
},
{
component: 'VbenSelect',
componentProps: {
allowClear: true,
filterOption: true,
showSearch: true,
options: [
{
label: '中药',
value: 1,
},
{
label: '西药',
value: 2,
},
{
label: '中成药',
value: 3,
},
{
label: '产品服务包',
value: 5,
},
],
placeholder: '请选择',
},
fieldName: 'prescription_type',
label: '订单类型',
},
{
component: 'RangePicker',
componentProps: {
format: 'YYYY-MM-DD', // 确保格式与你需要的一致
},
defaultValue: [dayjs().startOf('month'), dayjs()],
fieldName: 'search_time',
label: '时间范围',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
// submitOnChange: true,
// submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,103 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getOrderList } from '#/views/business/order/product-order/api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columns: [
// { type: 'checkbox', width: 60 },
{ type: 'expand', width: 80, slots: { content: 'expand-content' } },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'order_no', align: 'left', title: '订单号' },
{
field: 'user.avatarUrl',
title: '下单用户信息',
slots: { default: 'avatar' },
width: 100,
},
{ field: 'store.name', title: '诊所名称' },
{
field: 'address.name',
title: '收货人姓名',
slots: { default: 'address-name' },
},
{
field: 'address.mobile',
title: '收货人联系方式',
slots: { default: 'address-mobile' },
},
{
field: 'delivery_method',
title: '订单类型&邮寄方式&订单状态',
slots: { default: 'delivery-method' },
},
{ field: 'items_price', title: '总价' },
{ field: 'pay_time', title: '支付时间', slots: { default: 'pay-time' } },
{ field: 'created_at', title: '下单时间' },
{
type: 'html',
title: '操作',
align: 'right',
slots: { default: 'action' },
width: 200,
},
],
keepSource: true,
pagerConfig: {},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
// scrollY: {
// enabled: true,
// gt: 0,
// },
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getOrderList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
expandConfig: {
// expandAll: true,
},
showOverflow: false,
};

View File

@@ -0,0 +1,315 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import FormModalDemo from './components/modal.vue';
import DetailModal from './components/detail.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const [Modal, modalApi] = useVbenModal({
connectedComponent: DetailModal,
});
const infoModal = (data = {}) => {
modalApi.setData({
// 表单值
values: data,
gridApi,
});
modalApi.open();
// message.success(`正在开发${JSON.stringify(data)}`);
};
// const showModal = (data = {}, isUpdate = false) => {
// formModalApi.setData({
// // 表单值
// values: data,
// update: isUpdate,
// gridApi,
// });
// formModalApi.open();
// };
const wareSend = (data = {}) => {
formModalApi.setData({
// 表单值
values: {
order_id: data?.id,
order_no: data?.order_no,
},
gridApi,
});
formModalApi.open();
};
const expandAll = () => {
gridApi.grid?.setAllRowExpand(true);
};
const collapseAll = () => {
gridApi.grid?.setAllRowExpand(false);
};
</script>
<template>
<Page auto-content-height title="订单管理">
<FormModal />
<Modal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '展开全部',
type: 'primary',
// auth: ['超级菜单', 'sys:user:save'],
onClick: expandAll.bind(null),
},
{
label: '收起全部',
type: 'primary',
// auth: ['超级菜单', 'sys:user:save'],
onClick: collapseAll.bind(null),
},
]"
:drop-down-actions="[]"
>
<template #more>
<Button style="margin-left: 16px">
批量操作
<Icon icon="ant-design:down-outlined" />
</Button>
</template>
</TableAction>
</template>
<template #avatar="{ row }">
<Image :src="row.user.avatarurl || '/img/user-default-avatar.png'" />
{{ row.user.nickname }}
</template>
<template #address-name="{ row }">
{{
row.address?.name ||
row.express_name ||
row.patient?.name ||
row.cancel_remark
}}
</template>
<template #address-mobile="{ row }">
{{ row.address?.mobile || row.patient_mobile }}
</template>
<template #pay-time="{ row }">
{{ row?.pay_time || '未支付' }}
</template>
<template #delivery-method="{ row }">
<div v-if="row.order_type === 1">
<Tag v-if="row.prescription_type === 1" color="orange">中药订单</Tag>
<Tag v-else-if="row.prescription_type === 2" color="blue">
商品订单西药
</Tag>
<Tag v-else-if="row.prescription_type === 3" color="purple">
商品订单非处方药
</Tag>
<Tag v-else-if="row.prescription_type === 4" color="green">
已退款
</Tag>
<Tag v-else-if="row.prescription_type === 5" color="pink">
产品服务包
</Tag>
</div>
<div class="mt-3">
<Tag v-if="row.delivery_method === 1" color="blue">到店自提</Tag>
<Tag v-else-if="row.delivery_method === 0" color="green">
上门快递
</Tag>
</div>
<div class="mt-3">
<Tag v-if="row.status === 0" color="red">待支付</Tag>
<Tag v-else-if="row.status === 1" color="orange">待发货</Tag>
<Tag v-else-if="row.status === 2" color="blue">待收货</Tag>
<Tag v-else-if="row.status === 3" color="purple">待评价</Tag>
<Tag v-else-if="row.status === 4" color="green">已退款</Tag>
<Tag v-else-if="row.status === 5" color="pink">退款中</Tag>
<Tag v-else-if="row.status === 6" color="cyan">已收货</Tag>
<Tag v-else-if="row.status === 7" color="green">确认收货</Tag>
<Tag v-else-if="row.status === 8" color="#B22222">拒绝退款</Tag>
<Tag v-else-if="row.status === 9" color="gray">已取消</Tag>
</div>
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '详情',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['order', 'sys:role:detail'],
onClick: infoModal.bind(null, row),
},
{
label: '发货',
type: 'link',
icon: 'ri:send-plane-fill',
// auth: ['超级订单', 'sys:user:save'],
onClick: wareSend.bind(null, row),
// popConfirm: {
// title: '确定发货吗?',
// confirm: wareSend.bind(null, row),
// },
},
]"
:drop-down-actions="[
// {
// label: '发货',
// type: 'link',
// icon: 'ri:send-plane-fill',
// // auth: ['超级订单', 'sys:user:save'],
// popConfirm: {
// title: '确定发货吗',
// confirm: wareSend.bind(null, false),
// },
// },
]"
/>
</template>
<template #expand-content="{ row }">
<div
v-if="row.prescription_type === 2 || row.prescription_type === 5"
class="product-list mt-5"
>
<div
v-for="(item, index) in row.product_order_items"
:key="index"
class="box-card mb-6 rounded-lg p-4"
>
<div class="flex items-start">
<div>
<div
class="mr-6 h-32 w-32 overflow-hidden rounded-lg bg-gray-200 dark:bg-gray-800"
>
<Image
:src="item.drug_image"
alt=""
height="100%"
width="100%"
/>
</div>
<div class="ml-3 mt-5 text-sm">
<span class="text-gray-500 dark:text-gray-400">商品编号</span>
X00{{ index + 1 }}
</div>
</div>
<div class="flex-grow space-y-2">
<div class="text-sm">
<span class="text-gray-500 dark:text-gray-400">商品名称:</span>
{{ item.drug_name }}
</div>
<div class="text-sm">
<span class="text-gray-500 dark:text-gray-400">商品规格:</span>
{{ item.drug ? item.drug.specification : '暂无' }}
</div>
<div class="text-sm">
<span class="text-gray-500 dark:text-gray-400">商品数量:</span>
{{ item.number }}
</div>
<div class="text-sm">
<span class="text-gray-500 dark:text-gray-400">商品厂家:</span>
{{ item.drug ? item.drug.source_info != null ? item.drug.source_info.name : item.drug.source : '暂无' }}
</div>
<div class="text-sm">
<span class="text-gray-500 dark:text-gray-400">商品价格:</span>
{{ item.price }}
</div>
</div>
</div>
</div>
</div>
<div v-if="row.prescription_type === 1" class="medication-details mt-5 p-10">
<span class="mb-2 font-medium">药品明细</span>
<ul class="custom-list pl-6">
<li
v-for="(item, index) in row.product_order_items"
:key="index"
class="custom-list-item"
>
{{ item.drug.drug_number }} {{ item.drug_name }} * {{item.number}}
</li>
</ul>
</div>
<div v-if="!row.prescription_type"></div>
<div
class="order-summary mt-6 flex justify-center space-x-4 rounded-lg bg-gray-100 p-4 dark:bg-gray-800"
>
<span class="text-sm">采购价{{ row.market_price || '0.00' }}</span>
<span class="text-sm">快递费{{ row.trans_expenses || '0.00' }}</span>
<span class="text-sm">诊疗费{{ row.treatement_price || '0.00' }}</span>
<span class="text-sm">加工费{{ row.process_price || '0.00' }}</span>
</div>
</template>
</Grid>
</Page>
</template>
<style scoped lang="scss">
.custom-list {
list-style-type: none;
padding-left: 0;
}
.custom-list-item {
background-color: rgba(64, 158, 255, 0.04);
border-radius: 4px;
margin-bottom: 8px;
padding: 8px 12px;
font-size: 14px;
transition: background-color 0.3s;
&:hover {
background-color: rgba(64, 158, 255, 0.1);
}
&::before {
content: '';
display: inline-block;
width: 6px;
height: 6px;
background-color: #409eff;
border-radius: 50%;
margin-right: 8px;
vertical-align: middle;
}
}
</style>

View File

@@ -0,0 +1,49 @@
import { requestClient } from '#/api/request';
const prefix = 'service-pack/';
/**
* 分页查询用户列表
* @param data
*/
export async function getServicePackList(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 分页查询用户列表
* @param data
*/
export async function getServicePackOption(data: any) {
return requestClient.get<any>(`${prefix}option`, { params: data });
}
/**
* 获取产品服务包详情
* @param id
*/
export async function getServicePackInfo(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增产品服务包
* @param data
*/
export async function createServicePack(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑产品服务包
* @param data
*/
export async function updateServicePack(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除产品服务包
* @param data
*/
export async function deleteServicePack(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,75 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { useUserStore } from '@vben/stores';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createServicePack, updateServicePack } from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'FormModelDemo',
});
const userStore = useUserStore();
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value
? updateServicePack
: createServicePack;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
if (userStore?.userInfo?.roles?.user_type === 3 && !isUpdate.value) {
formApi.setValues({
supplier_id: userStore?.userInfo?.supplier_id,
});
}
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}产品服务包`"
class="w-[30%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,243 @@
import type { VbenFormProps } from '#/adapter/form';
import { useUserStore } from '@vben/stores';
import { getSupplierOption } from '#/views/system/supplier/api';
const userStore = useUserStore();
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
{
component: 'VbenInput',
fieldName: 'showSelect',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
{
component: 'Avatar',
fieldName: 'image',
label: '产品图片',
rules: 'required',
formItemClass: 'col-span-6',
},
{
component: 'ApiSelect',
componentProps: {
allowClear: true,
showSearch: true,
filterOption: (input: string, option: any) => {
// 自定义过滤逻辑,确保可以根据 name 进行搜索
return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
}));
},
api: getSupplierOption,
placeholder: '请选择',
},
dependencies: {
disabled() {
return userStore?.userInfo?.roles?.user_type === 3;
},
triggerFields: ['supplier_id'],
},
fieldName: 'supplier_id',
formItemClass: 'col-span-6',
label: '所属供应商',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入服务包昵称',
},
fieldName: 'drug_name',
formItemClass: 'col-span-6',
label: '服务包昵称',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入服务包别名',
},
fieldName: 'drug_alias',
formItemClass: 'col-span-6',
label: '服务包别名',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入服务包编号',
},
fieldName: 'drug_number',
formItemClass: 'col-span-6',
label: '服务包编号',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入功能',
},
fieldName: 'function',
formItemClass: 'col-span-6',
label: '功能',
rules: 'required',
},
// {
// component: 'VbenInput',
// componentProps: {
// placeholder: '请输入规格',
// },
// fieldName: 'specification',
// formItemClass: 'col-span-6',
// label: '规格',
// rules: 'required',
// },
{
component: 'VbenSelect',
componentProps: {
placeholder: '请选择规格',
options: [
{
label: '7天',
value: '7天',
},
{
label: '14天',
value: '14天',
},
{
label: '30天',
value: '30天',
},
{
label: '60天',
value: '60天',
},
{
label: '90天',
value: '90天',
},
{
label: '180天',
value: '180天',
},
],
},
fieldName: 'specification',
formItemClass: 'col-span-6',
label: '规格',
rules: 'required',
},
{
component: 'RadioGroup',
componentProps: {
placeholder: '请选择',
options: [
{
label: '草稿',
value: 1,
},
{
label: '下架',
value: 2,
},
{
label: '上架',
value: 3,
},
],
},
defaultValue: 1,
formItemClass: 'col-span-6',
fieldName: 'status',
label: '商品状态',
rules: 'required',
},
{
component: 'InputNumber',
componentProps: {
placeholder: '请输入平台分成',
},
suffix: () => '%',
defaultValue: 15,
fieldName: 'platform_ledger',
formItemClass: 'col-span-4',
label: '平台分成',
rules: 'required',
},
{
component: 'InputNumber',
componentProps: {
placeholder: '请输入诊所分成',
},
suffix: () => '%',
defaultValue: 35,
fieldName: 'store_ledger',
formItemClass: 'col-span-4',
label: '诊所分成',
rules: 'required',
},
{
component: 'InputNumber',
componentProps: {
placeholder: '请输入供应商分成',
},
suffix: () => '%',
defaultValue: 50,
fieldName: 'supplier_ledger',
formItemClass: 'col-span-4',
label: '供应商分成',
rules: 'required',
},
{
component: 'Textarea',
componentProps: {
placeholder: '请输入服务包介绍',
},
fieldName: 'small_info',
label: '服务包介绍',
rules: 'required',
},
{
component: 'Avatar',
fieldName: 'instruction',
label: '产品说明书',
rules: 'required',
formItemClass: 'col-span-6',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,37 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'VbenInput',
componentProps: {
placeholder: '输入名称',
},
defaultValue: '',
fieldName: 'name',
label: '产品服务包名称',
},
// {
// component: 'RangePicker',
// componentProps: {
// format: 'YYYY-MM-DD', // 确保格式与你需要的一致
// },
// // defaultValue: [dayjs().startOf('month'), dayjs()],
// fieldName: 'search_time',
// label: '时间范围',
// },
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,84 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getServicePackList } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'drug_name', align: 'left', title: '服务包名称' },
{ field: 'pinyin_simple', title: '拼音' },
{ field: 'supplier.name', title: '供应商' },
{
field: 'image',
align: 'left',
title: '产品图片',
slots: { default: 'image' },
width: 130,
},
{
field: 'instruction',
align: 'left',
title: '说明书',
slots: { default: 'instruction' },
width: 130,
},
{ field: 'function', title: '主要功能' },
{ field: 'specification', title: '规格' },
{ field: 'status', title: '状态', slots: { default: 'status' } },
{ field: 'created_at', title: '发布时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getServicePackList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,163 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteServicePack } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const deleteApi = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteServicePack({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="产品服务包管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
// auth: ['超级产品服务包', 'sys:user:save'],
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
// auth: ['超级产品服务包', 'sys:user:save'],
popConfirm: {
title: '确定删除吗',
confirm: deleteApi.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px">
批量操作
<Icon icon="ant-design:down-outlined" />
</Button>
</template>
</TableAction>
</template>
<template #image="{ row }">
<Image :src="row.image" height="30" width="30" />
</template>
<template #instruction="{ row }">
<div class="div" style="width: 120px;height: 120px; overflow: scroll">
<Image :src="row.instruction" height="30" width="30" />
</div>
</template>
<template #status="{ row }">
<Tag :color="row.status_color">{{ row.status_txt }}</Tag>
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['service-pack', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['service-pack', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: deleteApi.bind(null, row.id),
},
},
]"
:drop-down-actions="[
// {
// label: '编辑',
// type: 'link',
// icon: 'ant-design:delete-outlined',
// size: 'small',
// // auth: ['service-pack', 'sys:role:detail'],
// onClick: showModal.bind(null, row, true),
// },
// {
// label: '删除',
// type: 'link',
// icon: 'ant-design:delete-outlined',
// size: 'small',
// // auth: ['service-pack', 'sys:role:detail'],
// popConfirm: {
// title: '确定删除吗',
// confirm: deleteApi.bind(null, row.id),
// },
// },
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -17,6 +17,7 @@ import {
WorkbenchTodo,
WorkbenchTrends,
} from '@vben/common-ui';
import { LogoSvgICON } from '@vben/icons';
import { preferences } from '@vben/preferences';
import { useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';
@@ -30,59 +31,77 @@ const userStore = useUserStore();
// 例如url: /dashboard/workspace
const projectItems: WorkbenchProjectItem[] = [
{
color: '',
color: 'blue',
content: '不要等待机会,而要创造机会。',
date: '2021-04-01',
group: '开源组',
icon: 'carbon:logo-github',
title: 'Github',
url: 'https://github.com',
group: '萧康云医科技',
icon: LogoSvgICON,
title: '萧康后台管理系统-旧版本',
url: 'https://admin.xiaokang88.com',
},
{
color: '#3fb27f',
content: '现在的你决定将来的你。',
color: 'blue',
content: '数据可视化平台。',
date: '2021-04-01',
group: '算法组',
icon: 'ion:logo-vue',
title: 'Vue',
url: 'https://vuejs.org',
},
{
color: '#e18525',
content: '没有什么才能比努力更重要。',
date: '2021-04-01',
group: '上班摸鱼',
icon: 'ion:logo-html5',
title: 'Html5',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
},
{
color: '#bf0c2c',
content: '热情和欲望可以突破一切难关。',
date: '2021-04-01',
group: 'UI',
icon: 'ion:logo-angular',
title: 'Angular',
url: 'https://angular.io',
},
{
color: '#00d8ff',
content: '健康的身体是实现目标的基石。',
date: '2021-04-01',
group: '技术牛',
icon: 'bx:bxl-react',
title: 'React',
url: 'https://reactjs.org',
},
{
color: '#EBD94E',
content: '路是走出来的,而不是空想出来的。',
date: '2021-04-01',
group: '架构组',
icon: 'ion:logo-javascript',
title: 'Js',
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
group: '萧康云医科技',
icon: 'fluent-color:data-bar-vertical-ascending-20',
title: '数据大屏',
url: 'http://dls.xiaokang88.com',
},
// {
// color: '',
// content: '不要等待机会,而要创造机会。',
// date: '2021-04-01',
// group: '开源组',
// icon: 'carbon:logo-github',
// title: 'Github',
// url: 'https://github.com',
// },
// {
// color: '#3fb27f',
// content: '现在的你决定将来的你。',
// date: '2021-04-01',
// group: '算法组',
// icon: 'ion:logo-vue',
// title: 'Vue',
// url: 'https://vuejs.org',
// },
// {
// color: '#e18525',
// content: '没有什么才能比努力更重要。',
// date: '2021-04-01',
// group: '上班摸鱼',
// icon: 'ion:logo-html5',
// title: 'Html5',
// url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
// },
// {
// color: '#bf0c2c',
// content: '热情和欲望可以突破一切难关。',
// date: '2021-04-01',
// group: 'UI',
// icon: 'ion:logo-angular',
// title: 'Angular',
// url: 'https://angular.io',
// },
// {
// color: '#00d8ff',
// content: '健康的身体是实现目标的基石。',
// date: '2021-04-01',
// group: '技术牛',
// icon: 'bx:bxl-react',
// title: 'React',
// url: 'https://reactjs.org',
// },
// {
// color: '#EBD94E',
// content: '路是走出来的,而不是空想出来的。',
// date: '2021-04-01',
// group: '架构组',
// icon: 'ion:logo-javascript',
// title: 'Js',
// url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
// },
];
// 同样,这里的 url 也可以使用以 http 开头的外部链接
@@ -102,26 +121,26 @@ const quickNavItems: WorkbenchQuickNavItem[] = [
{
color: '#e18525',
icon: 'ion:layers-outline',
title: '组件',
url: '/demos/features/icons',
},
{
color: '#3fb27f',
icon: 'ion:settings-outline',
title: '系统管理',
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
title: '管理员管理',
url: '/system/user',
},
// {
// color: '#3fb27f',
// icon: 'ion:settings-outline',
// title: '系统管理',
// url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
// },
{
color: '#4daf1bc9',
icon: 'ion:key-outline',
title: '权限管理',
url: '/demos/access/page-control',
title: '平台管理',
url: '/platform',
},
{
color: '#00d8ff',
icon: 'ion:bar-chart-outline',
title: '图表',
url: '/analytics',
title: '供应商管理',
url: '/supplier',
},
];
@@ -159,59 +178,59 @@ const todoItems = ref<WorkbenchTodoItem[]>([
]);
const trendItems: WorkbenchTrendItem[] = [
{
avatar: 'svg:avatar-1',
content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,
date: '刚刚',
title: '威廉',
},
{
avatar: 'svg:avatar-2',
content: `关注了 <a>威廉</a> `,
date: '1个小时前',
title: '艾文',
},
{
avatar: 'svg:avatar-3',
content: `发布了 <a>个人动态</a> `,
date: '1天前',
title: '克里斯',
},
{
avatar: 'svg:avatar-4',
content: `发表文章 <a>如何编写一个Vite插件</a> `,
date: '2天前',
title: 'Vben',
},
{
avatar: 'svg:avatar-1',
content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>`,
date: '3天前',
title: '皮特',
},
{
avatar: 'svg:avatar-2',
content: `关闭了问题 <a>如何运行项目</a> `,
date: '1周前',
title: '杰克',
},
{
avatar: 'svg:avatar-3',
content: `发布了 <a>个人动态</a> `,
date: '1周前',
title: '威廉',
},
{
avatar: 'svg:avatar-4',
content: `推送了代码到 <a>Github</a>`,
date: '2021-04-01 20:00',
title: '威廉',
},
{
avatar: 'svg:avatar-4',
content: `发表文章 <a>如何编写使用 Admin Vben</a> `,
date: '2021-03-01 20:00',
title: 'Vben',
avatar: 'svg:logo',
content: `萧康云医上线啦!`,
date: '2025-1-7',
title: '萧康云医',
},
// {
// avatar: 'svg:avatar-2',
// content: `关注了 <a>威廉</a> `,
// date: '1个小时前',
// title: '艾文',
// },
// {
// avatar: 'svg:avatar-3',
// content: `发布了 <a>个人动态</a> `,
// date: '1天前',
// title: '克里斯',
// },
// {
// avatar: 'svg:avatar-4',
// content: `发表文章 <a>如何编写一个Vite插件</a> `,
// date: '2天前',
// title: 'Vben',
// },
// {
// avatar: 'svg:avatar-1',
// content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>`,
// date: '3天前',
// title: '皮特',
// },
// {
// avatar: 'svg:avatar-2',
// content: `关闭了问题 <a>如何运行项目</a> `,
// date: '1周前',
// title: '杰克',
// },
// {
// avatar: 'svg:avatar-3',
// content: `发布了 <a>个人动态</a> `,
// date: '1周前',
// title: '威廉',
// },
// {
// avatar: 'svg:avatar-4',
// content: `推送了代码到 <a>Github</a>`,
// date: '2021-04-01 20:00',
// title: '威廉',
// },
// {
// avatar: 'svg:avatar-4',
// content: `发表文章 <a>如何编写使用 Admin Vben</a> `,
// date: '2021-03-01 20:00',
// title: 'Vben',
// },
];
const router = useRouter();
@@ -231,6 +250,20 @@ function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
}
}
// 获取当天的日期和天气
function getTodayDate() {
const today = new Date();
const options: Intl.DateTimeFormatOptions = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
};
return today.toLocaleDateString('zh-CN', options);
}
const todayDate = getTodayDate();
</script>
<template>
@@ -239,9 +272,15 @@ function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
:avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar"
>
<template #title>
早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧
哈喽, {{ userStore.userInfo?.nick_name }} -
{{ userStore.userInfo?.roles?.name }}, 开始您一天的工作吧
<!-- <Button type="primary" v-access:code="['Super Admin']">测我是超级管理员</Button>-->
<!-- <Button type="primary" v-access:code="['Admin']">测我是管理员</Button>-->
</template>
<!-- <template #description> 今日晴20 - 32 </template>-->
<template #description>
{{ todayDate }}
</template>
<template #description> 今日晴20 - 32 </template>
</WorkbenchHeader>
<div class="mt-5 flex flex-col lg:flex-row">
@@ -256,10 +295,10 @@ function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
title="快捷导航"
@click="navTo"
/>
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
<AnalysisChartCard class="mt-5" title="访问来源">
<AnalyticsVisitsSource />
</AnalysisChartCard>
<!-- <WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />-->
<!-- <AnalysisChartCard class="mt-5" title="访问来源">-->
<!-- <AnalyticsVisitsSource />-->
<!-- </AnalysisChartCard>-->
</div>
</div>
</div>

View File

@@ -1,66 +0,0 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { Button, Card, message, notification, Space } from 'ant-design-vue';
type NotificationType = 'error' | 'info' | 'success' | 'warning';
function info() {
message.info('How many roads must a man walk down');
}
function error() {
message.error({
content: 'Once upon a time you dressed so fine',
duration: 2500,
});
}
function warning() {
message.warning('How many roads must a man walk down');
}
function success() {
message.success('Cause you walked hand in hand With another man in my place');
}
function notify(type: NotificationType) {
notification[type]({
duration: 2500,
message: '说点啥呢',
type,
});
}
</script>
<template>
<Page
description="支持多语言,主题功能集成切换等"
title="Ant Design Vue组件使用演示"
>
<Card class="mb-5" title="按钮">
<Space>
<Button>Default</Button>
<Button type="primary"> Primary </Button>
<Button> Info </Button>
<Button danger> Error </Button>
</Space>
</Card>
<Card class="mb-5" title="Message">
<Space>
<Button @click="info"> 信息 </Button>
<Button danger @click="error"> 错误 </Button>
<Button @click="warning"> 警告 </Button>
<Button @click="success"> 成功 </Button>
</Space>
</Card>
<Card class="mb-5" title="Notification">
<Space>
<Button @click="notify('info')"> 信息 </Button>
<Button danger @click="notify('error')"> 错误 </Button>
<Button @click="notify('warning')"> 警告 </Button>
<Button @click="notify('success')"> 成功 </Button>
</Space>
</Card>
</Page>
</template>

View File

@@ -0,0 +1,47 @@
import { requestClient } from '#/api/request';
const prefix = 'doctor-reception/';
/**
* 分页查询用户列表
* @param data
*/
export async function getPatientList(data: any) {
return requestClient.get<any>(`${prefix}patient-list`, { params: data });
}
/**
* 获取患者详情
* @param id
*/
export async function getPatientItem(id: number) {
return requestClient.get<any>(`${prefix}patient-item`, { params: { id } });
}
/**
* 获取商品列表
* @param id
*/
export async function getProductListDoctorReception(data: any) {
return requestClient.get<any>(`${prefix}product-list`, { params: data });
}
/**
* 获取药品使用方式列表
*/
export async function getDrugUseList() {
return requestClient.get<any>(`${prefix}drug-use-list`, {});
}
/**
* 接诊
*/
export async function receptionApi(id) {
return requestClient.post<any>(`${prefix}reception`, {id:id});
}
/**
* 添加西药药方
* @param data
*/
export async function addWestPrescription(data: any) {
return requestClient.post<any>(`${prefix}add-west-prescription`, data);
}

View File

@@ -0,0 +1,292 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button } from 'ant-design-vue';
const item = ref<Record<string, any>>({});
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
if (values) item.value = values;
console.log(item.value, 'sssssssssssssssss');
}
},
});
function handleWindowPrint(ele, fileName) {
// 获取要打印的元素
const printBox = document.querySelector('.print-box');
if (!printBox) {
console.error('找不到具有 "print-box" 类的元素');
return;
}
// 创建一个隐藏的iframe
const iframe = document.createElement('iframe');
iframe.style.position = 'fixed';
iframe.style.right = '0';
iframe.style.bottom = '0';
iframe.style.width = '0';
iframe.style.height = '0';
iframe.style.border = '0';
document.body.append(iframe);
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
// 写入HTML结构
iframeDoc.open();
iframeDoc.write(`
<!DOCTYPE html>
<html>
<head>
<title>${fileName || '文档打印'}</title>
</head>
<body>
${printBox.outerHTML}
</body>
</html>
`);
// 复制原页面的所有样式
const styles = document.querySelectorAll('style, link[rel="stylesheet"]');
styles.forEach((style) => {
if (style.tagName === 'LINK') {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = style.href; // 使用绝对路径
iframeDoc.head.append(link);
} else {
iframeDoc.head.append(style.cloneNode(true));
}
});
iframeDoc.close();
// 加载完成后触发打印
iframe.contentWindow.addEventListener('load', () => {
iframe.contentWindow.print();
// 打印后移除iframe
setTimeout(() => {
iframe.remove();
}, 1000); // 确保打印对话框已弹出
});
}
// 解密方法示例(需要根据实际加密方式实现)
const decrypt = (str: string) => str; // 简单base64解码示例
</script>
<template>
<Modal class="w-[60%]" title="中药处方详情">
<Page>
<Button type="primary" @click="handleWindowPrint('#demo', '处方')">
打印处方
</Button>
<div class="prescription-container print-box">
<!-- 头部信息 -->
<div class="prescription-header">
<div class="header-top">
<span>处方编号: {{ item.prescription_no }}</span>
<div class="prescription-type">普通处方</div>
</div>
<h2 class="clinic-name">{{ item.content?.doctor.name }} 处方笺</h2>
<div class="prescription-date">开具日期: {{ item.created_at }}</div>
</div>
<!-- 患者信息 -->
<div class="patient-info">
<div class="info-row">
<span>姓名: {{ decrypt(item.content?.patient.name) }}</span>
<span>性别: {{ item.content?.patient.sex === 1 ? '男' : '女' }}</span>
<span>年龄: {{ item.content?.patient.age }}</span>
<span>类别: {{ item.content?.category }}</span>
</div>
<div class="info-row">
<span>科室: {{ item.content?.doctor.depart?.name }}</span>
<span>诊断: {{ item.content?.clinical_diagnose }}</span>
</div>
</div>
<!-- 药品列表 -->
<div class="medicine-list">
<div class="rp-title">Rp</div>
<div
v-for="(recipe, index) in item.content?.repice"
:key="index"
class="recipe-item"
>
<!-- <div class="medicine-item" v-for="drug in JSON.parse(recipe.content)" :key="drug?.id">-->
<div class="medicine-item">
<!-- {{JSON.parse(recipe.content)}}-->
<span class="drug-name">{{
JSON.parse(recipe.content).name ||
JSON.parse(recipe.content).drug_name
}}</span>
<span class="drug-quantity">{{ JSON.parse(recipe.content).number
}}{{ JSON.parse(recipe.content).unit?.name }}</span>
<div v-if="JSON.parse(recipe.content).useWay" class="usage-info">
{{ JSON.parse(recipe.content).useWay }}
</div>
<!-- <span class="drug-name">{{ drug?.name || drug?.drug_name }}</span>-->
<!-- <span class="drug-quantity">{{ drug?.number }}{{ drug?.unit?.name }}</span>-->
<!-- <div v-if="drug?.useWay" class="usage-info">-->
<!-- {{ drug.useWay }}-->
<!-- </div>-->
</div>
<!-- <div class="preparation-info">-->
<!-- 煎服方法: 每日{{ recipe.deployment }}{{ recipe.dosage }}-->
<!-- </div>-->
<div class="preparation-info">
使用方法: {{ recipe.instruction }}
</div>
</div>
</div>
<!-- 医嘱及签名 -->
<div class="footer-section">
<div class="medical-advice">
<label>医嘱:</label>
{{ item.content?.doctor_order }}
</div>
<div class="signature-area">
<div class="signature">
<label>开方医生:</label>
{{ item.content?.doctor.name }}
</div>
<div class="signature">
<label>审核药师:</label>
{{ item?.first_view_info?.name || '' }}
</div>
<div class="signature">
<label>调配人:</label>
{{ item.content?.doctor.name }}
</div>
<div class="signature">
<label>核对人:</label>
{{ item?.again_view_info?.name || '' }}
</div>
<div class="signature">
<label>发药人:</label>
{{ item.content?.doctor.name }}
</div>
</div>
<div class="price-info">总价: {{ item.total_pay_price }}</div>
<div class="validity">处方有效期: {{ item.valid_hours }}小时</div>
</div>
</div>
</Page>
</Modal>
</template>
<style scoped>
.prescription-container {
padding: 20px;
font-family: 'SimSun', serif;
}
.prescription-header {
border-bottom: 2px solid #000;
padding-bottom: 15px;
margin-bottom: 20px;
}
.header-top {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
}
.prescription-type {
border: 1px solid #666;
padding: 2px 8px;
border-radius: 4px;
}
.clinic-name {
text-align: center;
font-size: 24px;
margin: 15px 0;
}
.patient-info .info-row {
display: flex;
justify-content: space-between;
margin: 8px 0;
}
.medicine-list {
margin: 20px 0 200px 0;
border-top: 1px solid #ccc;
padding-top: 15px;
}
.rp-title {
font-size: 32px;
font-weight: bold;
margin-bottom: 15px;
}
.recipe-item {
margin-bottom: 25px;
}
.medicine-item {
display: flex;
justify-content: space-between;
margin: 8px 0;
padding: 4px 0;
}
.drug-name {
font-weight: 500;
}
.preparation-info {
color: #666;
margin-top: 12px;
font-size: 0.9em;
}
.footer-section {
position: absolute;
bottom: 0;
margin-top: 30px;
}
.medical-advice {
color: #c00;
margin-bottom: 20px;
}
.signature-area {
display: flex;
justify-content: space-between;
margin-top: 40px;
}
.signature {
min-width: 200px;
}
.price-info {
margin-top: 20px;
position: absolute;
right: 0;
font-weight: bold;
}
.validity {
color: #666;
text-align: center;
margin-top: 25px;
}
</style>

View File

@@ -0,0 +1,549 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import {
ContainerOutlined,
MinusOutlined,
PlusOutlined,
} from '@ant-design/icons-vue';
import {
Badge,
Card,
CardMeta,
Col,
Image,
InputNumber,
InputSearch,
message,
Popover,
Row,
Select,
SelectOption,
} from 'ant-design-vue';
import {
getDrugUseList,
getProductListDoctorReception,
} from '#/views/doctor/doctor-reception/api';
const searchKey = ref('');
const type = ref(1);
const currentDrugs = ref();
const activePatientId = ref(0);
const drugList = ref([]);
const selectList = ref([]);
const drugUseNum = ref([]);
const drugUseFrequency = ref([]);
const drugUseType = ref([]);
const drugUnit = ref([]);
const drugTime = ref([]);
const selectProductId = ref(0);
const previewImage = ref('');
const visible = ref<boolean>(false);
const setVisible = (value, instruction = ''): void => {
visible.value = value;
previewImage.value = instruction;
};
const getCurrentDrugs = () => {
selectList.value = JSON.parse(
localStorage.getItem(`prescriptionData${activePatientId.value}`) || '{}',
);
};
/**
* 获取商品列表
* @param searchKey
*/
function getDrugListByWesternModal(searchKey = '') {
if (searchKey === '' && type.value === 1) {
drugList.value = [];
return;
}
getCurrentDrugs();
getProductListDoctorReception({
store_id: 2,
type: type.value,
name: searchKey,
}).then((res) => {
// 循环res添加select_number = 0
res.forEach((item) => {
const matched = selectList.value.find(
(value) => value.index_id === item.id,
);
item.select_number = matched ? matched.select_number : 0;
item.drug.number = matched ? matched.number : 1;
});
drugList.value = res;
});
}
function getDrugUseListByWesternModal() {
getDrugUseList().then((res) => {
drugUseNum.value = res.drug_use_num;
drugUseFrequency.value = res.drug_use_frequency;
drugUseType.value = res.drug_use_type;
drugUnit.value = res.drug_unit;
drugTime.value = res.drug_time;
});
}
/**
* 添加商品
* @param {object} data - 商品对象
*/
function addProducts(data) {
// 查找是否已存在
const existItem = selectList.value.find((item) => item.index_id === data.id);
// 新建商品对象避免污染原始数据
const newProduct = {
index_id: data.id,
id: data.drug.id,
drug_name: data.drug.drug_name,
number: data.drug.number,
use_num: drugUseNum.value.find(
(item) => item.id === data.drug.frequency_id,
),
use_type: drugUseType.value.find((item) => item.id === data.drug.type_id),
use_frequency: drugUseFrequency.value.find(
(item) => item.id === data.drug.frequency_id,
),
unit: drugUnit.value.find((item) => item.id === data.drug.unit_id),
price: data.price,
time_id: data.drug.time_id,
type_id: data.drug.type_id,
frequency_id: data.drug.frequency_id,
unit_id: data.drug.unit_id,
image: data.drug.image,
instruction: data.drug.instruction,
type: data.drug.type,
};
if (existItem) {
// 增量操作
const newNumber = existItem.select_number + 1;
// 提前验证
if (newNumber > 100) {
message.error(`${existItem.drug_name}数量不能大于100`);
return;
}
// 更新选中列表
selectList.value = selectList.value.map((item) =>
item.index_id === data.id ? { ...item, select_number: newNumber } : item,
);
syncDrugList(data.id, newNumber); // 同步到药品列表
message.success(`${existItem.drug_name}数量已增加至${newNumber}`);
} else {
// 首次添加初始化数量为1
newProduct.select_number = 1;
// 添加前验证
if (newProduct.select_number > 100) {
message.error(`${newProduct.drug_name}数量不能大于100`);
return;
}
selectList.value.push(newProduct);
console.log(selectList.value, 'ssssssssss');
syncDrugList(newProduct.index_id, 1); // 同步到药品列表
message.success(`已将${newProduct.drug_name}添加到清单中!`);
}
}
/**
* 减少商品
* @param {object} data - 商品对象
*/
function propProducts(data) {
const existItem = selectList.value.find((item) => item.index_id === data.id);
if (!existItem) {
message.error(`${data.drug.drug_name}不在清单中!`);
return;
}
const newNumber = existItem.select_number - 1;
// 提前验证
if (newNumber < 0) {
message.error(`${data.drug.drug_name}数量不能小于0`);
return;
}
if (newNumber === 0) {
// 移出清单
selectList.value = selectList.value.filter(
(item) => item.index_id !== data.id,
);
syncDrugList(data.id, 0); // 同步清零
message.success(`${data.drug.drug_name}已从清单移出!`);
} else {
// 更新数量
selectList.value = selectList.value.map((item) =>
item.index_id === data.id ? { ...item, select_number: newNumber } : item,
);
syncDrugList(data.id, newNumber); // 同步更新
message.success(`${data.drug.drug_name}数量已减少至${newNumber}`);
}
}
/**
* 同步更新药品列表
* @param {number} productId - 药品ID
* @param {number} number - 新的数量
*/
function syncDrugList(productId, number) {
drugList.value = drugList.value.map((item) =>
item.id === productId ? { ...item, select_number: number } : item,
);
}
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
localStorage.setItem(
`prescriptionData${activePatientId.value}`,
// JSON.stringify(selectData),
JSON.stringify(selectList.value),
);
currentDrugs.value();
modalApi.close();
message.success('保存成功');
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
// getCurrentDrugs
currentDrugs.value = isOpen ? modalApi.getData()?.getCurrentDrugs : null;
const { values, activePatient_id } =
modalApi.getData<Record<string, any>>();
if (values) {
type.value = values;
activePatientId.value = activePatient_id;
getDrugListByWesternModal();
getDrugUseListByWesternModal();
}
}
},
});
function selectProductChange(id) {
selectProductId.value = id;
}
/**
* 选择频次
* @param id
*/
function selectFrequencyChange(id) {
drugList.value = drugList.value.map((item) =>
// 修改item下面drug的 frequency_id = id
item.id === selectProductId.value
? {
...item,
drug: {
...item.drug,
frequency_id: id,
},
}
: item,
);
selectList.value = selectList.value.map((item) =>
// 修改item下面drug的 frequency_id = id
item.index_id === selectProductId.value
? {
...item,
frequency_id: id,
use_frequency: drugUseFrequency.value.find(
(value) => value.id === id,
),
}
: item,
);
}
/**
* 选择使用时间
* @param id
*/
function selectTimeChange(id) {
drugList.value = drugList.value.map((item) =>
// 修改item下面drug的 frequency_id = id
item.id === selectProductId.value
? {
...item,
drug: {
...item.drug,
time_id: id,
},
}
: item,
);
selectList.value = selectList.value.map((item) =>
// 修改item下面drug的 frequency_id = id
item.index_id === selectProductId.value
? {
...item,
time_id: id,
use_num: drugTime.value.find((value) => value.id === id),
}
: item,
);
}
/**
* 选择使用方法
* @param id
*/
function selectTypeChange(id) {
drugList.value = drugList.value.map((item) =>
// 修改item下面drug的 frequency_id = id
item.id === selectProductId.value
? {
...item,
drug: {
...item.drug,
type_id: id,
},
}
: item,
);
selectList.value = selectList.value.map((item) =>
// 修改item下面drug的 frequency_id = id
item.index_id === selectProductId.value
? {
...item,
type_id: id,
use_type: drugUseType.value.find((value) => value.id === id),
}
: item,
);
}
/**
* 选择单位
* @param id
*/
function selectUnitChange(id) {
drugList.value = drugList.value.map((item) =>
// 修改item下面drug的 frequency_id = id
item.id === selectProductId.value
? {
...item,
drug: {
...item.drug,
unit_id: id,
},
}
: item,
);
selectList.value = selectList.value.map((item) =>
// 修改item下面drug的 frequency_id = id
item.index_id === selectProductId.value
? {
...item,
unit_id: id,
unit: drugUnit.value.find((value) => value.id === id),
}
: item,
);
}
function updateProductNumber(id, number) {
if (number > 100 || number <= 0) {
message.error('数量不能大于100或小于1');
return;
}
drugList.value = drugList.value.map((item) =>
// 修改item下面drug的 frequency_id = id
item.id === id
? {
...item,
drug: {
...item.drug,
number,
},
}
: item,
);
selectList.value = selectList.value.map((item) =>
// 修改item下面drug的 frequency_id = id
item.index_id === id
? {
...item,
number,
}
: item,
);
}
</script>
<template>
<!-- <Modal class="w-[60%]" title="西(中成)药列表">-->
<Modal class="w-[60%]" title="商品列表">
<Image
:preview="{
visible,
onVisibleChange: setVisible,
}"
:src="previewImage"
:style="{ display: 'none' }"
:width="200"
/>
<Page>
<Row :gutter="12">
<Col :span="24">
<InputSearch
:model="searchKey"
enter-button
placeholder="请输入商品名称或拼音首拼"
@search="getDrugListByWesternModal"
/>
</Col>
<Col v-for="item in drugList" :key="item.id" :span="6">
<Badge :count="item.select_number" class="mt-5">
<Card hoverable>
<template #cover>
<Image :height="200" :src="item.drug?.image" />
</template>
<template #actions>
<MinusOutlined key="prop" @click="propProducts(item)" />
<ContainerOutlined
@click="setVisible(true, item.drug.instruction)"
/>
<PlusOutlined key="add" @click="addProducts(item)" />
<!-- <EllipsisOutlined key="ellipsis" />-->
</template>
<CardMeta
:description="item.drug?.pinyin_simple"
:title="item.drug?.drug_name"
>
<template #avatar>
<a-avatar src="https://joeschmoe.io/api/v1/random" />
</template>
</CardMeta>
<div class="card-box mt-5" style="padding: 10px 5px">
<Popover>
<p class="drug-function text-xs">
功效{{ item.drug?.function }}
</p>
<template #content>
<p>功效{{ item.drug?.function }}</p>
<p>用法{{ item.drug?.usage }}</p>
</template>
</Popover>
<p class="text-source mt-5">{{ item.drug?.source }}</p>
<!-- 选择频次-->
<Select
:value="item.drug?.frequency_id"
class="mt-3 w-1/3"
@change="selectFrequencyChange"
@dropdown-visible-change="selectProductChange(item.id)"
>
<SelectOption
v-for="(value, index) in drugUseFrequency"
:key="index"
:value="value.id"
>
{{ value.name }}
</SelectOption>
</Select>
<!-- 选择时间-->
<Select
:value="item.drug?.time_id"
class="mt-3 w-1/3"
@change="selectTimeChange"
@dropdown-visible-change="selectProductChange(item.id)"
>
<SelectOption
v-for="(value, index) in drugTime"
:key="index"
:value="value.id"
>
{{ value.name }}
</SelectOption>
</Select>
<!-- 选择使用方法-->
<Select
:value="item.drug?.type_id"
class="mt-3 w-1/3"
@change="selectTypeChange"
@dropdown-visible-change="selectProductChange(item.id)"
>
<SelectOption
v-for="(value, index) in drugUseType"
:key="index"
:value="value.id"
>
{{ value.name }}
</SelectOption>
</Select>
<div class="mt-3">
<!-- 选择单位-->
<InputNumber :value="item.drug.number" class="w-1/2">
<template #addonBefore>
<MinusOutlined
key="prop"
@click="
updateProductNumber(
item.id,
item.drug.number > 1 ? item.drug.number - 1 : 0,
)
"
/>
</template>
<template #addonAfter>
<PlusOutlined
key="add"
@click="
updateProductNumber(
item.id,
item.drug.number < 100 ? item.drug.number + 1 : 100,
)
"
/>
</template>
</InputNumber>
<Select
:value="item.drug?.unit_id"
class="w-1/2"
@change="selectUnitChange"
@dropdown-visible-change="selectProductChange(item.id)"
>
<SelectOption
v-for="(value, index) in drugUnit"
:key="index"
:value="value.id"
>
{{ value.name }}
</SelectOption>
</Select>
</div>
</div>
</Card>
</Badge>
</Col>
</Row>
</Page>
</Modal>
</template>
<style lang="scss">
.drug-function {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.text-source {
font-size: 0.8em;
}
</style>

View File

@@ -0,0 +1,840 @@
<script setup lang="ts">
import {computed, ref, watch} from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import {
Button,
Descriptions,
Empty,
Image,
ImagePreviewGroup,
InputNumber,
message,
Tag,
Textarea,
Timeline,
TimelineItem,
RadioGroup,
RadioButton,
} from 'ant-design-vue';
import { getRegisterStatus } from '#/util/tool';
import {
addWestPrescription,
getPatientItem,
getPatientList,
receptionApi,
} from '#/views/doctor/doctor-reception/api';
import PrescrtionDetail from './components/PrescrtionDetail.vue';
import WesternModal from './components/WesternModal.vue';
interface Patient {
id: number;
name: string;
mobile: string;
status: number;
}
interface UserPatientHealthInquiry {
id: number;
user_patient_id: number;
liver_function: number;
renal_function: number;
liver_index: string;
renal_index: string;
person_history: string;
allergic_history: string;
person_status: number;
allergic_status: number;
family_status: number;
family_history: number;
is_delete: number;
}
const tabType = ref(0);
const category = ref(0);
const receptionStatus = ref(0);
const updateTabType = ref(false);
// 患者数据
const patients = ref<Patient[]>([]);
/**
* 获取患者列表
*/
function getPatientListByReception() {
getPatientList({}).then((res) => {
patients.value = res;
if (updateTabType.value === false) {
const activePatientId = localStorage.getItem(`doctorReception-id`);
if (activePatientId !== null) {
// 从patients.value中找到当前选中的id并且触发selectPatient方法
patients.value.forEach((patient) => {
if (patient.id === Number.parseInt(activePatientId)) {
selectPatient(patient, false);
updateTabType.value = true;
}
});
}
}
});
}
getPatientListByReception();
// 每5秒更新数据
setInterval(getPatientListByReception, 30_000);
// 药品分类
const categories = [
{ label: '中药', value: 1 },
{ label: '中成(西)药', value: 2 },
// { label: '西药', value: 2 },
{ label: '保健商品', value: 3 },
// { label: '中成药商品', value: 4 },
{ label: '服务包', value: 5 },
];
// 当前状态
const activePatient = ref<null | Patient>(null);
const patientInfo = ref<null | Patient>(null);
const userPatientHealthInquiry = ref<null | UserPatientHealthInquiry>(null);
const activeCategory = ref(2);
const diagnosis = ref('');
const medicalAdvice = ref('');
const treatmentPrice = ref(0);
const selectPatientId = ref(0);
const prescriptionList = ref([]);
const totalCost = computed(() => {
if (currentDrugs.value.length === 0) {
return 0;
}
return currentDrugs.value.reduce(
(sum, drug) => sum + drug.price * (drug.select_number || 1),
0,
);
});
const visible = ref<boolean>(false);
const previewImage = ref([]);
/**
* 说明书预览
* @param value
* @param instruction
*/
const setVisible = (value, instruction = ''): void => {
previewImage.value = [];
if (instruction == '') {
message.error('该产品没有说明书!');
} else {
if (typeof instruction === 'string') {
// 如果有逗号,则进行分割 没有则追加到previewImage
console.log(instruction, value, 'sssssss');
if (instruction.includes(',')) {
instruction = instruction.split(',');
previewImage.value = instruction;
} else {
previewImage.value.push(instruction);
}
}
visible.value = value;
}
};
/**
* 选择华女这操作
* @param patient
* @param isUpdateTabType
*/
const selectPatient = (patient: Patient, isUpdateTabType = true) => {
selectPatientId.value = patient.id;
receptionStatus.value = patient.status;
if (isUpdateTabType === true) {
tabType.value = 1;
} else {
// 获取doctorReception-type并赋值
if (localStorage.getItem(`doctorReception-type`) !== null) {
tabType.value = Number.parseInt(
localStorage.getItem(`doctorReception-type`),
);
}
}
activePatient.value = patient.user_patient;
getPatientItem(patient.id).then((value) => {
patientInfo.value = value;
userPatientHealthInquiry.value = value.user_patient_health_inquiry;
prescriptionList.value = value.prescription;
});
localStorage.setItem(
`doctorReception-id`,
// JSON.stringify(selectData),
JSON.stringify(patient.id),
);
getCurrentDrugs();
};
// 数量增加
const increment = (index: number) => {
currentDrugs.value[index].select_number++;
saveToLocalStorage();
};
// 数量减少
const decrement = (index: number) => {
if (currentDrugs.value[index].select_number > 1) {
currentDrugs.value[index].select_number--;
saveToLocalStorage();
}
};
// 保存到本地存储
const saveToLocalStorage = () => {
localStorage.setItem(
`prescriptionData${activePatient.value?.id}`,
JSON.stringify(currentDrugs.value),
);
};
/**
* 删除药品(本地)
* @param index
*/
const removeDrug = (index: number) => {
currentDrugs.value.splice(index, 1);
updateLocalStorage();
};
/**
* 修改缓存的处方信息
*/
const updateLocalStorage = () => {
localStorage.setItem(
`prescriptionData${activePatient.value?.id}`,
JSON.stringify(currentDrugs.value),
);
getCurrentDrugs();
};
const currentDrugs = ref([]);
/**
* 获取缓存的处方信息
*/
const getCurrentDrugs = () => {
currentDrugs.value = JSON.parse(
localStorage.getItem(`prescriptionData${activePatient.value?.id}`) || '[]',
);
};
getCurrentDrugs();
/**
* 发送处方
*/
const sendPrescription = () => {
if (diagnosis.value === '') {
message.error('诊断结果不能为空');
return;
}
if (medicalAdvice.value === '') {
message.error('医嘱不能为空');
return;
}
// 发送处方逻辑
console.log('处方已发送', {
patient: activePatient.value,
drugs: currentDrugs.value,
diagnosis: diagnosis.value,
medicalAdvice: medicalAdvice.value,
total: totalCost.value,
});
addWestPrescription({
patient: activePatient.value,
drugs: currentDrugs.value,
diagnosis: diagnosis.value,
medicalAdvice: medicalAdvice.value,
total: totalCost.value,
category: category.value,
drug_type: tabType.value,
register_id: Number.parseInt(localStorage.getItem(`doctorReception-id`)),
treatment_price: treatmentPrice.value,
}).then(() => {
message.success('处方已发送');
// 清空当前数据
// currentDrugs.value = [];
// diagnosis.value = '';
// medicalAdvice.value = '';
// updateLocalStorage();
});
};
// 进入处方界面
const showPrescription = () => {
// 打开处方模态框逻辑
tabType.value = 2;
};
// 监听tabType.value变化
watch(
() => tabType.value,
(newValue) => {
if (newValue === 1) {
getPatientItem(selectPatientId.value).then((value) => {
patientInfo.value = value;
userPatientHealthInquiry.value = value.user_patient_health_inquiry;
prescriptionList.value = value.prescription;
});
}
localStorage.setItem(
`doctorReception-type`,
// JSON.stringify(selectData),
JSON.stringify(newValue),
);
},
);
const [WesternDrugModal, WesternDrugModalApi] = useVbenModal({
connectedComponent: WesternModal,
});
const [PrescrtionDetailModal, PrescrtionDetailModalApi] = useVbenModal({
connectedComponent: PrescrtionDetail,
});
const openWesternModal = () => {
// 打开西药处方模态框逻辑
WesternDrugModalApi.setData({
values: activeCategory.value,
activePatient_id: activePatient.value?.id,
getCurrentDrugs,
});
WesternDrugModalApi.open();
};
const openPrescriptionDetail = (values) => {
// 打开西药处方模态框逻辑
PrescrtionDetailModalApi.setData({
values,
});
PrescrtionDetailModalApi.open();
};
// 根据创制string来把字符串的逗号分开变成数组
function splitString(str: string) {
if (!str) {
return [];
}
return str.split(',');
}
/**
* 切换tab
* @param id
*/
function tabChange(id) {
localStorage.setItem(
`prescriptionData${activePatient.value?.id}`,
JSON.stringify([]),
);
currentDrugs.value = [];
activeCategory.value = id;
}
function reception() {
receptionApi(localStorage.getItem(`doctorReception-id`)).then((value) => {
message.success('接诊成功');
updateTabType.value = false;
getPatientListByReception();
receptionStatus.value = 2;
});
}
</script>
<template>
<div class="container-box">
<!-- 左侧患者列表 -->
<div class="patient-panel">
<div
v-for="patient in patients"
:key="patient.user_patient.id"
:class="{ active: selectPatientId === patient.id }"
class="patient-card"
@click="selectPatient(patient)"
>
<div class="patient-info">
<h3>{{ patient.user_patient.name }}</h3>
<p class="phone">{{ patient.user_patient.mobile }}</p>
<span :class="getRegisterStatus(patient.status)" class="status">{{
getRegisterStatus(patient.status)
}}</span>
</div>
</div>
</div>
<!-- 右侧处方区域 -->
<Page v-if="tabType === 1" class="prescription-panel">
<div class="prescription-header">
<Button v-if="receptionStatus === 1" class="send-btn" type="primary" @click="reception">
接诊
</Button>
<Button v-if="receptionStatus === 2" class="send-btn" type="primary" @click="showPrescription">
开具处方
</Button>
</div>
<div class="prescription-content">
<Descriptions
:column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 2, xs: 2 }"
bordered
title="患者信息"
>
<Descriptions.Item label="患者姓名">
{{ activePatient.name }}
</Descriptions.Item>
<Descriptions.Item label="患者年龄">
{{ activePatient.age }}
</Descriptions.Item>
<Descriptions.Item label="患者性别">
{{
activePatient.sex === 1
? '男'
: activePatient.sex === 2
? '女'
: '未填写'
}}
</Descriptions.Item>
</Descriptions>
<Descriptions
v-if="userPatientHealthInquiry"
:column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 2, xs: 2 }"
bordered
class="mt-5"
title="健康信息"
>
<Descriptions.Item label="肝功能">
<span>{{
userPatientHealthInquiry.liver_function === 0 ? '正常' : '异常'
}}</span>
<p
v-if="userPatientHealthInquiry.liver_function === 1"
class="mt-3"
>
<Tag
v-for="item in splitString(
userPatientHealthInquiry.liver_index,
)"
color="#455cda"
>
{{ item }}
</Tag>
</p>
</Descriptions.Item>
<Descriptions.Item label="肾功能">
<span>{{
userPatientHealthInquiry.renal_function === 0 ? '正常' : '异常'
}}</span>
<p
v-if="userPatientHealthInquiry.renal_function === 1"
class="mt-3"
>
<Tag
v-for="item in splitString(
userPatientHealthInquiry.renal_index,
)"
color="#455cda"
>
{{ item }}
</Tag>
</p>
</Descriptions.Item>
<Descriptions.Item label="既往史">
<span>{{
userPatientHealthInquiry.person_status === 0 ? '无' : '有'
}}</span>
<p v-if="userPatientHealthInquiry.person_status === 1" class="mt-3">
<Tag
v-for="item in splitString(
userPatientHealthInquiry.person_history,
)"
color="#455cda"
>
{{ item }}
</Tag>
</p>
</Descriptions.Item>
<Descriptions.Item label="过敏史">
<span>{{
userPatientHealthInquiry.allergic_status === 0 ? '无' : '有'
}}</span>
<p
v-if="userPatientHealthInquiry.allergic_status === 1"
class="mt-3"
>
<Tag
v-for="item in splitString(
userPatientHealthInquiry.allergic_history,
)"
color="#455cda"
>
{{ item }}
</Tag>
</p>
</Descriptions.Item>
<Descriptions.Item label="家庭遗传史">
<span>{{
userPatientHealthInquiry.family_status === 0 ? '无' : '有'
}}</span>
<p v-if="userPatientHealthInquiry.family_status === 1" class="mt-3">
<Tag
v-for="item in splitString(
userPatientHealthInquiry.family_history,
)"
color="#455cda"
>
{{ item }}
</Tag>
</p>
</Descriptions.Item>
</Descriptions>
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"
class="mt-5"
title="处方记录"
>
<Descriptions.Item>
<Timeline>
<TimelineItem v-for="item in prescriptionList" :key="item.id">
<Tag color="#455cda">{{ item.prescription_no }}</Tag>
<Button type="link" @click="openPrescriptionDetail(item)">
查看处方
</Button>
<span class="time-line-item-created">{{
item.created_at
}}</span>
</TimelineItem>
</Timeline>
</Descriptions.Item>
</Descriptions>
</div>
</Page>
<Page v-if="tabType === 2" class="prescription-panel">
<Button class="mb-10" type="primary" @click="tabType = 1">
返回上一页
</Button>
<div class="prescription-header">
<h2>{{ activePatient.name }} 的处方</h2>
<Button class="send-btn" type="primary" @click="sendPrescription">
发送处方
</Button>
</div>
<!-- 药品分类导航 -->
<div class="drug-categories">
<button
v-for="category in categories"
:key="category.value"
:class="{ active: activeCategory === category.value }"
@click="tabChange(category.value)"
>
{{ category.label }}
</button>
</div>
<div class="mt-5">
<Button type="primary" @click="openWesternModal"> 添加药品 </Button>
<RadioGroup v-model:value="category">
<RadioButton value="1">自费</RadioButton>
<RadioButton value="2">医保</RadioButton>
</RadioGroup>
</div>
<div style="width: 0; height: 0; overflow: hidden">
<ImagePreviewGroup
:preview="{
visible,
onVisibleChange: setVisible,
}"
:style="{ display: 'none' }"
>
<Image
v-for="image in previewImage"
v-if="visible === true"
:src="image"
:width="200"
/>
</ImagePreviewGroup>
</div>
<!-- 已选药品列表 -->
<div class="selected-drugs">
<div class="drug-table">
<div class="table-header">
<span>商品图片</span>
<span>商品名称</span>
<span>数量</span>
<span>单价</span>
<span>操作</span>
</div>
<div class="table-body">
<div
v-for="(drug, index) in currentDrugs"
:key="drug.id"
class="table-row"
>
<div style="width: 120px; margin: 0 auto;">
<Image :src="drug.image" />
</div>
<div>
<p>
<span>药品名称:{{ drug.drug_name }}</span>
</p>
<p>
<span
>用法:{{
`${drug.use_type?.name}${drug.use_num?.name}${drug.use_type?.name},每次${drug.number}${drug.unit?.name}`
}}</span>
</p>
</div>
<div class="quantity-control">
<Button type="primary" @click="decrement(index)">-</Button>
<span style="margin: 0 20px">{{ drug.select_number }}</span>
<Button type="primary" @click="increment(index)">+</Button>
</div>
<span>{{ drug.price }}</span>
<div>
<Button type="link" @click="removeDrug(index)">删除</Button>
<Button
v-if="drug.instruction !== ''"
type="link"
@click="setVisible(true, drug.instruction)"
>
查看说明书
</Button>
</div>
</div>
</div>
</div>
</div>
<!-- 诊断和医嘱 -->
<div class="diagnosis-area">
<Textarea
v-model:value="diagnosis"
placeholder="输入诊断结果..."
/>
<Textarea
v-model:value="medicalAdvice"
placeholder="输入医嘱..."
/>
<InputNumber
v-model:value="treatmentPrice"
placeholder="请输入诊疗价格"
>
<template #addonAfter> /元 </template>
</InputNumber>
</div>
<!-- 费用总计 -->
<div class="total-cost">总计:¥{{ totalCost.toFixed(2) }}</div>
</Page>
<div v-else-if="tabType === 0" class="prescription-panel">
<Empty />
</div>
<WesternDrugModal />
<PrescrtionDetailModal />
</div>
</template>
<style scoped>
.container-box {
display: grid;
grid-template-columns: 300px 1fr;
min-height: 90vh;
}
.patient-panel {
border-right: 1px solid #e4e7ed;
overflow-y: auto;
}
.dark .patient-panel {
border-right: 1px solid #333;
}
.patient-card {
padding: 1rem;
border-bottom: 1px solid #eee;
cursor: pointer;
transition: all 0.3s;
}
.dark .patient-card {
border-bottom: 1px solid #333;
}
.patient-card.active {
border-left: 4px solid #455cda;
}
.status {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.8em;
}
.status.待支付 {
background: #f4f4f5;
color: #909399;
}
.status.待接诊 {
background: #fef0f0;
color: #f56c6c;
}
.status.接诊中 {
background: #f0f9eb;
color: #67c23a;
}
.status.已结束 {
background: #f4f4f5;
color: #909399;
}
.status.已取消 {
background: #fef0f0;
color: #f56c6c;
}
.status.待评价 {
background: #f4f4f5;
color: #909399;
}
.status.已评价 {
background: #f4f4f5;
color: #909399;
}
.status.已拒诊 {
background: #f4f4f5;
color: #909399;
}
.dark .status.待接诊,
.dark .status.已接诊 {
background: #333;
}
.prescription-panel {
padding: 1rem;
}
.drug-categories {
display: flex;
gap: 1rem;
margin: 1rem 0;
}
.drug-categories button {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 10px;
cursor: pointer;
}
.dark .drug-categories button {
border: 1px solid #333;
}
.drug-categories button.active {
color: #fff;
border-color: transparent;
animation: pulse 0.5s /*infinite*/;
animation-fill-mode: forwards;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
background: #455cda;
transform: scale(1);
}
}
.drug-item {
display: flex;
justify-content: space-between;
padding: 12px;
margin: 8px 0;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.drug-item:hover {
transform: translateX(4px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.selected-drugs {
margin: 1rem 0;
border: 1px solid #eee;
border-radius: 8px;
}
.dark .selected-drugs {
border: 1px solid #333;
}
.table-body {
max-height: 30vh;
overflow-y: auto;
}
.table-header {
display: grid;
grid-template-columns: 0.5fr 1fr 1fr 1fr 1fr;
padding: 12px;
font-weight: bold;
text-align: center;
}
.table-row {
display: grid;
grid-template-columns: 0.5fr 1fr 1fr 1fr 1fr;
padding: 12px;
border-bottom: 1px solid #eee;
text-align: center;
height: auto;
align-items: center;
}
.dark .table-row {
border-bottom: 1px solid #333;
}
.send-btn {
cursor: pointer;
float: right;
}
.diagnosis-area textarea {
width: 100%;
height: 100px;
padding: 8px;
margin: 8px 0;
border: 1px solid #ddd;
border-radius: 4px;
}
.dark .diagnosis-area textarea {
border: 1px solid #333;
}
.total-cost {
text-align: right;
font-size: 1.2em;
font-weight: bold;
color: #f56c6c;
margin-top: 1rem;
}
.prescription-content {
margin-top: 3%;
}
.time-line-item-created {
color: #909399;
font-size: 0.8em;
}
</style>

View File

@@ -0,0 +1,61 @@
import { requestClient } from '#/api/request';
const prefix = 'withdrawal-application/';
/**
* 分页查询用户列表
* @param data
*/
export async function getWithdrawalApplicationList(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取提现记录详情
* @param id
*/
export async function getWithdrawalApplicationInfo(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增提现记录
* @param data
*/
export async function createWithdrawalApplicationWithdrawal(
data: Record<string, any>,
) {
return requestClient.post<any>(`${prefix}withdrawal`, data);
}
/**
* 新增提现记录
* @param data
*/
export async function createWithdrawalApplication(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑提现记录
* @param data
*/
export async function updateWithdrawalApplication(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除提现记录
* @param data
*/
export async function deleteWithdrawalApplication(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}
/**
* 删除提现记录
* @param id
*/
export async function resetPassword(id: number) {
return requestClient.post<any>(`${prefix}reset-password`, {
id,
});
}

View File

@@ -0,0 +1,51 @@
import { requestClient } from '#/api/request';
const prefix = 'settlement/';
/**
* 分页查询用户列表
* @param data
*/
export async function getSettlementList(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 获取管理员详情
* @param id
*/
export async function getSettlementInfo(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增管理员
* @param data
*/
export async function createSettlement(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑管理员
* @param data
*/
export async function updateSettlement(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除管理员
* @param data
*/
export async function deleteSettlement(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}
/**
* 删除管理员
* @param id
*/
export async function resetPassword(id: number) {
return requestClient.post<any>(`${prefix}reset-password`, {
id,
});
}

View File

@@ -0,0 +1,63 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { saveCard } from '#/views/system/admin/api';
import { modalCardFormProps } from '../config/form';
const gridApi = ref();
const showCard = ref();
const maxAmount = ref(0);
const [Form, formApi] = useVbenForm(modalCardFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = saveCard;
submitApi(values)
.then(() => {
message.success('保存成功');
// gridApi.value?.reload();
showCard.value();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
showCard.value = isOpen ? modalApi.getData()?.showCard : null;
if (isOpen) {
const { values, balance } = modalApi.getData<Record<string, any>>();
if (values) {
maxAmount.value = balance;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal class="w-[30%]" title="编辑银行卡账户">
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,176 @@
<script setup lang="ts">
import { ref } from 'vue';
import { type AnalysisOverviewItem, useVbenModal } from '@vben/common-ui';
import { AnalysisOverview } from '@vben/common-ui';
import { SvgCakeIcon } from '@vben/icons';
import { Button, Card, message } from 'ant-design-vue';
import { getAdminAccountBalance, getMyCard } from '#/views/system/admin/api';
import SaveMyCard from '../components/card-modal.vue';
import FormModalDemo from '../components/withdrawal-modal.vue';
defineOptions({
name: 'Statistics',
});
const props = defineProps({
gridApi: {
required: true,
},
});
const balance = ref(0);
const pendingEarnings = ref(0);
const settledEarnings = ref(0);
const totalEarnings = ref(0);
const withdrawnAmount = ref(0);
const withdrawnFrozenAmount = ref(0);
const frozenAmount = ref(0);
// const arrivedAmount = ref(0);
const fee = ref(0);
const myCard = ref();
const overviewItems = ref<AnalysisOverviewItem[]>([]);
/**
* 获取余额卡片列表数据
*/
function showCard() {
showMyCard();
getAdminAccountBalance().then((res) => {
balance.value = res.balance;
pendingEarnings.value = res.pending_earnings; // 待结算
settledEarnings.value = res.settled_earnings;
totalEarnings.value = res.total; // 累计收益
withdrawnAmount.value = res.withdrawn; // 已提现金额
withdrawnFrozenAmount.value = res.withdrawn_frozen; // 已提现金额
frozenAmount.value = res.frozen; // 已提现金额
// arrivedAmount.value = res.data.arrived_amount.toNumber();
fee.value = res.charge;
overviewItems.value = [
{
icon: SvgCakeIcon,
title: '累计收益',
totalTitle: '累计收益',
totalValue: totalEarnings.value,
value: totalEarnings.value,
},
{
icon: 'fluent-emoji:balance-scale',
title: '账户余额',
totalTitle: '账户余额',
totalValue: balance.value,
value: balance.value,
},
{
icon: 'game-icons:frozen-orb',
title: '冻结金额(用户未确认收货)',
totalTitle: '冻结金额(用户未确认收货)',
totalValue: frozenAmount.value,
value: frozenAmount.value,
},
{
icon: 'fxemoji:hourglassflowingsand',
title: '待结算收益',
totalTitle: '待结算收益',
totalValue: pendingEarnings.value,
value: pendingEarnings.value,
},
{
icon: 'fluent-emoji:alarm-clock',
title: '审核中金额',
totalTitle: '审核中金额',
totalValue: withdrawnFrozenAmount.value,
value: withdrawnFrozenAmount.value,
},
{
icon: 'flat-color-icons:ok',
title: '已提现金额',
totalTitle: '已提现金额',
totalValue: withdrawnAmount.value,
value: withdrawnAmount.value,
},
// {
// icon: SvgCardIcon,
// title: '已提现金额',
// totalTitle: '已提现金额',
// totalValue: withdrawnAmount.value,
// value: withdrawnAmount.value,
// },
// {
// icon: 'flat-color-icons:ok',
// title: '到账金额',
// totalTitle: '到账金额',
// totalValue: arrivedAmount.value,
// value: arrivedAmount.value,
// },
// {
// icon: 'icon-park:flash-payment',
// title: '手续费',
// totalTitle: '手续费',
// totalValue: fee.value,
// value: fee.value,
// },
];
});
}
function showMyCard() {
getMyCard().then((res) => {
res.user_name = res?.user_name || res?.store?.name || res?.supplier?.name || res?.platform?.name;
myCard.value = res;
});
}
showCard();
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const [saveMyCardModal, saveMyCardApi] = useVbenModal({
connectedComponent: SaveMyCard,
});
function refresh() {
message.success('刷新成功');
showCard();
}
const showModal = (data = {}) => {
formModalApi.setData({
// 表单值
values: data,
balance,
gridApi: props.gridApi,
showCard,
});
formModalApi.open();
};
const showCardModal = () => {
saveMyCardApi.setData({
// 表单值
values: myCard.value,
balance,
gridApi: props.gridApi,
showCard,
});
saveMyCardApi.open();
};
</script>
<template>
<Card
:title="`${myCard?.user_name || myCard?.store?.name || myCard?.supplier?.name}--账户信息`"
class="p-5"
>
<FormModal />
<saveMyCardModal />
<Button class="ml-5" type="primary" @click="showModal"> 申请提现 </Button>
<Button class="ml-3" type="primary" @click="showCardModal">
编辑账户
</Button>
<Button type="link" @click="refresh">刷新</Button>
<AnalysisOverview :items="overviewItems" :my-card="myCard" class="mt-5" />
</Card>
</template>

View File

@@ -0,0 +1,79 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Button, message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createWithdrawalApplicationWithdrawal } from '#/views/finance/withdrawal/api';
import { modalFormProps } from '../config/form';
const gridApi = ref();
const showCard = ref();
const maxAmount = ref(0);
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = createWithdrawalApplicationWithdrawal;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
showCard.value();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
showCard.value = isOpen ? modalApi.getData()?.showCard : null;
if (isOpen) {
const { values, balance } = modalApi.getData<Record<string, any>>();
if (values) {
maxAmount.value = balance;
formApi.setValues(values);
}
}
},
});
const setAmount = (amount: number) => {
formApi.setValues({ amount });
};
</script>
<template>
<Modal class="w-[30%]" title="申请提现">
<Form />
<div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
<Button type="primary" @click="setAmount(1000)"> 1000 </Button>
<Button type="primary" @click="setAmount(3000)"> 3000 </Button>
<Button type="primary" @click="setAmount(5000)"> 5000 </Button>
<Button type="primary" @click="setAmount(10000)"> 10000 </Button>
<Button type="primary" @click="setAmount(30000)"> 30000 </Button>
<Button type="primary" @click="setAmount(50000)"> 50000 </Button>
<Button type="primary" @click="setAmount(100000)"> 100000 </Button>
<Button type="primary" @click="setAmount(maxAmount.value)">
全部提现
</Button>
</div>
</Modal>
</template>

View File

@@ -0,0 +1,172 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入提现金额',
},
fieldName: 'amount',
label: '提现金额',
rules: 'required',
},
],
showDefaultActions: false,
};
/**
* 银行卡编辑
*/
export const modalCardFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入用户',
disabled: true,
},
fieldName: 'user_name',
label: '账户名称',
rules: 'required',
dependencies: {
show: (values) => {
return values.id != null;
},
triggerFields: ['id'],
},
},
{
component: 'VbenSelect',
componentProps: {
placeholder: '请输入用户类型',
options: [
{ label: '诊所', value: 1 },
{ label: '平台', value: 2 },
{ label: '供应商', value: 3 },
],
disabled: true,
},
fieldName: 'type',
label: '用户类型',
rules: 'required',
dependencies: {
show: (values) => {
return values.id != null;
},
triggerFields: ['id'],
},
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入开户人姓名',
},
dependencies: {
disabled: (values) => {
return values.id != null;
},
triggerFields: ['id'],
},
fieldName: 'bank_user_name',
label: '开户人姓名',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入银行卡号',
},
dependencies: {
disabled: (values) => {
return values.id != null;
},
triggerFields: ['id'],
},
fieldName: 'bank_card',
label: '银行卡号',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入银行名称',
},
dependencies: {
disabled: (values) => {
return values.id != null;
},
triggerFields: ['id'],
},
fieldName: 'bank_name',
label: '银行名称',
rules: 'required',
},
{
component: 'RadioGroup',
componentProps: {
options: [
{ label: '对公', value: 1 },
{ label: '对私', value: 2 },
{ label: '存折', value: 5 },
],
placeholder: '请选择银行账户类型',
},
dependencies: {
disabled: (values) => {
return values.id != null;
},
triggerFields: ['id'],
},
fieldName: 'bank_account_type',
label: '银行账户类型',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入银行联行号',
},
dependencies: {
disabled: (values) => {
return values.id != null;
},
triggerFields: ['id'],
},
fieldName: 'bank_no',
label: '银行联行号',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,67 @@
import type { VbenFormProps } from '#/adapter/form';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'VbenSelect',
componentProps: {
placeholder: '选择审核状态',
options: [
{
label: '审核中',
value: 1,
},
{
label: '已通过',
value: 2,
},
{
label: '已拒绝',
value: 3,
},
{
label: '提现失败',
value: 4,
},
],
},
defaultValue: '',
fieldName: 'check_status',
label: '审核状态',
},
{
component: 'VbenSelect',
componentProps: {
placeholder: '选择打款状态',
options: [
{
label: '失败',
value: -1,
},
{
label: '未知',
value: 0,
},
{
label: '成功',
value: 1,
},
],
},
defaultValue: '',
fieldName: 'dakuan_status',
label: '打款状态',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,75 @@
import type { VbenFormProps } from '#/adapter/form';
export const formOptions2: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'VbenSelect',
componentProps: {
placeholder: '选择费用类型',
options: [
{
label: '药品费用',
value: 1,
},
{
label: '挂号费用',
value: 2,
},
{
label: '快递费用',
value: 3,
},
{
label: '代煎费用',
value: 4,
},
{
label: '加工费用',
value: 5,
},
{
label: '诊疗费用',
value: 6,
},
],
},
defaultValue: '',
fieldName: 'fee_type',
label: '费用类型',
},
{
component: 'VbenSelect',
componentProps: {
placeholder: '选择结算状态',
options: [
{
label: '待结算',
value: 0,
},
{
label: '已结算',
value: 1,
},
{
label: '已取消',
value: 2,
},
],
},
defaultValue: '',
fieldName: 'status',
label: '状态',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,75 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getSettlementList } from '#/views/finance/withdrawal/api/settlement';
interface RowType {
id: string;
nick_name: string;
email: string;
avatar: string;
roles: { name: string }[];
open_id: string;
code: string;
platform_id: string;
phone: string;
desc: string;
created_at: string;
}
export const gridOptions2: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'order.order_no', title: '订单号' },
{ field: 'user_id', title: '名称', slots: { default: 'user_id' } },
{ field: 'money', title: '分账金额' },
{ field: 'fee_type_txt', title: '费用类型' },
{ field: 'status_txt', title: '结算状态' },
{ field: 'created_at', title: '创建时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getSettlementList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
// height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,84 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getWithdrawalApplicationList } from '../api';
interface RowType {
id: string;
nick_name: string;
email: string;
avatar: string;
roles: { name: string }[];
open_id: string;
code: string;
platform_id: string;
phone: string;
desc: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'user_id', title: '名称', slots: { default: 'user_id' } },
{ field: 'apply_cash', title: '申请金额' },
{ field: 'true_cash', title: '实际到账金额' },
{ field: 'charge_cash', title: '手续费' },
{ field: 'apply_time', title: '申请时间' },
{ field: 'check_id', title: '审核人', slots: { default: 'check_id' } },
{ field: 'check_status', title: '审核状态', slots: { default: 'check_status'} },
{ field: 'check_result', title: '审核结果', slots: { default: 'check_result'} },
{ field: 'dakuan_status', title: '打款状态' },
{ field: 'dakuan_time', title: '打款时间' },
{ field: 'created_at', title: '注册时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getWithdrawalApplicationList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
// scrollY: {
// enabled: true,
// gt: 100,
// },
// height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,113 @@
<script lang="ts" setup>
import { AnalysisChartsTabs, Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { Tag } from 'ant-design-vue';
import Statistics from './components/statistics.vue';
import { formOptions } from './config/search';
import { formOptions2 } from './config/settlement-search';
import { gridOptions2 } from './config/settlement-table';
import { gridOptions } from './config/table';
const [Grid, GridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [Grid2] = useVbenVxeGrid({
formOptions: formOptions2,
gridOptions: gridOptions2,
});
const chartTabs = [
{
label: '提现记录',
value: 'trends',
},
{
label: '结算记录',
value: 'visits',
},
];
</script>
<template>
<Page
auto-content-height
description="提现前请确认好您的打款账户"
title="提现管理"
>
<Statistics :grid-api="GridApi" />
<AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
<template #trends>
<Grid>
<template #toolbar-buttons></template>
<template #toolbar-tools></template>
<template #user_id="{ row }">
<span v-if="row.user_type === 1">{{ row.store.name }}</span>
<span v-else-if="row.user_type === 2">萧康平台</span>
<span v-else-if="row.user_type === 3">{{ row.supplier.name }}</span>
</template>
<template #check_status="{ row }">
<Tag v-if="row.check_status === 1" color="blue">待审核</Tag>
<Tag v-else-if="row.check_status === 2" color="green">审核成功</Tag>
<Tag v-else-if="row.check_status === 3" color="red">拒绝</Tag>
</template>
<template #check_result="{ row }">
<Tag v-if="row.check_status === 2" color="green">
{{ row.check_result }}
</Tag>
<Tag v-else-if="row.check_status === 3" color="red">
{{ row.check_result }}
</Tag>
</template>
<template #check_id="{ row }">
<span>{{ row.check_admin?.username || '暂无' }}</span>
</template>
<template #action="{ row }">
<TableAction
:actions="[
// {
// label: '编辑',
// type: 'link',
// icon: 'uil:edit',
// size: 'small',
// onClick: showModal.bind(null, row, true),
// },
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</template>
<template #visits>
<Grid2>
<template #toolbar-buttons></template>
<template #toolbar-tools></template>
<template #user_id="{ row }">
<span v-if="row.user_type === 1">{{ row.store.name }}</span>
<span v-else-if="row.user_type === 2">萧康平台</span>
<span v-else-if="row.user_type === 3">{{ row.supplier.name }}</span>
</template>
<template #action="{ row }">
<TableAction
:actions="[
// {
// label: '编辑',
// type: 'link',
// icon: 'uil:edit',
// size: 'small',
// // auth: ['admin', 'sys:role:detail'],
// onClick: showModal.bind(null, row, true),
// },
]"
:drop-down-actions="[]"
/>
</template>
</Grid2>
</template>
</AnalysisChartsTabs>
</Page>
</template>

View File

@@ -0,0 +1,11 @@
import { requestClient } from '#/api/request';
const prefix = 'log/';
/**
* 分页查询用户列表
* @param data
*/
export async function getOldApiLogList(data: any) {
return requestClient.get<any>(`${prefix}api-list`, { params: data });
}
// Api访问日志

View File

@@ -0,0 +1,107 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Descriptions } from 'ant-design-vue';
import { Icon } from '#/components/icon';
import { getIcon } from '#/util/tool';
defineOptions({
name: 'FormModelDemo',
});
const gridApi = ref();
const data = ref({});
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
modalApi.close();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
if (values) {
data.value = values;
}
}
},
});
</script>
<template>
<Modal class="w-[80%]" title="Api访问日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 2, xs: 2 }"
bordered
>
<Descriptions.Item label="ID">{{ data.id }}</Descriptions.Item>
<Descriptions.Item label="操作人员">
{{ data?.admin?.nick_name || '获取失败' }}
</Descriptions.Item>
<Descriptions.Item label="URL">{{ data.url }}</Descriptions.Item>
<Descriptions.Item label="控制器">
{{ data.controller }}
</Descriptions.Item>
<Descriptions.Item label="IP">
{{ data.ip === '127.0.0.1' ? '本地' : data.ip }}
</Descriptions.Item>
<Descriptions.Item label="类型">
{{ data.type === 0 ? '访问成功' : '访问失败' }}
</Descriptions.Item>
<Descriptions.Item label="返回结果代码">
{{ data.result_code }}
</Descriptions.Item>
<Descriptions.Item label="平台类型">
{{ data.platform_type === 0 ? '平台' : '诊所' }}
</Descriptions.Item>
<Descriptions.Item label="设备">
<Icon :icon="getIcon(data.equipment)" :size="20" />
{{ data.equipment }}
</Descriptions.Item>
<Descriptions.Item label="浏览器">
<Icon :icon="getIcon(data.browser)" :size="20" />
{{ data.browser }}
</Descriptions.Item>
<Descriptions.Item label="平台">
{{ data.platform?.name || '获取失败' }}
</Descriptions.Item>
<Descriptions.Item label="访问时间">
{{ data.created_at }}
</Descriptions.Item>
</Descriptions>
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"
bordered
>
<Descriptions.Item label="参数">
<pre>{{ JSON.parse(data.param) }}</pre>
</Descriptions.Item>
<Descriptions.Item label="结果">
<pre>{{ JSON.parse(data.result) }}</pre>
</Descriptions.Item>
</Descriptions>
</div>
</Modal>
</template>
<style scoped>
.ant-descriptions-item-label {
font-weight: bold;
}
pre {
padding: 8px;
border-radius: 4px;
overflow-x: auto;
white-space: pre-wrap;
}
</style>

View File

@@ -0,0 +1,28 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'RangePicker',
componentProps: {
format: 'YYYY-MM-DD', // 确保格式与你需要的一致
},
// defaultValue: [dayjs().startOf('month'), dayjs()],
fieldName: 'search_time',
label: '时间范围',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,85 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getOldApiLogList } from '../api';
interface RowType {
id: string;
admin: string;
url: string;
ip: string;
param: string;
type: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{
field: 'admin',
align: 'left',
title: '操作管理员',
slots: { default: 'admin' },
},
{ field: 'url', title: '访问路由' },
{ field: 'ip', title: '用户IP地址' },
{ field: 'controller', title: '访问控制器' },
// { field: 'param', title: '携带参数' },
{ field: 'equipment', title: '操作系统', slots: { default: 'equipment' }, width: 80 },
{ field: 'browser', title: '浏览器', slots: { default: 'browser' }, width: 80 },
{ field: 'type', title: '操作简述', slots: { default: 'type' } },
{ field: 'created_at', title: '访问时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getOldApiLogList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
exportConfig: {
// 导出配置
filename: 'api-log',
remote: true,
type: 'csv',
},
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
// import: true, // 导入
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,75 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
import { getIcon } from '#/util/tool';
import {Icon} from "#/components/icon";
ref(false);
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
</script>
<template>
<Page auto-content-height title="Api访问日志管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
</template>
<template #type="{ row }">
<Tag v-if="row.type === 0" color="green"> 访问成功 </Tag>
<Tag v-else color="red"> 访问失败 </Tag>
</template>
<template #admin="{ row }">
{{ row?.admin?.nick_name || '获取失败' }}
</template>
<template #toolbar-tools></template>
<template #equipment="{ row }">
<Icon :icon="getIcon(row.equipment)" :size="20" />
</template>
<template #browser="{ row }">
<Icon :icon="getIcon(row.browser)" :size="20" />
</template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '查看',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['op-log', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,10 @@
import { requestClient } from '#/api/request';
const prefix = 'log/';
/**
* 分页查询用户列表
* @param data
*/
export async function getLedgerLogList(data: any) {
return requestClient.get<any>(`${prefix}ledger-list`, { params: data });
}

View File

@@ -0,0 +1,70 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Descriptions } from 'ant-design-vue';
defineOptions({
name: 'FormModelDemo',
});
const gridApi = ref();
const data = ref({});
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
modalApi.close();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
if (values) {
data.value = values;
}
}
},
});
</script>
<template>
<Modal class="w-[80%]" title="分账日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"
bordered
>
<Descriptions.Item label="ID">{{ data.id }}</Descriptions.Item>
<Descriptions.Item label="管理员ID">{{ data.admin_id }}</Descriptions.Item>
<Descriptions.Item label="理员名称">{{ data.old_admin.username }}</Descriptions.Item>
<Descriptions.Item label="类型">{{ data.type }}</Descriptions.Item>
<Descriptions.Item label="内容">
<pre>{{ JSON.parse(data.content) }}</pre>
</Descriptions.Item>
<Descriptions.Item label="模式">
{{ data.mold_txt }}
</Descriptions.Item>
<Descriptions.Item label="操作时间">
{{ data.operate_time }}
</Descriptions.Item>
<Descriptions.Item label="创建时间">{{ data.created_at }}</Descriptions.Item>
</Descriptions>
</div>
</Modal>
</template>
<style scoped>
.ant-descriptions-item-label {
font-weight: bold;
}
pre {
padding: 8px;
border-radius: 4px;
overflow-x: auto;
}
</style>

View File

@@ -0,0 +1,28 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'RangePicker',
componentProps: {
format: 'YYYY-MM-DD', // 确保格式与你需要的一致
},
// defaultValue: [dayjs().startOf('month'), dayjs()],
fieldName: 'search_time',
label: '时间范围',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,64 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getLedgerLogList } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columns: [
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'order.order_no', align: 'left', title: '订单编号' },
{ field: 'store', title: '关联账户', slots: { default: 'store' } },
{ field: 'user_type_txt', title: '账户类型' },
{ field: 'fee_type_txt', title: '费用类型' },
{ field: 'type_txt', title: '类型' },
{ field: 'content', title: '操作内容' },
{ field: 'amount', title: '金额' },
{ field: 'created_at', title: '操作时间' },
// { type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getLedgerLogList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,66 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
ref(false);
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
</script>
<template>
<Page auto-content-height title="分账日志管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
</template>
<template #store="{ row }">
<span v-if="row.user_type === 1">{{ row.store.name }}</span>
<span v-else-if="row.user_type === 3">{{ row.supplier.name }}</span>
<span v-else>萧康-平台</span>
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '查看',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['op-log', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,11 @@
import { requestClient } from '#/api/request';
const prefix = 'log/';
/**
* 分页查询用户列表
* @param data
*/
export async function getOldApiLogList(data: any) {
return requestClient.get<any>(`${prefix}old-api-list`, { params: data });
}
// Api访问日志

View File

@@ -0,0 +1,86 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Descriptions } from 'ant-design-vue';
defineOptions({
name: 'FormModelDemo',
});
const gridApi = ref();
const data = ref({});
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
modalApi.close();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
if (values) {
data.value = values;
}
}
},
});
</script>
<template>
<Modal class="w-[80%]" title="Api访问日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"
bordered
>
<Descriptions.Item label="ID">{{ data.id }}</Descriptions.Item>
<Descriptions.Item label="用户ID">{{ data.user_id }}</Descriptions.Item>
<Descriptions.Item label="URL">{{ data.url }}</Descriptions.Item>
<Descriptions.Item label="IP">{{ data.ip === '127.0.0.1' ? '本地' : data.ip }}</Descriptions.Item>
<Descriptions.Item label="参数">
<pre>{{ JSON.parse(JSON.parse(data.param)) }}</pre>
</Descriptions.Item>
<Descriptions.Item label="结果">
<pre>{{ JSON.parse(JSON.parse(data.result)) }}</pre>
</Descriptions.Item>
<Descriptions.Item label="类型">{{ data.type === 0 ? '访问成功' : '访问失败' }}</Descriptions.Item>
<Descriptions.Item label="返回结果代码">
{{ data.result_code }}
</Descriptions.Item>
<Descriptions.Item label="平台类型">
{{ data.platform_type === 0 ? '平台' : '诊所' }}
</Descriptions.Item>
<Descriptions.Item label="平台">
{{ data.platform_id === 0 ? '萧康医药' : data?.old_admin?.username || '获取失败' }}
</Descriptions.Item>
<Descriptions.Item label="设备">{{ data.equipment }}</Descriptions.Item>
<Descriptions.Item label="浏览器">{{ data.browser }}</Descriptions.Item>
<Descriptions.Item label="创建时间">
{{ data.created_at }}
</Descriptions.Item>
<Descriptions.Item label="管理员名称">
{{ data?.old_admin?.username || '获取失败' }}
</Descriptions.Item>
</Descriptions>
</div>
</Modal>
</template>
<style scoped>
.ant-descriptions-item-label {
font-weight: bold;
}
pre {
padding: 8px;
border-radius: 4px;
overflow-x: auto;
}
</style>

View File

@@ -0,0 +1,28 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'RangePicker',
componentProps: {
format: 'YYYY-MM-DD', // 确保格式与你需要的一致
},
// defaultValue: [dayjs().startOf('month'), dayjs()],
fieldName: 'search_time',
label: '时间范围',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,73 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getOldApiLogList } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{
field: 'old_admin',
align: 'left',
title: '操作管理员',
slots: { default: 'old_admin' },
},
{ field: 'url', title: '访问路由' },
{ field: 'ip', title: '用户IP地址' },
{ field: 'param', title: '携带参数' },
{ field: 'type', title: '操作简述', slots: { default: 'type' } },
{ field: 'created_at', title: '上传时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getOldApiLogList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,68 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
ref(false);
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
</script>
<template>
<Page auto-content-height title="Api访问日志管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
</template>
<template #type="{ row }">
<Tag v-if="row.type === 0" color="green"> 访问成功 </Tag>
<Tag v-else color="red"> 访问失败 </Tag>
</template>
<template #old_admin="{ row }">
{{ row?.old_admin?.username || '获取失败' }}
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '查看',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['op-log', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,10 @@
import { requestClient } from '#/api/request';
const prefix = 'log/';
/**
* 分页查询用户列表
* @param data
*/
export async function getOpLogList(data: any) {
return requestClient.get<any>(`${prefix}op-list`, { params: data });
}

View File

@@ -0,0 +1,71 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Descriptions } from 'ant-design-vue';
defineOptions({
name: 'FormModelDemo',
});
const gridApi = ref();
const data = ref({});
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
modalApi.close();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
if (values) {
data.value = values;
}
}
},
});
</script>
<template>
<Modal class="w-[80%]" title="操作日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"
bordered
>
<Descriptions.Item label="ID">{{ data.id }}</Descriptions.Item>
<Descriptions.Item label="管理员ID">{{ data.admin_id }}</Descriptions.Item>
<Descriptions.Item label="理员名称">{{ data.old_admin?.username || data.admin?.nick_name }}</Descriptions.Item>
<Descriptions.Item label="类型">{{ data.type }}</Descriptions.Item>
<Descriptions.Item label="内容">
<pre>{{ JSON.parse(data.content) }}</pre>
</Descriptions.Item>
<Descriptions.Item label="模式">
{{ data.mold_txt }}
</Descriptions.Item>
<Descriptions.Item label="操作时间">
{{ data.operate_time }}
</Descriptions.Item>
<Descriptions.Item label="创建时间">{{ data.created_at }}</Descriptions.Item>
</Descriptions>
</div>
</Modal>
</template>
<style scoped>
.ant-descriptions-item-label {
font-weight: bold;
}
pre {
padding: 8px;
border-radius: 4px;
overflow-x: auto;
white-space: pre-wrap;
}
</style>

View File

@@ -0,0 +1,28 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'RangePicker',
componentProps: {
format: 'YYYY-MM-DD', // 确保格式与你需要的一致
},
// defaultValue: [dayjs().startOf('month'), dayjs()],
fieldName: 'search_time',
label: '时间范围',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,73 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getOpLogList } from '../api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{
field: 'old_admin',
align: 'left',
title: '操作管理员',
slots: { default: 'old_admin' },
},
{ field: 'type', title: '操作简述' },
{ field: 'mold_txt', title: '操作类型' },
{ field: 'content', title: '操作内容', slots: { default: 'content'} },
{ field: 'operate_time', title: '操作时间' },
{ field: 'created_at', title: '上传时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getOpLogList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,84 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
ref(false);
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
</script>
<template>
<Page auto-content-height title="操作日志管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
]"
:drop-down-actions="[
]"
>
<template #more>
<Button style="margin-left: 16px">
批量操作
<Icon icon="ant-design:down-outlined" />
</Button>
</template>
</TableAction>
</template>
<template #old_admin="{ row }">
{{ row.old_admin?.username || row.admin?.nick_name }}
</template>
<template #content="{ row }">
{{
row.content.length > 100
? `${row.content.slice(0, 100)}...`
: row.content
}}
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '查看',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['op-log', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,10 @@
import { requestClient } from '#/api/request';
const prefix = 'log/';
/**
* 分页查询用户列表
* @param data
*/
export async function getOrderLogList(data: any) {
return requestClient.get<any>(`${prefix}order-list`, { params: data });
}

View File

@@ -0,0 +1,70 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Descriptions } from 'ant-design-vue';
defineOptions({
name: 'FormModelDemo',
});
const gridApi = ref();
const data = ref({});
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
modalApi.close();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
if (values) {
data.value = values;
}
}
},
});
</script>
<template>
<Modal class="w-[80%]" title="订单日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"
bordered
>
<Descriptions.Item label="ID">{{ data.id }}</Descriptions.Item>
<Descriptions.Item label="管理员ID">{{ data.admin_id }}</Descriptions.Item>
<Descriptions.Item label="理员名称">{{ data.old_admin.username }}</Descriptions.Item>
<Descriptions.Item label="类型">{{ data.type }}</Descriptions.Item>
<Descriptions.Item label="内容">
<pre>{{ JSON.parse(data.content) }}</pre>
</Descriptions.Item>
<Descriptions.Item label="模式">
{{ data.mold_txt }}
</Descriptions.Item>
<Descriptions.Item label="操作时间">
{{ data.operate_time }}
</Descriptions.Item>
<Descriptions.Item label="创建时间">{{ data.created_at }}</Descriptions.Item>
</Descriptions>
</div>
</Modal>
</template>
<style scoped>
.ant-descriptions-item-label {
font-weight: bold;
}
pre {
padding: 8px;
border-radius: 4px;
overflow-x: auto;
}
</style>

View File

@@ -0,0 +1,28 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'RangePicker',
componentProps: {
format: 'YYYY-MM-DD', // 确保格式与你需要的一致
},
// defaultValue: [dayjs().startOf('month'), dayjs()],
fieldName: 'search_time',
label: '时间范围',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,65 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getOrderLogList } from '../api';
interface RowType {
id: string;
name: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'order.order_no', align: 'left', title: '操作订单号' },
{ field: 'content', title: '操作内容' },
{ field: 'created_at', title: '操作时间' },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getOrderLogList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
scrollY: {
enabled: true,
gt: 100,
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,64 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
ref(false);
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
</script>
<template>
<Page auto-content-height title="订单日志管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
</template>
<template #logo="{ row }">
<Image :src="row.logo" height="30" width="30" />
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '查看',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['order-log', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,10 @@
import { requestClient } from '#/api/request';
const prefix = 'log/';
/**
* 分页查询用户列表
* @param data
*/
export async function getPrescriptionList(data: any) {
return requestClient.get<any>(`${prefix}prescription-list`, { params: data });
}

View File

@@ -0,0 +1,62 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Descriptions } from 'ant-design-vue';
defineOptions({
name: 'FormModelDemo',
});
const gridApi = ref();
const data = ref({});
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
modalApi.close();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values } = modalApi.getData<Record<string, any>>();
if (values) {
data.value = values;
}
}
},
});
</script>
<template>
<Modal class="w-[80%]" title="处方日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"
bordered
>
<Descriptions.Item label="ID">{{ data.id }}</Descriptions.Item>
<Descriptions.Item label="处方">{{ data.p_id }}</Descriptions.Item>
<Descriptions.Item label="内容">
<pre>{{ JSON.parse(data.prescription.content) }}</pre>
</Descriptions.Item>
<Descriptions.Item label="操作时间">{{ data.created_at }}</Descriptions.Item>
</Descriptions>
</div>
</Modal>
</template>
<style scoped>
.ant-descriptions-item-label {
font-weight: bold;
}
pre {
padding: 8px;
border-radius: 4px;
overflow-x: auto;
}
</style>

View File

@@ -0,0 +1,28 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'RangePicker',
componentProps: {
format: 'YYYY-MM-DD', // 确保格式与你需要的一致
},
// defaultValue: [dayjs().startOf('month'), dayjs()],
fieldName: 'search_time',
label: '时间范围',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,66 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getPrescriptionList } from '../api';
interface RowType {
id: string;
name: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'p_id', align: 'left', title: '处方ID' },
{ field: 'content', title: '操作内容' },
{ field: 'created_at', title: '操作时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getPrescriptionList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
scrollY: {
enabled: true,
gt: 100,
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,61 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
ref(false);
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
</script>
<template>
<Page auto-content-height title="处方日志管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '查看',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['order-log', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
]"
:drop-down-actions="[]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,74 @@
import { requestClient } from '#/api/request';
const prefix = 'admin/';
/**
* 分页查询用户列表
* @param data
*/
export async function getAdminList(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 分页查询用户列表
* @param data
*/
export async function getAdminAccountBalance(data: any = {}) {
return requestClient.get<any>(`${prefix}my-balance`, { params: data });
}
/**
* 获取管理员详情
* @param id
*/
export async function getAdminInfo(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增管理员
* @param data
*/
export async function createAdmin(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑管理员
* @param data
*/
export async function updateAdmin(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除管理员
* @param data
*/
export async function deleteAdmin(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}
/**
* 删除管理员
* @param id
*/
export async function resetPassword(id: number) {
return requestClient.post<any>(`${prefix}reset-password`, {
id,
});
}
/**
* 获取我绑定的银行卡
*/
export async function getMyCard() {
return requestClient.get<any>(`${prefix}my-card`);
}
/**
* 编辑账户绑定银行卡
* @param data
*/
export async function saveCard(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}save-card`, data);
}

View File

@@ -0,0 +1,65 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createAdmin, updateAdmin } from '#/views/system/admin/api';
import { modalFormProps } from '#/views/system/admin/config/form';
defineOptions({
name: 'FormModelDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateAdmin : createAdmin;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}管理员`"
class="w-[60%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,172 @@
import type { VbenFormProps } from '#/adapter/form';
import { z } from '#/adapter/form';
import { getRoleOption } from '#/views/system/role/api';
import { getSupplierOption } from '#/views/system/supplier/api';
const defaultPassword = 'Xk123456@';
const supplierId = 7;
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入管理员昵称',
},
fieldName: 'nick_name',
label: '昵称',
rules: 'required',
formItemClass: 'col-span-6',
},
{
component: 'Avatar',
fieldName: 'avatar',
label: '头像',
rules: 'required',
formItemClass: 'col-span-6',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入管理员手机号码',
},
fieldName: 'phone',
label: '手机号',
rules: 'required',
formItemClass: 'col-span-6',
},
{
component: 'ApiSelect',
componentProps: {
allowClear: true,
filterOption: (input: string, option: any) => {
// 自定义过滤逻辑,确保可以根据 name 进行搜索
return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
}));
},
api: getRoleOption,
placeholder: '请选择',
},
fieldName: 'role_id',
formItemClass: 'col-span-6',
label: '角色',
rules: 'required',
},
{
component: 'ApiSelect',
componentProps: {
allowClear: true,
filterOption: true,
showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
}));
},
api: getSupplierOption,
placeholder: '请选择',
},
dependencies: {
show: (values) => {
return values.role_id === supplierId;
},
triggerFields: ['role_id'],
},
fieldName: 'supplier_id',
formItemClass: 'col-span-6',
label: '所属供应商',
rules: 'required',
},
{
fieldName: 'password',
label: '密码',
component: 'InputPassword',
help: '5-18位数字、字母、特殊字符组成。',
componentProps: {
placeholder: '请输入密码',
allowClear: true,
},
defaultValue: defaultPassword,
rules: z
.string()
.regex(
/^(?=.*[A-Z0-9])(?=.*[!@#$%^&*])[A-Z0-9!@#$%^&*]{5,18}$/i,
'密码由5-18位数字、字母、特殊字符组成。',
),
dependencies: {
if({ id }) {
return !id;
},
triggerFields: ['id'],
},
formItemClass: 'col-span-6',
},
{
fieldName: 'confirmPassword',
label: '确认密码',
component: 'InputPassword',
componentProps: {
placeholder: '请输入确认密码',
allowClear: true,
},
defaultValue: defaultPassword,
rules: z
.string()
.regex(/[\w!@#$%^&*]{5,18}/, '密码由5-18位数字、字母、特殊字符组成。'),
dependencies: {
if({ id }) {
return !id;
},
triggerFields: ['id', 'confirmPassword'],
rules: (values) => {
return z
.string()
.regex(
/[\w!@#$%^&*]{5,18}/,
'密码由5-18位数字、字母、特殊字符组成。',
)
.refine(
(confirmPassword) => {
return confirmPassword === values.password;
},
{
message: '确认密码必须与密码一致',
},
);
},
},
formItemClass: 'col-span-6',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,56 @@
import type { VbenFormProps } from '#/adapter/form';
import { getRoleOption } from '#/views/system/role/api';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'VbenInput',
componentProps: {
placeholder: '输入名称',
},
defaultValue: '',
fieldName: 'nick_name',
label: '管理员名称',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '输入手机号码',
},
defaultValue: '',
fieldName: 'phone',
label: '手机号码',
},
{
component: 'ApiSelect',
componentProps: {
allowClear: true,
filterOption: true,
showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
}));
},
api: getRoleOption,
placeholder: '请选择',
},
fieldName: 'role_id',
label: '角色',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,86 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getAdminList } from '#/views/system/admin/api';
interface RowType {
id: string;
nick_name: string;
email: string;
avatar: string;
roles: { name: string }[];
open_id: string;
code: string;
platform_id: string;
phone: string;
desc: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'nick_name', align: 'left', title: '名称' },
{
field: 'avatar',
align: 'left',
title: '头像',
slots: { default: 'avatar' },
width: 130,
},
{ field: 'roles.name', title: '角色' },
{ field: 'open_id', title: 'Open ID' },
{ field: 'code', title: '业务推广码' },
{ field: 'platform.name', title: '所属平台' },
{ field: 'supplier.name', title: '所属供应商' },
{ field: 'phone', title: '手机号码' },
{ field: 'email', title: '邮箱' },
{ field: 'desc', title: '备注' },
{ field: 'created_at', title: '注册时间' },
{ type: 'html', title: '操作', width: 200, slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getAdminList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,153 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import {deleteAdmin, resetPassword} from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const deleteApi = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteAdmin({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
const resetPasswordApi = (id: number) => {
resetPassword(id).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="管理员管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
// auth: ['超级管理员', 'sys:user:save'],
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
// auth: ['超级管理员', 'sys:user:save'],
popConfirm: {
title: '确定删除吗',
confirm: deleteApi.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px">
批量操作
<Icon icon="ant-design:down-outlined" />
</Button>
</template>
</TableAction>
</template>
<template #avatar="{ row }">
<Image :src="row.avatar" height="30" width="30" />
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: deleteApi.bind(null, row.id),
},
},
]"
:drop-down-actions="[
{
label: '重置密码',
type: 'link',
icon: 'bitcoin-icons:refresh-filled',
size: 'small',
popConfirm: {
title: '确定重置密码吗',
confirm: resetPasswordApi.bind(null, row.id),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,75 @@
import {requestClient} from '#/api/request';
const prefix = 'menu/';
/**
* 分页查询用户列表
* @param data
*/
export async function getMenuList(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 查询菜单下拉框
* @param data
*/
export async function getMenuOption(data: any) {
return requestClient.get<any>(`${prefix}option`, {
params: {
...data,
is_select: true,
},
});
}
/**
* 查询菜单树形下拉框
* @param data
*/
export async function getMenuTreeOption(data: any) {
return requestClient.get<any>(`${prefix}get-tree-option`, data);
}
/**
* 查询菜单树形下拉框
*/
export async function getMenuTreeOptionSelect() {
return requestClient.get<any>(`${prefix}get-tree-option`, {
params: {
is_select: true,
},
});
}
/**
* 获取详情
* @param id
*/
export async function getMenuInfo(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增角色
* @param data
*/
export async function createMenu(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑角色
* @param data
*/
export async function updateMenu(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除角色
* @param data
*/
export async function deleteMenu(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,65 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createMenu, updateMenu } from '../api';
import { modalFormProps } from '../config/form';
defineOptions({
name: 'FormModelDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateMenu : createMenu;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}菜单`"
class="w-[60%]"
>
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,180 @@
import type { VbenFormProps } from '#/adapter/form';
import { getMenuTreeOptionSelect } from '#/views/system/menu/api';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-6',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入菜单标题',
},
fieldName: 'title',
label: '菜单标题',
rules: 'required',
},
{
component: 'ApiTreeSelect',
// 对应组件的参数
componentProps: {
childrenField: 'children',
labelField: 'title',
valueField: 'id',
// 菜单接口
api: getMenuTreeOptionSelect,
},
defaultValue: 0,
fieldName: 'pid',
label: '父级菜单',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入菜单图标',
},
fieldName: 'icon',
label: '菜单图标',
rules: 'required',
},
// {
// component: 'IconPicker',
// componentProps: {
// placeholder: '请输入菜单图标',
// },
// fieldName: 'icon',
// label: '菜单图标',
// rules: 'required',
// },
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入路由名称',
},
fieldName: 'name',
label: '路由名称',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入访问路由',
},
fieldName: 'path',
label: '访问路由',
rules: 'required',
},
{
component: 'InputNumber',
componentProps: {
placeholder: '请输入排序',
},
defaultValue: 0,
fieldName: 'sort',
label: '排序',
rules: 'required',
},
{
component: 'RadioGroup',
componentProps: {
options: [
{
label: '开启',
value: 0,
},
{
label: '关闭',
value: 1,
},
],
},
defaultValue: 1,
fieldName: 'keep_alive',
label: '缓存',
rules: 'required',
},
{
component: 'RadioGroup',
componentProps: {
options: [
{
label: '展示',
value: 0,
},
{
label: '隐藏',
value: 1,
},
],
},
defaultValue: 0,
fieldName: 'hide_in_menu',
label: '是否展示',
rules: 'required',
},
{
component: 'RadioGroup',
componentProps: {
options: [
{
label: '是',
value: 0,
},
{
label: '否',
value: 1,
},
],
},
defaultValue: 1,
fieldName: 'affix_tab',
label: '是否置顶',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入组件地址',
},
defaultValue: 'BasicLayout',
fieldName: 'component',
label: '组件地址',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入重定向地址',
},
fieldName: 'redirect',
label: '重定向地址',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入携带参数',
},
fieldName: 'query',
label: '携带参数',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,47 @@
import type { VbenFormProps } from '#/adapter/form';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'VbenInput',
componentProps: {
placeholder: '输入标题',
},
defaultValue: '',
fieldName: 'title',
label: '菜单标题',
},
// {
// component: 'VbenSelect',
// componentProps: {
// allowClear: true,
// filterOption: true,
// showSearch: true,
// options: [
// {
// label: '超管',
// value: 1,
// },
// {
// label: '菜单',
// value: 2,
// },
// ],
// placeholder: '请选择',
// },
// fieldName: 'role_id',
// label: '角色',
// },
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,97 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getMenuList } from '../api';
interface RowType {
id: string;
nick_name: string;
email: string;
avatar: string;
roles: { name: string }[];
open_id: string;
code: string;
platform_id: string;
phone: string;
desc: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ width: 60, treeNode: true },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'title', align: 'left', title: '菜单名称' },
{ field: 'icon', title: '图标', slots: { default: 'icon' } },
{ field: 'path', title: '路由' },
{ field: 'name', title: '路由Name' },
{ field: 'component', title: '组件地址' },
{ field: 'redirect', title: '重定向' },
{ field: 'keep_alive', title: '页面缓存' },
{ field: 'hide_in_menu', title: '菜单展示' },
{ field: 'badge', title: '徽标' },
{ field: 'badge_type', title: '徽标类型' },
{ field: 'badge_variants', title: '徽标颜色' },
{ field: 'iframe_src', title: '引用的页面地址' },
{ field: 'sort', title: '排序' },
{ field: 'query', title: '默认参数' },
{ field: 'created_at', title: '创建时间' },
{
type: 'html',
align: 'right',
title: '操作',
slots: { default: 'action' },
width: 200,
},
],
treeConfig: {
parentField: 'pid',
rowField: 'id',
transform: true,
expandAll: true,
},
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getMenuList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,169 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import AuthMenu from '../role/components/auth-menu.vue';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteMenu } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
import {Icon} from "#/components/icon";
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const deleteApi = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteMenu({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
const expandAll = () => {
gridApi.grid?.setAllTreeExpand(true);
};
const collapseAll = () => {
gridApi.grid?.setAllTreeExpand(false);
};
</script>
<template>
<Page auto-content-height title="菜单管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
// auth: ['超级菜单', 'sys:user:save'],
onClick: showModal.bind(null),
},
{
label: '展开全部',
type: 'primary',
// auth: ['超级菜单', 'sys:user:save'],
onClick: expandAll.bind(null),
},
{
label: '收起全部',
type: 'primary',
// auth: ['超级菜单', 'sys:user:save'],
onClick: collapseAll.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
// auth: ['超级菜单', 'sys:user:save'],
popConfirm: {
title: '确定删除吗',
confirm: deleteApi.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px">
批量操作
<Icon icon="ant-design:down-outlined" />
</Button>
</template>
</TableAction>
</template>
<template #icon="{ row }">
<Icon :icon="row.icon" />
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
]"
:drop-down-actions="[
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗',
confirm: deleteApi.bind(null, row.id),
},
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗',
confirm: deleteApi.bind(null, row.id),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,41 @@
import { requestClient } from '#/api/request';
/**
* 分页查询用户列表
* @param data
*/
export async function getPlatformList(data: any) {
return requestClient.get<any>('platform/list', { params: data });
}
/**
* 获取平台详情
* @param id
*/
export async function getPlatformInfo(id: number) {
return requestClient.get<any>('platform/detail', { params: { id } });
}
/**
* 新增平台
* @param data
*/
export async function createPlatform(data: Record<string, any>) {
return requestClient.post<any>('platform/create', data);
}
/**
* 编辑平台
* @param data
*/
export async function updatePlatform(data: Record<string, any>) {
return requestClient.post<any>('platform/update', data);
}
/**
* 删除平台
* @param data
*/
export async function deletePlatform(data: Record<string, any>) {
return requestClient.post<any>('platform/delete', data);
}

View File

@@ -0,0 +1,62 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createPlatform, updatePlatform } from '#/views/system/platform/api';
import { modalFormProps } from '#/views/system/platform/config/form';
defineOptions({
name: 'FormModelDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updatePlatform : createPlatform;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}平台`" class="w-[30%]">
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,51 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入平台昵称',
},
fieldName: 'name',
label: '平台名称',
rules: 'required',
},
{
component: 'Avatar',
fieldName: 'logo',
label: 'LOGO',
rules: 'required',
},
{
component: 'Textarea',
componentProps: {
placeholder: '请输入平台介绍',
},
fieldName: 'introduce',
label: '平台介绍',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,28 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'VbenInput',
componentProps: {
placeholder: '输入名称',
},
defaultValue: '',
fieldName: 'name',
label: '平台名称',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,73 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getPlatformList } from '#/views/system/platform/api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'name', align: 'left', title: '平台名称' },
{
field: 'logo',
align: 'left',
title: 'LOGO',
slots: { default: 'logo' },
width: 130,
},
{ field: 'introduce', title: '平台介绍' },
{ field: 'created_at', title: '注册时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getPlatformList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,155 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deletePlatform } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const deleteApi = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deletePlatform({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="平台管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
// auth: ['超级平台', 'sys:user:save'],
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
// auth: ['超级平台', 'sys:user:save'],
popConfirm: {
title: '确定删除吗',
confirm: deleteApi.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px">
批量操作
<Icon icon="ant-design:down-outlined" />
</Button>
</template>
</TableAction>
</template>
<template #logo="{ row }">
<Image :src="row.logo" height="30" width="30" />
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['platform', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['platform', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: deleteApi.bind(null, row.id),
},
},
]"
:drop-down-actions="[
// {
// label: '编辑',
// type: 'link',
// icon: 'ant-design:delete-outlined',
// size: 'small',
// // auth: ['platform', 'sys:role:detail'],
// onClick: showModal.bind(null, row, true),
// },
// {
// label: '删除',
// type: 'link',
// icon: 'ant-design:delete-outlined',
// size: 'small',
// // auth: ['platform', 'sys:role:detail'],
// popConfirm: {
// title: '确定删除吗',
// confirm: deleteApi.bind(null, row.id),
// },
// },
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,69 @@
import { requestClient } from '#/api/request';
const prefix = 'role/';
/**
* 分页查询用户列表
* @param data
*/
export async function getRoleList(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 分页查询用户列表
* @param data
*/
export async function getRoleOption(data: any) {
return requestClient.get<any>(`${prefix}option`, { params: data });
}
/**
* 获取详情
* @param id
*/
export async function getRoleInfo(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 查询菜单下拉框
* @param data
*/
export async function getMenuIdsByRoleIds(data: any) {
return requestClient.get<any>(`${prefix}get-menu-ids-by-role-ids`, {
params: {
role_id: data.id,
},
});
}
/**
* 编辑角色
* @param data
*/
export async function saveRoleMenu(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}save-role-menu`, data);
}
/**
* 新增角色
* @param data
*/
export async function createRole(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑角色
* @param data
*/
export async function updateRole(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除角色
* @param data
*/
export async function deleteRole(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,141 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { Button, message, Tree } from 'ant-design-vue';
import { $t } from '#/locales';
import { getAllNodeIds, getLeafNodeIds } from '#/util/tool';
import { getMenuTreeOption } from '#/views/system/menu/api';
import { getMenuIdsByRoleIds, saveRoleMenu } from '../api';
import {Icon} from "#/components/icon";
const record = ref();
const treeRef = ref();
const treeData = ref([]);
const isExpand = ref(false);
// 勾选的key
const checkedKeys = ref([]);
// 提交的勾选的key,会进行特殊处理包含半勾状态的父节点halfCheckedKeys
const submitCheckedKeys = ref<any>([]);
// 所有叶子节点key
const leafKeys = ref<any>([]);
// 所有节点key
const allNodeIds = ref([]);
// 当前展开的key
const currentExpandedKeys = ref([]);
/**
* api请求成功回调
*/
const handleFetchSuccess = () => {
getMenuIdsByRoleIds({
id: record.value.id,
// appCode: props.appCode,
}).then((res: any) => {
// 设置的勾选节点只能为叶子节点
checkedKeys.value = res.filter((item: any) => {
return leafKeys.value.includes(item);
});
submitCheckedKeys.value = res;
});
};
const [Drawer, DrawerApi] = useVbenDrawer({
onOpenChange(isOpen) {
record.value = isOpen ? DrawerApi.getData()?.record : {};
if (isOpen) {
DrawerApi.setState({
loading: true,
});
getMenuTreeOption({
filterByUser: 1,
})
.then((res) => {
treeData.value = res;
leafKeys.value = getLeafNodeIds(res);
allNodeIds.value = getAllNodeIds(res);
handleFetchSuccess();
})
.finally(() => {
DrawerApi.setState({
loading: false,
});
});
}
},
onConfirm() {
const menus = submitCheckedKeys.value.map((item: any) => {
return item;
});
DrawerApi.setState({
loading: true,
confirmLoading: true,
});
saveRoleMenu({
role_id: record.value.id,
menu_id: menus,
})
.then(() => {
message.success('保存成功');
DrawerApi.close();
})
.finally(() => {
DrawerApi.setState({
loading: false,
confirmLoading: false,
});
});
},
});
/**
* 点击复选框触发处理
* @param mCheckedKeys
*/
const handleCheck = (mCheckedKeys: any, e: any) => {
checkedKeys.value = mCheckedKeys;
// 提交的时候需要将半选的父节点也提交上
submitCheckedKeys.value = [...mCheckedKeys, ...e.halfCheckedKeys];
};
// 展开折叠事件
const handleExpand = (expandedKeys: any) => {
currentExpandedKeys.value = expandedKeys;
};
// 展开折叠按钮事件
const handleExpandAndCollapse = () => {
isExpand.value = !isExpand.value;
currentExpandedKeys.value = isExpand.value ? allNodeIds.value : [];
};
defineExpose(DrawerApi);
</script>
<template>
<div>
<Drawer class="w-[60%]" title="授权菜单">
<Button type="primary" @click="handleExpandAndCollapse">
{{ isExpand ? '折叠' : '展开' }}
</Button>
<Tree
ref="treeRef"
v-model:checked-keys="checkedKeys"
:expanded-keys="currentExpandedKeys"
:field-names="{
title: 'title',
key: 'id',
}"
:show-line="true"
:tree-data="treeData"
checkable
style="margin: 20px auto"
@check="handleCheck"
@expand="handleExpand"
>
<template #title="{ title, icon }">
<Icon :icon="icon" />
{{ $t(title) }}
</template>
</Tree>
</Drawer>
</div>
</template>

View File

@@ -0,0 +1,62 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createRole, updateRole } from '#/views/system/role/api';
import { modalFormProps } from '#/views/system/role/config/form';
defineOptions({
name: 'FormModelDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateRole : createRole;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}角色`" class="w-[30%]">
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,53 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入角色昵称',
},
fieldName: 'name',
label: '昵称',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入角色代码',
},
fieldName: 'value',
label: '角色代码',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入角色说明',
},
fieldName: 'desc',
label: '角色说明',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,35 @@
import type { VbenFormProps } from '#/adapter/form';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'VbenInput',
componentProps: {
placeholder: '输入名称',
},
defaultValue: '',
fieldName: 'name',
label: '角色名称',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '输入角色代码',
},
defaultValue: '',
fieldName: 'value',
label: '角色代码',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,73 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getRoleList } from '#/views/system/role/api';
interface RowType {
id: string;
nick_name: string;
email: string;
avatar: string;
roles: { name: string }[];
open_id: string;
code: string;
platform_id: string;
phone: string;
desc: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'name', align: 'left', title: '名称' },
{ field: 'value', title: '角色代码' },
{ field: 'desc', title: '备注' },
{ field: 'created_at', title: '创建时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getRoleList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,159 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteRole } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
import AuthMenu from "#/views/system/role/components/auth-menu.vue";
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: {
id: data?.id,
name: data?.name,
value: data?.value,
desc: data?.desc,
},
update: isUpdate,
gridApi,
});
formModalApi.open();
};
// 授权菜单
const authMenuRef = ref();
const handleAuthMenu = (record: any) => {
authMenuRef.value.setData({
record,
});
authMenuRef.value.open();
};
const deleteApi = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteRole({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="角色管理">
<FormModal />
<AuthMenu ref="authMenuRef" />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
// auth: ['超级角色', 'sys:user:save'],
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
// auth: ['超级角色', 'sys:user:save'],
popConfirm: {
title: '确定删除吗',
confirm: deleteApi.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px">
批量操作
<Icon icon="ant-design:down-outlined" />
</Button>
</template>
</TableAction>
</template>
<template #avatar="{ row }">
<Image :src="row.avatar" height="30" width="30" />
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '授权菜单',
type: 'link',
icon: 'arcticons:microsoft-authenticator',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
onClick: handleAuthMenu.bind(null, row),
},
]"
:drop-down-actions="[
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['admin', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗',
confirm: deleteApi.bind(null, row.id),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,49 @@
import { requestClient } from '#/api/request';
const prefix = 'supplier/';
/**
* 分页查询用户列表
* @param data
*/
export async function getSupplierList(data: any) {
return requestClient.get<any>(`${prefix}list`, { params: data });
}
/**
* 分页查询用户列表
* @param data
*/
export async function getSupplierOption(data: any) {
return requestClient.get<any>(`${prefix}option`, { params: data });
}
/**
* 获取供应商详情
* @param id
*/
export async function getSupplierInfo(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 新增供应商
* @param data
*/
export async function createSupplier(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}create`, data);
}
/**
* 编辑供应商
* @param data
*/
export async function updateSupplier(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}update`, data);
}
/**
* 删除供应商
* @param data
*/
export async function deleteSupplier(data: Record<string, any>) {
return requestClient.post<any>(`${prefix}delete`, data);
}

View File

@@ -0,0 +1,62 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { createSupplier, updateSupplier } from '#/views/system/supplier/api';
import { modalFormProps } from '#/views/system/supplier/config/form';
defineOptions({
name: 'FormModelDemo',
});
const isUpdate = ref(false);
const gridApi = ref();
const [Form, formApi] = useVbenForm(modalFormProps);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
formApi.validate().then(async (e: any) => {
if (e.valid) {
const values = await formApi.getValues();
modalApi.setState({ loading: true, confirmLoading: true });
const submitApi = isUpdate.value ? updateSupplier : createSupplier;
submitApi(values)
.then(() => {
message.success('保存成功');
gridApi.value?.reload();
modalApi.close();
})
.finally(() => {
modalApi.setState({ loading: false, confirmLoading: false });
});
}
});
await formApi.validateAndSubmitForm();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
},
});
</script>
<template>
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}供应商`" class="w-[30%]">
<Form />
</Modal>
</template>

View File

@@ -0,0 +1,87 @@
import type { VbenFormProps } from '#/adapter/form';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12', // 24栅格,
commonConfig: {
formItemClass: 'col-span-12',
// 所有表单项
componentProps: {
class: 'w-full',
},
},
// handleSubmit: onSubmit,
layout: 'horizontal',
schema: [
// {
// fieldName: 'baseinfo',
// component: 'Divider',
// label: '基础信息',
// formItemClass: 'col-span-12',
// componentProps: {},
// hideLabel: true,
// renderComponentContent: () => {
// return {
// default: () => {
// return '基础信息';
// },
// };
// },
// },
{
component: 'VbenInput',
fieldName: 'id',
label: 'ID',
formItemClass: 'col-span-6',
dependencies: {
show: false,
triggerFields: ['id'],
},
},
{
component: 'Avatar',
fieldName: 'logo',
label: 'LOGO',
rules: 'required',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '请输入供应商昵称',
},
fieldName: 'name',
label: '供应商名称',
rules: 'required',
},
{
component: 'Textarea',
componentProps: {
placeholder: '请输入供应商介绍',
},
fieldName: 'introduce',
label: '供应商介绍',
rules: 'required',
},
{
component: 'Avatar',
fieldName: 'open_business_license',
label: '营业执照',
rules: 'required',
formItemClass: 'col-span-6',
},
{
component: 'Avatar',
fieldName: 'business_license',
label: '生产许可证',
rules: 'required',
formItemClass: 'col-span-6',
},
{
component: 'Avatar',
fieldName: 'product_registration_certificate',
label: '产品注册证',
formItemClass: 'col-span-6',
rules: 'required',
},
],
showDefaultActions: false,
};

View File

@@ -0,0 +1,37 @@
import type { VbenFormProps } from '#/adapter/form';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
// 默认展开
collapsed: false,
schema: [
{
component: 'VbenInput',
componentProps: {
placeholder: '输入名称',
},
defaultValue: '',
fieldName: 'name',
label: '供应商名称',
},
// {
// component: 'RangePicker',
// componentProps: {
// format: 'YYYY-MM-DD', // 确保格式与你需要的一致
// },
// // defaultValue: [dayjs().startOf('month'), dayjs()],
// fieldName: 'search_time',
// label: '时间范围',
// },
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -0,0 +1,94 @@
import type { VxeGridProps } from '#/adapter/vxe-table';
import { getSupplierList } from '#/views/system/supplier/api';
interface RowType {
id: string;
name: string;
logo: string;
introduce: string;
created_at: string;
}
export const gridOptions: VxeGridProps<RowType> = {
checkboxConfig: {
highlight: true,
labelField: '',
},
columnConfig: {
useKey: true,
},
rowConfig: {
useKey: true,
},
columns: [
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'name', align: 'left', title: '供应商名称' },
{
field: 'logo',
align: 'left',
title: 'LOGO',
slots: { default: 'logo' },
width: 130,
},
{ field: 'introduce', title: '供应商介绍' },
{
field: 'open_business_license',
align: 'left',
title: '营业执照',
slots: { default: 'open_business_license' },
width: 130,
},
{
field: 'business_license',
align: 'left',
title: '生产/经营许可证',
slots: { default: 'business_license' },
width: 130,
},
{
field: 'product_registration_certificate',
align: 'left',
title: '产品注册证',
slots: { default: 'product_registration_certificate' },
width: 130,
},
{ field: 'created_at', title: '注册时间' },
{ type: 'html', title: '操作', slots: { default: 'action' } },
],
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
// 请求后端接口方法
query: async ({ page }, formValues) => {
return await getSupplierList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
height: 'auto',
border: false,
toolbarConfig: {
// 是否显示搜索表单控制按钮
// @ts-ignore 正式环境时有完整的类型声明
search: true,
refresh: true, // 刷新
print: false, // 打印
export: false, // 导出
// custom: true, // 自定义列
zoom: true, // 最大化最小化
slots: {
buttons: 'toolbar-buttons',
},
custom: {
// 自定义列-图标
icon: 'vxe-icon-menu',
},
},
showOverflow: false,
};

View File

@@ -0,0 +1,168 @@
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Button, Image, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import { deleteSupplier } from './api';
import FormModalDemo from './components/modal.vue';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
const deleteApi = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteSupplier({ ids }).then(() => {
message.success('删除成功!');
gridApi.reload();
});
};
</script>
<template>
<Page auto-content-height title="供应商管理">
<FormModal />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
// auth: ['超级供应商', 'sys:user:save'],
onClick: showModal.bind(null),
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
// auth: ['超级供应商', 'sys:user:save'],
popConfirm: {
title: '确定删除吗',
confirm: deleteApi.bind(null, false),
},
},
]"
>
<template #more>
<Button style="margin-left: 16px">
批量操作
<Icon icon="ant-design:down-outlined" />
</Button>
</template>
</TableAction>
</template>
<template #logo="{ row }">
<Image :src="row.logo" height="30" width="30" />
</template>
<template #open_business_license="{ row }">
<Image :src="row.open_business_license" height="30" width="30" />
</template>
<template #business_license="{ row }">
<Image :src="row.business_license" height="30" width="30" />
</template>
<template #product_registration_certificate="{ row }">
<Image
:src="row.product_registration_certificate"
height="30"
width="30"
/>
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
// auth: ['supplier', 'sys:role:detail'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
// auth: ['supplier', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: deleteApi.bind(null, row.id),
},
},
]"
:drop-down-actions="[
// {
// label: '编辑',
// type: 'link',
// icon: 'ant-design:delete-outlined',
// size: 'small',
// // auth: ['supplier', 'sys:role:detail'],
// onClick: showModal.bind(null, row, true),
// },
// {
// label: '删除',
// type: 'link',
// icon: 'ant-design:delete-outlined',
// size: 'small',
// // auth: ['supplier', 'sys:role:detail'],
// popConfirm: {
// title: '确定删除吗',
// confirm: deleteApi.bind(null, row.id),
// },
// },
]"
/>
</template>
</Grid>
</Page>
</template>