进度保存

master
2358328281@qq.com 2 months ago
parent 9325c26061
commit 6b7f627c7c

@ -4,8 +4,8 @@
<!-- 管理端显示 Logo / 医院端显示医院名 --> <!-- 管理端显示 Logo / 医院端显示医院名 -->
<div class="logo-bar"> <div class="logo-bar">
<span v-if="!isHospitalMode" class="logo-text"> CSM<span>v1.0</span> </span> <span v-if="!isHospitalMode" class="logo-text admin"> 康策CSM系统管理平台<span class="version">v1.0</span> </span>
<span v-else class="logo-text">{{ userStore.userInfo?.hospitalName || "医院端" }}</span> <span v-else class="logo-text hospital">{{ userStore.userInfo?.hospitalName || "医院端" }}</span>
</div> </div>
<div class="header-middle"> <div class="header-middle">
@ -322,6 +322,11 @@ const handleCommand = async (cmd) => {
background: linear-gradient(to right, #3fc5fd, #3b90fa); background: linear-gradient(to right, #3fc5fd, #3b90fa);
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
font-size: 22px;
.version {
font-size: 14px;
}
} }
} }

@ -79,25 +79,13 @@ export const constantRoutes = [
component: () => import("@/views/admin/hospitals/list.vue"), component: () => import("@/views/admin/hospitals/list.vue"),
meta: { title: "医院信息", icon: "admin-router-yiyuan-xx" }, meta: { title: "医院信息", icon: "admin-router-yiyuan-xx" },
}, },
// 客户服务(必须在 hospitals/detail/:id 之前声明) // 客户服务
{ {
path: "hospitals/service", path: "hospitals/service",
name: "AdminHospitalService", name: "AdminHospitalService",
component: () => import("@/views/admin/hospitals/service.vue"), component: () => import("@/views/admin/hospitals/service.vue"),
meta: { title: "客户服务", icon: "admin-router-kehu-fw" }, meta: { title: "客户服务", icon: "admin-router-kehu-fw" },
}, },
{
path: "hospitals/detail/:id",
name: "AdminHospitalDetail",
component: () => import("@/views/admin/hospitals/detail.vue"),
meta: { title: "医院详情", hidden: true },
},
{
path: "hospitals/edit/:id?",
name: "AdminHospitalEdit",
component: () => import("@/views/admin/hospitals/edit.vue"),
meta: { title: "编辑医院", hidden: true },
},
// 客户经理积分 // 客户经理积分
{ {
path: "performance/score", path: "performance/score",

@ -0,0 +1,73 @@
import dayjs from "dayjs";
import isoWeek from "dayjs/plugin/isoWeek";
import "dayjs/locale/zh-cn";
dayjs.extend(isoWeek);
dayjs.locale("zh-cn");
const fmt = "YYYY-MM-DD";
/**
* 日期范围快捷选项(用于 el-date-picker type="daterange" :shortcuts)
* 周定义:周一为一周开始(基于 ISO week)
*/
export const dateRangeShortcuts = [
{
text: "今天",
value: () => {
const d = dayjs().format(fmt);
return [d, d];
},
},
{
text: "昨天",
value: () => {
const d = dayjs().subtract(1, "day").format(fmt);
return [d, d];
},
},
{
text: "本周",
value: () => [
dayjs().startOf("isoWeek").format(fmt),
dayjs().endOf("isoWeek").format(fmt),
],
},
{
text: "上周",
value: () => [
dayjs().subtract(1, "isoWeek").startOf("isoWeek").format(fmt),
dayjs().subtract(1, "isoWeek").endOf("isoWeek").format(fmt),
],
},
{
text: "本月",
value: () => [
dayjs().startOf("month").format(fmt),
dayjs().endOf("month").format(fmt),
],
},
{
text: "上月",
value: () => [
dayjs().subtract(1, "month").startOf("month").format(fmt),
dayjs().subtract(1, "month").endOf("month").format(fmt),
],
},
{
text: "最近六个月",
value: () => [
dayjs().subtract(6, "month").format(fmt),
dayjs().format(fmt),
],
},
{
text: "今年至今",
value: () => [
dayjs().startOf("year").format(fmt),
dayjs().format(fmt),
],
},
];
export default dateRangeShortcuts;

@ -0,0 +1,152 @@
<template>
<el-dialog :model-value="visible" class="hospital-detail-dialog" top="6vh" width="880px" destroy-on-close
:close-on-click-modal="false" @update:model-value="(v) => !v && close()">
<div class="body-wrap">
<div class="body-header">
<span class="body-title">
{{ $t("modal.detailHospital") }}
</span>
<img src="@/assets/image/close-btn.svg" alt="" class="close-btn" @click="close">
</div>
<div class="hospital-detail" v-loading="loading">
<el-card shadow="never" v-if="detail.id">
<template #header>
<div class="card-header">
<span class="title">{{ detail.name }}</span>
<el-tag size="small">
{{ detail.customerCategory }}
{{ $t("category.AShort").replace("A", "") }}
</el-tag>
<el-button type="primary" size="small" style="margin-left: auto" @click="goEdit">
{{ $t("btn.edit") }}
</el-button>
</div>
</template>
<el-descriptions :column="3" border>
<el-descriptions-item :label="$t('label.contactDept')">
{{ detail.contactDept }}
</el-descriptions-item>
<el-descriptions-item :label="$t('label.contactPerson')">
{{ detail.contactPerson }}
</el-descriptions-item>
<el-descriptions-item :label="$t('label.contactPosition')">
{{ detail.contactPosition }}
</el-descriptions-item>
<el-descriptions-item :label="$t('label.contactPhone')">
{{ detail.contactPhone }}
</el-descriptions-item>
<el-descriptions-item :label="$t('table.signDate')">
{{ formatDate(detail.signDate) }}
</el-descriptions-item>
<el-descriptions-item :label="$t('label.acceptDate')">
{{ formatDate(detail.acceptDate) }}
</el-descriptions-item>
<el-descriptions-item :label="$t('label.years')">
{{ detail.contractYears }}
</el-descriptions-item>
<el-descriptions-item :label="$t('table.maintenanceEnd')">
{{ formatDate(detail.maintenanceEnd) }}
</el-descriptions-item>
<el-descriptions-item :label="$t('table.manager')">
{{ detail.managerName }}
</el-descriptions-item>
</el-descriptions>
<div class="section">
<div class="section-title">{{ $t("common.remark") }}</div>
<div class="html-content">{{ detail.remark || "—" }}</div>
</div>
</el-card>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, watch } from "vue";
import dayjs from "dayjs";
import { getAdminHospitalDetail } from "@/service/modular/admin";
const props = defineProps({
visible: { type: Boolean, default: false },
// id
hospitalId: { type: [String, Number], default: "" },
});
const emit = defineEmits(["update:visible", "close", "edit"]);
const loading = ref(false);
const detail = ref({});
const formatDate = (v) => (v ? dayjs(v).format("YYYY-MM-DD") : "—");
const loadDetail = async (id) => {
if (!id) {
detail.value = {};
return;
}
loading.value = true;
try {
detail.value = await getAdminHospitalDetail(id);
} finally {
loading.value = false;
}
};
// id
watch(
() => [props.visible, props.hospitalId],
([vis, id]) => {
if (vis) loadDetail(id);
},
{ immediate: true }
);
const close = () => emit("update:visible", false);
// ""
const goEdit = () => emit("edit", props.hospitalId);
</script>
<style scoped lang="scss">
.hospital-detail-dialog {
.body-header {
margin-bottom: 18px;
}
}
.hospital-detail {
.card-header {
display: flex;
align-items: center;
gap: 8px;
.title {
font-size: 16px;
font-weight: 600;
margin-right: 8px;
}
}
.section {
margin-top: 20px;
.section-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 10px;
}
}
.html-content {
background: #fafafa;
padding: 12px;
border-radius: 4px;
min-height: 60px;
line-height: 1.6;
}
}
</style>

