21 changed files with 2340 additions and 137 deletions
@ -1,22 +1,99 @@ |
|||||
package com.zdxt.enforcementcode.controller.app.enforcement; |
package com.zdxt.enforcementcode.controller.app.enforcement; |
||||
|
|
||||
import com.zdxt.enforcementcode.controller.web.supervision.EfcodeCpNoticeController; |
import com.zdxt.enforcementcode.domain.bo.EfcodeCpNoticeBo; |
||||
|
import com.zdxt.enforcementcode.domain.bo.EfcodeCpNoticeMessageBo; |
||||
|
import com.zdxt.enforcementcode.domain.vo.EfcodeCpNoticeMessageVo; |
||||
|
import com.zdxt.enforcementcode.domain.vo.EfcodeCpNoticeVo; |
||||
|
import com.zdxt.enforcementcode.service.IEfcodeCpNoticeMessageService; |
||||
import com.zdxt.enforcementcode.service.IEfcodeCpNoticeService; |
import com.zdxt.enforcementcode.service.IEfcodeCpNoticeService; |
||||
|
import jakarta.validation.constraints.NotBlank; |
||||
|
import jakarta.validation.constraints.NotEmpty; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.dromara.common.core.domain.R; |
||||
|
import org.dromara.common.mybatis.core.page.PageQuery; |
||||
|
import org.dromara.common.mybatis.core.page.TableDataInfo; |
||||
import org.springframework.validation.annotation.Validated; |
import org.springframework.validation.annotation.Validated; |
||||
import org.springframework.web.bind.annotation.*; |
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
/** |
/** |
||||
* 行政执法检查计划-预约 |
* 执法端-预告管理 |
||||
* |
* |
||||
* @author Lion Li |
* @author zdxt |
||||
* @date 2026-05-16 |
* @date 2026-05-28 |
||||
*/ |
*/ |
||||
@Validated |
@Validated |
||||
|
@RequiredArgsConstructor |
||||
@RestController |
@RestController |
||||
@RequestMapping("/app-enf/cpNotice") |
@RequestMapping("/app-enf/cpNotice") |
||||
public class AppEnfCpNoticeController extends EfcodeCpNoticeController { |
public class AppEnfCpNoticeController { |
||||
|
|
||||
|
private final IEfcodeCpNoticeService efcodeCpNoticeService; |
||||
|
private final IEfcodeCpNoticeMessageService efcodeCpNoticeMessageService; |
||||
|
|
||||
|
/** |
||||
|
* 查询我的预告列表(分页,只返回当前用户创建的预告) |
||||
|
* 通过status区分tab:0=草稿, 1=待执法(已发布), 3=历史(已执行) |
||||
|
*/ |
||||
|
@GetMapping("/myList") |
||||
|
public TableDataInfo<EfcodeCpNoticeVo> myList(EfcodeCpNoticeBo bo, PageQuery pageQuery) { |
||||
|
return efcodeCpNoticeService.queryMyPageList(bo, pageQuery); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取预告详情 |
||||
|
*/ |
||||
|
@GetMapping("/{id}") |
||||
|
public R<EfcodeCpNoticeVo> getInfo(@NotBlank(message = "主键不能为空") @PathVariable String id) { |
||||
|
return R.ok(efcodeCpNoticeService.queryById(id)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 保存预告(草稿/发布) |
||||
|
* status=0 存为草稿, status=1 发布预告 |
||||
|
*/ |
||||
|
@PostMapping() |
||||
|
public R<String> save(@RequestBody EfcodeCpNoticeBo bo) { |
||||
|
return R.ok(efcodeCpNoticeService.saveNotice(bo)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 修改预告(仅草稿状态可修改) |
||||
|
*/ |
||||
|
@PutMapping() |
||||
|
public R<String> update(@RequestBody EfcodeCpNoticeBo bo) { |
||||
|
return R.ok(efcodeCpNoticeService.updateNotice(bo)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除预告(仅草稿状态可删除) |
||||
|
*/ |
||||
|
@DeleteMapping("/{ids}") |
||||
|
public R<String> remove(@NotEmpty(message = "主键不能为空") @PathVariable String[] ids) { |
||||
|
return R.ok(efcodeCpNoticeService.deleteNotice(List.of(ids))); |
||||
|
} |
||||
|
|
||||
|
// =================== 留言相关 ===================
|
||||
|
|
||||
|
/** |
||||
|
* 查询某个预告的留言列表 |
||||
|
*/ |
||||
|
@GetMapping("/message/list") |
||||
|
public R<List<EfcodeCpNoticeMessageVo>> messageList(@RequestParam String noticeId) { |
||||
|
EfcodeCpNoticeMessageBo bo = new EfcodeCpNoticeMessageBo(); |
||||
|
bo.setNoticeId(noticeId); |
||||
|
bo.setIsDelete(0L); |
||||
|
return R.ok(efcodeCpNoticeMessageService.queryList(bo)); |
||||
|
} |
||||
|
|
||||
public AppEnfCpNoticeController(IEfcodeCpNoticeService efcodeCpNoticeService) { |
/** |
||||
super(efcodeCpNoticeService); |
* 新增留言 |
||||
|
*/ |
||||
|
@PostMapping("/message") |
||||
|
public R<Void> addMessage(@RequestBody EfcodeCpNoticeMessageBo bo) { |
||||
|
bo.setIsDelete(0L); |
||||
|
efcodeCpNoticeMessageService.insertByBo(bo); |
||||
|
return R.ok(); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,888 @@ |
|||||
|
<template> |
||||
|
<div class="notice-edit-page app_content"> |
||||
|
<!-- 顶部导航 --> |
||||
|
<van-nav-bar |
||||
|
:title="pageTitle" |
||||
|
left-arrow |
||||
|
@click-left="onBack" |
||||
|
/> |
||||
|
|
||||
|
<div class="content"> |
||||
|
<!-- 表单 --> |
||||
|
<div class="form-card"> |
||||
|
<!-- 从计划导入 --> |
||||
|
<van-field |
||||
|
v-model="planImportDisplay" |
||||
|
label="从计划导入" |
||||
|
placeholder="请选择计划" |
||||
|
readonly |
||||
|
is-link |
||||
|
@click="showPlanPicker = true" |
||||
|
/> |
||||
|
|
||||
|
<!-- 执法日期 --> |
||||
|
<van-field |
||||
|
v-model="form.enforceDate" |
||||
|
label="执法日期" |
||||
|
placeholder="请选择" |
||||
|
required |
||||
|
readonly |
||||
|
is-link |
||||
|
@click="showDatePicker = true" |
||||
|
/> |
||||
|
|
||||
|
<!-- 执法时段 --> |
||||
|
<van-field |
||||
|
v-model="enforceTimeDisplay" |
||||
|
label="执法时段" |
||||
|
placeholder="请选择" |
||||
|
required |
||||
|
readonly |
||||
|
is-link |
||||
|
@click="showTimePicker = true" |
||||
|
/> |
||||
|
|
||||
|
<!-- 执法单位 --> |
||||
|
<van-field |
||||
|
v-model="form.bureauDeptName" |
||||
|
label="执法单位" |
||||
|
required |
||||
|
readonly |
||||
|
/> |
||||
|
|
||||
|
<!-- 执法人员 --> |
||||
|
<van-field label="执法人员" required> |
||||
|
<template #input> |
||||
|
<div class="person-box"> |
||||
|
<div |
||||
|
v-for="(person, index) in personList" |
||||
|
:key="index" |
||||
|
class="person-line" |
||||
|
> |
||||
|
<span class="person-name">{{ person.name }}</span> |
||||
|
<span class="person-phone">{{ person.phone }}</span> |
||||
|
<van-icon |
||||
|
v-if="personList.length > 1" |
||||
|
name="close" |
||||
|
class="person-remove" |
||||
|
@click="removePerson(index)" |
||||
|
/> |
||||
|
</div> |
||||
|
<div class="add-person-line" @click="onAddPerson"> |
||||
|
<span class="placeholder-text">请输入</span> |
||||
|
<van-icon name="add-o" color="#2382cf" size="20" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
</van-field> |
||||
|
|
||||
|
<!-- 企业名称 --> |
||||
|
<van-field |
||||
|
v-model="form.enterpriseName" |
||||
|
label="企业名称" |
||||
|
placeholder="请选择企业" |
||||
|
required |
||||
|
readonly |
||||
|
is-link |
||||
|
@click="onOpenEnterprisePicker" |
||||
|
/> |
||||
|
|
||||
|
<!-- 企业联系人 --> |
||||
|
<van-field |
||||
|
v-model="form.contactPerson" |
||||
|
label="企业联系人" |
||||
|
placeholder="请输入联系人" |
||||
|
/> |
||||
|
|
||||
|
<!-- 企业联系电话 --> |
||||
|
<van-field |
||||
|
v-model="form.contactPhone" |
||||
|
label="企业联系电话" |
||||
|
placeholder="请输入联系电话" |
||||
|
/> |
||||
|
|
||||
|
<!-- 执法事项 --> |
||||
|
<van-field |
||||
|
v-model="form.enforceContent" |
||||
|
label="执法事项" |
||||
|
type="textarea" |
||||
|
placeholder="请输入执法事项" |
||||
|
required |
||||
|
:maxlength="1000" |
||||
|
show-word-limit |
||||
|
rows="3" |
||||
|
autosize |
||||
|
/> |
||||
|
|
||||
|
<!-- 执法要求 --> |
||||
|
<van-field |
||||
|
v-model="form.enforceRequirement" |
||||
|
label="执法要求" |
||||
|
type="textarea" |
||||
|
placeholder="请输入执法要求" |
||||
|
required |
||||
|
:maxlength="1000" |
||||
|
show-word-limit |
||||
|
rows="2" |
||||
|
autosize |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 底部按钮 --> |
||||
|
<div class="submit-wrap"> |
||||
|
<van-button |
||||
|
plain |
||||
|
type="default" |
||||
|
class="draft-btn" |
||||
|
:loading="savingDraft" |
||||
|
loading-text="保存中..." |
||||
|
@click="onSaveDraft" |
||||
|
> |
||||
|
保存草稿 |
||||
|
</van-button> |
||||
|
<van-button |
||||
|
type="primary" |
||||
|
class="publish-btn" |
||||
|
:loading="publishing" |
||||
|
loading-text="发布中..." |
||||
|
@click="onPublish" |
||||
|
> |
||||
|
提交预告 |
||||
|
</van-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 日期+时段选择器 --> |
||||
|
<van-popup v-model:show="showDatePicker" position="bottom" round> |
||||
|
<van-date-picker |
||||
|
v-model="datePickerValue" |
||||
|
title="选择执法日期" |
||||
|
:min-date="minDate" |
||||
|
@confirm="onDateConfirm" |
||||
|
@cancel="showDatePicker = false" |
||||
|
/> |
||||
|
</van-popup> |
||||
|
|
||||
|
<!-- 时段选择 --> |
||||
|
<van-popup v-model:show="showTimePicker" position="bottom" round> |
||||
|
<van-picker |
||||
|
:columns="timeColumns" |
||||
|
@confirm="onTimeConfirm" |
||||
|
@cancel="showTimePicker = false" |
||||
|
/> |
||||
|
</van-popup> |
||||
|
|
||||
|
<!-- 计划选择弹层 --> |
||||
|
<van-popup v-model:show="showPlanPicker" position="bottom" round :style="{ height: '60%' }"> |
||||
|
<div class="plan-picker"> |
||||
|
<div class="plan-picker-header"> |
||||
|
<span class="plan-picker-title">选择我的计划</span> |
||||
|
<van-icon name="cross" @click="showPlanPicker = false" /> |
||||
|
</div> |
||||
|
<div class="plan-picker-body"> |
||||
|
<div v-if="planLoading" class="plan-loading"> |
||||
|
<van-loading size="18" /> 加载中... |
||||
|
</div> |
||||
|
<template v-else> |
||||
|
<div |
||||
|
v-for="(plan, idx) in planOptions" |
||||
|
:key="plan.id || idx" |
||||
|
class="plan-item" |
||||
|
@click="onSelectPlan(plan)" |
||||
|
> |
||||
|
<div class="plan-item-title">{{ plan.enforceContent || '无事项' }}</div> |
||||
|
<div class="plan-item-meta"> |
||||
|
{{ plan.planDate }} | {{ plan.enterpriseName }} |
||||
|
</div> |
||||
|
</div> |
||||
|
<van-empty v-if="!planOptions.length" description="暂无可导入计划" image-size="80" /> |
||||
|
</template> |
||||
|
</div> |
||||
|
</div> |
||||
|
</van-popup> |
||||
|
|
||||
|
<!-- 执法人员检索弹层 --> |
||||
|
<van-popup |
||||
|
v-model:show="showPersonPicker" |
||||
|
position="bottom" |
||||
|
round |
||||
|
:style="{ height: '70%' }" |
||||
|
> |
||||
|
<div class="person-picker"> |
||||
|
<div class="person-picker-header"> |
||||
|
<span class="person-picker-title">选择执法人员</span> |
||||
|
<van-icon name="cross" @click="showPersonPicker = false" /> |
||||
|
</div> |
||||
|
<van-search |
||||
|
v-model="personSearchKeyword" |
||||
|
placeholder="输入姓名/账号检索单位人员" |
||||
|
shape="round" |
||||
|
@update:model-value="onPersonSearchInput" |
||||
|
/> |
||||
|
<div class="person-picker-body"> |
||||
|
<div v-if="personSearching" class="person-loading"> |
||||
|
<van-loading size="18" /> 加载中... |
||||
|
</div> |
||||
|
<template v-else> |
||||
|
<div |
||||
|
v-for="(user, idx) in personSearchResult" |
||||
|
:key="user.userId || idx" |
||||
|
class="person-item" |
||||
|
@click="onSelectPerson(user)" |
||||
|
> |
||||
|
<div class="person-item-name">{{ user.fullname || user.userName }}</div> |
||||
|
<div class="person-item-meta"> |
||||
|
<span v-if="user.mobileNumber">{{ user.mobileNumber }}</span> |
||||
|
<span v-if="user.loginName" class="person-item-login">@{{ user.loginName }}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<van-empty v-if="!personSearchResult.length" description="未检索到人员" image-size="80" /> |
||||
|
</template> |
||||
|
</div> |
||||
|
</div> |
||||
|
</van-popup> |
||||
|
|
||||
|
<!-- 企业检索弹层 --> |
||||
|
<van-popup |
||||
|
v-model:show="showEnterprisePicker" |
||||
|
position="bottom" |
||||
|
round |
||||
|
:style="{ height: '70%' }" |
||||
|
> |
||||
|
<div class="enterprise-picker"> |
||||
|
<div class="enterprise-picker-header"> |
||||
|
<span class="enterprise-picker-title">选择企业</span> |
||||
|
<van-icon name="cross" @click="showEnterprisePicker = false" /> |
||||
|
</div> |
||||
|
<van-search |
||||
|
v-model="enterpriseSearchKeyword" |
||||
|
placeholder="输入≥ 3 个字符检索企业,不要输入深圳,深圳市" |
||||
|
shape="round" |
||||
|
@update:model-value="onEnterpriseSearchInput" |
||||
|
/> |
||||
|
<div class="enterprise-picker-body"> |
||||
|
<div v-if="enterpriseSearching" class="enterprise-loading"> |
||||
|
<van-loading size="18" /> 检索中... |
||||
|
</div> |
||||
|
<template v-else> |
||||
|
<div |
||||
|
v-for="(item, idx) in enterpriseOptions" |
||||
|
:key="idx" |
||||
|
class="enterprise-item" |
||||
|
@click="onSelectEnterprise(item)" |
||||
|
> |
||||
|
<div class="enterprise-item-name">{{ item.enterpriseName }}</div> |
||||
|
<div class="enterprise-item-number">{{ item.enterpriseNumber }}</div> |
||||
|
</div> |
||||
|
<van-empty v-if="enterpriseSearchKeyword.length >= 3 && !enterpriseOptions.length" description="未检索到匹配企业" image-size="80" /> |
||||
|
</template> |
||||
|
</div> |
||||
|
</div> |
||||
|
</van-popup> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup name="cpNoticeEdit"> |
||||
|
import { useRouter, useRoute } from 'vue-router'; |
||||
|
import { showToast, showSuccessToast, showConfirmDialog } from 'vant'; |
||||
|
import { addNotice, editNotice, getNoticeDetail } from '@/api/enforcement/notice'; |
||||
|
import { getPlanList, searchUnionByEnterpriseName, searchBureauUnitUsers, getEnterpriseContactInfo } from '@/api/enforcement/plan'; |
||||
|
|
||||
|
const router = useRouter(); |
||||
|
const route = useRoute(); |
||||
|
const { useCommon } = $globalStore; |
||||
|
|
||||
|
const isEdit = computed(() => !!route.query.id); |
||||
|
const isReinitiate = computed(() => !!route.query.reinitiate); |
||||
|
const pageTitle = computed(() => { |
||||
|
if (isReinitiate.value) return '新增预告'; |
||||
|
return isEdit.value ? '编辑预告' : '新增预告'; |
||||
|
}); |
||||
|
|
||||
|
const savingDraft = ref(false); |
||||
|
const publishing = ref(false); |
||||
|
const showDatePicker = ref(false); |
||||
|
const showTimePicker = ref(false); |
||||
|
const showPlanPicker = ref(false); |
||||
|
|
||||
|
// 表单数据 |
||||
|
const form = ref({ |
||||
|
id: '', |
||||
|
planId: null, |
||||
|
enforceDate: '', |
||||
|
enforceTime: 1, |
||||
|
bureauDeptId: '', |
||||
|
bureauDeptName: '', |
||||
|
organId: '', |
||||
|
organName: '', |
||||
|
deptId: '', |
||||
|
deptName: '', |
||||
|
enforceUserNames: '', |
||||
|
enforceUserIds: '', |
||||
|
enforceUserTels: '', |
||||
|
enforceUserCodes: '', |
||||
|
enterpriseId: '', |
||||
|
enterpriseName: '', |
||||
|
enterpriseNumber: '', |
||||
|
contactPerson: '', |
||||
|
contactPhone: '', |
||||
|
enforceContent: '', |
||||
|
enforceRequirement: '', |
||||
|
status: 0, |
||||
|
}); |
||||
|
|
||||
|
// 人员列表 |
||||
|
const personList = ref([]); |
||||
|
const maxPersonNum = 3; |
||||
|
|
||||
|
// 日期相关 |
||||
|
const today = new Date(); |
||||
|
const minDate = new Date(today.getFullYear(), today.getMonth(), today.getDate()); |
||||
|
const datePickerValue = ref([ |
||||
|
String(today.getFullYear()), |
||||
|
String(today.getMonth() + 1).padStart(2, '0'), |
||||
|
String(today.getDate()).padStart(2, '0'), |
||||
|
]); |
||||
|
|
||||
|
const timeColumns = [ |
||||
|
{ text: '上午', value: 1 }, |
||||
|
{ text: '下午', value: 2 }, |
||||
|
{ text: '其他', value: 3 }, |
||||
|
]; |
||||
|
|
||||
|
// 日期+时段展示 |
||||
|
const enforceTimeDisplay = computed(() => { |
||||
|
const timeText = { 1: '上午', 2: '下午', 3: '其他' }; |
||||
|
return timeText[form.value.enforceTime] || ''; |
||||
|
}); |
||||
|
|
||||
|
// 计划导入展示 |
||||
|
const planImportDisplay = computed(() => { |
||||
|
if (form.value.planId) return '已导入计划'; |
||||
|
return ''; |
||||
|
}); |
||||
|
|
||||
|
// 企业搜索状态 |
||||
|
const enterpriseOptions = ref([]); |
||||
|
const showEnterprisePicker = ref(false); |
||||
|
const enterpriseSearchKeyword = ref(''); |
||||
|
const enterpriseSearching = ref(false); |
||||
|
let enterpriseSearchTimer = null; |
||||
|
let enterpriseSearchSeq = 0; |
||||
|
|
||||
|
// 执法人员检索 |
||||
|
const showPersonPicker = ref(false); |
||||
|
const personSearchKeyword = ref(''); |
||||
|
const personSearchResult = ref([]); |
||||
|
const personSearching = ref(false); |
||||
|
let personSearchTimer = null; |
||||
|
let personSearchSeq = 0; |
||||
|
|
||||
|
// 计划列表 |
||||
|
const planOptions = ref([]); |
||||
|
const planLoading = ref(false); |
||||
|
|
||||
|
onMounted(() => { |
||||
|
// 默认绑定当前用户的执法单位 |
||||
|
form.value.bureauDeptId = useCommon.bureauUnitId || ''; |
||||
|
form.value.bureauDeptName = useCommon.bureauUnitName || ''; |
||||
|
form.value.organId = useCommon.organId || ''; |
||||
|
form.value.organName = useCommon.organName || ''; |
||||
|
form.value.deptId = useCommon.deptId || ''; |
||||
|
form.value.deptName = useCommon.deptName || ''; |
||||
|
// 默认绑定当前用户为首位执法人员 |
||||
|
bindCurrentUserAsDefaultPerson(); |
||||
|
|
||||
|
if (isEdit.value) { |
||||
|
loadDetail(route.query.id); |
||||
|
} else if (isReinitiate.value) { |
||||
|
loadDetail(route.query.reinitiate, true); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
const bindCurrentUserAsDefaultPerson = () => { |
||||
|
const name = useCommon.nickName || useCommon.userName || ''; |
||||
|
const phone = useCommon.phonenumber || ''; |
||||
|
const id = useCommon.userId || ''; |
||||
|
if (!name && !id) return; |
||||
|
personList.value = [{ id, name, phone, code: '' }]; |
||||
|
syncPersonData(); |
||||
|
}; |
||||
|
|
||||
|
const loadDetail = (id, isReinitiateMode = false) => { |
||||
|
getNoticeDetail(id, (res) => { |
||||
|
if (res.code === 200 && res.data) { |
||||
|
const data = res.data; |
||||
|
Object.keys(form.value).forEach(key => { |
||||
|
if (data[key] !== undefined && data[key] !== null) { |
||||
|
form.value[key] = data[key]; |
||||
|
} |
||||
|
}); |
||||
|
// 如果是再次发起,清除id,变为新增 |
||||
|
if (isReinitiateMode) { |
||||
|
form.value.id = ''; |
||||
|
form.value.status = 0; |
||||
|
form.value.planId = null; |
||||
|
} |
||||
|
// 解析人员 |
||||
|
if (data.enforceUserNames) { |
||||
|
const names = data.enforceUserNames.split(','); |
||||
|
const ids = data.enforceUserIds ? data.enforceUserIds.split(',') : []; |
||||
|
const tels = data.enforceUserTels ? data.enforceUserTels.split(',') : []; |
||||
|
personList.value = names.map((name, i) => ({ |
||||
|
id: i < ids.length ? ids[i] : '', |
||||
|
name, |
||||
|
phone: i < tels.length ? tels[i] : '', |
||||
|
code: '', |
||||
|
})); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
// 日期确认 |
||||
|
const onDateConfirm = ({ selectedValues }) => { |
||||
|
form.value.enforceDate = selectedValues.join('-'); |
||||
|
showDatePicker.value = false; |
||||
|
}; |
||||
|
|
||||
|
// 时段确认 |
||||
|
const onTimeConfirm = ({ selectedValues }) => { |
||||
|
form.value.enforceTime = selectedValues[0]; |
||||
|
showTimePicker.value = false; |
||||
|
}; |
||||
|
|
||||
|
// 打开企业检索弹层 |
||||
|
const onOpenEnterprisePicker = () => { |
||||
|
enterpriseSearchKeyword.value = ''; |
||||
|
enterpriseOptions.value = []; |
||||
|
showEnterprisePicker.value = true; |
||||
|
}; |
||||
|
|
||||
|
// 企业检索 |
||||
|
const onEnterpriseSearchInput = (val) => { |
||||
|
if (enterpriseSearchTimer) clearTimeout(enterpriseSearchTimer); |
||||
|
const keyword = (val || '').trim(); |
||||
|
if (keyword.length < 3) { |
||||
|
enterpriseOptions.value = []; |
||||
|
return; |
||||
|
} |
||||
|
enterpriseSearchTimer = setTimeout(() => { |
||||
|
const seq = ++enterpriseSearchSeq; |
||||
|
enterpriseSearching.value = true; |
||||
|
searchUnionByEnterpriseName(keyword, (res) => { |
||||
|
if (seq !== enterpriseSearchSeq) return; |
||||
|
enterpriseSearching.value = false; |
||||
|
if (res && res.code === 200 && Array.isArray(res.data)) { |
||||
|
enterpriseOptions.value = res.data; |
||||
|
} else { |
||||
|
enterpriseOptions.value = []; |
||||
|
} |
||||
|
}, () => { |
||||
|
if (seq !== enterpriseSearchSeq) return; |
||||
|
enterpriseSearching.value = false; |
||||
|
enterpriseOptions.value = []; |
||||
|
}); |
||||
|
}, 350); |
||||
|
}; |
||||
|
|
||||
|
const onSelectEnterprise = (item) => { |
||||
|
form.value.enterpriseName = item.enterpriseName || ''; |
||||
|
form.value.enterpriseNumber = item.enterpriseNumber || ''; |
||||
|
form.value.enterpriseId = item.enterpriseNumber || ''; |
||||
|
showEnterprisePicker.value = false; |
||||
|
enterpriseOptions.value = []; |
||||
|
// 根据企业信用代码拉取联系人和联系电话 |
||||
|
if (item.enterpriseNumber) { |
||||
|
getEnterpriseContactInfo(item.enterpriseNumber, (res) => { |
||||
|
if (res && res.code === 200 && res.data) { |
||||
|
if (res.data.contactPerson) { |
||||
|
form.value.contactPerson = res.data.contactPerson; |
||||
|
} |
||||
|
if (res.data.contactPhone) { |
||||
|
form.value.contactPhone = res.data.contactPhone; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// 人员添加 |
||||
|
const onAddPerson = () => { |
||||
|
if (personList.value.length >= maxPersonNum) { |
||||
|
showToast(`最多只能添加 ${maxPersonNum} 名执法人员`); |
||||
|
return; |
||||
|
} |
||||
|
personSearchKeyword.value = ''; |
||||
|
personSearchResult.value = []; |
||||
|
showPersonPicker.value = true; |
||||
|
triggerPersonSearch(''); |
||||
|
}; |
||||
|
|
||||
|
const onPersonSearchInput = (val) => { |
||||
|
if (personSearchTimer) clearTimeout(personSearchTimer); |
||||
|
const keyword = (val || '').trim(); |
||||
|
personSearchTimer = setTimeout(() => triggerPersonSearch(keyword), 300); |
||||
|
}; |
||||
|
|
||||
|
const triggerPersonSearch = (keyword) => { |
||||
|
const seq = ++personSearchSeq; |
||||
|
personSearching.value = true; |
||||
|
searchBureauUnitUsers( |
||||
|
{ sysDeptId: useCommon.bureauUnitId || '', keyword, globalSearch: false, pageNum: 1, pageSize: 30 }, |
||||
|
(res) => { |
||||
|
if (seq !== personSearchSeq) return; |
||||
|
personSearching.value = false; |
||||
|
if (res && res.code === 200 && Array.isArray(res.rows)) { |
||||
|
personSearchResult.value = res.rows; |
||||
|
} else { |
||||
|
personSearchResult.value = []; |
||||
|
} |
||||
|
}, |
||||
|
() => { |
||||
|
if (seq !== personSearchSeq) return; |
||||
|
personSearching.value = false; |
||||
|
personSearchResult.value = []; |
||||
|
} |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
const onSelectPerson = (user) => { |
||||
|
const uid = user.userId != null ? String(user.userId) : ''; |
||||
|
const exists = personList.value.some(p => String(p.id || '') === uid); |
||||
|
if (exists) { showToast('该人员已添加'); return; } |
||||
|
if (personList.value.length >= maxPersonNum) { showToast(`最多只能添加 ${maxPersonNum} 名执法人员`); return; } |
||||
|
personList.value.push({ id: uid, name: user.fullname || user.userName || '', phone: user.mobileNumber || '', code: '' }); |
||||
|
syncPersonData(); |
||||
|
showPersonPicker.value = false; |
||||
|
}; |
||||
|
|
||||
|
const removePerson = (index) => { |
||||
|
personList.value.splice(index, 1); |
||||
|
syncPersonData(); |
||||
|
}; |
||||
|
|
||||
|
const syncPersonData = () => { |
||||
|
form.value.enforceUserNames = personList.value.map(p => p.name).join(','); |
||||
|
form.value.enforceUserIds = personList.value.map(p => p.id || '').join(','); |
||||
|
form.value.enforceUserTels = personList.value.map(p => p.phone || '').join(','); |
||||
|
form.value.enforceUserCodes = personList.value.map(p => p.code || '').join(','); |
||||
|
}; |
||||
|
|
||||
|
// 计划导入 |
||||
|
watch(showPlanPicker, (val) => { |
||||
|
if (val && planOptions.value.length === 0) { |
||||
|
loadPlans(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
const loadPlans = () => { |
||||
|
planLoading.value = true; |
||||
|
getPlanList({ pageNum: 1, pageSize: 50 }, (res) => { |
||||
|
planLoading.value = false; |
||||
|
if (res.code === 200) { |
||||
|
planOptions.value = res.rows || []; |
||||
|
} |
||||
|
}, () => { planLoading.value = false; }); |
||||
|
}; |
||||
|
|
||||
|
const onSelectPlan = (plan) => { |
||||
|
form.value.planId = plan.id; |
||||
|
form.value.enforceDate = plan.planDate || ''; |
||||
|
form.value.enforceTime = plan.planTime || 1; |
||||
|
form.value.enterpriseName = plan.enterpriseName || ''; |
||||
|
form.value.enterpriseNumber = plan.enterpriseNumber || ''; |
||||
|
form.value.enterpriseId = plan.enterpriseId || plan.enterpriseNumber || ''; |
||||
|
form.value.contactPerson = plan.contactPerson || ''; |
||||
|
form.value.contactPhone = plan.contactPhone || ''; |
||||
|
form.value.enforceContent = plan.enforceContent || ''; |
||||
|
// 同步人员 |
||||
|
if (plan.enforceUserNames) { |
||||
|
const names = plan.enforceUserNames.split(','); |
||||
|
const ids = plan.enforceUserIds ? plan.enforceUserIds.split(',') : []; |
||||
|
const tels = plan.enforceUserTels ? plan.enforceUserTels.split(',') : []; |
||||
|
personList.value = names.map((name, i) => ({ |
||||
|
id: i < ids.length ? ids[i] : '', |
||||
|
name, |
||||
|
phone: i < tels.length ? tels[i] : '', |
||||
|
code: '', |
||||
|
})); |
||||
|
syncPersonData(); |
||||
|
} |
||||
|
showPlanPicker.value = false; |
||||
|
}; |
||||
|
|
||||
|
// 返回 |
||||
|
const onBack = () => { router.back(); }; |
||||
|
|
||||
|
// 表单校验 |
||||
|
const validate = (isDraft) => { |
||||
|
if (!isDraft) { |
||||
|
if (!form.value.enforceDate) { showToast('请选择执法日期'); return false; } |
||||
|
if (!form.value.enforceTime) { showToast('请选择执法时段'); return false; } |
||||
|
if (!form.value.enforceContent) { showToast('请输入执法事项'); return false; } |
||||
|
if (!form.value.enforceRequirement) { showToast('请输入执法要求'); return false; } |
||||
|
if (!form.value.enterpriseName) { showToast('请输入企业名称'); return false; } |
||||
|
if (personList.value.length === 0) { showToast('请添加执法人员'); return false; } |
||||
|
} |
||||
|
return true; |
||||
|
}; |
||||
|
|
||||
|
// 存为草稿 |
||||
|
const onSaveDraft = () => { |
||||
|
if (!validate(true)) return; |
||||
|
form.value.status = 0; |
||||
|
doSubmit(true); |
||||
|
}; |
||||
|
|
||||
|
// 发布预告 |
||||
|
const onPublish = () => { |
||||
|
if (!validate(false)) return; |
||||
|
showConfirmDialog({ |
||||
|
title: '提示', |
||||
|
message: '预告发布后,不能删除,不能修改。确认发布?', |
||||
|
confirmButtonText: '确认发布', |
||||
|
cancelButtonText: '取消', |
||||
|
}).then(() => { |
||||
|
form.value.status = 1; |
||||
|
doSubmit(false); |
||||
|
}).catch(() => {}); |
||||
|
}; |
||||
|
|
||||
|
// 实际提交 |
||||
|
const doSubmit = (isDraft) => { |
||||
|
if (isDraft) { savingDraft.value = true; } else { publishing.value = true; } |
||||
|
// 兜底字段 |
||||
|
form.value.organId = form.value.organId || useCommon.organId || '-'; |
||||
|
form.value.organName = form.value.organName || useCommon.organName || '-'; |
||||
|
form.value.deptId = form.value.deptId || useCommon.deptId || '-'; |
||||
|
form.value.deptName = form.value.deptName || useCommon.deptName || '-'; |
||||
|
form.value.bureauDeptId = form.value.bureauDeptId || useCommon.bureauUnitId || '-'; |
||||
|
form.value.bureauDeptName = form.value.bureauDeptName || useCommon.bureauUnitName || '-'; |
||||
|
if (!form.value.enterpriseId) form.value.enterpriseId = form.value.enterpriseNumber || '-'; |
||||
|
|
||||
|
const api = isEdit.value ? editNotice : addNotice; |
||||
|
api( |
||||
|
form.value, |
||||
|
(res) => { |
||||
|
savingDraft.value = false; |
||||
|
publishing.value = false; |
||||
|
if (res.code === 200) { |
||||
|
showSuccessToast(isDraft ? '已保存草稿' : '发布成功'); |
||||
|
setTimeout(() => router.back(), 800); |
||||
|
} else { |
||||
|
showToast(res.msg || '操作失败'); |
||||
|
} |
||||
|
}, |
||||
|
(errRes) => { |
||||
|
savingDraft.value = false; |
||||
|
publishing.value = false; |
||||
|
showToast(errRes?.msg || '操作失败,请重试'); |
||||
|
} |
||||
|
); |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.notice-edit-page { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100%; |
||||
|
background: #eaf4ff; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
:deep(.van-nav-bar) { |
||||
|
background: #347bc3; |
||||
|
flex-shrink: 0; |
||||
|
.van-nav-bar__title { color: #fff; font-size: 18px; font-weight: 600; } |
||||
|
.van-icon { color: #fff; } |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
flex: 1; |
||||
|
overflow-y: auto; |
||||
|
padding: 12px; |
||||
|
} |
||||
|
|
||||
|
.form-card { |
||||
|
background: #fff; |
||||
|
border-radius: 14px; |
||||
|
overflow: hidden; |
||||
|
box-shadow: 0 4px 14px rgba(44, 116, 190, 0.08); |
||||
|
:deep(.van-field) { padding: 14px; } |
||||
|
:deep(.van-field__label) { width: 90px; font-size: 15px; color: #333; } |
||||
|
} |
||||
|
|
||||
|
.person-box { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 8px; |
||||
|
} |
||||
|
|
||||
|
.person-line { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
font-size: 15px; |
||||
|
color: #666; |
||||
|
gap: 8px; |
||||
|
.person-name { margin-right: 4px; } |
||||
|
.person-phone { color: #999; } |
||||
|
.person-remove { margin-left: auto; color: #999; font-size: 16px; } |
||||
|
} |
||||
|
|
||||
|
.add-person-line { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
color: #c4c9cf; |
||||
|
font-size: 15px; |
||||
|
cursor: pointer; |
||||
|
.placeholder-text { color: #c4c9cf; } |
||||
|
} |
||||
|
|
||||
|
.enterprise-picker { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100%; |
||||
|
.enterprise-picker-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 14px 16px 8px; |
||||
|
.enterprise-picker-title { font-size: 16px; font-weight: 600; color: #333; } |
||||
|
:deep(.van-icon) { font-size: 18px; color: #999; cursor: pointer; } |
||||
|
} |
||||
|
.enterprise-picker-body { |
||||
|
flex: 1; |
||||
|
overflow-y: auto; |
||||
|
padding: 0 12px 12px; |
||||
|
} |
||||
|
.enterprise-loading { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 30px 0; |
||||
|
color: #999; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
.enterprise-item { |
||||
|
padding: 12px 14px; |
||||
|
background: #fff; |
||||
|
border: 1px solid #e6edf5; |
||||
|
border-radius: 10px; |
||||
|
margin-bottom: 8px; |
||||
|
cursor: pointer; |
||||
|
&:active { background: #f5faff; } |
||||
|
.enterprise-item-name { font-size: 15px; color: #333; font-weight: 500; } |
||||
|
.enterprise-item-number { font-size: 12px; color: #999; margin-top: 4px; } |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.submit-wrap { |
||||
|
display: flex; |
||||
|
gap: 16px; |
||||
|
padding: 22px 0 0; |
||||
|
.draft-btn { |
||||
|
flex: 1; |
||||
|
height: 50px; |
||||
|
font-size: 17px; |
||||
|
font-weight: 500; |
||||
|
border-radius: 25px; |
||||
|
} |
||||
|
.publish-btn { |
||||
|
flex: 1; |
||||
|
height: 50px; |
||||
|
font-size: 17px; |
||||
|
font-weight: 500; |
||||
|
border-radius: 25px; |
||||
|
background: linear-gradient(135deg, #1890ff, #0b63d1); |
||||
|
border: none; |
||||
|
box-shadow: 0 8px 20px rgba(24, 144, 255, 0.28); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.plan-picker { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100%; |
||||
|
.plan-picker-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 14px 16px 8px; |
||||
|
.plan-picker-title { font-size: 16px; font-weight: 600; color: #333; } |
||||
|
:deep(.van-icon) { font-size: 18px; color: #999; cursor: pointer; } |
||||
|
} |
||||
|
.plan-picker-body { |
||||
|
flex: 1; |
||||
|
overflow-y: auto; |
||||
|
padding: 0 12px 12px; |
||||
|
} |
||||
|
.plan-loading { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 30px 0; |
||||
|
color: #999; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
.plan-item { |
||||
|
padding: 12px 14px; |
||||
|
background: #fff; |
||||
|
border: 1px solid #e6edf5; |
||||
|
border-radius: 10px; |
||||
|
margin-bottom: 8px; |
||||
|
cursor: pointer; |
||||
|
&:active { background: #f5faff; } |
||||
|
.plan-item-title { font-size: 15px; color: #333; font-weight: 500; } |
||||
|
.plan-item-meta { font-size: 12px; color: #999; margin-top: 4px; } |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.person-picker { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100%; |
||||
|
.person-picker-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 14px 16px 8px; |
||||
|
.person-picker-title { font-size: 16px; font-weight: 600; color: #333; } |
||||
|
:deep(.van-icon) { font-size: 18px; color: #999; cursor: pointer; } |
||||
|
} |
||||
|
.person-picker-body { |
||||
|
flex: 1; |
||||
|
overflow-y: auto; |
||||
|
padding: 0 12px 12px; |
||||
|
} |
||||
|
.person-loading { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 30px 0; |
||||
|
color: #999; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
.person-item { |
||||
|
padding: 12px 14px; |
||||
|
background: #fff; |
||||
|
border: 1px solid #e6edf5; |
||||
|
border-radius: 10px; |
||||
|
margin-bottom: 8px; |
||||
|
cursor: pointer; |
||||
|
&:active { background: #f5faff; } |
||||
|
.person-item-name { font-size: 15px; color: #333; font-weight: 500; } |
||||
|
.person-item-meta { |
||||
|
display: flex; |
||||
|
gap: 12px; |
||||
|
margin-top: 4px; |
||||
|
font-size: 12px; |
||||
|
color: #999; |
||||
|
.person-item-login { color: #b8c1cc; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -1,11 +1,234 @@ |
|||||
<script setup> |
<template> |
||||
|
<div class="notice-page app_content"> |
||||
|
<!-- 顶部导航 --> |
||||
|
<van-nav-bar |
||||
|
title="预告" |
||||
|
left-arrow |
||||
|
@click-left="onBack" |
||||
|
/> |
||||
|
|
||||
|
<!-- Tab 切换 --> |
||||
|
<van-tabs |
||||
|
v-model:active="activeTab" |
||||
|
title-active-color="#111" |
||||
|
title-inactive-color="#666" |
||||
|
color="#2f8bd3" |
||||
|
line-width="36px" |
||||
|
line-height="2px" |
||||
|
:border="false" |
||||
|
@change="onTabChange" |
||||
|
> |
||||
|
<van-tab title="草稿" name="draft"> |
||||
|
<notice-tab-content |
||||
|
ref="draftRef" |
||||
|
:status="0" |
||||
|
:can-publish="true" |
||||
|
:can-edit="true" |
||||
|
:can-delete="true" |
||||
|
:can-message="false" |
||||
|
:can-reinitiate="false" |
||||
|
@publish="onPublish" |
||||
|
@edit="onEdit" |
||||
|
@delete="onDelete" |
||||
|
/> |
||||
|
</van-tab> |
||||
|
<van-tab title="待执法预告" name="pending"> |
||||
|
<notice-tab-content |
||||
|
ref="pendingRef" |
||||
|
:status="1" |
||||
|
:can-edit="false" |
||||
|
:can-delete="false" |
||||
|
:can-message="true" |
||||
|
:can-reinitiate="false" |
||||
|
@message="onMessage" |
||||
|
/> |
||||
|
</van-tab> |
||||
|
<van-tab title="历史预告" name="history"> |
||||
|
<notice-tab-content |
||||
|
ref="historyRef" |
||||
|
:status="3" |
||||
|
:can-edit="false" |
||||
|
:can-delete="false" |
||||
|
:can-message="true" |
||||
|
:can-reinitiate="true" |
||||
|
:message-readonly="true" |
||||
|
@message="onMessage" |
||||
|
@reinitiate="onReinitiate" |
||||
|
/> |
||||
|
</van-tab> |
||||
|
</van-tabs> |
||||
|
|
||||
|
<!-- 悬浮新增按钮(仅草稿Tab显示) --> |
||||
|
<div v-if="activeTab === 'draft'" class="add-btn" @click="onAdd"> |
||||
|
<van-icon name="plus" size="22" color="#fff" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup name="cpNoticeList"> |
||||
|
import { useRouter } from 'vue-router'; |
||||
|
import { showConfirmDialog, showToast } from 'vant'; |
||||
|
import { deleteNotice, editNotice } from '@/api/enforcement/notice'; |
||||
|
import NoticeTabContent from './cp_notice_tab_content.vue'; |
||||
|
|
||||
|
const router = useRouter(); |
||||
|
const activeTab = ref('draft'); |
||||
|
|
||||
|
const draftRef = ref(null); |
||||
|
const pendingRef = ref(null); |
||||
|
const historyRef = ref(null); |
||||
|
|
||||
|
const onBack = () => { |
||||
|
router.back(); |
||||
|
}; |
||||
|
|
||||
|
const onTabChange = () => { |
||||
|
// Tab切换后自动刷新(子组件内部onMounted会自行加载) |
||||
|
}; |
||||
|
|
||||
|
const onAdd = () => { |
||||
|
router.push({ path: '/enforcement/notice/edit' }); |
||||
|
}; |
||||
|
|
||||
|
const onEdit = (item) => { |
||||
|
router.push({ path: '/enforcement/notice/edit', query: { id: item.id } }); |
||||
|
}; |
||||
|
|
||||
|
const onDelete = (item) => { |
||||
|
showConfirmDialog({ |
||||
|
title: '提示', |
||||
|
message: '确定要删除该预告吗?', |
||||
|
}).then(() => { |
||||
|
deleteNotice(item.id, (res) => { |
||||
|
if (res.code === 200) { |
||||
|
showToast('删除成功'); |
||||
|
draftRef.value && draftRef.value.refresh(); |
||||
|
} else { |
||||
|
showToast(res.msg || '删除失败'); |
||||
|
} |
||||
|
}); |
||||
|
}).catch(() => {}); |
||||
|
}; |
||||
|
|
||||
|
const onMessage = (item) => { |
||||
|
const readonly = activeTab.value === 'history' ? '1' : '0'; |
||||
|
router.push({ path: '/enforcement/notice/message', query: { id: item.id, readonly } }); |
||||
|
}; |
||||
|
|
||||
|
const onReinitiate = (item) => { |
||||
|
// 再次发起:进入新增页面并带入已有信息 |
||||
|
router.push({ |
||||
|
path: '/enforcement/notice/edit', |
||||
|
query: { reinitiate: item.id }, |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const onPublish = (item) => { |
||||
|
// 发布前验证必填字段 |
||||
|
if (!item.enforceDate) { showToast('该预告缺少执法日期,请编辑后再发布'); return; } |
||||
|
if (!item.enforceTime) { showToast('该预告缺少执法时段,请编辑后再发布'); return; } |
||||
|
if (!item.enterpriseName) { showToast('该预告缺少企业名称,请编辑后再发布'); return; } |
||||
|
if (!item.enforceUserNames) { showToast('该预告缺少执法人员,请编辑后再发布'); return; } |
||||
|
if (!item.enforceContent) { showToast('该预告缺少执法事项,请编辑后再发布'); return; } |
||||
|
if (!item.enforceRequirement) { showToast('该预告缺少执法要求,请编辑后再发布'); return; } |
||||
|
|
||||
|
showConfirmDialog({ |
||||
|
title: '提示', |
||||
|
message: '预告发布后,不能删除,不能修改。确认发布?', |
||||
|
confirmButtonText: '确认发布', |
||||
|
cancelButtonText: '取消', |
||||
|
}).then(() => { |
||||
|
const submitData = { ...item, status: 1 }; |
||||
|
editNotice(submitData, (res) => { |
||||
|
if (res.code === 200) { |
||||
|
showToast('发布成功'); |
||||
|
draftRef.value && draftRef.value.refresh(); |
||||
|
} else { |
||||
|
showToast(res.msg || '发布失败'); |
||||
|
} |
||||
|
}, (errRes) => { |
||||
|
showToast(errRes?.msg || '发布失败,请重试'); |
||||
|
}); |
||||
|
}).catch(() => {}); |
||||
|
}; |
||||
</script> |
</script> |
||||
|
|
||||
<template> |
<style lang="less" scoped> |
||||
|
.notice-page { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100%; |
||||
|
background: #eaf4ff; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
</template> |
:deep(.van-nav-bar) { |
||||
|
background: #347bc3; |
||||
|
flex-shrink: 0; |
||||
|
|
||||
|
.van-nav-bar__title { |
||||
|
color: #fff; |
||||
|
font-size: 18px; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.van-icon { |
||||
|
color: #fff; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:deep(.van-tabs) { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
flex: 1; |
||||
|
min-height: 0; |
||||
|
} |
||||
|
|
||||
|
:deep(.van-tabs__wrap) { |
||||
|
height: 54px; |
||||
|
background: #fff; |
||||
|
border-bottom: 1px solid #e4edf7; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
:deep(.van-tab) { |
||||
|
font-size: 16px; |
||||
|
} |
||||
|
|
||||
|
:deep(.van-tab--active) { |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
:deep(.van-tabs__content), |
||||
|
:deep(.van-tabs__track), |
||||
|
:deep(.van-tab__panel-wrapper), |
||||
|
:deep(.van-tab__panel) { |
||||
|
flex: 1; |
||||
|
min-height: 0; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
:deep(.van-tabs__content) { |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
<style scoped lang="less"> |
.add-btn { |
||||
|
position: fixed; |
||||
|
right: 22px; |
||||
|
bottom: 28px; |
||||
|
width: 56px; |
||||
|
height: 56px; |
||||
|
border-radius: 50%; |
||||
|
background: linear-gradient(135deg, #1890ff, #0b63d1); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
box-shadow: 0 8px 20px rgba(24, 144, 255, 0.38); |
||||
|
z-index: 100; |
||||
|
|
||||
|
&:active { |
||||
|
transform: scale(0.96); |
||||
|
} |
||||
|
} |
||||
</style> |
</style> |
||||
|
|||||
@ -0,0 +1,277 @@ |
|||||
|
<template> |
||||
|
<div class="notice-message-page app_content"> |
||||
|
<!-- 顶部导航 --> |
||||
|
<van-nav-bar |
||||
|
title="预告留言" |
||||
|
left-arrow |
||||
|
@click-left="onBack" |
||||
|
/> |
||||
|
|
||||
|
<!-- 消息列表区域 --> |
||||
|
<div class="message-area" ref="messageAreaRef"> |
||||
|
<div v-if="loading" class="loading-wrap"> |
||||
|
<van-loading size="18" /> 加载中... |
||||
|
</div> |
||||
|
<template v-else> |
||||
|
<van-empty v-if="!messageList.length" description="暂无留言" image-size="100" /> |
||||
|
<div |
||||
|
v-for="(msg, idx) in messageList" |
||||
|
:key="msg.id || idx" |
||||
|
class="message-item" |
||||
|
:class="{ 'message-right': msg.msgFrom === 0, 'message-left': msg.msgFrom === 1 }" |
||||
|
> |
||||
|
<!-- 左侧(企业消息) --> |
||||
|
<div v-if="msg.msgFrom === 1" class="msg-avatar left-avatar">企</div> |
||||
|
<div class="msg-bubble" :class="{ 'bubble-left': msg.msgFrom === 1, 'bubble-right': msg.msgFrom === 0 }"> |
||||
|
<div class="msg-sender">{{ msg.userName }}</div> |
||||
|
<div class="msg-content">{{ msg.message }}</div> |
||||
|
<div class="msg-time">{{ formatTime(msg.createTime) }}</div> |
||||
|
</div> |
||||
|
<!-- 右侧(执法单位消息) --> |
||||
|
<div v-if="msg.msgFrom === 0" class="msg-avatar right-avatar">执</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 底部输入区域(非只读时显示) --> |
||||
|
<div v-if="!isReadonly" class="input-area"> |
||||
|
<van-field |
||||
|
v-model="inputMessage" |
||||
|
type="textarea" |
||||
|
placeholder="输入留言内容..." |
||||
|
rows="1" |
||||
|
autosize |
||||
|
class="input-field" |
||||
|
/> |
||||
|
<van-button |
||||
|
type="primary" |
||||
|
size="small" |
||||
|
class="send-btn" |
||||
|
:disabled="!inputMessage.trim()" |
||||
|
:loading="sending" |
||||
|
@click="onSend" |
||||
|
> |
||||
|
发送 |
||||
|
</van-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup name="cpNoticeMessage"> |
||||
|
import { useRouter, useRoute } from 'vue-router'; |
||||
|
import { showToast } from 'vant'; |
||||
|
import { getNoticeMessages, addNoticeMessage } from '@/api/enforcement/notice'; |
||||
|
|
||||
|
const router = useRouter(); |
||||
|
const route = useRoute(); |
||||
|
const { useCommon } = $globalStore; |
||||
|
|
||||
|
const noticeId = computed(() => route.query.id || ''); |
||||
|
const isReadonly = computed(() => route.query.readonly === '1'); |
||||
|
|
||||
|
const messageList = ref([]); |
||||
|
const loading = ref(false); |
||||
|
const inputMessage = ref(''); |
||||
|
const sending = ref(false); |
||||
|
const messageAreaRef = ref(null); |
||||
|
|
||||
|
onMounted(() => { |
||||
|
loadMessages(); |
||||
|
}); |
||||
|
|
||||
|
const loadMessages = () => { |
||||
|
if (!noticeId.value) return; |
||||
|
loading.value = true; |
||||
|
getNoticeMessages(noticeId.value, (res) => { |
||||
|
loading.value = false; |
||||
|
if (res.code === 200 && Array.isArray(res.data)) { |
||||
|
messageList.value = res.data; |
||||
|
scrollToBottom(); |
||||
|
} |
||||
|
}, () => { |
||||
|
loading.value = false; |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const onSend = () => { |
||||
|
const text = inputMessage.value.trim(); |
||||
|
if (!text) return; |
||||
|
sending.value = true; |
||||
|
addNoticeMessage({ |
||||
|
noticeId: noticeId.value, |
||||
|
userId: String(useCommon.userId || ''), |
||||
|
userName: useCommon.nickName || useCommon.userName || '', |
||||
|
msgFrom: 0, // 执法单位=0 |
||||
|
message: text, |
||||
|
}, (res) => { |
||||
|
sending.value = false; |
||||
|
if (res.code === 200) { |
||||
|
inputMessage.value = ''; |
||||
|
loadMessages(); |
||||
|
} else { |
||||
|
showToast(res.msg || '发送失败'); |
||||
|
} |
||||
|
}, () => { |
||||
|
sending.value = false; |
||||
|
showToast('发送失败,请重试'); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const scrollToBottom = () => { |
||||
|
nextTick(() => { |
||||
|
if (messageAreaRef.value) { |
||||
|
messageAreaRef.value.scrollTop = messageAreaRef.value.scrollHeight; |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const formatTime = (dateStr) => { |
||||
|
if (!dateStr) return ''; |
||||
|
return String(dateStr).substring(0, 16).replace('T', ' '); |
||||
|
}; |
||||
|
|
||||
|
const onBack = () => { |
||||
|
router.back(); |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.notice-message-page { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100%; |
||||
|
background: #f0f6ff; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
:deep(.van-nav-bar) { |
||||
|
background: #347bc3; |
||||
|
flex-shrink: 0; |
||||
|
.van-nav-bar__title { color: #fff; font-size: 18px; font-weight: 600; } |
||||
|
.van-icon { color: #fff; } |
||||
|
} |
||||
|
|
||||
|
.message-area { |
||||
|
flex: 1; |
||||
|
overflow-y: auto; |
||||
|
padding: 16px 12px; |
||||
|
} |
||||
|
|
||||
|
.loading-wrap { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 30px 0; |
||||
|
color: #999; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.message-item { |
||||
|
display: flex; |
||||
|
align-items: flex-start; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.message-left { |
||||
|
flex-direction: row; |
||||
|
} |
||||
|
|
||||
|
.message-right { |
||||
|
flex-direction: row-reverse; |
||||
|
} |
||||
|
|
||||
|
.msg-avatar { |
||||
|
width: 36px; |
||||
|
height: 36px; |
||||
|
border-radius: 50%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
font-size: 14px; |
||||
|
font-weight: 600; |
||||
|
color: #fff; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.left-avatar { |
||||
|
background: linear-gradient(135deg, #f5a623, #f7c948); |
||||
|
margin-right: 10px; |
||||
|
} |
||||
|
|
||||
|
.right-avatar { |
||||
|
background: linear-gradient(135deg, #1890ff, #0b63d1); |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
|
||||
|
.msg-bubble { |
||||
|
max-width: 70%; |
||||
|
padding: 10px 14px; |
||||
|
border-radius: 12px; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.bubble-left { |
||||
|
background: #fff; |
||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); |
||||
|
border-top-left-radius: 4px; |
||||
|
} |
||||
|
|
||||
|
.bubble-right { |
||||
|
background: linear-gradient(135deg, #1890ff, #0b63d1); |
||||
|
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2); |
||||
|
border-top-right-radius: 4px; |
||||
|
.msg-sender { color: rgba(255, 255, 255, 0.8); } |
||||
|
.msg-content { color: #fff; } |
||||
|
.msg-time { color: rgba(255, 255, 255, 0.6); } |
||||
|
} |
||||
|
|
||||
|
.msg-sender { |
||||
|
font-size: 12px; |
||||
|
color: #999; |
||||
|
margin-bottom: 4px; |
||||
|
} |
||||
|
|
||||
|
.msg-content { |
||||
|
font-size: 15px; |
||||
|
color: #333; |
||||
|
line-height: 1.5; |
||||
|
word-break: break-all; |
||||
|
} |
||||
|
|
||||
|
.msg-time { |
||||
|
font-size: 11px; |
||||
|
color: #bbb; |
||||
|
margin-top: 4px; |
||||
|
text-align: right; |
||||
|
} |
||||
|
|
||||
|
.input-area { |
||||
|
display: flex; |
||||
|
align-items: flex-end; |
||||
|
gap: 10px; |
||||
|
padding: 10px 12px; |
||||
|
background: #fff; |
||||
|
border-top: 1px solid #e4edf7; |
||||
|
flex-shrink: 0; |
||||
|
|
||||
|
.input-field { |
||||
|
flex: 1; |
||||
|
background: #f5f7fa; |
||||
|
border-radius: 20px; |
||||
|
padding: 8px 14px; |
||||
|
|
||||
|
:deep(.van-field__control) { |
||||
|
min-height: 24px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.send-btn { |
||||
|
height: 36px; |
||||
|
border-radius: 18px; |
||||
|
padding: 0 18px; |
||||
|
font-size: 14px; |
||||
|
background: linear-gradient(135deg, #1890ff, #0b63d1); |
||||
|
border: none; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,423 @@ |
|||||
|
<template> |
||||
|
<div class="notice-tab-content"> |
||||
|
<van-pull-refresh v-model="refreshing" @refresh="onRefresh"> |
||||
|
<van-list |
||||
|
v-model:loading="loading" |
||||
|
:finished="finished" |
||||
|
finished-text="没有更多了" |
||||
|
@load="onLoad" |
||||
|
> |
||||
|
<div class="notice-list"> |
||||
|
<section |
||||
|
v-for="item in noticeList" |
||||
|
:key="item.id" |
||||
|
class="notice-card" |
||||
|
> |
||||
|
<div class="card-header"> |
||||
|
<div class="status-tag" :class="'status-' + item.status"> |
||||
|
{{ getStatusText(item.status) }} |
||||
|
</div> |
||||
|
<div class="card-date">{{ formatDate(item.createTime) }}</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="notice-title">{{ item.enforceContent || '暂无执法事项' }}</div> |
||||
|
|
||||
|
<div class="info-row"> |
||||
|
<div class="label">执法日期:</div> |
||||
|
<div class="value">{{ item.enforceDate || '-' }} {{ getTimeText(item.enforceTime) }}</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="info-row"> |
||||
|
<div class="label">执法单位:</div> |
||||
|
<div class="value">{{ item.bureauDeptName || '-' }}</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="info-row"> |
||||
|
<div class="label">企业名称:</div> |
||||
|
<div class="value">{{ item.enterpriseName || '-' }}</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="info-row"> |
||||
|
<div class="label">执法人员:</div> |
||||
|
<div class="value">{{ item.enforceUserNames || '-' }}</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="info-row"> |
||||
|
<div class="label">联系人:</div> |
||||
|
<div class="value">{{ item.contactPerson || '-' }}</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="info-row"> |
||||
|
<div class="label">联系电话:</div> |
||||
|
<div class="value">{{ item.contactPhone || '-' }}</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 操作按钮 --> |
||||
|
<div class="card-actions" v-if="canPublish || canEdit || canDelete || canMessage || canReinitiate || item.planId"> |
||||
|
<div v-if="item.planId" class="action-item blue-action" @click="onViewPlan(item.planId)"> |
||||
|
<van-icon name="orders-o" /> |
||||
|
<span>关联计划</span> |
||||
|
</div> |
||||
|
<div v-if="canPublish" class="action-item green-action" @click="$emit('publish', item)"> |
||||
|
<van-icon name="passed" /> |
||||
|
<span>发布</span> |
||||
|
</div> |
||||
|
<div v-if="canEdit" class="action-item blue-action" @click="$emit('edit', item)"> |
||||
|
<van-icon name="edit" /> |
||||
|
<span>编辑</span> |
||||
|
</div> |
||||
|
<div v-if="canDelete" class="action-item red-action" @click="$emit('delete', item)"> |
||||
|
<van-icon name="delete-o" /> |
||||
|
<span>删除</span> |
||||
|
</div> |
||||
|
<div v-if="canMessage" class="action-item blue-action" @click="$emit('message', item)"> |
||||
|
<van-icon name="chat-o" /> |
||||
|
<span>留言</span> |
||||
|
</div> |
||||
|
<div v-if="canReinitiate" class="action-item green-action" @click="$emit('reinitiate', item)"> |
||||
|
<van-icon name="replay" /> |
||||
|
<span>再次发起</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 空状态 --> |
||||
|
<van-empty v-if="!loading && noticeList.length === 0" description="暂无预告" /> |
||||
|
</van-list> |
||||
|
</van-pull-refresh> |
||||
|
|
||||
|
<!-- 关联计划详情弹层 --> |
||||
|
<van-popup v-model:show="showPlanPopup" position="bottom" round :style="{ height: '65%' }"> |
||||
|
<div class="plan-detail-popup"> |
||||
|
<div class="plan-detail-header"> |
||||
|
<span class="plan-detail-title">关联计划详情</span> |
||||
|
<van-icon name="cross" @click="showPlanPopup = false" /> |
||||
|
</div> |
||||
|
<div class="plan-detail-body"> |
||||
|
<div v-if="planDetailLoading" class="plan-detail-loading"> |
||||
|
<van-loading size="18" /> 加载中... |
||||
|
</div> |
||||
|
<template v-else-if="planDetail"> |
||||
|
<div class="plan-info-row"> |
||||
|
<div class="plan-info-label">计划日期</div> |
||||
|
<div class="plan-info-value">{{ planDetail.planDate || '-' }} {{ getPlanTimeText(planDetail.planTime) }}</div> |
||||
|
</div> |
||||
|
<div class="plan-info-row"> |
||||
|
<div class="plan-info-label">计划类别</div> |
||||
|
<div class="plan-info-value">{{ planDetail.planType === 1 ? '日常计划' : '专项计划' }}</div> |
||||
|
</div> |
||||
|
<div class="plan-info-row"> |
||||
|
<div class="plan-info-label">企业名称</div> |
||||
|
<div class="plan-info-value">{{ planDetail.enterpriseName || '-' }}</div> |
||||
|
</div> |
||||
|
<div class="plan-info-row"> |
||||
|
<div class="plan-info-label">企业信用代码</div> |
||||
|
<div class="plan-info-value">{{ planDetail.enterpriseNumber || '-' }}</div> |
||||
|
</div> |
||||
|
<div class="plan-info-row"> |
||||
|
<div class="plan-info-label">企业联系人</div> |
||||
|
<div class="plan-info-value">{{ planDetail.contactPerson || '-' }}</div> |
||||
|
</div> |
||||
|
<div class="plan-info-row"> |
||||
|
<div class="plan-info-label">联系电话</div> |
||||
|
<div class="plan-info-value">{{ planDetail.contactPhone || '-' }}</div> |
||||
|
</div> |
||||
|
<div class="plan-info-row"> |
||||
|
<div class="plan-info-label">执法人员</div> |
||||
|
<div class="plan-info-value">{{ planDetail.enforceUserNames || '-' }}</div> |
||||
|
</div> |
||||
|
<div class="plan-info-row"> |
||||
|
<div class="plan-info-label">执法事项</div> |
||||
|
<div class="plan-info-value">{{ planDetail.enforceContent || '-' }}</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<van-empty v-else description="未查询到计划信息" image-size="80" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</van-popup> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { getMyNoticeList } from '@/api/enforcement/notice'; |
||||
|
import { getPlanDetail } from '@/api/enforcement/plan'; |
||||
|
|
||||
|
const props = defineProps({ |
||||
|
status: { type: Number, required: true }, |
||||
|
canPublish: { type: Boolean, default: false }, |
||||
|
canEdit: { type: Boolean, default: false }, |
||||
|
canDelete: { type: Boolean, default: false }, |
||||
|
canMessage: { type: Boolean, default: false }, |
||||
|
canReinitiate: { type: Boolean, default: false }, |
||||
|
messageReadonly: { type: Boolean, default: false }, |
||||
|
}); |
||||
|
|
||||
|
const emit = defineEmits(['publish', 'edit', 'delete', 'message', 'reinitiate']); |
||||
|
|
||||
|
const noticeList = ref([]); |
||||
|
const loading = ref(false); |
||||
|
const finished = ref(false); |
||||
|
const refreshing = ref(false); |
||||
|
const pageNum = ref(1); |
||||
|
const pageSize = 10; |
||||
|
|
||||
|
const getStatusText = (status) => { |
||||
|
const map = { 0: '草稿', 1: '已发布', 2: '已取消', 3: '已执行' }; |
||||
|
return map[status] || '未知'; |
||||
|
}; |
||||
|
|
||||
|
const getTimeText = (time) => { |
||||
|
const map = { 1: '上午', 2: '下午', 3: '其他' }; |
||||
|
return map[time] || ''; |
||||
|
}; |
||||
|
|
||||
|
const formatDate = (dateStr) => { |
||||
|
if (!dateStr) return ''; |
||||
|
return String(dateStr).substring(0, 16).replace('T', ' '); |
||||
|
}; |
||||
|
|
||||
|
const onLoad = () => { |
||||
|
fetchList(); |
||||
|
}; |
||||
|
|
||||
|
const onRefresh = () => { |
||||
|
pageNum.value = 1; |
||||
|
finished.value = false; |
||||
|
noticeList.value = []; |
||||
|
fetchList(); |
||||
|
}; |
||||
|
|
||||
|
const refresh = () => { |
||||
|
onRefresh(); |
||||
|
}; |
||||
|
|
||||
|
const fetchList = () => { |
||||
|
loading.value = true; |
||||
|
const params = { |
||||
|
status: props.status, |
||||
|
pageNum: pageNum.value, |
||||
|
pageSize, |
||||
|
}; |
||||
|
getMyNoticeList( |
||||
|
params, |
||||
|
(res) => { |
||||
|
refreshing.value = false; |
||||
|
loading.value = false; |
||||
|
if (res.code === 200) { |
||||
|
const rows = res.rows || []; |
||||
|
if (pageNum.value === 1) { |
||||
|
noticeList.value = rows; |
||||
|
} else { |
||||
|
noticeList.value.push(...rows); |
||||
|
} |
||||
|
if (rows.length < pageSize) { |
||||
|
finished.value = true; |
||||
|
} else { |
||||
|
pageNum.value++; |
||||
|
} |
||||
|
} else { |
||||
|
finished.value = true; |
||||
|
} |
||||
|
}, |
||||
|
() => { |
||||
|
refreshing.value = false; |
||||
|
loading.value = false; |
||||
|
finished.value = true; |
||||
|
} |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
// 关联计划弹层 |
||||
|
const showPlanPopup = ref(false); |
||||
|
const planDetail = ref(null); |
||||
|
const planDetailLoading = ref(false); |
||||
|
|
||||
|
const getPlanTimeText = (time) => { |
||||
|
const map = { 1: '上午', 2: '下午', 3: '其他' }; |
||||
|
return map[time] || ''; |
||||
|
}; |
||||
|
|
||||
|
const onViewPlan = (planId) => { |
||||
|
showPlanPopup.value = true; |
||||
|
planDetail.value = null; |
||||
|
planDetailLoading.value = true; |
||||
|
getPlanDetail(planId, (res) => { |
||||
|
planDetailLoading.value = false; |
||||
|
if (res.code === 200 && res.data) { |
||||
|
planDetail.value = res.data; |
||||
|
} |
||||
|
}, () => { |
||||
|
planDetailLoading.value = false; |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
// 暴露 refresh 方法给父组件 |
||||
|
defineExpose({ refresh }); |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.notice-tab-content { |
||||
|
height: 100%; |
||||
|
overflow-y: auto; |
||||
|
padding: 12px; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.notice-list { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 14px; |
||||
|
} |
||||
|
|
||||
|
.notice-card { |
||||
|
background: #fff; |
||||
|
border-radius: 14px; |
||||
|
padding: 16px; |
||||
|
box-shadow: 0 4px 14px rgba(44, 116, 190, 0.08); |
||||
|
} |
||||
|
|
||||
|
.card-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 12px; |
||||
|
} |
||||
|
|
||||
|
.status-tag { |
||||
|
font-size: 12px; |
||||
|
font-weight: 500; |
||||
|
padding: 3px 10px; |
||||
|
border-radius: 10px; |
||||
|
} |
||||
|
|
||||
|
.status-0 { |
||||
|
background: #f0f0f0; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.status-1 { |
||||
|
background: #e6f7ff; |
||||
|
color: #1890ff; |
||||
|
} |
||||
|
|
||||
|
.status-2 { |
||||
|
background: #fff2e8; |
||||
|
color: #fa8c16; |
||||
|
} |
||||
|
|
||||
|
.status-3 { |
||||
|
background: #f6ffed; |
||||
|
color: #52c41a; |
||||
|
} |
||||
|
|
||||
|
.card-date { |
||||
|
font-size: 12px; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.notice-title { |
||||
|
font-size: 16px; |
||||
|
font-weight: 700; |
||||
|
color: #111; |
||||
|
line-height: 1.5; |
||||
|
margin-bottom: 12px; |
||||
|
white-space: nowrap; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
} |
||||
|
|
||||
|
.info-row { |
||||
|
display: flex; |
||||
|
align-items: flex-start; |
||||
|
margin-bottom: 6px; |
||||
|
font-size: 14px; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
|
||||
|
.label { |
||||
|
width: 72px; |
||||
|
flex-shrink: 0; |
||||
|
color: #333; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.value { |
||||
|
flex: 1; |
||||
|
color: #8a8a8a; |
||||
|
} |
||||
|
|
||||
|
.card-actions { |
||||
|
margin-top: 14px; |
||||
|
padding-top: 12px; |
||||
|
border-top: 1px solid rgba(0, 0, 0, 0.06); |
||||
|
display: flex; |
||||
|
justify-content: flex-end; |
||||
|
gap: 20px; |
||||
|
} |
||||
|
|
||||
|
.action-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 4px; |
||||
|
font-size: 14px; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.blue-action { |
||||
|
color: #2382cf; |
||||
|
} |
||||
|
|
||||
|
.red-action { |
||||
|
color: #f56c6c; |
||||
|
} |
||||
|
|
||||
|
.green-action { |
||||
|
color: #52c41a; |
||||
|
} |
||||
|
|
||||
|
.plan-detail-popup { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100%; |
||||
|
.plan-detail-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 14px 16px 8px; |
||||
|
.plan-detail-title { font-size: 16px; font-weight: 600; color: #333; } |
||||
|
:deep(.van-icon) { font-size: 18px; color: #999; cursor: pointer; } |
||||
|
} |
||||
|
.plan-detail-body { |
||||
|
flex: 1; |
||||
|
overflow-y: auto; |
||||
|
padding: 8px 16px 16px; |
||||
|
} |
||||
|
.plan-detail-loading { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 30px 0; |
||||
|
color: #999; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
.plan-info-row { |
||||
|
display: flex; |
||||
|
padding: 12px 0; |
||||
|
border-bottom: 1px solid #f0f0f0; |
||||
|
&:last-child { border-bottom: none; } |
||||
|
.plan-info-label { |
||||
|
width: 90px; |
||||
|
flex-shrink: 0; |
||||
|
font-size: 14px; |
||||
|
color: #666; |
||||
|
} |
||||
|
.plan-info-value { |
||||
|
flex: 1; |
||||
|
font-size: 14px; |
||||
|
color: #333; |
||||
|
word-break: break-all; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue