fix: 处方列表
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
const prefix = 'prescription/';
|
||||||
|
/**
|
||||||
|
* 分页查询用户列表
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
export async function getPrescriptionListApi(data: any) {
|
||||||
|
return requestClient.get<any>(`${prefix}list`, { params: data });
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import type { VbenFormProps } from '#/adapter/form';
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
export const formOptions: VbenFormProps = {
|
||||||
|
// 默认展开
|
||||||
|
collapsed: false,
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: 'VbenInput',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '输入订单号',
|
||||||
|
},
|
||||||
|
defaultValue: '',
|
||||||
|
fieldName: 'prescription_no',
|
||||||
|
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,
|
||||||
|
};
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { getPrescriptionListApi } from '#/views/business/prescriptions/prescription/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 },
|
||||||
|
{ field: 'id', align: 'left', title: 'ID' },
|
||||||
|
{ field: 'prescription_no', align: 'left', title: '处方订单号' },
|
||||||
|
{ field: 'doctor_info.name', align: 'left', title: '开方医生' },
|
||||||
|
{ field: 'user_patient.name', align: 'left', title: '就诊人名称' },
|
||||||
|
{ field: 'store.name', align: 'left', title: '开方诊所' },
|
||||||
|
{ field: 'status', title: '状态', slots: { default: 'status' } },
|
||||||
|
// { 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 getPrescriptionListApi({
|
||||||
|
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,
|
||||||
|
};
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { VxeGridListeners } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AnalysisOverview,
|
||||||
|
type AnalysisOverviewItem,
|
||||||
|
Page,
|
||||||
|
useVbenModal,
|
||||||
|
} from '@vben/common-ui';
|
||||||
|
import { SvgCakeIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Button, Image, Tag } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { TableAction } from '#/components/table-action';
|
||||||
|
import PrescrtionDetail from '#/views/doctor/doctor-reception/components/PrescrtionDetail.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 infoModal = (data = {}) => {
|
||||||
|
modalApi.setData({
|
||||||
|
// 表单值
|
||||||
|
values: data,
|
||||||
|
gridApi,
|
||||||
|
});
|
||||||
|
modalApi.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);
|
||||||
|
};
|
||||||
|
const overviewItems = ref<AnalysisOverviewItem[]>([]);
|
||||||
|
|
||||||
|
const [PrescrtionDetailModal, PrescrtionDetailModalApi] = useVbenModal({
|
||||||
|
connectedComponent: PrescrtionDetail,
|
||||||
|
});
|
||||||
|
const openPrescriptionDetail = (values) => {
|
||||||
|
// 打开西药处方模态框逻辑
|
||||||
|
PrescrtionDetailModalApi.setData({
|
||||||
|
values,
|
||||||
|
});
|
||||||
|
PrescrtionDetailModalApi.open();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height title="订单管理">
|
||||||
|
<PrescrtionDetailModal />
|
||||||
|
<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 #status="{ row }">
|
||||||
|
<div>
|
||||||
|
<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.status === 0" color="red">待审核</Tag>
|
||||||
|
<Tag v-else-if="row.status === 1" color="green">通过审核</Tag>
|
||||||
|
<Tag v-else-if="row.status === 2" color="red">拒绝审核</Tag>
|
||||||
|
<Tag v-else-if="row.status === 3" color="purple">无需审核</Tag>
|
||||||
|
<Tag v-else-if="row.status === 4" color="green">无需审核</Tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #toolbar-tools></template>
|
||||||
|
<template #action="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: '查看处方',
|
||||||
|
type: 'link',
|
||||||
|
icon: 'ri:send-plane-fill',
|
||||||
|
// auth: ['超级订单', 'sys:user:save'],
|
||||||
|
onClick: openPrescriptionDetail.bind(null, row.id),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
:drop-down-actions="[
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
@@ -167,9 +167,8 @@ const decrypt = (str: string) => str; // 简单base64解码示例
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.prescription_type === 2">
|
<div v-else-if="item.prescription_type === 2 || item.prescription_type === 5">
|
||||||
<div class="medicine-item">
|
<div class="medicine-item">
|
||||||
<!-- {{JSON.parse(recipe.content)}}-->
|
|
||||||
<span class="drug-name">{{
|
<span class="drug-name">{{
|
||||||
JSON.parse(recipe.content).name ||
|
JSON.parse(recipe.content).name ||
|
||||||
JSON.parse(recipe.content).drug_name
|
JSON.parse(recipe.content).drug_name
|
||||||
@@ -182,15 +181,7 @@ const decrypt = (str: string) => str; // 简单base64解码示例
|
|||||||
>
|
>
|
||||||
{{ JSON.parse(recipe.content).useWay }}
|
{{ JSON.parse(recipe.content).useWay }}
|
||||||
</div>
|
</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>
|
||||||
<!-- <div class="preparation-info">-->
|
|
||||||
<!-- 煎服方法: 每日{{ recipe.deployment }}次,共{{ recipe.dosage }}剂-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<div class="preparation-info">
|
<div class="preparation-info">
|
||||||
使用方法: {{ recipe.instruction }}
|
使用方法: {{ recipe.instruction }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,311 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
|
||||||
import { useTabs } from '@vben/hooks';
|
|
||||||
|
|
||||||
import {Button} from 'ant-design-vue'
|
|
||||||
|
|
||||||
import {
|
|
||||||
ArrowLeft,
|
|
||||||
Bell,
|
|
||||||
CalendarClock,
|
|
||||||
Clock,
|
|
||||||
Megaphone,
|
|
||||||
User,
|
|
||||||
} from 'lucide-vue-next';
|
|
||||||
|
|
||||||
import { formatTimeToRelative } from '#/util/tool';
|
|
||||||
import { getNoticeDetailApi } from '#/views/notice/api';
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const route = useRoute();
|
|
||||||
const noticeId = ref(route.params.id);
|
|
||||||
|
|
||||||
const notice = ref(null);
|
|
||||||
const loading = ref(true);
|
|
||||||
const error = ref(null);
|
|
||||||
|
|
||||||
const { setTabTitle } = useTabs();
|
|
||||||
|
|
||||||
// 消息类型定义
|
|
||||||
const messageTypes = [
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
name: '系统公告',
|
|
||||||
icon: Megaphone,
|
|
||||||
color: 'text-sky-500',
|
|
||||||
darkColor: 'dark:text-sky-400',
|
|
||||||
bgColor: 'bg-sky-50',
|
|
||||||
darkBgColor: 'dark:bg-sky-900/30',
|
|
||||||
borderColor: 'border-sky-200',
|
|
||||||
darkBorderColor: 'dark:border-sky-800',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: '系统通知',
|
|
||||||
icon: Bell,
|
|
||||||
color: 'text-amber-500',
|
|
||||||
darkColor: 'dark:text-amber-400',
|
|
||||||
bgColor: 'bg-amber-50',
|
|
||||||
darkBgColor: 'dark:bg-amber-900/30',
|
|
||||||
borderColor: 'border-amber-200',
|
|
||||||
darkBorderColor: 'dark:border-amber-800',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: '周报提醒',
|
|
||||||
icon: CalendarClock,
|
|
||||||
color: 'text-emerald-500',
|
|
||||||
darkColor: 'dark:text-emerald-400',
|
|
||||||
bgColor: 'bg-emerald-50',
|
|
||||||
darkBgColor: 'dark:bg-emerald-900/30',
|
|
||||||
borderColor: 'border-emerald-200',
|
|
||||||
darkBorderColor: 'dark:border-emerald-800',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// 获取通知详情
|
|
||||||
const getNoticeDetail = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
error.value = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await getNoticeDetailApi(noticeId.value);
|
|
||||||
notice.value = res;
|
|
||||||
setTabTitle(`通知详情-【${res.id}】`);
|
|
||||||
loading.value = false;
|
|
||||||
} catch (error_) {
|
|
||||||
console.error('获取通知详情失败:', error_);
|
|
||||||
error.value = '获取通知详情失败,请稍后重试';
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 返回列表
|
|
||||||
const goBack = () => {
|
|
||||||
router.push('/notice');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取类型信息
|
|
||||||
const getTypeInfo = (typeId) => {
|
|
||||||
return messageTypes.find((t) => t.id === typeId) || messageTypes[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
// 格式化内容,将换行符转换为<br>
|
|
||||||
const formatContent = (content) => {
|
|
||||||
return content ? content.replaceAll('\n', '<br>') : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getNoticeDetail();
|
|
||||||
});
|
|
||||||
|
|
||||||
const showUnreadList = () => {
|
|
||||||
alert('未实现')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Page>
|
|
||||||
<template #title>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<button
|
|
||||||
class="flex h-8 w-8 items-center justify-center rounded-full text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300"
|
|
||||||
@click="goBack"
|
|
||||||
>
|
|
||||||
<ArrowLeft class="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
<h1 class="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
|
||||||
通知详情
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="mx-auto max-w-3xl">
|
|
||||||
<!-- 加载状态 -->
|
|
||||||
<div v-if="loading" class="flex justify-center py-20">
|
|
||||||
<div
|
|
||||||
class="h-10 w-10 animate-spin rounded-full border-4 border-gray-200 border-t-sky-500 dark:border-gray-700 dark:border-t-sky-400"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 错误状态 -->
|
|
||||||
<div
|
|
||||||
v-else-if="error"
|
|
||||||
class="rounded-lg border border-red-200 bg-red-50 p-4 text-center text-red-600 transition-colors dark:border-red-800 dark:bg-red-900/20 dark:text-red-400"
|
|
||||||
>
|
|
||||||
{{ error }}
|
|
||||||
<button
|
|
||||||
class="mt-2 rounded-md bg-red-100 px-3 py-1 text-sm font-medium text-red-700 transition-colors hover:bg-red-200 dark:bg-red-900/30 dark:text-red-300 dark:hover:bg-red-900/50"
|
|
||||||
@click="getNoticeDetail"
|
|
||||||
>
|
|
||||||
重试
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 通知详情 -->
|
|
||||||
<div v-else-if="notice" class="space-y-6">
|
|
||||||
<!-- 标题和类型 -->
|
|
||||||
<div
|
|
||||||
class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-colors dark:border-gray-700 dark:bg-gray-800"
|
|
||||||
>
|
|
||||||
<div class="mb-4 flex items-start justify-between">
|
|
||||||
<h2
|
|
||||||
class="text-xl font-bold text-gray-900 transition-colors dark:text-gray-100"
|
|
||||||
>
|
|
||||||
{{ notice.title }}
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
getTypeInfo(notice.type).bgColor,
|
|
||||||
getTypeInfo(notice.type).darkBgColor,
|
|
||||||
]"
|
|
||||||
class="flex items-center gap-1.5 rounded-full px-3 py-1 transition-colors"
|
|
||||||
>
|
|
||||||
<component
|
|
||||||
:is="getTypeInfo(notice.type).icon"
|
|
||||||
:class="[
|
|
||||||
getTypeInfo(notice.type).color,
|
|
||||||
getTypeInfo(notice.type).darkColor,
|
|
||||||
]"
|
|
||||||
class="h-4 w-4"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
:class="[
|
|
||||||
getTypeInfo(notice.type).color,
|
|
||||||
getTypeInfo(notice.type).darkColor,
|
|
||||||
]"
|
|
||||||
class="text-xs font-medium"
|
|
||||||
>
|
|
||||||
{{ getTypeInfo(notice.type).name }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 元信息 -->
|
|
||||||
<div
|
|
||||||
class="mb-6 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-gray-500 transition-colors dark:text-gray-400"
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-1.5">
|
|
||||||
来自:
|
|
||||||
<User class="h-4 w-4" />
|
|
||||||
<span>萧康云医</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center gap-1.5">
|
|
||||||
收件人:
|
|
||||||
<User class="h-4 w-4" />
|
|
||||||
<span>{{ notice.admin?.nick_name || '系统' }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="mb-6 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-gray-500 transition-colors dark:text-gray-400"
|
|
||||||
>
|
|
||||||
<div v-html="formatContent(notice.detail)"></div>
|
|
||||||
<div class="flex items-center gap-1.5" style="margin-left: auto">
|
|
||||||
<Clock class="h-4 w-4" />
|
|
||||||
<span>{{ notice.created_at }}({{
|
|
||||||
formatTimeToRelative(notice.created_at)
|
|
||||||
}})</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分隔线 -->
|
|
||||||
<div
|
|
||||||
class="mb-6 h-px w-full bg-gray-100 transition-colors dark:bg-gray-700"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<!-- 内容 -->
|
|
||||||
<div
|
|
||||||
class="rounded-lg bg-gray-50 p-4 transition-colors dark:bg-gray-700"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="notice.edit_type === 0"
|
|
||||||
class="prose prose-sm dark:prose-invert max-w-none text-gray-700 transition-colors dark:text-gray-300"
|
|
||||||
v-html="formatContent(notice.content)"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<button
|
|
||||||
class="flex items-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm transition-colors hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
|
||||||
@click="goBack"
|
|
||||||
>
|
|
||||||
<ArrowLeft class="h-4 w-4" />
|
|
||||||
返回列表
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<!-- 这里可以添加其他操作按钮,如删除、转发等 -->
|
|
||||||
|
|
||||||
<button
|
|
||||||
v-if="notice.is_my === 1"
|
|
||||||
class="flex items-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm transition-colors hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
|
||||||
@click="showUnreadList"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
notice.is_all_read === 1
|
|
||||||
? '全部已读'
|
|
||||||
: `有 ${notice.unread_count} 人未读`
|
|
||||||
}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 通知不存在 -->
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="rounded-lg border border-gray-200 bg-white p-8 text-center shadow-sm transition-colors dark:border-gray-700 dark:bg-gray-800"
|
|
||||||
>
|
|
||||||
<Bell class="mx-auto mb-4 h-12 w-12 text-gray-300 dark:text-gray-600" />
|
|
||||||
<h3
|
|
||||||
class="mb-2 text-lg font-medium text-gray-900 transition-colors dark:text-gray-100"
|
|
||||||
>
|
|
||||||
通知不存在
|
|
||||||
</h3>
|
|
||||||
<p class="mb-4 text-gray-500 transition-colors dark:text-gray-400">
|
|
||||||
该通知可能已被删除或您没有权限查看
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
class="rounded-md bg-sky-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-sky-600 dark:bg-sky-600 dark:hover:bg-sky-500"
|
|
||||||
@click="goBack"
|
|
||||||
>
|
|
||||||
返回通知列表
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Page>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* 平滑过渡效果 */
|
|
||||||
.fade-enter-active,
|
|
||||||
.fade-leave-active {
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-enter-from,
|
|
||||||
.fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 确保内容中的换行正确显示 */
|
|
||||||
:deep(.prose) {
|
|
||||||
white-space: pre-line;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 暗黑模式特有样式 */
|
|
||||||
.dark .bg-gray-750 {
|
|
||||||
background-color: #1e293b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .bg-gray-650 {
|
|
||||||
background-color: #334155;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -40,10 +40,6 @@ const typeOption = ref([
|
|||||||
label: '公告',
|
label: '公告',
|
||||||
value: 0,
|
value: 0,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: '周报',
|
|
||||||
value: 2,
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 用户选项数据
|
// 用户选项数据
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
Input,
|
Input,
|
||||||
message,
|
message,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
Select,
|
Select,
|
||||||
SelectOption,
|
SelectOption,
|
||||||
Textarea,
|
Textarea,
|
||||||
@@ -17,20 +19,23 @@ import {
|
|||||||
import { uploadFile } from '#/api/core/upload';
|
import { uploadFile } from '#/api/core/upload';
|
||||||
import { getUserOptionApi, sendNoticeApi } from '#/views/notice/api';
|
import { getUserOptionApi, sendNoticeApi } from '#/views/notice/api';
|
||||||
|
|
||||||
|
// Import Markdown editor (using a popular Vue 3 Markdown editor)
|
||||||
|
import { MdEditor } from 'md-editor-v3';
|
||||||
|
|
||||||
|
import 'md-editor-v3/lib/style.css';
|
||||||
|
|
||||||
// eslint-disable-next-line n/no-extraneous-import
|
// eslint-disable-next-line n/no-extraneous-import
|
||||||
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
||||||
|
|
||||||
// 基础数据定义
|
// 基础数据定义
|
||||||
const title = ref('');
|
const title = ref('');
|
||||||
const detail = ref('');
|
const detail = ref('总结本周任务,计划下周任务');
|
||||||
const content = ref('');
|
const content = ref('');
|
||||||
const type = ref(0);
|
const markdownContent = ref(''); // For 1 content
|
||||||
|
const type = ref(2);
|
||||||
const userIds = ref([]);
|
const userIds = ref([]);
|
||||||
|
const editorMode = ref(1); // 0 or 1
|
||||||
const typeOption = ref([
|
const typeOption = ref([
|
||||||
{
|
|
||||||
label: '公告',
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: '周报',
|
label: '周报',
|
||||||
value: 2,
|
value: 2,
|
||||||
@@ -51,21 +56,59 @@ const getUserOption = () => {
|
|||||||
};
|
};
|
||||||
getUserOption();
|
getUserOption();
|
||||||
|
|
||||||
// 发送消息的函数
|
// Watch for editor mode changes to convert content between formats
|
||||||
|
watch(editorMode, (newMode, oldMode) => {
|
||||||
|
if (newMode === oldMode) return;
|
||||||
|
|
||||||
|
if (newMode === 1 && oldMode === 0) {
|
||||||
|
// Convert HTML to Markdown (simplified conversion)
|
||||||
|
// In a real app, you would use a proper HTML-to-Markdown converter library
|
||||||
|
try {
|
||||||
|
// This is a placeholder - you should use a proper library like turndown
|
||||||
|
const tempDiv = document.createElement('div');
|
||||||
|
tempDiv.innerHTML = content.value;
|
||||||
|
markdownContent.value = tempDiv.textContent || '';
|
||||||
|
message.info('已切换到Markdown编辑模式');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('转换HTML到Markdown失败:', error);
|
||||||
|
}
|
||||||
|
} else if (newMode === 0 && oldMode === 1) {
|
||||||
|
// Convert Markdown to HTML (simplified conversion)
|
||||||
|
// In a real app, you would use a proper Markdown-to-HTML converter library
|
||||||
|
try {
|
||||||
|
// This is a placeholder - you should use a proper library like marked
|
||||||
|
content.value = markdownContent.value;
|
||||||
|
message.info('已切换到富文本编辑模式');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('转换Markdown到HTML失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送周报的函数
|
||||||
const send = () => {
|
const send = () => {
|
||||||
|
// Use the appropriate content based on editor mode
|
||||||
|
const finalContent =
|
||||||
|
editorMode.value === 1 ? markdownContent.value : content.value;
|
||||||
|
|
||||||
sendNoticeApi({
|
sendNoticeApi({
|
||||||
title: title.value,
|
title: title.value,
|
||||||
type: type.value,
|
type: type.value,
|
||||||
|
edit_type: editorMode.value,
|
||||||
detail: detail.value,
|
detail: detail.value,
|
||||||
content: content.value,
|
content: finalContent,
|
||||||
|
content_type: editorMode.value, // Add content type to API
|
||||||
user_ids: userIds.value,
|
user_ids: userIds.value,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
detail.value = '';
|
detail.value = '';
|
||||||
content.value = '';
|
content.value = '';
|
||||||
|
markdownContent.value = '';
|
||||||
userIds.value = [];
|
userIds.value = [];
|
||||||
title.value = '';
|
title.value = '';
|
||||||
// 清除富文本编辑器内容
|
// 清除富文本编辑器内容
|
||||||
quillEditorRef.value?.getQuill().setContents([]);
|
if (quillEditorRef.value?.getQuill()) {
|
||||||
|
quillEditorRef.value.getQuill().setContents([]);
|
||||||
|
}
|
||||||
message.success('发送成功');
|
message.success('发送成功');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -92,7 +135,7 @@ const editorOptions = {
|
|||||||
['link', 'image', 'video'],
|
['link', 'image', 'video'],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
placeholder: '请输入消息内容',
|
placeholder: '请输入周报内容',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -112,7 +155,11 @@ const uploadImage = async (file: File): Promise<string> => {
|
|||||||
return uploadFile({
|
return uploadFile({
|
||||||
file,
|
file,
|
||||||
}).then((data: any) => {
|
}).then((data: any) => {
|
||||||
message.success({ content: '图片上传成功', key: 'imageUpload', duration: 2 });
|
message.success({
|
||||||
|
content: '图片上传成功',
|
||||||
|
key: 'imageUpload',
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
isUpload.value = false;
|
isUpload.value = false;
|
||||||
return data.url;
|
return data.url;
|
||||||
});
|
});
|
||||||
@@ -169,6 +216,26 @@ const insertImageToEditor = (quill: any, imageUrl: string): void => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理Markdown编辑器的图片上传
|
||||||
|
const handleMdImageUpload = async (
|
||||||
|
files: FileList,
|
||||||
|
callback: (urls: string[]) => void,
|
||||||
|
) => {
|
||||||
|
if (!files || files.length === 0) return;
|
||||||
|
|
||||||
|
const urls = [];
|
||||||
|
for (const file of files) {
|
||||||
|
if (isUpload.value === true) continue;
|
||||||
|
|
||||||
|
isUpload.value = true;
|
||||||
|
const url = await uploadImage(file);
|
||||||
|
if (url) urls.push(url);
|
||||||
|
isUpload.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(urls);
|
||||||
|
};
|
||||||
|
|
||||||
// 组件挂载后设置剪贴板事件监听
|
// 组件挂载后设置剪贴板事件监听
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 等待DOM更新后获取编辑器元素
|
// 等待DOM更新后获取编辑器元素
|
||||||
@@ -196,7 +263,7 @@ onMounted(() => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isUpload.value = true;
|
isUpload.value = true;
|
||||||
alert('2')
|
|
||||||
// 上传图片到服务器
|
// 上传图片到服务器
|
||||||
const imageUrl = await uploadImage(imageFile);
|
const imageUrl = await uploadImage(imageFile);
|
||||||
|
|
||||||
@@ -262,15 +329,16 @@ const setupQuillPasteHandler = () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page title="通知群发">
|
<Page title="通知群发">
|
||||||
<Card class="w-1/2" style="margin: 0 auto" title="发送消息">
|
<Card class="w-1/2" style="margin: 0 auto" title="发送周报">
|
||||||
<!-- 消息标题输入框 -->
|
<!-- 周报标题输入框 -->
|
||||||
<Input v-model:value="title" placeholder="请输入消息标题" />
|
<Input v-model:value="title" placeholder="请输入周报标题" />
|
||||||
|
|
||||||
<!-- 消息类型选择器 -->
|
<!-- 周报类型选择器 -->
|
||||||
<Select
|
<Select
|
||||||
v-model:value="type"
|
v-model:value="type"
|
||||||
class="mt-5 w-full"
|
class="mt-5 w-full"
|
||||||
placeholder="请选择消息类型"
|
placeholder="请选择周报类型"
|
||||||
|
disabled
|
||||||
>
|
>
|
||||||
<template v-for="item in typeOption" :key="item.value">
|
<template v-for="item in typeOption" :key="item.value">
|
||||||
<SelectOption :value="item.value">{{ item.label }}</SelectOption>
|
<SelectOption :value="item.value">{{ item.label }}</SelectOption>
|
||||||
@@ -283,27 +351,30 @@ const setupQuillPasteHandler = () => {
|
|||||||
v-model:value="userIds"
|
v-model:value="userIds"
|
||||||
class="mt-5 w-full"
|
class="mt-5 w-full"
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="请选择接受消息的用户"
|
placeholder="请选择接受周报的用户"
|
||||||
>
|
>
|
||||||
<template v-for="item in userOption" :key="item.value">
|
<template v-for="item in userOption" :key="item.value">
|
||||||
<SelectOption :value="item.id">{{ item.nick_name }}</SelectOption>
|
<SelectOption :value="item.id">{{ item.nick_name }}</SelectOption>
|
||||||
</template>
|
</template>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<!-- 消息简介输入框 -->
|
<!-- 周报简介输入框 -->
|
||||||
<Textarea
|
<Textarea
|
||||||
v-model:value="detail"
|
v-model:value="detail"
|
||||||
class="mt-5"
|
class="mt-5"
|
||||||
placeholder="请输入消息简介"
|
placeholder="请输入周报简介"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 富文本编辑器 -->
|
<!-- 编辑器类型选择 -->
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<!--
|
<RadioGroup v-model:value="editorMode" button-style="solid">
|
||||||
使用ref属性获取编辑器实例
|
<Radio :value="0">富文本编辑器</Radio>
|
||||||
content-type="html"表示内容以HTML格式存储
|
<Radio :value="1">Markdown编辑器</Radio>
|
||||||
theme="snow"使用雪主题样式
|
</RadioGroup>
|
||||||
-->
|
</div>
|
||||||
|
|
||||||
|
<!-- 富文本编辑器 - 仅在富文本模式下显示 -->
|
||||||
|
<div v-if="editorMode === 0" class="mt-5">
|
||||||
<QuillEditor
|
<QuillEditor
|
||||||
ref="quillEditorRef"
|
ref="quillEditorRef"
|
||||||
v-model:content="content"
|
v-model:content="content"
|
||||||
@@ -315,6 +386,28 @@ const setupQuillPasteHandler = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Markdown编辑器 - 仅在Markdown模式下显示 -->
|
||||||
|
<div v-if="editorMode === 1" class="mt-5">
|
||||||
|
<!-- <MdEditor-->
|
||||||
|
<!-- v-model="markdownContent"-->
|
||||||
|
<!-- :theme="prefersDarkMode ? 'dark' : 'light'"-->
|
||||||
|
<!-- :toolbars-exclude="['github']"-->
|
||||||
|
<!-- class="md-editor-container"-->
|
||||||
|
<!-- editor-id="notice-md-editor"-->
|
||||||
|
<!-- preview-only-->
|
||||||
|
<!-- @on-upload-img="handleMdImageUpload"-->
|
||||||
|
<!-- />-->
|
||||||
|
<MdEditor
|
||||||
|
v-model="markdownContent"
|
||||||
|
:toolbars-exclude="['github']"
|
||||||
|
class="md-editor-container"
|
||||||
|
editor-id="notice-md-editor"
|
||||||
|
preview-only
|
||||||
|
theme="dark"
|
||||||
|
@on-upload-img="handleMdImageUpload"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 发送按钮 -->
|
<!-- 发送按钮 -->
|
||||||
<Button class="mt-5 w-full" type="primary" @click="send">发送</Button>
|
<Button class="mt-5 w-full" type="primary" @click="send">发送</Button>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -329,6 +422,12 @@ const setupQuillPasteHandler = () => {
|
|||||||
/* 确保编辑器有足够的空间显示工具栏和内容区域 */
|
/* 确保编辑器有足够的空间显示工具栏和内容区域 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Markdown编辑器样式 */
|
||||||
|
.md-editor-container {
|
||||||
|
min-height: 400px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
/* 可以添加额外的样式来自定义编辑器外观 */
|
/* 可以添加额外的样式来自定义编辑器外观 */
|
||||||
:deep(.ql-editor) {
|
:deep(.ql-editor) {
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
@@ -341,4 +440,14 @@ const setupQuillPasteHandler = () => {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 自定义 Markdown 编辑器样式 */
|
||||||
|
:deep(.md-editor) {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.md-editor-dark) {
|
||||||
|
--md-bk-color: #1e1e1e;
|
||||||
|
--md-border-color: #333;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user