@ -0,0 +1,351 @@
<template>
<el-dialog :model-value="visible" class="hospital-edit-dialog" top="6vh" width="780px" destroy-on-close
:close-on-click-modal="false" @update:model-value="(v) => !v && close()">
<div class="body-wrap">
<div class="body-header">
<span class="body-title">
{{ isEdit ? $t("modal.editHospital") : $t("modal.addHospital") }}
</span>
<img src="@/assets/image/close-btn.svg" alt="" class="close-btn" @click="close">
</div>
<div class="hospital-edit" v-loading="loading">
<el-form ref="formRef" :model="form" :rules="rules" label-width="130px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item :label="$t('label.hospitalName')" prop="name">
<el-input v-model="form.name" :placeholder="$t('placeholder.hospital')" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('label.category')" prop="customerCategory">
<el-select v-model="form.customerCategory" :placeholder="$t('label.category')" style="width: 200px">
<el-option label="A类客户活跃" value="A" />
<el-option label="B类客户不活跃" value="B" />
<el-option label="C类客户待挽回" value="C" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('label.contactDept')">
<el-input v-model="form.contactDept" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('label.contactPerson')">
<el-input v-model="form.contactPerson" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item :label="$t('label.contactPosition')">
<el-input v-model="form.contactPosition" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('label.contactPhone')">
<el-input v-model="form.contactPhone" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('label.signDate')" prop="signDate">
<el-date-picker v-model="form.signDate" type="date" value-format="YYYY-MM-DD"
:placeholder="$t('common.pleaseSelect')" style="width: 200px" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('label.acceptDate')" prop="acceptDate">
<el-date-picker v-model="form.acceptDate" type="date" value-format="YYYY-MM-DD"
:placeholder="$t('common.pleaseSelect')" style="width: 200px" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item :label="$t('label.years')" prop="contractYears">
<el-input-number v-model="form.contractYears" :min="1" :precision="0" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('label.maintenanceEndDate')" prop="maintenanceEnd">
<el-date-picker v-model="form.maintenanceEnd" type="date" value-format="YYYY-MM-DD"
:placeholder="$t('common.pleaseSelect')" style="width: 200px" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('label.manager')">
<el-select v-model="form.managerId" :placeholder="$t('label.manager')" filterable clearable
style="width: 240px">
<el-option v-for="item in managerOptions" :key="item.id" :label="`${item.name}`" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="$t('label.acceptanceReport')" required>
<el-upload v-model:file-list="reportFileList" :auto-upload="false" :limit="1"
:on-exceed="handleReportExceed" accept=".pdf">
<el-button type="primary" plain>{{ $t("btn.upload") }}</el-button>
<template #tip>
<div class="el-upload__tip">
{{ $t("label.acceptanceReportTip") }}
</div>
</template>
</el-upload>
</el-form-item>
<el-form-item :label="$t('common.remark')">
<el-input v-model="form.remark" type="textarea" :rows="3" />
</el-form-item>
</el-form>
</div>
</div>
<div class="body-footer">
<el-button @click="close">{{ $t("common.cancel") }}</el-button>
<el-button type="primary" :loading="submitting" @click="submit">
{{ isEdit ? $t("common.save") : $t("common.add") }}
</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { computed, reactive, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n";
import {
getAdminHospitalDetail,
createAdminHospital,
updateAdminHospital,
getAdminAccounts,
} from "@/service/modular/admin";
const { t: $t } = useI18n();
const props = defineProps({
visible: { type: Boolean, default: false },
// id
hospitalId: { type: [String, Number], default: "" },
});
const emit = defineEmits(["update:visible", "close", "success"]);
const loading = ref(false);
const submitting = ref(false);
const formRef = ref();
const managerLoaded = ref(false);
const isEdit = computed(() => !!props.hospitalId);
//
const form = reactive({
name: "",
customerCategory: "",
contactDept: "",
contactPerson: "",
contactPosition: "",
contactPhone: "",
signDate: "",
acceptDate: "",
contractYears: 1,
maintenanceEnd: "",
managerId: undefined,
remark: "",
attachmentPath: "",
});
const rules = {
name: [
{
required: true,
message: () => $t("placeholder.hospital"),
trigger: "blur",
},
],
customerCategory: [
{ required: true, message: () => $t("label.category"), trigger: "change" },
],
signDate: [
{ required: true, message: () => $t("label.signDate"), trigger: "change" },
],
acceptDate: [
{
required: true,
message: () => $t("label.acceptDate"),
trigger: "change",
},
],
contractYears: [
{ required: true, message: () => $t("label.years"), trigger: "blur" },
],
maintenanceEnd: [
{
required: true,
message: () => $t("label.maintenanceEndDate"),
trigger: "change",
},
],
};
const managerOptions = ref([]);
const reportFileList = ref([]);
// limit
// attachmentPath
const handleReportExceed = (files) => {
reportFileList.value = files.slice(-1);
form.attachmentPath = "";
};
// / \
const getAttachmentFileName = (path) => {
if (!path) return "";
return String(path).replace(/\\/g, "/").split("/").pop() || "";
};
// 使
const resetForm = () => {
Object.assign(form, {
name: "",
customerCategory: "",
contactDept: "",
contactPerson: "",
contactPosition: "",
contactPhone: "",
signDate: "",
acceptDate: "",
contractYears: 1,
maintenanceEnd: "",
managerId: undefined,
remark: "",
attachmentPath: "",
});
reportFileList.value = [];
formRef.value?.clearValidate();
};
const loadManagers = async () => {
if (managerLoaded.value) return;
try {
const res = await getAdminAccounts({ type: "Admin" });
managerOptions.value = res?.list || [];
managerLoaded.value = true;
} catch (e) {
// request
}
};
const loadDetail = async (id) => {
if (!id) return;
loading.value = true;
try {
const data = await getAdminHospitalDetail(id);
Object.assign(form, data);
// "" file-list
// 便
const fileName = getAttachmentFileName(data?.attachmentPath);
if (fileName) {
reportFileList.value = [
{
uid: `attachment-${id}`,
name: fileName,
status: "success",
},
];
}
} finally {
loading.value = false;
}
};
const buildHospitalFormData = () => {
const fd = new FormData();
Object.entries(form).forEach(([key, value]) => {
if (value === undefined || value === null || value === "") return;
fd.append(key, value);
});
// el-upload / file-list
// item UploadFile raw File
// item File el-upload File
// raw size files
const item = (reportFileList.value || [])[0];
if (item) {
const file = item.raw || (typeof item.size === "number" ? item : null);
if (file) fd.append("files", file);
}
return fd;
};
const submit = async () => {
await formRef.value.validate(async (valid) => {
if (!valid) return;
// """/" file-list
const keepOriginal = isEdit.value && !!form.attachmentPath;
if (!reportFileList.value?.length && !keepOriginal) {
ElMessage.warning($t("label.acceptanceReport"));
return;
}
submitting.value = true;
try {
const fd = buildHospitalFormData();
if (isEdit.value) {
await updateAdminHospital(props.hospitalId, fd);
ElMessage.success($t("msg.hospitalEditSuccess"));
} else {
await createAdminHospital(fd);
ElMessage.success($t("msg.hospitalAddSuccess"));
}
emit("success");
emit("update:visible", false);
} finally {
submitting.value = false;
}
});
};
const close = () => emit("update:visible", false);
// id detail.vue
watch(
() => [props.visible, props.hospitalId],
([vis, id]) => {
if (vis) {
resetForm();
loadManagers();
if (id) loadDetail(id);
}
},
{ immediate: true }
);
</script>
<style scoped lang="scss">
.hospital-edit {
max-height: 70vh;
overflow-y: auto;
padding-right: 4px;
}
</style>
<style lang="scss">
.hospital-edit-dialog {
min-width: 1168px;
max-width: 90vw;
.body-header {
margin-bottom: 18px;
}
}
</style>

@ -1,115 +0,0 @@
<template>
<div class="hospital-detail" v-loading="loading">
<el-page-header :icon="ArrowLeft" :content="$t('common.back')" @back="goBack" class="page-header">
<template #content>
<span class="page-title">{{ $t('modal.detailHospital') }}</span>
</template>
</el-page-header>
<el-card shadow="never" v-if="detail.id">
<template #header>
<div class="card-header">
<span class="title">{{ detail.name }}</span>
<el-tag size="small">{{ detail.customerCategory }} {{ $t('category.AShort').replace('A', '') }}</el-tag>
<el-button type="primary" size="small" style="margin-left:auto" @click="goEdit">
{{ $t('btn.edit') }}
</el-button>
</div>
</template>
<el-descriptions :column="3" border>
<el-descriptions-item :label="$t('label.contactDept')">{{ detail.contactDept }}</el-descriptions-item>
<el-descriptions-item :label="$t('label.contactPerson')">{{ detail.contactPerson }}</el-descriptions-item>
<el-descriptions-item :label="$t('label.contactPosition')">{{ detail.contactPosition }}</el-descriptions-item>
<el-descriptions-item :label="$t('label.contactPhone')">{{ detail.contactPhone }}</el-descriptions-item>
<el-descriptions-item :label="$t('table.signDate')">{{ formatDate(detail.signDate) }}</el-descriptions-item>
<el-descriptions-item :label="$t('label.acceptDate')">{{ formatDate(detail.acceptDate) }}</el-descriptions-item>
<el-descriptions-item :label="$t('label.years')">{{ detail.contractYears }}</el-descriptions-item>
<el-descriptions-item :label="$t('table.maintenanceEnd')">{{ formatDate(detail.maintenanceEnd) }}</el-descriptions-item>
<el-descriptions-item :label="$t('table.manager')">{{ detail.managerName }}</el-descriptions-item>
</el-descriptions>
<div class="section">
<div class="section-title">{{ $t('common.remark') }}</div>
<div class="html-content">{{ detail.remark || "—" }}</div>
</div>
</el-card>
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { ArrowLeft } from "@element-plus/icons-vue";
import dayjs from "dayjs";
import { getAdminHospitalDetail } from "@/service/modular/admin";
const route = useRoute();
const router = useRouter();
const loading = ref(false);
const detail = ref({});
const formatDate = (v) => (v ? dayjs(v).format("YYYY-MM-DD") : "—");
// 使"XX"
const formatUsageYears = (signDate) => {
if (!signDate) return "—";
const start = dayjs(signDate);
const now = dayjs();
if (start.isAfter(now)) return "—";
const totalDays = now.diff(start, "day");
const years = Math.floor(totalDays / 365);
const days = totalDays - years * 365;
return `${years}${days}`;
};
const loadDetail = async () => {
loading.value = true;
try {
detail.value = await getAdminHospitalDetail(route.params.id);
} finally {
loading.value = false;
}
};
const goBack = () => router.back();
const goEdit = () => router.push(`/admin/hospitals/edit/${route.params.id}`);
onMounted(loadDetail);
</script>
<style scoped lang="scss">
.page-header {
margin-bottom: 12px;
.page-title {
font-size: 16px;
font-weight: 600;
}
}
.card-header {
display: flex;
align-items: center;
gap: 8px;
.title {
font-size: 16px;
font-weight: 600;
margin-right: 8px;
}
}
.section {
margin-top: 20px;
.section-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 10px;
}
}
.html-content {
background: #fafafa;
padding: 12px;
border-radius: 4px;
min-height: 60px;
line-height: 1.6;
}
</style>

