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>
|
||||
</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">
|
||||
<!-- {{JSON.parse(recipe.content)}}-->
|
||||
<span class="drug-name">{{
|
||||
JSON.parse(recipe.content).name ||
|
||||
JSON.parse(recipe.content).drug_name
|
||||
@@ -182,15 +181,7 @@ const decrypt = (str: string) => str; // 简单base64解码示例
|
||||
>
|
||||
{{ 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>
|
||||
|
||||
@@ -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: '公告',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '周报',
|
||||
value: 2,
|
||||
},
|
||||
]);
|
||||
|
||||
// 用户选项数据
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
Card,
|
||||
Input,
|
||||
message,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Select,
|
||||
SelectOption,
|
||||
Textarea,
|
||||
@@ -17,20 +19,23 @@ import {
|
||||
import { uploadFile } from '#/api/core/upload';
|
||||
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
|
||||
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
||||
|
||||
// 基础数据定义
|
||||
const title = ref('');
|
||||
const detail = ref('');
|
||||
const detail = ref('总结本周任务,计划下周任务');
|
||||
const content = ref('');
|
||||
const type = ref(0);
|
||||
const markdownContent = ref(''); // For 1 content
|
||||
const type = ref(2);
|
||||
const userIds = ref([]);
|
||||
const editorMode = ref(1); // 0 or 1
|
||||
const typeOption = ref([
|
||||
{
|
||||
label: '公告',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '周报',
|
||||
value: 2,
|
||||
@@ -51,21 +56,59 @@ const 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 = () => {
|
||||
// Use the appropriate content based on editor mode
|
||||
const finalContent =
|
||||
editorMode.value === 1 ? markdownContent.value : content.value;
|
||||
|
||||
sendNoticeApi({
|
||||
title: title.value,
|
||||
type: type.value,
|
||||
edit_type: editorMode.value,
|
||||
detail: detail.value,
|
||||
content: content.value,
|
||||
content: finalContent,
|
||||
content_type: editorMode.value, // Add content type to API
|
||||
user_ids: userIds.value,
|
||||
}).then(() => {
|
||||
detail.value = '';
|
||||
content.value = '';
|
||||
markdownContent.value = '';
|
||||
userIds.value = [];
|
||||
title.value = '';
|
||||
// 清除富文本编辑器内容
|
||||
quillEditorRef.value?.getQuill().setContents([]);
|
||||
if (quillEditorRef.value?.getQuill()) {
|
||||
quillEditorRef.value.getQuill().setContents([]);
|
||||
}
|
||||
message.success('发送成功');
|
||||
});
|
||||
};
|
||||
@@ -92,7 +135,7 @@ const editorOptions = {
|
||||
['link', 'image', 'video'],
|
||||
],
|
||||
},
|
||||
placeholder: '请输入消息内容',
|
||||
placeholder: '请输入周报内容',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -112,7 +155,11 @@ const uploadImage = async (file: File): Promise<string> => {
|
||||
return uploadFile({
|
||||
file,
|
||||
}).then((data: any) => {
|
||||
message.success({ content: '图片上传成功', key: 'imageUpload', duration: 2 });
|
||||
message.success({
|
||||
content: '图片上传成功',
|
||||
key: 'imageUpload',
|
||||
duration: 2,
|
||||
});
|
||||
isUpload.value = false;
|
||||
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(() => {
|
||||
// 等待DOM更新后获取编辑器元素
|
||||
@@ -196,7 +263,7 @@ onMounted(() => {
|
||||
return;
|
||||
}
|
||||
isUpload.value = true;
|
||||
alert('2')
|
||||
|
||||
// 上传图片到服务器
|
||||
const imageUrl = await uploadImage(imageFile);
|
||||
|
||||
@@ -262,15 +329,16 @@ const setupQuillPasteHandler = () => {
|
||||
|
||||
<template>
|
||||
<Page title="通知群发">
|
||||
<Card class="w-1/2" style="margin: 0 auto" title="发送消息">
|
||||
<!-- 消息标题输入框 -->
|
||||
<Input v-model:value="title" placeholder="请输入消息标题" />
|
||||
<Card class="w-1/2" style="margin: 0 auto" title="发送周报">
|
||||
<!-- 周报标题输入框 -->
|
||||
<Input v-model:value="title" placeholder="请输入周报标题" />
|
||||
|
||||
<!-- 消息类型选择器 -->
|
||||
<!-- 周报类型选择器 -->
|
||||
<Select
|
||||
v-model:value="type"
|
||||
class="mt-5 w-full"
|
||||
placeholder="请选择消息类型"
|
||||
placeholder="请选择周报类型"
|
||||
disabled
|
||||
>
|
||||
<template v-for="item in typeOption" :key="item.value">
|
||||
<SelectOption :value="item.value">{{ item.label }}</SelectOption>
|
||||
@@ -283,27 +351,30 @@ const setupQuillPasteHandler = () => {
|
||||
v-model:value="userIds"
|
||||
class="mt-5 w-full"
|
||||
mode="multiple"
|
||||
placeholder="请选择接受消息的用户"
|
||||
placeholder="请选择接受周报的用户"
|
||||
>
|
||||
<template v-for="item in userOption" :key="item.value">
|
||||
<SelectOption :value="item.id">{{ item.nick_name }}</SelectOption>
|
||||
</template>
|
||||
</Select>
|
||||
|
||||
<!-- 消息简介输入框 -->
|
||||
<!-- 周报简介输入框 -->
|
||||
<Textarea
|
||||
v-model:value="detail"
|
||||
class="mt-5"
|
||||
placeholder="请输入消息简介"
|
||||
placeholder="请输入周报简介"
|
||||
/>
|
||||
|
||||
<!-- 富文本编辑器 -->
|
||||
<!-- 编辑器类型选择 -->
|
||||
<div class="mt-5">
|
||||
<!--
|
||||
使用ref属性获取编辑器实例
|
||||
content-type="html"表示内容以HTML格式存储
|
||||
theme="snow"使用雪主题样式
|
||||
-->
|
||||
<RadioGroup v-model:value="editorMode" button-style="solid">
|
||||
<Radio :value="0">富文本编辑器</Radio>
|
||||
<Radio :value="1">Markdown编辑器</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<!-- 富文本编辑器 - 仅在富文本模式下显示 -->
|
||||
<div v-if="editorMode === 0" class="mt-5">
|
||||
<QuillEditor
|
||||
ref="quillEditorRef"
|
||||
v-model:content="content"
|
||||
@@ -315,6 +386,28 @@ const setupQuillPasteHandler = () => {
|
||||
/>
|
||||
</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>
|
||||
</Card>
|
||||
@@ -329,6 +422,12 @@ const setupQuillPasteHandler = () => {
|
||||
/* 确保编辑器有足够的空间显示工具栏和内容区域 */
|
||||
}
|
||||
|
||||
/* Markdown编辑器样式 */
|
||||
.md-editor-container {
|
||||
min-height: 400px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
/* 可以添加额外的样式来自定义编辑器外观 */
|
||||
:deep(.ql-editor) {
|
||||
min-height: 200px;
|
||||
@@ -341,4 +440,14 @@ const setupQuillPasteHandler = () => {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* 自定义 Markdown 编辑器样式 */
|
||||
:deep(.md-editor) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.md-editor-dark) {
|
||||
--md-bk-color: #1e1e1e;
|
||||
--md-border-color: #333;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user