csm调整

master
2358328281@qq.com 2 months ago
parent a158c5f322
commit 13c66287b2

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 323 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.6 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

@ -69,31 +69,26 @@ const rules = {
{ {
required: true, required: true,
message: () => t("password.pleaseInputOld"), message: () => t("password.pleaseInputOld"),
trigger: "blur",
}, },
], ],
newPassword: [ newPassword: [
{ {
required: true, required: true,
message: () => t("password.pleaseInputNew"), message: () => t("password.pleaseInputNew"),
trigger: "blur",
}, },
{ {
min: 6, min: 6,
message: () => t("password.passwordTooShort"), message: () => t("password.passwordTooShort"),
trigger: "blur",
}, },
], ],
confirmPassword: [ confirmPassword: [
{ {
required: true, required: true,
message: () => t("password.pleaseInputConfirm"), message: () => t("password.pleaseInputConfirm"),
trigger: "blur",
}, },
{ {
validator: (_, val, cb) => validator: (_, val, cb) =>
val === form.newPassword ? cb() : cb(new Error(t("password.passwordMismatch"))), val === form.newPassword ? cb() : cb(new Error(t("password.passwordMismatch"))),
trigger: "blur",
}, },
], ],
}; };

@ -240,13 +240,13 @@ const form = reactive(buildForm());
const rules = { const rules = {
username: [ username: [
{ required: true, message: () => $t('placeholder.account'), trigger: "blur" }, { required: true, message: () => $t('placeholder.account') },
], ],
password: [ password: [
{ required: true, message: () => $t('placeholder.password'), trigger: "blur" }, { required: true, message: () => $t('placeholder.password') },
], ],
name: [ name: [
{ required: true, message: () => $t('account.pleaseInputName'), trigger: "blur" }, { required: true, message: () => $t('account.pleaseInputName') },
], ],
hospitalId: [ hospitalId: [
{ {
@ -256,7 +256,6 @@ const rules = {
} }
cb(); cb();
}, },
trigger: "change",
}, },
], ],
role: [ role: [
@ -267,7 +266,6 @@ const rules = {
} }
cb(); cb();
}, },
trigger: "change",
}, },
], ],
}; };

@ -112,10 +112,10 @@ const form = reactive({
}); });
const rules = { const rules = {
username: [{ required: true, message: () => $t('placeholder.account'), trigger: "blur" }], username: [{ required: true, message: () => $t('placeholder.account') }],
password: [{ required: true, message: () => $t('placeholder.password'), trigger: "blur" }], password: [{ required: true, message: () => $t('placeholder.password') }],
name: [{ required: true, message: () => $t('account.pleaseInputName'), trigger: "blur" }], name: [{ required: true, message: () => $t('account.pleaseInputName') }],
userType: [{ required: true, message: () => $t('label.accountType'), trigger: "change" }], userType: [{ required: true, message: () => $t('label.accountType') }],
}; };
const loadDetail = async () => { const loadDetail = async () => {

@ -160,6 +160,7 @@ const goEdit = () => emit("edit", props.hospitalId);
white-space: nowrap; white-space: nowrap;
flex-shrink: 0; flex-shrink: 0;
flex-grow: 0; flex-grow: 0;
text-align: right;
} }
.value { .value {

@ -176,30 +176,27 @@ const rules = {
{ {
required: true, required: true,
message: () => $t("placeholder.hospital"), message: () => $t("placeholder.hospital"),
trigger: "blur",
}, },
], ],
customerCategory: [ customerCategory: [
{ required: true, message: () => $t("label.category"), trigger: "change" }, { required: true, message: () => $t("label.category") },
], ],
signDate: [ signDate: [
{ required: true, message: () => $t("label.signDate"), trigger: "change" }, { required: true, message: () => $t("label.signDate") },
], ],
acceptDate: [ acceptDate: [
{ {
required: true, required: true,
message: () => $t("label.acceptDate"), message: () => $t("label.acceptDate"),
trigger: "change",
}, },
], ],
contractYears: [ contractYears: [
{ required: true, message: () => $t("label.years"), trigger: "blur" }, { required: true, message: () => $t("label.years") },
], ],
maintenanceEnd: [ maintenanceEnd: [
{ {
required: true, required: true,
message: () => $t("label.maintenanceEndDate"), message: () => $t("label.maintenanceEndDate"),
trigger: "change",
}, },
], ],
}; };

@ -311,32 +311,32 @@ const form = reactive(defaultForm());
const buildRules = () => { const buildRules = () => {
if (props.type === "call") { if (props.type === "call") {
return { return {
callTime: [{ required: true, message: "请选择回访时间", trigger: "change" }], callTime: [{ required: true, message: "请选择回访时间" }],
callPerson: [{ required: true, message: "请输入回访人", trigger: "blur" }], callPerson: [{ required: true, message: "请输入回访人" }],
content: [{ required: true, message: "请输入回访内容", trigger: "blur" }], content: [{ required: true, message: "请输入回访内容" }],
}; };
} }
if (props.type === "inspection") { if (props.type === "inspection") {
console.log(111); console.log(111);
return { return {
inspectionTime: [{ required: true, message: "请选择巡检时间", trigger: "change" }], inspectionTime: [{ required: true, message: "请选择巡检时间" }],
inspectionPerson: [{ required: true, message: "请输入巡检人", trigger: "blur" }], inspectionPerson: [{ required: true, message: "请输入巡检人" }],
content: [{ required: true, message: "请输入巡检内容", trigger: "blur" }], content: [{ required: true, message: "请输入巡检内容" }],
}; };
} }
if (props.type === "training") { if (props.type === "training") {
return { return {
trainingDate: [{ required: true, message: "请选择培训日期", trigger: "change" }], trainingDate: [{ required: true, message: "请选择培训日期" }],
participants: [{ required: true, message: "请输入参与人数", trigger: "blur" }], participants: [{ required: true, message: "请输入参与人数" }],
trainer: [{ required: true, message: "请输入培训人员", trigger: "blur" }], trainer: [{ required: true, message: "请输入培训人员" }],
topic: [{ required: true, message: "请输入培训主题", trigger: "blur" }], topic: [{ required: true, message: "请输入培训主题" }],
}; };
} }
return { return {
date: [{ required: true, message: "请选择年份", trigger: "change" }], date: [{ required: true, message: "请选择年份" }],
quantity: [{ required: true, message: "请输入数量", trigger: "blur" }], quantity: [{ required: true, message: "请输入数量" }],
name: [{ required: true, message: "请输入纪念品名称", trigger: "blur" }], name: [{ required: true, message: "请输入纪念品名称" }],
}; };
}; };