@ -1,313 +0,0 @@
<template>
<div class="hospital-edit" v-loading="loading">
<el-page-header
:icon="ArrowLeft"
:content="$t('common.back')"
@back="goBack"
class="page-header"
>
<template #content>
<span class="page-title">{{
isEdit ? $t("modal.editHospital") : $t("modal.addHospital")
}}</span>
</template>
</el-page-header>
<el-card shadow="never">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="130px"
style="max-width: 800px"
>
<el-form-item :label="$t('label.hospitalName')" prop="name">
<el-input
v-model="form.name"
:placeholder="$t('placeholder.hospital')"
/>
</el-form-item>
<el-form-item :label="$t('label.category')" prop="customerCategory">
<el-select
v-model="form.customerCategory"
:placeholder="$t('label.category')"
style="width: 200px"
>
<el-option label="A类客户活跃" value="A" />
<el-option label="B类客户不活跃" value="B" />
<el-option label="C类客户待挽回" value="C" />
</el-select>
</el-form-item>
<el-form-item :label="$t('label.contactDept')">
<el-input v-model="form.contactDept" />
</el-form-item>
<el-form-item :label="$t('label.contactPerson')">
<el-input v-model="form.contactPerson" />
</el-form-item>
<el-form-item :label="$t('label.contactPosition')">
<el-input v-model="form.contactPosition" />
</el-form-item>
<el-form-item :label="$t('label.contactPhone')">
<el-input v-model="form.contactPhone" />
</el-form-item>
<el-form-item :label="$t('label.signDate')" prop="signDate">
<el-date-picker
v-model="form.signDate"
type="date"
value-format="YYYY-MM-DD"
:placeholder="$t('common.pleaseSelect')"
style="width: 200px"
/>
</el-form-item>
<el-form-item :label="$t('label.acceptDate')" prop="acceptDate">
<el-date-picker
v-model="form.acceptDate"
type="date"
value-format="YYYY-MM-DD"
:placeholder="$t('common.pleaseSelect')"
style="width: 200px"
/>
</el-form-item>
<el-form-item :label="$t('label.years')" prop="contractYears">
<el-input-number v-model="form.contractYears" :min="1" :precision="0" />
</el-form-item>
<el-form-item
:label="$t('label.maintenanceEndDate')"
prop="maintenanceEnd"
>
<el-date-picker
v-model="form.maintenanceEnd"
type="date"
value-format="YYYY-MM-DD"
:placeholder="$t('common.pleaseSelect')"
style="width: 200px"
/>
</el-form-item>
<el-form-item :label="$t('label.manager')">
<el-select
v-model="form.managerId"
:placeholder="$t('label.manager')"
filterable
clearable
style="width: 240px"
>
<el-option
v-for="item in managerOptions"
:key="item.id"
:label="`${item.name}`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="$t('label.acceptanceReport')" required>
<el-upload
v-model:file-list="reportFileList"
:auto-upload="false"
:limit="1"
:on-exceed="handleReportExceed"
accept=".pdf"
>
<el-button type="primary" plain>{{ $t("btn.upload") }}</el-button>
<template #tip>
<div class="el-upload__tip">
{{ $t("label.acceptanceReportTip") }}
</div>
</template>
</el-upload>
</el-form-item>
<el-form-item :label="$t('common.remark')">
<el-input v-model="form.remark" type="textarea" :rows="3" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="submitting" @click="submit">
{{ isEdit ? $t("common.save") : $t("common.add") }}
</el-button>
<el-button @click="goBack">{{ $t("common.cancel") }}</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { ArrowLeft } from "@element-plus/icons-vue";
import { useI18n } from "vue-i18n";
import {
getAdminHospitalDetail,
createAdminHospital,
updateAdminHospital,
getAdminAccounts,
} from "@/service/modular/admin";
const { t: $t } = useI18n();
const route = useRoute();
const router = useRouter();
const loading = ref(false);
const submitting = ref(false);
const formRef = ref();
const isEdit = computed(() => !!route.params.id);
//
const form = reactive({
name: "",
customerCategory: "",
contactDept: "",
contactPerson: "",
contactPosition: "",
contactPhone: "",
signDate: "",
acceptDate: "",
contractYears: 1,
maintenanceEnd: "",
managerId: undefined,
remark: "",
attachmentPath: "",
});
const rules = {
name: [
{
required: true,
message: () => $t("placeholder.hospital"),
trigger: "blur",
},
],
customerCategory: [
{ required: true, message: () => $t("label.category"), trigger: "change" },
],
signDate: [
{ required: true, message: () => $t("label.signDate"), trigger: "change" },
],
acceptDate: [
{
required: true,
message: () => $t("label.acceptDate"),
trigger: "change",
},
],
contractYears: [
{ required: true, message: () => $t("label.years"), trigger: "blur" },
],
maintenanceEnd: [
{
required: true,
message: () => $t("label.maintenanceEndDate"),
trigger: "change",
},
],
};
const managerOptions = ref([]);
const reportFileList = ref([]);
// limit
// attachmentPath
const handleReportExceed = (files) => {
reportFileList.value = files.slice(-1);
form.attachmentPath = "";
};
// / \
const getAttachmentFileName = (path) => {
if (!path) return "";
return String(path).replace(/\\/g, "/").split("/").pop() || "";
};
const buildHospitalFormData = () => {
const fd = new FormData();
Object.entries(form).forEach(([key, value]) => {
if (value === undefined || value === null || value === "") return;
fd.append(key, value);
});
// el-upload / file-list
// item UploadFile raw File
// item File el-upload File
// raw size files
const item = (reportFileList.value || [])[0];
if (item) {
const file = item.raw || (typeof item.size === "number" ? item : null);
if (file) fd.append("files", file);
}
return fd;
};
const loadManagers = async () => {
try {
const res = await getAdminAccounts({ type: "Admin" });
managerOptions.value = res?.list || [];
} catch (e) {
// request
}
};
const loadDetail = async () => {
if (!isEdit.value) return;
loading.value = true;
try {
const data = await getAdminHospitalDetail(route.params.id);
Object.assign(form, data);
// "" file-list
// 便
const fileName = getAttachmentFileName(data?.attachmentPath);
if (fileName) {
reportFileList.value = [
{
uid: `attachment-${route.params.id}`,
name: fileName,
status: "success",
},
];
}
} finally {
loading.value = false;
}
};
const submit = async () => {
await formRef.value.validate(async (valid) => {
if (!valid) return;
// """/" file-list
const keepOriginal = isEdit.value && !!form.attachmentPath;
if (!reportFileList.value?.length && !keepOriginal) {
ElMessage.warning($t("label.acceptanceReport"));
return;
}
submitting.value = true;
try {
const fd = buildHospitalFormData();
if (isEdit.value) {
await updateAdminHospital(route.params.id, fd);
ElMessage.success($t("msg.hospitalEditSuccess"));
} else {
await createAdminHospital(fd);
ElMessage.success($t("msg.hospitalAddSuccess"));
}
router.replace("/admin/hospitals");
} finally {
submitting.value = false;
}
});
};
const goBack = () => router.back();
onMounted(() => {
loadManagers();
loadDetail();
});
</script>
<style scoped lang="scss">
.page-header {
margin-bottom: 12px;
.page-title {
font-size: 16px;
font-weight: 600;
}
}
</style>