@ -8,10 +8,21 @@
<el-select v-model="filters.keyword" placeholder="全部医院" clearable filterable style="width: 220px"> <el-select v-model="filters.keyword" placeholder="全部医院" clearable filterable style="width: 220px">
<el-option v-for="h in hospitals" :key="h.id" :label="h.name" :value="h.name" /> <el-option v-for="h in hospitals" :key="h.id" :label="h.name" :value="h.name" />
</el-select> </el-select>
<span class="filter-label">客户分类</span>
<el-select v-model="filters.category" :placeholder="$t('common.all')" clearable style="width: 140px">
<el-option label="A类客户活跃" value="A" />
<el-option label="B类客户不活跃" value="B" />
<el-option label="C类客户待挽回" value="C" />
</el-select>
<span class="filter-label">维保截止日期</span>
<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" :shortcuts="dateRangeShortcuts" style="width: 240px" />
<el-button type="primary" plain @click="onSearch"></el-button> <el-button type="primary" plain @click="onSearch"></el-button>
</div> </div>
<div class="filter-right"> <div class="filter-right">
<div class="month-switcher"> <!-- <div class="month-switcher">
<el-button class="switch-btn" plain @click="prevMonth"> <el-button class="switch-btn" plain @click="prevMonth">
<div class="inner"> <div class="inner">
< </div> < </div>
@ -20,7 +31,7 @@
<el-button class="switch-btn" plain @click="nextMonth"> <el-button class="switch-btn" plain @click="nextMonth">
<div class="inner">></div> <div class="inner">></div>
</el-button> </el-button>
</div> </div> -->
<el-button class="monthly-view-btn" type="primary" @click="monthlyVisible = true"> <el-button class="monthly-view-btn" type="primary" @click="monthlyVisible = true">
<el-icon> <el-icon>
<Calendar /> <Calendar />
@ -113,7 +124,7 @@
<!-- 本月到期提醒 --> <!-- 本月到期提醒 -->
<div class="expiring-section admin-page-card"> <!-- <div class="expiring-section admin-page-card">
<div class="expiring-title border-title"> <div class="expiring-title border-title">
本月到期提醒 本月到期提醒
</div> </div>
@ -140,12 +151,12 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div> -->
<!-- 维保详情/编辑弹窗 --> <!-- 维保详情/编辑弹窗 -->
<MaintenanceDetailDialog v-model="detailVisible" :hospital-id="currentHospitalId" @saved="onSearch" /> <MaintenanceDetailDialog v-model="detailVisible" :hospital-id="currentHospitalId" @saved="onSearch" />
<!-- 服务记录弹窗电话回访/现场巡检/培训/纪念品 --> <!-- 服务记录弹窗电话回访/现场巡检/培训/纪念品 -->
<!-- v-if 让弹窗关闭时整个组件卸载下次打开会重新 setuprules 会按最新 type 生成 --> <!-- v-if 让弹窗关闭时整个组件卸载下次打开会重新 setuprules 会按最新 type 生成 -->
<ServiceRecordDialog v-if="recordVisible" v-model="recordVisible" :type="recordType" <ServiceRecordDialog v-if="recordVisible" v-model="recordVisible" :type="recordType"
:hospital-id="currentHospitalId" :hospital-name="currentHospitalName" /> :hospital-id="currentHospitalId" :hospital-name="currentHospitalName" />
@ -164,7 +175,8 @@ import { useHospitalStore } from "@/stores/api/hospital";
import MaintenanceDetailDialog from "./components/MaintenanceDetailDialog.vue"; import MaintenanceDetailDialog from "./components/MaintenanceDetailDialog.vue";
import ServiceRecordDialog from "./components/ServiceRecordDialog.vue"; import ServiceRecordDialog from "./components/ServiceRecordDialog.vue";
import MonthlyViewDialog from "./components/MonthlyViewDialog.vue"; import MonthlyViewDialog from "./components/MonthlyViewDialog.vue";
import { dateRangeShortcuts } from "@/utils/dateShortcuts";
const dateRange = ref([]);
const list = ref([]); const list = ref([]);
const loading = ref(false); const loading = ref(false);
const expiringLoading = ref(false); const expiringLoading = ref(false);
@ -198,13 +210,19 @@ const formatExpiringDays = (n) => {
const loadList = async () => { const loadList = async () => {
loading.value = true; loading.value = true;
try { try {
const res = await getAdminMaintenance({ const params = {
page: 1, page: 1,
pageSize: 200, pageSize: 200,
keyword: filters.keyword || undefined, keyword: filters.keyword || undefined,
category: filters.category || undefined,
year: currentMonth.value.year(), year: currentMonth.value.year(),
month: currentMonth.value.month() + 1, month: currentMonth.value.month() + 1,
}); }
if (dateRange.value?.length === 2) {
params.maintenanceStart = dateRange.value[0];
params.maintenanceEnd = dateRange.value[1];
}
const res = await getAdminMaintenance(params);
list.value = res?.list || []; list.value = res?.list || [];
} finally { } finally {
loading.value = false; loading.value = false;
@ -252,7 +270,7 @@ const monthlyVisible = ref(false);
onMounted(() => { onMounted(() => {
hospitalStore.ensureLoaded(); hospitalStore.ensureLoaded();
loadList(); loadList();
loadExpiring(); // loadExpiring();
}); });
</script> </script>

@ -0,0 +1,83 @@
<template>
<el-dialog :model-value="modelValue" class="order-award-dialog" :show-close="false" :close-on-click-modal="false"
:close-on-press-escape="false" align-center @update:model-value="(v) => $emit('update:modelValue', v)">
<div class="body-wrap">
<img :src="awardIcon" alt="award" />
<div class="award-title">你真棒!</div>
<div class="award-subtitle">恭喜您完成服务工单</div>
<div class="award-points"><span class="light">+{{ points }}</span>&nbsp;&nbsp;积分</div>
</div>
<div class="body-footer" style="justify-content: center;">
<el-button type="primary" class="award-btn" @click="onContinue">
继续处理
</el-button>
</div>
</el-dialog>
</template>
<script setup>
import awardIcon from "@/assets/image/admin/orders/award.svg";
defineProps({
modelValue: { type: Boolean, default: false },
points: { type: [String, Number], default: 5 },
});
const emit = defineEmits(["update:modelValue", "continue"]);
const onContinue = () => {
emit("update:modelValue", false);
emit("continue");
};
</script>
<style lang="scss">
.order-award-dialog {
width: 458px;
.body-wrap {
padding: 28px;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
img {
width: 90px;
height: 83px;
object-fit: contain;
}
.award-title {
font-size: 22px;
line-height: 38px;
color: #1D2129;
font-weight: 600;
}
.award-subtitle {
font-size: 16px;
line-height: 30px;
color: #1D2129;
}
.award-points {
font-weight: 600;
font-size: 16px;
color: #FF8D28;
line-height: 34px;
.light {
font-size: 24px;
}
}
}
.award-btn {
font-size: 14px;
color: #FFFFFF;
padding: 16px 46px;
}
}
</style>

@ -28,6 +28,25 @@
<div class="value">{{ detail.serviceType }}</div> <div class="value">{{ detail.serviceType }}</div>
</div> </div>
</div> </div>
<!-- 第五行4 -->
<div class="detail-row row-4">
<div class="detail-item">
<div class="label">提出医院</div>
<div class="value">{{ detail.hospitalName || '-' }}</div>
</div>
<div class="detail-item">
<div class="label">提出科室</div>
<div class="value">{{ detail.department || '-' }}</div>
</div>
<div class="detail-item">
<div class="label">提出人员</div>
<div class="value">{{ detail.submitter || '-' }}</div>
</div>
<div class="detail-item">
<div class="label">提交时间</div>
<div class="value">{{ detail.createdAt?.split('T').join(' ') || '-' }}</div>
</div>
</div>
<!-- 第二行1 --> <!-- 第二行1 -->
<div class="detail-row row-1"> <div class="detail-row row-1">
<div class="detail-item"> <div class="detail-item">
@ -49,8 +68,7 @@
<div class="detail-item"> <div class="detail-item">
<div class="label">附件</div> <div class="label">附件</div>
<div class="value"> <div class="value">
<el-empty style="padding:0;" v-if="!detail.attachments?.length" <span v-if="!detail.attachments?.length" v-html="$t('orderDetail.noAttachment')"></span>
:description="$t('orderDetail.noAttachment')" :image-size="80" />
<div v-else class="files"> <div v-else class="files">
<el-button v-for="(f, i) in detail.attachments" :key="i" link type="primary" <el-button v-for="(f, i) in detail.attachments" :key="i" link type="primary"
class="file-item" @click="viewReport(f.filePath, f.fileName)"> class="file-item" @click="viewReport(f.filePath, f.fileName)">
@ -62,25 +80,6 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 第五行4 -->
<div class="detail-row row-4">
<div class="detail-item">
<div class="label">提出医院</div>
<div class="value">{{ detail.hospitalName || '-' }}</div>
</div>
<div class="detail-item">
<div class="label">提出科室</div>
<div class="value">{{ detail.department || '-' }}</div>
</div>
<div class="detail-item">
<div class="label">提出人员</div>
<div class="value">{{ detail.submitter || '-' }}</div>
</div>
<div class="detail-item">
<div class="label">提交时间</div>
<div class="value">{{ detail.createdAt?.split('T').join(' ') || '-' }}</div>
</div>
</div>
</div> </div>
@ -90,7 +89,12 @@
<el-timeline-item v-for="(log, i) in detail.statusLogs || []" :key="i" :timestamp="log.createdAt" <el-timeline-item v-for="(log, i) in detail.statusLogs || []" :key="i" :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="remark-wrap" style="display: flex; align-items: start;">
<div class="remark-title" style="font-weight: 600;color:var(--el-text-color-primary);">处理备注
</div>
<div class="log-remark" v-html="log.remark"></div>
</div>
</el-timeline-item> </el-timeline-item>
</el-timeline> </el-timeline>
</div> </div>
@ -322,6 +326,7 @@ watch(
white-space: nowrap; white-space: nowrap;
flex-shrink: 0; flex-shrink: 0;
flex-grow: 0; flex-grow: 0;
text-align: right;
} }
.value { .value {
@ -401,7 +406,7 @@ watch(
font-weight: 600; font-weight: 600;
} }
.log-remark { .remark-wrap {
color: #606266; color: #606266;
margin-top: 4px; margin-top: 4px;
} }

@ -253,35 +253,30 @@ const rules = {
{ {
required: true, required: true,
message: () => $t('msg.pleaseSelectHospital'), message: () => $t('msg.pleaseSelectHospital'),
trigger: 'change',
}, },
], ],
title: [ title: [
{ {
required: true, required: true,
message: () => $t('msg.pleaseInputTitle'), message: () => $t('msg.pleaseInputTitle'),
trigger: 'blur',
}, },
], ],
serviceType: [ serviceType: [
{ {
required: true, required: true,
message: () => $t('msg.pleaseSelectType'), message: () => $t('msg.pleaseSelectType'),
trigger: 'blur',
}, },
], ],
priority: [ priority: [
{ {
required: true, required: true,
message: () => $t('msg.pleaseSelectPriority'), message: () => $t('msg.pleaseSelectPriority'),
trigger: 'change',
}, },
], ],
feedbackchannel: [ feedbackchannel: [
{ {
required: true, required: true,
message: () => $t('msg.pleaseSelectFeedbackChannel'), message: () => $t('msg.pleaseSelectFeedbackChannel'),
trigger: 'change',
}, },
], ],
department: [ department: [
@ -289,7 +284,6 @@ const rules = {
required: true, required: true,
whitespace: true, whitespace: true,
message: () => $t('msg.pleaseInputDept'), message: () => $t('msg.pleaseInputDept'),
trigger: 'blur',
}, },
], ],
submitter: [ submitter: [
@ -297,7 +291,6 @@ const rules = {
required: true, required: true,
whitespace: true, whitespace: true,
message: () => $t('msg.pleaseInputName'), message: () => $t('msg.pleaseInputName'),
trigger: 'blur',
}, },
], ],
description: [ description: [
@ -313,7 +306,6 @@ const rules = {
return callback(new Error($t('msg.pleaseInputDescription'))); return callback(new Error($t('msg.pleaseInputDescription')));
callback(); callback();
}, },
trigger: 'change',
}, },
], ],
}; };