@ -94,20 +94,32 @@
@close="cleanupPreview" @close="cleanupPreview"
@download="onPreviewDownload" @download="onPreviewDownload"
/> />
<HospitalDetailDialog
v-model:visible="detailVisible"
:hospital-id="detailId"
@edit="onGoEdit"
/>
<HospitalEditDialog
v-model:visible="editVisible"
:hospital-id="editId"
@success="loadList"
/>
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted, reactive, ref } from "vue"; import { onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { getAdminHospitals, getUploadFile } from "@/service/modular/admin"; import { getAdminHospitals, getUploadFile } from "@/service/modular/admin";
import FilePreviewDialog from "@/components/FilePreviewDialog.vue"; import FilePreviewDialog from "@/components/FilePreviewDialog.vue";
import HospitalDetailDialog from "./components/HospitalDetailDialog.vue";
import HospitalEditDialog from "./components/HospitalEditDialog.vue";
const { t: $t } = useI18n(); const { t: $t } = useI18n();
const router = useRouter();
const list = ref([]); const list = ref([]);
const total = ref(0); const total = ref(0);
const page = ref(1); const page = ref(1);
@ -162,9 +174,29 @@ const onReset = () => {
onSearch(); onSearch();
}; };
const goDetail = (id) => router.push(`/admin/hospitals/detail/${id}`); // / /
const goEdit = (id) => router.push(`/admin/hospitals/edit/${id}`); const detailVisible = ref(false);
const goAdd = () => router.push("/admin/hospitals/edit"); const detailId = ref("");
const editVisible = ref(false);
const editId = ref("");
const goDetail = (id) => {
detailId.value = id;
detailVisible.value = true;
};
const goEdit = (id) => {
editId.value = id;
editVisible.value = true;
};
const goAdd = () => {
editId.value = "";
editVisible.value = true;
};
//
const onGoEdit = (id) => {
detailVisible.value = false;
goEdit(id);
};
const previewVisible = ref(false); const previewVisible = ref(false);
const previewUrl = ref(""); const previewUrl = ref("");