@ -106,8 +106,7 @@
<!-- 附件 --> <!-- 附件 -->
<el-form-item label="附件"> <el-form-item label="附件">
<div class="attachment"> <div class="attachment">
<el-empty style="padding:0;" v-if="!detail.attachments?.length" <span v-if="!detail.attachments?.length" v-html="$t('orderDetail.noAttachment')"></span>
:description="$t('orderDetail.noAttachment')" :image-size="80" />
<div v-else class="files"> <div v-else class="files">
<el-button v-for="(f, i) in detail.attachments" :key="i" link <el-button v-for="(f, i) in detail.attachments" :key="i" link
class="file-item" @click="viewReport(f)"> class="file-item" @click="viewReport(f)">

@ -3,15 +3,27 @@
top="5vh" @update:model-value="(v) => $emit('update:modelValue', v)" @open="onOpen"> top="5vh" @update:model-value="(v) => $emit('update:modelValue', v)" @open="onOpen">
<div class="body-wrap" v-loading="loading"> <div class="body-wrap" v-loading="loading">
<div class="body-header"> <div class="body-header">
<div class="body-title">处理工单</div> <div class="body-title">{{ isCompleted ? '修改工单' : '处理工单' }}</div>
<img src="@/assets/image/close-btn.svg" alt="" class="close-btn" @click="closeDialog" /> <img src="@/assets/image/close-btn.svg" alt="" class="close-btn" @click="closeDialog" />
</div> </div>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" v-if="detail.id"> <el-form ref="formRef" :model="form" :rules="rules" label-width="100px" v-if="detail.id">
<!-- 工单号只读 -->
<el-form-item :label="$t('table.id')">
<el-input :model-value="detail.orderNo" readonly /> <!-- 科室 / 提交人可编辑回显原值 -->
</el-form-item> <el-row :gutter="16">
<el-col :span="12">
<!-- 工单号只读 -->
<el-form-item :label="$t('table.id')">
<el-input :model-value="detail.orderNo" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('table.title')">
<el-input :model-value="detail.title" readonly />
</el-form-item>
</el-col>
</el-row>
<!-- 科室 / 提交人可编辑回显原值 --> <!-- 科室 / 提交人可编辑回显原值 -->
<el-row :gutter="16"> <el-row :gutter="16">
@ -73,7 +85,7 @@
<div class="body-footer"> <div class="body-footer">
<el-button @click="closeDialog">{{ $t("common.cancel") }}</el-button> <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") isCompleted ? '确认修改' : '确认处理'
}}</el-button> }}</el-button>
</div> </div>
</el-dialog> </el-dialog>
@ -99,14 +111,16 @@ import {
} from "@/service/modular/admin"; } from "@/service/modular/admin";
import { useUserStore } from "@/stores/api/user"; import { useUserStore } from "@/stores/api/user";
const { t: $t } = useI18n(); const { t: $t } = useI18n();
const userStore = useUserStore(); const userStore = useUserStore();
const props = defineProps({ const props = defineProps({
modelValue: { type: Boolean, default: false }, modelValue: { type: Boolean, default: false },
orderId: { type: [String, Number], default: "" }, orderId: { type: [String, Number], default: "" },
isCompleted: { type: Boolean, default: false },
}); });
const emit = defineEmits(["update:modelValue", "processed"]); const emit = defineEmits(["update:modelValue", "processed","openAward"]);
const loading = ref(false); const loading = ref(false);
const submitting = ref(false); const submitting = ref(false);
@ -133,7 +147,7 @@ const form = reactive({
const rules = { const rules = {
status: [ status: [
{ required: true, message: () => $t("table.status"), trigger: "change" }, { required: true, message: () => $t("table.status") },
], ],
processRemark: [ processRemark: [
{ {
@ -148,7 +162,6 @@ const rules = {
return callback(new Error($t("placeholder.remark"))); return callback(new Error($t("placeholder.remark")));
callback(); callback();
}, },
trigger: "change",
}, },
], ],
}; };
@ -296,6 +309,7 @@ watch(
}, },
); );
const submit = async () => { const submit = async () => {
if (!formRef.value) return; if (!formRef.value) return;
await formRef.value.validate(async (valid) => { await formRef.value.validate(async (valid) => {
@ -309,7 +323,13 @@ const submit = async () => {
department: form.department, department: form.department,
submitter: form.submitter, submitter: form.submitter,
}); });
ElMessage.success($t("msg.editSuccess"));
//
if (!props.isCompleted) {
emit("openAward");
} else {
ElMessage.success($t("msg.editSuccess"));
}
emit("update:modelValue", false); emit("update:modelValue", false);
emit("processed"); emit("processed");
} finally { } finally {

@ -38,9 +38,9 @@
<div class="summary-text"> <div class="summary-text">
<span class="light">感谢贵院一直以来对上海康策的信任与配合</span> <br> <span class="light">感谢贵院一直以来对上海康策的信任与配合</span> <br>
<span class="em"> <span class="em">
本期我们累计处理服务工单<span class="blue">{{ 28 本期我们累计处理服务工单<span class="blue">{{detail.total
}}</span>完成率达<span class="green">{{ 95 }}%</span>同时安排回访<span class="cyan">{{ 3 }}</span>完成率达<span class="green">{{ detail.rate }}</span>同时安排回访<span class="cyan">{{ detail.callRecord
}}</span>现场巡检<span class="yellow">{{ 1 }}</span>及参与培训<span class="purple">{{ 1 }}</span>现场巡检<span class="yellow">{{ detail.inspectionRecord }}</span>及参与培训<span class="purple">{{ detail.trainingRecord
}}</span>力求从响应预防到能力共建全方位护航医院信息化环境 }}</span>力求从响应预防到能力共建全方位护航医院信息化环境
</span> <br> </span> <br>
<span class="light"> <span class="light">
@ -372,7 +372,7 @@ const loadDetail = async (overrideId) => {
// name: "", // name: "",
// count: 3 // count: 3
// }, { // }, {
// name: "", // name: "",/
// count: 3 // count: 3
// }, { // }, {
// name: "", // name: "",
@ -527,7 +527,7 @@ const deptOption = computed(() => {
.sort((a, b) => b.count - a.count); .sort((a, b) => b.count - a.count);
return { return {
grid: { left: 10, right: 10, top: 10, bottom: 10 }, grid: { left: 10, right: 10, top: 20, bottom: 10 },
tooltip: { tooltip: {
trigger: "axis", trigger: "axis",
axisPointer: { type: "shadow" }, axisPointer: { type: "shadow" },
@ -898,7 +898,7 @@ const statusClass = (s) =>
width: 100%; width: 100%;
position: relative; position: relative;
height: 28px; height: 28px;
background: #E7EDF6; background: #C5D0E0;
border-radius: 12px 12px 12px 12px; border-radius: 12px 12px 12px 12px;
padding: 4px; padding: 4px;

@ -109,9 +109,9 @@ const form = reactive({
}); });
const rules = { const rules = {
hospitalId: [{ required: true, message: "请选择医院名称", trigger: "change" }], hospitalId: [{ required: true, message: "请选择医院名称" }],
type: [{ required: true, message: "请选择报告类型", trigger: "change" }], type: [{ required: true, message: "请选择报告类型" }],
year: [{ required: true, message: "请选择年份", trigger: "change" }], year: [{ required: true, message: "请选择年份" }],
quarter: [ quarter: [
{ {
validator: (_, value, cb) => { validator: (_, value, cb) => {
@ -119,7 +119,6 @@ const rules = {
return cb(new Error("季度报告必须选择季度")); return cb(new Error("季度报告必须选择季度"));
cb(); cb();
}, },
trigger: "change",
}, },
], ],
}; };

@ -108,8 +108,9 @@
</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 }">
<span class="title-link" :style="{color:row.colorTag}" style="text-decoration: underline;" @click="goHandle(row.id)">{{ row.title <span class="title-link" :style="{ color: row.colorTag }" style="text-decoration: underline;"
}}</span> @click="goHandle(row.id)">{{ row.title
}}</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" />
@ -133,7 +134,7 @@
<el-table-column :label="$t('table.action')" width="160" fixed="right"> <el-table-column :label="$t('table.action')" width="160" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button class="handle smaller" plain :class="row.status === '已完成' ? 'purple-sign' : 'rosein-sign'" <el-button class="handle smaller" plain :class="row.status === '已完成' ? 'purple-sign' : 'rosein-sign'"
@click="goProcess(row.id)">{{ @click="goProcess(row.id, row.status === '已完成' ? true : false)">{{
row.status === "已完成" ? $t("btn.modify") : $t("btn.process") row.status === "已完成" ? $t("btn.modify") : $t("btn.process")
}}</el-button> }}</el-button>
<el-button class="blue-sign smaller" type="primary" plain @click="goDetail(row.id)">{{ <el-button class="blue-sign smaller" type="primary" plain @click="goDetail(row.id)">{{
@ -156,10 +157,15 @@
<OrderDetailDialog v-model="detailVisible" :order-id="currentOrderId" @updated="loadList" /> <OrderDetailDialog v-model="detailVisible" :order-id="currentOrderId" @updated="loadList" />
<!-- 处理工单 弹窗 --> <!-- 处理工单 弹窗 -->
<OrderProcessDialog v-model="processVisible" :order-id="currentOrderId" @processed="loadList" /> <OrderProcessDialog v-model="processVisible" :order-id="currentOrderId" :is-completed="isCompleted"
@processed="loadList" @openAward="awardVisible = true" />
<!-- 工单处理/详情一体化 弹窗点击标题进入 --> <!-- 工单处理/详情一体化 弹窗点击标题进入 -->
<OrderHandleDialog v-model="handleVisible" :order-id="currentOrderId" @updated="loadList" /> <OrderHandleDialog v-model="handleVisible" :order-id="currentOrderId" @updated="loadList" />
<!-- 处理成功奖励弹窗 -->
<OrderAwardDialog v-model="awardVisible" @continue="onAwardContinue" />
</div> </div>
</template> </template>
@ -183,7 +189,7 @@ import iconAll from "@/assets/image/admin/orders/all.svg";
import iconPending from "@/assets/image/admin/orders/pending.svg"; import iconPending from "@/assets/image/admin/orders/pending.svg";
import iconComplete from "@/assets/image/admin/orders/complete.svg"; import iconComplete from "@/assets/image/admin/orders/complete.svg";
import iconHigh from "@/assets/image/admin/orders/high.svg"; import iconHigh from "@/assets/image/admin/orders/high.svg";
import OrderAwardDialog from "./components/OrderAwardDialog.vue";
const { t: $t } = useI18n(); const { t: $t } = useI18n();
const userStore = useUserStore(); const userStore = useUserStore();
const hospitalStore = useHospitalStore(); const hospitalStore = useHospitalStore();
@ -202,7 +208,12 @@ const filters = reactive({
type: "", type: "",
keyword: "", keyword: "",
}); });
//
const awardVisible = ref(false);
// + +
const onAwardContinue = () => {
awardVisible.value = false;
};
const statusClass = (s) => const statusClass = (s) =>
s === "已完成" ? "done" : s === "待处理" ? "pending" : 'info'; s === "已完成" ? "done" : s === "待处理" ? "pending" : 'info';
@ -305,14 +316,15 @@ const detailVisible = ref(false);
const processVisible = ref(false); const processVisible = ref(false);
const handleVisible = ref(false); const handleVisible = ref(false);
const currentOrderId = ref(""); const currentOrderId = ref("");
const isCompleted = ref(false);
const goDetail = (id) => { const goDetail = (id) => {
currentOrderId.value = id; currentOrderId.value = id;
detailVisible.value = true; detailVisible.value = true;
}; };
const goProcess = (id) => { const goProcess = (id, isCompletedFlag) => {
currentOrderId.value = id; currentOrderId.value = id;
processVisible.value = true; processVisible.value = true;
isCompleted.value = isCompletedFlag || false;
}; };
const goHandle = (id) => { const goHandle = (id) => {
currentOrderId.value = id; currentOrderId.value = id;

@ -10,7 +10,7 @@
<el-button :class="['period-tab', { active: isCustom }]" plain @click="onPickCustom"></el-button> <el-button :class="['period-tab', { active: isCustom }]" plain @click="onPickCustom"></el-button>
</div> </div>
<div v-if="isCustom" class="custom-range"> <div v-if="isCustom" class="custom-range">
<el-date-picker v-model="customRange" type="daterange" range-separator="-" start-placeholder="" <el-date-picker :shortcuts="dateRangeShortcuts" v-model="customRange" type="daterange" range-separator="-" start-placeholder=""
end-placeholder="结束日期" value-format="YYYY-MM-DD" style="width: 240px" /> end-placeholder="结束日期" value-format="YYYY-MM-DD" style="width: 240px" />
<el-button type="primary" class="search-btn" plain @click="onSearch"></el-button> <el-button type="primary" class="search-btn" plain @click="onSearch"></el-button>
</div> </div>
@ -133,6 +133,7 @@ import inspectorIcon from "@/assets/image/admin/score/inspector.svg";
import trainIcon from "@/assets/image/admin/score/train.svg"; import trainIcon from "@/assets/image/admin/score/train.svg";
import huifangIcon from "@/assets/image/admin/score/huifang.svg"; import huifangIcon from "@/assets/image/admin/score/huifang.svg";
import complaintIcon from "@/assets/image/admin/score/complaint.svg"; import complaintIcon from "@/assets/image/admin/score/complaint.svg";
import { dateRangeShortcuts } from "@/utils/dateShortcuts";
import { import {
getManagerPointsDetails, getManagerPointsDetails,
getManagerPointsLeaderboard, getManagerPointsLeaderboard,

@ -42,7 +42,7 @@
</div> </div>
<div class="detail"> <div class="detail">
<span class="detail-name">今日提交工单</span> <span class="detail-name">今日提交工单</span>
<span class="detail-points">+{{ todayOrder }} </span> <span class="detail-points">+{{ todayOrder }} </span>
</div> </div>
</div> </div>
<div class="detail-item"> <div class="detail-item">

@ -5,24 +5,26 @@
<!-- ============ 头部标题 / 副标题 / 装饰图 ============ --> <!-- ============ 头部标题 / 副标题 / 装饰图 ============ -->
<div class="signin-header"> <div class="signin-header">
<h3 class="signin-title" :class="{ 'is-signed': signedIn }"> <h3 class="signin-title" :class="{ 'is-signed': signedIn }">
<template v-if="signedIn"> <!-- <template v-if="signedIn"> -->
<template v-if="true">
连续签到<span class="title-num">{{ streakDays }}</span> 连续签到<span class="title-num">{{ streakDays }}</span>
<template v-if="rank > 0">排名第<span class="title-num">{{ rank }}</span></template> <template v-if="rank > 0">排名第<span class="title-num">{{ rank }}</span></template>
<template v-else></template> <template v-else></template>
</template> </template>
<template v-else> <!-- <template v-else>
每日签到 <span class="title-num">领积分</span> 每日签到 <span class="title-num">领积分</span>
</template> </template> -->
</h3> </h3>
<p class="signin-subtitle"> <p class="signin-subtitle">
<template v-if="signedIn"> <!-- <template v-if="signedIn"> -->
<template v-if="true">
当前积分: <span class="title-num">{{ totalPoints }}</span> 当前积分: <span class="title-num">{{ totalPoints }}</span>
<template v-if="beatPercent > 0">{{ beatPercent }}%</template> &nbsp;您已击败全国{{ beatPercent }}%的医院
<template v-else></template> <!-- <template v-else></template> -->
</template> </template>
<template v-else> <!-- <template v-else>
坚持签到积分兑好礼 坚持签到积分兑好礼
</template> </template> -->
</p> </p>
</div> </div>
@ -66,13 +68,8 @@
</el-dialog> </el-dialog>
<!-- 签到成功 今日未抽奖则弹出抽奖弹窗(抽过则不再弹,避免重复打扰) --> <!-- 签到成功 今日未抽奖则弹出抽奖弹窗(抽过则不再弹,避免重复打扰) -->
<LotteryDialog <LotteryDialog v-model="lotteryVisible" :drawable="!hasDrawn" :batch-goal="500" :batch-current="0"
v-model="lotteryVisible" @success="onLotterySuccess" />
:drawable="!hasDrawn"
:batch-goal="500"
:batch-current="0"
@success="onLotterySuccess"
/>
</template> </template>
<script setup> <script setup>

@ -28,6 +28,21 @@
<div class="value">{{ detail.serviceType }}</div> <div class="value">{{ detail.serviceType }}</div>
</div> </div>
</div> </div>
<!-- 第五行3不展示"提出医院"医院端默认就是当前登录医院 -->
<div class="detail-row row-3">
<div class="detail-item">
<div class="label">提出科室</div>
<div class="value">{{ detail.department || '-' }}</div>
</div>
<div class="detail-item">
<div class="label">提出人员</div>
<div class="value">{{ detail.submitter || '-' }}</div>
</div>
<div class="detail-item">
<div class="label">提交时间</div>
<div class="value">{{ detail.createdAt?.split('T').join(' ') || '-' }}</div>
</div>
</div>
<!-- 第二行1 --> <!-- 第二行1 -->
<div class="detail-row row-1"> <div class="detail-row row-1">
<div class="detail-item"> <div class="detail-item">
@ -49,11 +64,10 @@
<div class="detail-item"> <div class="detail-item">
<div class="label">附件</div> <div class="label">附件</div>
<div class="value"> <div class="value">
<el-empty style="padding:0;" v-if="!detail.attachments?.length" <span v-if="!detail.attachments?.length" v-html="$t('orderDetail.noAttachment')"></span>
:description="$t('orderDetail.noAttachment')" :image-size="80" />
<div v-else class="files"> <div v-else class="files">
<el-button v-for="(f, i) in detail.attachments" :key="i" link type="primary" <el-button v-for="(f, i) in detail.attachments" :key="i" link type="primary" class="file-item"
class="file-item" @click="viewReport(f.filePath, f.fileName)"> @click="viewReport(f.filePath, f.fileName)">
<el-icon> <el-icon>
<Document /> <Document />
</el-icon>{{ f.fileName }} </el-icon>{{ f.fileName }}
@ -62,29 +76,13 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 第五行3不展示"提出医院"医院端默认就是当前登录医院 -->
<div class="detail-row row-3">
<div class="detail-item">
<div class="label">提出科室</div>
<div class="value">{{ detail.department || '-' }}</div>
</div>
<div class="detail-item">
<div class="label">提出人员</div>
<div class="value">{{ detail.submitter || '-' }}</div>
</div>
<div class="detail-item">
<div class="label">提交时间</div>
<div class="value">{{ detail.createdAt?.split('T').join(' ') || '-' }}</div>
</div>
</div>
</div> </div>
<div class="section" v-if="detail.logs?.length"> <div class="section" v-if="detail.logs?.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.logs || []" :key="i" :timestamp="log.createdAt" <el-timeline-item v-for="(log, i) in detail.logs || []" :key="i" :timestamp="log.createdAt" placement="top">
placement="top">
<div class="log-title">{{ log.action }} · {{ log.operator }}</div> <div class="log-title">{{ log.action }} · {{ 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>
@ -97,8 +95,7 @@
<el-button type="primary" plain style="margin-left:auto" @click="closeDialog"> <el-button type="primary" plain style="margin-left:auto" @click="closeDialog">
取消 取消
</el-button> </el-button>
<el-button v-if="detail.status !== '' && detail.status !== ''" type="primary" <el-button v-if="detail.status !== '已完成' && detail.status !== '已取消'" type="primary" @click="editVisible = true">
@click="editVisible = true">
{{ $t('btn.edit') }} {{ $t('btn.edit') }}
</el-button> </el-button>
</div> </div>
@ -291,6 +288,7 @@ const onEditSuccess = () => {
white-space: nowrap; white-space: nowrap;
flex-shrink: 0; flex-shrink: 0;
flex-grow: 0; flex-grow: 0;
text-align: right;
} }
.value { .value {

@ -236,17 +236,17 @@ const form = reactive({
}); });
const rules = { const rules = {
title: [{ required: true, message: () => $t('msg.pleaseInputTitle'), trigger: "blur" }], title: [{ required: true, message: () => $t('msg.pleaseInputTitle') }],
serviceType: [{ required: true, message: () => $t('msg.pleaseSelectType'), trigger: "change" }], serviceType: [{ required: true, message: () => $t('msg.pleaseSelectType') }],
priority: [{ required: true, message: () => $t('msg.pleaseSelectPriority'), trigger: "change" }], priority: [{ required: true, message: () => $t('msg.pleaseSelectPriority') }],
feedbackchannel: [ feedbackchannel: [
{ required: true, message: () => $t('msg.pleaseSelectFeedbackChannel'), trigger: "change" }, { required: true, message: () => $t('msg.pleaseSelectFeedbackChannel') },
], ],
department: [ department: [
{ required: true, whitespace: true, message: () => $t('msg.pleaseInputDept'), trigger: "blur" }, { required: true, whitespace: true, message: () => $t('msg.pleaseInputDept') },
], ],
submitter: [ submitter: [
{ required: true, whitespace: true, message: () => $t('msg.pleaseInputName'), trigger: "blur" }, { required: true, whitespace: true, message: () => $t('msg.pleaseInputName') },
], ],
description: [ description: [
{ {
@ -260,7 +260,6 @@ const rules = {
if (!text && !hasImage) return callback(new Error($t('msg.pleaseInputDescription'))); if (!text && !hasImage) return callback(new Error($t('msg.pleaseInputDescription')));
callback(); callback();
}, },
trigger: "change",
}, },
], ],
}; };

@ -39,7 +39,7 @@
<div class="section"> <div class="section">
<div class="section-title">{{ $t('label.attachment') }} ({{ (detail.attachments || []).length }})</div> <div class="section-title">{{ $t('label.attachment') }} ({{ (detail.attachments || []).length }})</div>
<el-empty v-if="!detail.attachments?.length" :description="$t('orderDetail.noAttachment')" :image-size="80" /> <span v-if="!detail.attachments?.length" v-html="$t('orderDetail.noAttachment')"></span>
<div v-else class="files"> <div v-else class="files">
<el-button <el-button
v-for="(f, i) in detail.attachments" v-for="(f, i) in detail.attachments"

@ -130,7 +130,7 @@
</div> </div>
</template> </template>
<el-table class="jifen-table" header-cell-class-name="jifen-table-header-cell" :data="details" height="400" v-loading="loading" <el-table class="jifen-table" header-cell-class-name="jifen-table-header-cell" :data="details" max-height="400" v-loading="loading"
border stripe> border stripe>
<el-table-column label="时间" prop="createdAt" width="170" /> <el-table-column label="时间" prop="createdAt" width="170" />
<el-table-column label="来源" prop="source" width="140"> <el-table-column label="来源" prop="source" width="140">
@ -206,7 +206,7 @@
<!-- ========== 签到弹窗 ========== --> <!-- ========== 签到弹窗 ========== -->
<SignInDialog v-model="signInVisible" :signed-in="!!summary.isSignIn" :total-points="totalPoints" <SignInDialog v-model="signInVisible" :signed-in="!!summary.isSignIn" :total-points="totalPoints"
:consecutive-sign-in-days="consecutiveSignInDays" :sign-in-rank="signInRank" :beat-percent="beatPercent" :consecutive-sign-in-days="consecutiveSignInDays" :sign-in-rank="signInRank" :beat-percent="beatPercent"
@success="onSignInSuccess" /> @success="onSignInSuccess" :hasDrawn="summary.isDraw" />
<!-- 我的资产弹窗已迁到 AdminLayout 全局(AssetDialogStore 控制)避免重复弹窗 --> <!-- 我的资产弹窗已迁到 AdminLayout 全局(AssetDialogStore 控制)避免重复弹窗 -->
@ -474,18 +474,17 @@ const exchangeForm = reactive({
}); });
const exchangeRules = { const exchangeRules = {
recipientName: [ recipientName: [
{ required: true, message: "请输入收件人姓名", trigger: "blur" }, { required: true, message: "请输入收件人姓名" },
], ],
recipientPhone: [ recipientPhone: [
{ required: true, message: "请输入手机号码", trigger: "blur" }, { required: true, message: "请输入手机号码" },
{ {
pattern: /^[\d\-+ ]{7,15}$/, pattern: /^[\d\-+ ]{7,15}$/,
message: "请输入有效的手机号码", message: "请输入有效的手机号码",
trigger: "blur",
}, },
], ],
recipientAddress: [ recipientAddress: [
{ required: true, message: "请输入详细收件地址", trigger: "blur" }, { required: true, message: "请输入详细收件地址" },
], ],
}; };
const exchangeFormRef = ref(); const exchangeFormRef = ref();

@ -13,7 +13,7 @@
}}</el-button> }}</el-button>
</div> </div>
<div v-if="isCustom" class="custom-range"> <div v-if="isCustom" class="custom-range">
<el-date-picker v-model="dateRange" type="daterange" range-separator="-" <el-date-picker :shortcuts="dateRangeShortcuts" v-model="dateRange" type="daterange" range-separator="-"
:start-placeholder="$t('common.startDate')" :end-placeholder="$t('common.endDate')" :start-placeholder="$t('common.startDate')" :end-placeholder="$t('common.endDate')"
value-format="YYYY-MM-DD" style="width: 240px" /> value-format="YYYY-MM-DD" style="width: 240px" />
<el-button type="primary" class="search-btn" plain @click="onSearch"></el-button> <el-button type="primary" class="search-btn" plain @click="onSearch"></el-button>
@ -294,12 +294,34 @@ const chartOption = computed(() => {
height: 14, height: 14,
startValue: sliderStartValue, startValue: sliderStartValue,
endValue: sliderEndValue, endValue: sliderEndValue,
zoomLock: true,
showDetail: false, showDetail: false,
showDataShadow: false, showDataShadow: false,
fillerColor: "rgba(50,137,251,0.12)", fillerColor: "rgba(50,137,251,0.12)",
borderColor: "transparent", borderColor: "transparent",
backgroundColor: "#F2F3F5", backgroundColor: "#F2F3F5",
moveHandleSize: 0,
zoomLock: true,
brushSelect: false,
fillerColor: "#3289fb66", // 0.12
// 14
handleSize: 14,
handleIcon: 'path://M2,2 L22,2 L22,22 L2,22 Z M9,5 L9,19 M12,5 L12,19 M15,5 L15,19',
// +
handleStyle: {
color: '#3289fb',
// borderRadius: 2, // 0 2
// borderWidth: 7,
shadowBlur: 0,
cursor: 'pointer',
},
//
emphasis: {
handleStyle: {
cursor: 'pointer',
}
},
}, },
] ]
: [], : [],

@ -97,14 +97,12 @@ const rules = {
{ {
required: true, required: true,
message: () => t("login.pleaseEnterUserName"), message: () => t("login.pleaseEnterUserName"),
trigger: "blur",
}, },
], ],
password: [ password: [
{ {
required: true, required: true,
message: () => t("login.pleaseEnterPassWord"), message: () => t("login.pleaseEnterPassWord"),
trigger: "blur",
}, },
], ],
}; };

Loading…
Cancel
Save