@ -84,16 +84,16 @@
</div> </div>
<!-- <div class="section"> <div class="section" v-if="detail.statusLogs?.length">
<div class="section-title">{{ $t('orderDetail.statusChange') }}</div> <div class="section-title">{{ $t('orderDetail.statusChange') }}</div>
<el-timeline> <el-timeline>
<el-timeline-item v-for="(log, i) in detail.statusLogs || []" :key="i" <el-timeline-item v-for="(log, i) in detail.statusLogs || []" :key="i" :timestamp="log.createdAt"
:timestamp="log.createdAt" placement="top"> placement="top">
<div class="log-title">{{ log.toStatus }} · {{ log.operator }}</div> <div class="log-title">{{ log.toStatus }} · {{ log.operator }}</div>
<div class="log-remark" v-html="log.remark"></div> <div class="log-remark" v-html="log.remark"></div>
</el-timeline-item> </el-timeline-item>
</el-timeline> </el-timeline>
</div> --> </div>
</div> </div>

@ -10,7 +10,7 @@
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" v-loading="formLoading"> <el-form ref="formRef" :model="form" :rules="rules" label-width="100px" v-loading="formLoading">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="8"> <el-col :span="8">
<el-form-item :label="$t('label.hospitalName')" prop="hospitalId"> <el-form-item :label="'所属医院'" prop="hospitalId">
<el-select v-model="form.hospitalId" :placeholder="$t('msg.pleaseSelectHospital')" filterable <el-select v-model="form.hospitalId" :placeholder="$t('msg.pleaseSelectHospital')" filterable
style="width: 100%"> style="width: 100%">
<el-option v-for="h in hospitals" :key="h.id" :label="h.name" :value="h.id" /> <el-option v-for="h in hospitals" :key="h.id" :label="h.name" :value="h.id" />
@ -118,11 +118,21 @@
</div> </div>
</el-form-item> </el-form-item>
<el-form-item class="fujian" :label="$t('label.attachment')"> <el-form-item class="fujian" :label="$t('label.attachment')">
<el-upload v-model:file-list="form.files" :auto-upload="false" :limit="1" :on-exceed="handleAttachmentExceed" <el-upload class="upload-wrap" v-model:file-list="form.files" :auto-upload="false" :limit="1"
:on-remove="handleAttachmentRemove" :on-change="handleAttachmentChange"> :on-exceed="handleAttachmentExceed" :on-remove="handleAttachmentRemove" :on-change="handleAttachmentChange">
<el-button type="primary" plain>{{ $t('btn.upload') }}</el-button> <div class="upload-btn">
<template #tip> <div class="left">+ 添加文件</div>
<div class="el-upload__tip">{{ $t('label.attachmentTip') }}</div> <div class="right">{{ $t('label.attachmentTip') }}</div>
</div>
<template #file="{ file }">
<div class="file-item">
<img class="file-icon" src="@/assets/image/admin/orders/file.svg" alt="">
<div class="file-name">&nbsp;&nbsp;{{ file.name }}</div>
<div class="file-size">&nbsp;&nbsp;{{ (file.size / 1024 / 1024).toFixed(2) }}MB</div>
<!-- <div class="file-rename">重命名</div> -->
<div class="file-delete" @click="handleAttachmentDelete(file)"></div>
</div>
</template> </template>
</el-upload> </el-upload>
</el-form-item> </el-form-item>
@ -230,6 +240,12 @@ const handleAttachmentExceed = () => {
const handleAttachmentRemove = () => { const handleAttachmentRemove = () => {
// el-upload v-model:file-list // el-upload v-model:file-list
}; };
const handleAttachmentDelete = (file) => {
// "" uid form.files
// - (raw )
// - ( raw)onSubmit
form.files = (form.files || []).filter((f) => f.uid !== file?.uid);
};
const rules = { const rules = {
hospitalId: [ hospitalId: [
@ -447,6 +463,7 @@ const fillFormFromOrder = (d) => {
uid: `attachment-${d.id || ''}-0`, uid: `attachment-${d.id || ''}-0`,
name: fileName, name: fileName,
status: 'success', status: 'success',
size: first.fileSize || 0,
}, },
]; ];
} }
@ -763,29 +780,93 @@ onBeforeUnmount(() => {
height: 40px; height: 40px;
} }
.fujian {
.upload-wrap {
width: 100%;
.el-upload-list__item {
&:hover {
background: transparent;
}
.file-item {
display: flex;
align-items: center;
font-weight: 400;
font-size: 14px;
.file-icon {
width: 18px;
height: 18px;
}
.file-name,
.file-rename,
.file-delete {
color: #1D2129;
&:hover {
color: #1890ff;
}
}
.file-rename,
.file-delete {
margin-left: 14px;
cursor: pointer;
}
// popover teleport body scoped .file-size {
.color-tag-popper { color: #BCBCBC;
}
}
}
}
.upload-btn {
display: flex;
align-items: center;
.left {
font-weight: 400;
font-size: 14px;
color: #2B324C;
}
.right {
font-weight: 400;
font-size: 14px;
color: #666666;
}
}
}
}
// popover teleport body scoped
.color-tag-popper {
padding: 12px 14px !important; padding: 12px 14px !important;
border-radius: 10px !important; border-radius: 10px !important;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12) !important; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12) !important;
min-width: 180px; min-width: 180px;
}
.color-tag-popper .color-tag-title { .color-tag-title {
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
color: #333; color: #333;
margin-bottom: 10px; margin-bottom: 10px;
} }
.color-tag-popper .color-tag-options { .color-tag-options {
display: flex; display: flex;
gap: 8px; gap: 8px;
flex-wrap: wrap; flex-wrap: wrap;
} }
.color-tag-popper .color-tag-option { .color-tag-option {
width: 24px; width: 24px;
height: 24px; height: 24px;
border-radius: 6px; border-radius: 6px;
@ -796,11 +877,11 @@ onBeforeUnmount(() => {
border-color 0.2s ease; border-color 0.2s ease;
} }
.color-tag-popper .color-tag-option:hover { .color-tag-option:hover {
transform: scale(1.12); transform: scale(1.12);
} }
.color-tag-popper .color-tag-option.active { .color-tag-option.active {
border-color: #1890ff; border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
} }

@ -86,7 +86,7 @@
<span>复制工单</span> <span>复制工单</span>
</div> </div>
<div class="line"></div> <div class="line"></div>
<el-popconfirm <el-popconfirm width="auto"
title="确定删除该工单吗?" title="确定删除该工单吗?"
confirm-button-text="确定" confirm-button-text="确定"
cancel-button-text="取消" cancel-button-text="取消"
@ -161,6 +161,7 @@ import iconEdit from "@/assets/image/admin/orders/edit.svg";
import iconCopy from "@/assets/image/admin/orders/copy.svg"; import iconCopy from "@/assets/image/admin/orders/copy.svg";
import iconDelete from "@/assets/image/admin/orders/delete.svg"; import iconDelete from "@/assets/image/admin/orders/delete.svg";
import iconFile from "@/assets/image/admin/orders/file.svg"; import iconFile from "@/assets/image/admin/orders/file.svg";
import dayjs from "dayjs";
const { t: $t } = useI18n(); const { t: $t } = useI18n();
@ -214,7 +215,10 @@ const closeDialog = () => emit("update:modelValue", false);
// //
const copyOrderData = computed(() => { const copyOrderData = computed(() => {
console.log(detail.value);
const { attachments: _attachments, ...rest } = detail.value || {}; const { attachments: _attachments, ...rest } = detail.value || {};
rest.createdAt = dayjs().format("YYYY-MM-DD HH:mm:ss");
return rest; return rest;
}); });

@ -17,24 +17,27 @@
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="12"> <el-col :span="12">
<el-form-item :label="$t('table.dept')" prop="department"> <el-form-item :label="$t('table.dept')" prop="department">
<el-input v-model="form.department" :placeholder="'请输入科室'" /> <el-input v-model="form.department" :placeholder="'请输入科室'" readonly />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item :label="$t('table.submitter')" prop="submitter"> <el-form-item :label="$t('table.submitter')" prop="submitter">
<el-input v-model="form.submitter" :placeholder="'请输入提交人'" /> <el-input v-model="form.submitter" :placeholder="'请输入提交人'" readonly />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<!-- 问题描述可编辑富文本默认回显原值 --> <!-- 问题描述可编辑富文本默认回显原值 -->
<el-form-item :label="$t('label.description')" prop="description"> <el-form-item :label="$t('label.description')" prop="description">
<div class="rich-editor"> <!-- <div class="rich-editor">
<Toolbar :editor="descEditorRef" :default-config="toolbarConfig" mode="default" <Toolbar :editor="descEditorRef" :default-config="toolbarConfig" mode="default"
style="border-bottom: 1px solid #dcdfe6" /> style="border-bottom: 1px solid #dcdfe6" />
<Editor v-model="form.description" :default-config="descEditorConfig" mode="default" <Editor v-model="form.description" :default-config="descEditorConfig" mode="default"
style="height: 200px; overflow-y: auto" @on-created="handleDescEditorCreated" style="height: 200px; overflow-y: auto" @on-created="handleDescEditorCreated"
@custom-paste="handleEditorPasteImage" /> @custom-paste="handleEditorPasteImage" />
</div> -->
<div class="html-wrap" style="max-height: 300px; overflow: auto;">
<div class="html-content" v-html="detail.description || '—'"></div>
</div> </div>
</el-form-item> </el-form-item>
@ -49,25 +52,29 @@
</div> </div>
</el-form-item> </el-form-item>
<el-row :gutter="16">
<el-col :span="12">
<!-- 新状态只允许"已完成" --> <!-- 新状态只允许"已完成" -->
<el-form-item :label="$t('table.status')" prop="status"> <el-form-item :label="'新状态'" prop="status">
<el-select v-model="form.status" style="width: 240px"> <el-select v-model="form.status">
<el-option :label="$t('status.completed')" value="已完成" /> <el-option :label="$t('status.completed')" value="已完成" />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<!-- 处理人取当前登录用户名 --> <!-- 处理人取当前登录用户名 -->
<el-form-item :label="$t('table.handler')"> <el-form-item :label="$t('table.handler')">
<el-input :model-value="handlerName" readonly style="width: 240px" /> <el-input :model-value="handlerName" readonly />
</el-form-item> </el-form-item>
</el-col>
<el-form-item> </el-row>
</el-form>
</div>
<div class="body-footer">
<el-button @click="closeDialog">{{ $t("common.cancel") }}</el-button>
<el-button type="primary" :loading="submitting" @click="submit">{{ <el-button type="primary" :loading="submitting" @click="submit">{{
$t("btn.confirm") $t("btn.confirm")
}}</el-button> }}</el-button>
<el-button @click="closeDialog">{{ $t("common.cancel") }}</el-button>
</el-form-item>
</el-form>
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
@ -335,6 +342,11 @@ onBeforeUnmount(() => {
.admin-order-process-dialog { .admin-order-process-dialog {
max-width: 90vw; max-width: 90vw;
min-width: 70vw; min-width: 70vw;
--el-component-size: 40px;
.el-select__wrapper {
height: 40px;
}
.body-header { .body-header {
margin-bottom: 16px; margin-bottom: 16px;
@ -349,6 +361,23 @@ onBeforeUnmount(() => {
background: #fff; background: #fff;
} }
.html-wrap {
border: 1px solid #ebeef5;
border-radius: 4px;
width: 100%;
overflow: hidden;
background: #fff;
padding: 1px 11px;
.html-content {
min-height: 40px;
img {
max-width: 100%;
}
}
}
.rich-editor :deep(.w-e-toolbar) { .rich-editor :deep(.w-e-toolbar) {
background: #fafafa; background: #fafafa;
} }

@ -41,7 +41,7 @@
</div> </div>
</div> </div>
<el-form :inline="true" :model="filters" class="filter-form"> <el-form :inline="true" :model="filters" class="filter-form" :style="formStyle">
<el-form-item :label="''"> <el-form-item :label="''">
<el-input v-model="filters.keyword" :placeholder="'搜索问题名称/工单号'" clearable style="width: 200px" /> <el-input v-model="filters.keyword" :placeholder="'搜索问题名称/工单号'" clearable style="width: 200px" />
</el-form-item> </el-form-item>
@ -74,11 +74,11 @@
<el-form-item :label="'提交日期:'"> <el-form-item :label="'提交日期:'">
<el-date-picker v-model="dateRange" type="daterange" range-separator="-" <el-date-picker v-model="dateRange" type="daterange" range-separator="-"
:start-placeholder="$t('common.startDate')" :end-placeholder="$t('common.endDate')" value-format="YYYY-MM-DD" :start-placeholder="$t('common.startDate')" :end-placeholder="$t('common.endDate')" value-format="YYYY-MM-DD"
style="width: 240px" /> :shortcuts="dateRangeShortcuts" style="width: 240px" />
</el-form-item> </el-form-item>
<el-form-item :label="'处理时间:'"> <el-form-item :label="'处理时间:'">
<el-date-picker v-model="processDateRange" type="datetimerange" range-separator="-" :start-placeholder="'开始时间'" <el-date-picker v-model="processDateRange" type="daterange" range-separator="-" :start-placeholder="'开始时间'"
:end-placeholder="'结束时间'" value-format="YYYY-MM-DD HH:mm:ss" style="width: 400px" /> :end-placeholder="'结束时间'" value-format="YYYY-MM-DD" :shortcuts="dateRangeShortcuts" style="width: 240px" />
</el-form-item> </el-form-item>
<el-button type="primary" plain @click="onSearch">{{ <el-button type="primary" plain @click="onSearch">{{
$t("btn.query") $t("btn.query")
@ -108,8 +108,8 @@
</el-table-column> </el-table-column>
<el-table-column :label="$t('table.title')" min-width="300" show-overflow-tooltip> <el-table-column :label="$t('table.title')" min-width="300" show-overflow-tooltip>
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link style="text-decoration: underline;" @click="goHandle(row.id)">{{ row.title <span class="title-link" style="text-decoration: underline;" @click="goHandle(row.id)">{{ row.title
}}</el-button> }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('table.type')" prop="serviceType" width="120" /> <el-table-column :label="$t('table.type')" prop="serviceType" width="120" />
@ -167,7 +167,8 @@
<script setup> <script setup>
import dayjs from "dayjs"; import dayjs from "dayjs";
import { onMounted, reactive, ref } from "vue"; import { computed, onBeforeUnmount, onMounted, reactive, ref } from "vue";
import { dateRangeShortcuts } from "@/utils/dateShortcuts";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { import {
@ -347,9 +348,26 @@ const onQuickSubmit = async ({ formData }) => {
} }
}; };
// filter-form 1920
const viewportWidth = ref(typeof window !== "undefined" ? (window.innerWidth - 120 ) : 1800);
const onResize = () => {
viewportWidth.value = window.innerWidth - 120;
};
onMounted(() => { onMounted(() => {
loadHospitals(); loadHospitals();
loadList(); loadList();
window.addEventListener("resize", onResize);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", onResize);
});
const formStyle = computed(() => {
const scale = (viewportWidth.value / 1800).toFixed(4);
return {
transform: `scale(${scale})`,
transformOrigin: "center left",
};
}); });
</script> </script>
@ -360,13 +378,27 @@ onMounted(() => {
border: 1px solid #e0e0e0; border: 1px solid #e0e0e0;
padding: 24px; padding: 24px;
& .title-link {
color: var(--el-color-primary);
cursor: pointer;
user-select: text;
-webkit-user-select: text;
&:hover {
color: var(--el-color-primary-light-3);
}
}
:deep(.el-button.is-plain) { :deep(.el-button.is-plain) {
background-color: transparent; background-color: transparent;
// el-button hover :hover hover-* // el-button hover / active
// plain hover hoverhover // plain hover active hover/active
--el-button-hover-bg-color: var(--el-button-bg-color); --el-button-hover-bg-color: var(--el-button-bg-color);
--el-button-hover-text-color: var(--el-button-text-color); --el-button-hover-text-color: var(--el-button-text-color);
--el-button-hover-border-color: var(--el-button-border-color); --el-button-hover-border-color: var(--el-button-border-color);
--el-button-active-bg-color: var(--el-button-bg-color);
--el-button-active-text-color: var(--el-button-text-color);
--el-button-active-border-color: var(--el-button-border-color);
} }
:deep(.handle.el-button--success.is-plain) { :deep(.handle.el-button--success.is-plain) {
@ -384,8 +416,9 @@ onMounted(() => {
margin-bottom: 12px; margin-bottom: 12px;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 24px; gap: 12px;
align-items: center; align-items: center;
width: 1800px;
:deep(.el-form-item), :deep(.el-form-item),
:deep(.el-button) { :deep(.el-button) {

@ -20,6 +20,7 @@
:start-placeholder="$t('common.startDate')" :start-placeholder="$t('common.startDate')"
:end-placeholder="$t('common.endDate')" :end-placeholder="$t('common.endDate')"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
:shortcuts="dateRangeShortcuts"
style="width:240px" style="width:240px"
/> />
</el-form-item> </el-form-item>
@ -69,6 +70,7 @@
<script setup> <script setup>
import { onMounted, reactive, ref } from "vue"; import { onMounted, reactive, ref } from "vue";
import { dateRangeShortcuts } from "@/utils/dateShortcuts";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";

@ -55,6 +55,7 @@
:start-placeholder="$t('common.startDate')" :start-placeholder="$t('common.startDate')"
:end-placeholder="$t('common.endDate')" :end-placeholder="$t('common.endDate')"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
:shortcuts="dateRangeShortcuts"
style="width: 240px" style="width: 240px"
/> />
</el-form-item> </el-form-item>
@ -216,6 +217,7 @@
<script setup> <script setup>
import dayjs from "dayjs"; import dayjs from "dayjs";
import { onMounted, onBeforeUnmount, reactive, ref } from "vue"; import { onMounted, onBeforeUnmount, reactive, ref } from "vue";
import { dateRangeShortcuts } from "@/utils/dateShortcuts";
import { onBeforeRouteLeave, useRoute, useRouter } from "vue-router"; import { onBeforeRouteLeave, useRoute, useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";

@ -16,6 +16,7 @@
:start-placeholder="$t('common.startDate')" :start-placeholder="$t('common.startDate')"
:end-placeholder="$t('common.endDate')" :end-placeholder="$t('common.endDate')"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
:shortcuts="dateRangeShortcuts"
size="small" size="small"
style="margin-left: 12px" style="margin-left: 12px"
@change="loadStats" @change="loadStats"
@ -68,6 +69,7 @@
<script setup> <script setup>
import dayjs from "dayjs"; import dayjs from "dayjs";
import { computed, onMounted, ref } from "vue"; import { computed, onMounted, ref } from "vue";
import { dateRangeShortcuts } from "@/utils/dateShortcuts";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { Tickets, Check, Bell, Warning } from "@element-plus/icons-vue"; import { Tickets, Check, Bell, Warning } from "@element-plus/icons-vue";
import VChart from "vue-echarts"; import VChart from "vue-echarts";

Loading…
Cancel
Save