fix: 修复日期筛选与表单参数双写、下线范围预估,并改造列表三列样式
build / build (push) Successful in 13m15s

- v29 删除前端对 notice/ScopePreview(推送范围「已选学员数」预估)的请求:
  api 方法、ScopePreviewVo 类型、dev:mock 桩、ClassScopeInput/PushScopePanel 两个组件的预估 UI 一并移除。
- v30 修复发文部门/联系组名称重复且带逗号(如「校园部,校园部」并逐次翻倍):
  postForm 原先把参数同时拼进 URL query 与 urlencoded body,Servlet 会合并两处取值、
  Spring @RequestParam String 再以「,」拼接。改为只发 body,仅在 dev:mock 下附带 query
  (vite-plugin-mock 不解析 urlencoded body),不影响线上。
- 修复时间筛选失效:DatePicker 的 Date 实例被 alova 直接 encodeURIComponent 成浏览器本地化
  日期串(Fri Aug 21 2026 ... GMT+0800),后端按 yyyy-MM-dd 解析不了。列表侧提交前归一化为
  短日期,alova-core 另加一层 Date → 本地 'yyyy-MM-dd HH:mm:ss' 的兜底归一化。
- 列表「类型 / 状态 / 阅读情况」三列改醒目样式(口径不变,仅样式):
  类型改胶囊(重要=实心红、普通=浅灰弱化);状态改彩色圆点胶囊,已发布绿/定时中蓝/
  待审批橙/已驳回红/草稿灰,已撤回、置顶改实心小徽标;阅读情况改渐变进度条 + 已读/应读·百分比,
  读满转绿。同步微调类型(80→90)、阅读情况(110→180)列宽。
- 同步更新《执行计划任务方案.md》v29 / v30 记录。
This commit is contained in:
lijing
2026-09-22 09:46:33 +08:00
parent b623e18b9b
commit a48c5f6343
8 changed files with 244 additions and 152 deletions
+7 -15
View File
@@ -23,8 +23,6 @@ import type {
ClassTreeNode,
ApprovalQuery,
ApprovalTabCountVo,
PushScopeItem,
ScopePreviewVo,
SubmitRequest,
} from "@/types/notice";
@@ -46,7 +44,7 @@ import type {
* 变更要点(P1-7 / 校园通知改造):
* 7. 全部方法加返回值泛型标注,替换 Record<string, any>
* 8. 列表分页 content 字段对齐后端 BasePageResult(非旧 list)
* 9. 新增:ScopeTree/ScopePreview 推送范围、SaveDraft/DraftList 草稿闭环、
* 9. 新增:ScopeTree 推送范围、SaveDraft/DraftList 草稿闭环、
* Submit 提交审批、ReadReceipt 阅读回执、Approve 带 opinion
*/
@@ -88,9 +86,12 @@ function postForm<T = any>(url: string, data?: Record<string, any>): Method<Alov
? Object.entries(data).filter(([, v]) => v !== undefined && v !== null)
: [];
const form = pairs.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&");
// 同时把参数拼到 URL query:后端 @RequestParam 表单/查询参数都能接收,
// 且本地 vite-plugin-mock 不解析 urlencoded body,靠 query 才能拿到参数(不影响线上)。
const fullUrl = form ? `${url}${url.includes("?") ? "&" : "?"}${form}` : url;
// 真后端:参数只放进 x-www-form-urlencoded body,URL 不带 query。
// 修复 v30:若 query 与 body 同时携带同名参数,Servlet 会合并两处取值,
// Spring @RequestParam String 再以「,」拼接,导致部门/联系组等名称变成「校园部,校园部」,
// 且每次改名/启停把已含逗号的值原样回发再翻倍。只发 body 后每个参数只被取到一次。
// 本地 dev:mock:vite-plugin-mock 不解析 urlencoded body,仅在此模式下把参数附带进 query(不影响线上)。
const fullUrl = conf.isMock && form ? `${url}${url.includes("?") ? "&" : "?"}${form}` : url;
return new Method<AlovaGenerics<T>>(
"POST",
alovaInstance,
@@ -194,15 +195,6 @@ class NoticeApi {
return postForm<void>(`${NOTICE}/ReceiveUsers`, { serid, users });
}
// ========== 推送范围(改造方案 3.1 · v3 换源) ==========
/** 范围树数据源已改 base/getStuTree(见 baseApi.getStuTree);此处仅按已选范围预估学员数 */
scopePreview(scopes: PushScopeItem[], receiveList?: string) {
return get<ScopePreviewVo>(`${NOTICE}/ScopePreview`, {
scopes: JSON.stringify(scopes || []),
receiveList: receiveList || "",
});
}
// ========== 发布/编辑/删除/草稿 ==========
/** 新建通知(JSON body,fileIds 为附件ID逗号串,smsFlag 为短信标记;带 flow 即送审) */
create(data: NoticeRequest) {
-25
View File
@@ -1184,31 +1184,6 @@ export default [
},
]),
},
{
// 范围预估:全校=ALL_STUDENTS;年级/班级按 studentCount 汇总;教师按名单数
url: "/api/notice/ScopePreview",
method: "get",
response: ({ query }: any) => {
let studentCount = 0;
try {
const scopes = JSON.parse(query.scopes || "[]");
for (const s of scopes) {
if (s.scopeType === 1) studentCount += ALL_STUDENTS;
else if (s.scopeType === 3) {
const g: any = gradeClasses.find((x) => x.gradeId === s.scopeId);
if (g) studentCount += g.classes.reduce((sum: number, c: any) => sum + c.studentCount, 0);
} else if (s.scopeType === 4) {
studentCount += (classMap[String(s.scopeId)] || { studentCount: 0 }).studentCount;
}
}
} catch {
/* ignore */
}
const receive = String(query.receiveList || "").trim();
const teacherCount = !receive || /^all$/i.test(receive) ? 0 : receive.split(/[,,\s]+/).filter(Boolean).length;
return ok({ studentCount, teacherCount, totalCount: studentCount + teacherCount });
},
},
{
// 存为草稿:不圈人,仅内容 + 范围快照 + 附件
url: "/api/notice/SaveDraft",
+4 -29
View File
@@ -10,7 +10,6 @@
<Button icon="md-add" @click="openPicker" :disabled="mode === 0"></Button>
<Button icon="md-trash" @click="reset" :disabled="mode === 0"></Button>
</div>
<div class="scope-preview" v-if="modelValue.length && preview">已选学员 <b>{{ preview.studentCount }}</b> 人</div>
<UserCheckModal ref="picker" :sources="sources" :userids="classIds" @on-submit="submit" />
</div>
</template>
@@ -25,8 +24,8 @@
* 树数据源 base/getStuTree?toUser=false(后端无 GetClassTree),自底向上归出「年级→班级」两层。
*/
import UserCheckModal from "@/components/UserCheckModal.vue";
import { baseApi, noticeApi } from "@/api/module/notice";
import type { PushScopeItem, SchTreeNode, ScopePreviewVo } from "@/types/notice";
import { baseApi } from "@/api/module/notice";
import type { PushScopeItem, SchTreeNode } from "@/types/notice";
/** 全部班级范围项(后端 scopeType=1 不解析 scopeId) */
const ALL_SCOPE: PushScopeItem = { scopeType: 1, scopeId: 0, scopeName: "全部班级" };
@@ -56,8 +55,6 @@ export default {
treeFailed: false,
/** 树未就绪时的回显缓冲(对齐 PushScopePanel pendingScopes 口径) */
pendingScopes: null as PushScopeItem[] | null,
preview: null as ScopePreviewVo | null,
previewTimer: undefined as number | undefined,
sources: [{ name: "班级", loader: () => this.loadTree() }],
};
},
@@ -93,9 +90,8 @@ export default {
created() {
if (this.modelValue.length) {
this.syncFromValue(this.modelValue);
this.runPreview();
} else {
this.applyAll(); // 新建默认全部班级(emit 后由 watch 触发预估)
this.applyAll(); // 新建默认全部班级
}
},
mounted() {
@@ -268,24 +264,12 @@ export default {
this.classStr = [...extras, ...classes].map((x) => x.scopeName).join("、");
}
},
async runPreview() {
if (!this.modelValue.length) {
this.preview = null;
return;
}
try {
this.preview = await noticeApi.scopePreview(this.modelValue as PushScopeItem[]).send();
} catch {
this.preview = null;
}
},
},
watch: {
modelValue: {
handler(val: PushScopeItem[]) {
// 自身 emit 的变化只跳过状态回灌,预估仍按最新值刷新
// 自身 emit 的变化只跳过状态回灌
if (!this.suppressWatch) this.syncFromValue(val || []);
this.runPreview();
},
deep: true,
},
@@ -311,14 +295,5 @@ export default {
flex: 1;
}
}
.scope-preview {
margin-top: 8px;
font-size: 12px;
color: #808695;
b {
color: #2d8cf0;
font-size: 14px;
}
}
}
</style>
+3 -47
View File
@@ -35,11 +35,6 @@
</span>
</div>
<div v-else class="scope-empty">未选择推送范围</div>
<!-- 选中学员数预估(ScopePreview 按范围展开去重) -->
<div class="scope-preview" v-if="modelValue.length">
<span>已选学员 <b>{{ preview ? preview.studentCount : "…" }}</b></span>
</div>
</div>
</template>
@@ -49,13 +44,12 @@
* v-model = PushScopeItem[](最粗粒度勾选:父节点勾选则忽略其子孙)。
* getStuTree 组节点不区分学校/学段/年级/班级,toScopeNode 自底向上归类(对齐 PushScopeTypeEnum):
* 无组子节点=班级(4),其父=年级(3),再上=学部(2),顶层根强制=全校(1)。
* 选中学员数:notice/ScopePreview.studentCount。
* 编辑回显:父组件加载详情后调用 setScopes(pushScopes) 勾选对应节点;
* 旧链路班级通知(receiveType=class)由父组件映射为 scopeType=4 的班级项。
*/
import { ref, computed, onMounted, watch } from "vue";
import { baseApi, noticeApi } from "@/api/module/notice";
import type { PushScopeItem, SchTreeNode, ScopeTreeNode, ScopePreviewVo } from "@/types/notice";
import { ref, computed, onMounted } from "vue";
import { baseApi } from "@/api/module/notice";
import type { PushScopeItem, SchTreeNode, ScopeTreeNode } from "@/types/notice";
const props = defineProps<{
modelValue: PushScopeItem[];
@@ -232,31 +226,6 @@ function applyScopes(scopes?: PushScopeItem[] | null) {
defineExpose({ setScopes });
/* ============ 选中学员数预估(ScopePreview,防抖 350ms) ============ */
const preview = ref<ScopePreviewVo | null>(null);
let previewTimer: number | undefined;
async function runPreview() {
if (!props.modelValue.length) {
preview.value = null;
return;
}
try {
preview.value = await noticeApi.scopePreview(props.modelValue).send();
} catch {
preview.value = null;
}
}
watch(
() => props.modelValue,
() => {
window.clearTimeout(previewTimer);
previewTimer = window.setTimeout(runPreview, 350);
},
{ deep: true }
);
/** getStuTree 组节点 → 面板范围节点:自底向上归类 scopeType(对齐 api-test toScopeNode,见头注释) */
function toScopeNode(node: SchTreeNode, forcedType?: number): ScopeTreeNode | null {
if (!node || node.type !== 1) return null; // toUser=false 仅组节点;出现人员叶子防御性跳过
@@ -368,18 +337,5 @@ onMounted(async () => {
font-size: 12px;
margin-top: 8px;
}
.scope-preview {
margin-top: 8px;
background: #f8fafc;
border-radius: 4px;
padding: 8px 12px;
font-size: 13px;
display: flex;
gap: 16px;
b {
font-size: 15px;
color: #2d8cf0;
}
}
}
</style>
+1 -11
View File
@@ -12,7 +12,7 @@
* - NoticeStatisticsVo → 后端 model/notice/NoticeStatisticsVo.java
* - NoticeRequest → 后端 model/notice/NoticeRequest.java
* - NoticeQuery → 后端 model/notice/NoticeQuery.java + BasePageQuery.java
* - PushScopeItem / ScopeTreeNode / ScopePreviewVo → 改造方案 3.1 推送范围
* - PushScopeItem / ScopeTreeNode → 改造方案 3.1 推送范围
* - ReadReceiptVo → 后端 model/notice/ReadReceiptVo.java(阅读回执分组)
* - ReadTabCountVo → 后端 model/notice/ReadTabCountVo.java(v17「已收到」三 tab 统计)
* - SubmitRequest / ApproverSuggestVo → 审核改造(步骤表 + 审核记录表 + 记忆表)
@@ -152,16 +152,6 @@ export interface ScopeTreeNode {
children?: ScopeTreeNode[];
}
/** 推送范围预估 VO(notice/ScopePreview) */
export interface ScopePreviewVo {
/** 范围展开学员数(按账号去重) */
studentCount: number;
/** 教师定向名单数(receiveList 解析) */
teacherCount: number;
/** 合计预估接收人数 */
totalCount: number;
}
// ============================ 阅读回执 ============================
/** 已发送通知阅读回执(notice/ReadReceipt,按 年级→班级→学生 分组 + 教师组) */
+29
View File
@@ -33,6 +33,33 @@ const defHooks: Required<RequestHooks> = {
onHttpError: () => {},
};
/* ============= query 参数归一化 ============= */
const pad2 = (n: number) => String(n).padStart(2, "0");
/** 本地时间 → 'yyyy-MM-dd HH:mm:ss' */
function localDateTime(d: Date): string {
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(
d.getMinutes()
)}:${pad2(d.getSeconds())}`;
}
/**
* 兜底:把 params 里的 Date 实例转成本地 'yyyy-MM-dd HH:mm:ss'。
*
* alova 拼 query 时对参数只做 `encodeURIComponent(params[key])`,Date 会被隐式转成
* 浏览器本地化串「Fri Aug 21 2026 00:00:00 GMT+0800 (中国标准时间)」,后端无法解析。
* 业务侧应显式传字符串(如校园公告列表的 startTime/endTime 用 yyyy-MM-dd 短日期),
* 这里只做防止 Date 直接泄漏到 URL 的最后一道保险,不改变已是字符串的参数。
*/
function normalizeParams(method: Method) {
const params: any = method.config?.params;
if (!params || typeof params !== "object" || Array.isArray(params)) return;
Object.keys(params).forEach((k) => {
const v = params[k];
if (v instanceof Date && !isNaN(v.getTime())) params[k] = localDateTime(v);
});
}
/* ============= 错误处理(原 axios 逻辑平移) ============= */
function errorHandle<T>(res: BschResponse<T>, status: number): T {
// http 层错误
@@ -95,6 +122,8 @@ export function createBschAlova(
onLoadingEnd();
}
});
// 0. query 参数兜底归一化(Date → 本地时间串),避免本地化日期串进 URL
normalizeParams(method);
// 1. 用户注入票据/角色等头
injectHeaders(method);
+97 -25
View File
@@ -28,6 +28,22 @@ function fmtDate(d: Date) {
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
}
/**
* 日期筛选值 → 接口短日期(yyyy-MM-dd)。
*
* DatePicker 的 v-model 绑定的是 **Date 实例**(视图内部值),而 alova/fetch 拼 query
* 时对参数只做 encodeURIComponent(String(v)),Date 会退化成浏览器本地化串:
* startTime=Fri%20Aug%2021%202026%2000:00:00%20GMT+0800%20(中国标准时间)
* 后端按 yyyy-MM-dd 解析不了(NoticeQuery.startTime 契约即短日期),时间筛选因此失效。
* 故发请求前统一归一化:Date 走本地日期,字符串(resetFilter 写入的默认值)取其前 10 位。
*/
function shortDate(v: unknown): string {
if (!v) return "";
if (v instanceof Date) return isNaN(v.getTime()) ? "" : fmtDate(v);
const m = String(v).match(/^(\d{4})-(\d{1,2})-(\d{1,2})/);
return m ? `${m[1]}-${m[2].padStart(2, "0")}-${m[3].padStart(2, "0")}` : "";
}
/* ============ 分页(规范 §3:customPageCount 页长记忆) ============ */
const pageNum = ref(1);
const pageSize = ref(parseInt(localStorage.getItem("customPageCount") || "10"));
@@ -47,8 +63,10 @@ async function run() {
};
if (statusType.value !== "") params.status = Number(statusType.value);
if (noticeType.value !== "") params.type = Number(noticeType.value);
if (startDt.value) params.startTime = startDt.value;
if (endDt.value) params.endTime = endDt.value;
const startTime = shortDate(startDt.value);
const endTime = shortDate(endDt.value);
if (startTime) params.startTime = startTime;
if (endTime) params.endTime = endTime;
if (!isManage.value) {
params.scope = "user";
}
@@ -226,31 +244,93 @@ function downloadFiles(row: NoticeVo & { UpFile?: string }) {
});
}
/* 状态列:草稿/已发布(定时中)/待审批(步骤进度)/已驳回 + 已撤回/置顶徽标 */
const STATUS_TAG: Record<string, string> = { pending: "warning", approved: "success", rejected: "error", none: "default" };
/* ============ 单元格视觉(纯样式,口径不变;视觉语言对齐家庭活动「我发布的」列表) ============
* render 单元格由 Table 内部渲染,不吃本文件的 scoped 样式,故统一用内联样式。
* 色板:灰 #c5c8ce / 蓝 #2d8cf0 / 橙 #ff9900 / 绿 #19be6b / 红 #ed4014(与参考列表同一套)。
*/
const C_GREY = "#c5c8ce";
const C_BLUE = "#2d8cf0";
const C_ORANGE = "#ff9900";
const C_GREEN = "#19be6b";
const C_RED = "#ed4014";
/** 状态胶囊:彩色圆点 + 文字(参考列表 statusPill 同款) */
function statusPill(h: any, hex: string, text: string) {
return h("span", { style: "display:inline-flex;align-items:center;gap:6px;font-size:13px;color:#515a6e;white-space:nowrap;" }, [
h("i", { style: `width:8px;height:8px;border-radius:50%;display:inline-block;flex:none;background:${hex};` }),
text,
]);
}
/** 实心小徽标:置顶 / 已撤回(参考列表「顶」「已撤回」同款) */
function miniBadge(h: any, hex: string, text: string) {
return h(
"span",
{ style: `display:inline-block;padding:0 5px;margin-left:4px;font-size:11px;line-height:16px;color:#fff;background:${hex};border-radius:3px;vertical-align:1px;white-space:nowrap;` },
text
);
}
/** 类型胶囊:重要=实心红醒目,普通=浅灰弱化(同列对比出重要程度) */
function typeCell(h: any, row: any) {
const important = row.type == 2;
return h(
"span",
{
style: important
? `display:inline-block;padding:0 9px;font-size:12px;line-height:18px;font-weight:600;color:#fff;background:${C_RED};border-radius:9px;`
: "display:inline-block;padding:0 9px;font-size:12px;line-height:18px;color:#808695;background:#f5f7fa;border:1px solid #e8eaec;border-radius:9px;",
},
row.typeDesc || (important ? "重要" : "普通")
);
}
/** 阅读情况:渐变进度条 + 已读/应读 · 百分比(参考列表回执进度同款;读满转绿加粗) */
function readCell(h: any, row: any) {
// 未发布(草稿/待审批/已驳回)无阅读数据,不占进度条
if (row.status !== 1) return h("span", { style: `color:${C_GREY};font-size:13px;` }, "—");
const allow = Number(row.allowCount) || 0;
const read = Number(row.readCount) || 0;
if (!allow) return h("span", { style: "color:#808695;font-size:13px;" }, "暂无应读人");
const pct = Math.min(100, Math.round((read / allow) * 100));
const done = read >= allow;
return h("div", { style: "display:flex;align-items:center;gap:8px;" }, [
h("div", { style: "flex:1;min-width:54px;height:6px;background:#f0f2f5;border-radius:4px;overflow:hidden;" }, [
h("div", {
style: `width:${pct}%;height:100%;border-radius:4px;background:${done ? `linear-gradient(90deg,${C_GREEN},#3fc98a)` : `linear-gradient(90deg,${C_BLUE},#5cadff)`};`,
}),
]),
h(
"span",
{ style: `font-size:12px;white-space:nowrap;color:${done ? C_GREEN : "#515a6e"};font-weight:${done ? 600 : 400};` },
`${read}/${allow} · ${pct}%`
),
]);
}
/* 状态列:草稿/已发布(定时中)/待审批(步骤进度)/已驳回 + 已撤回/置顶徽标 */
function renderStatus(h: any, row: any) {
const parts: any[] = [];
if (row.status === 1) {
// 定时未到点:定时中(已排期)
if (row.timingSendTime && row.timingPublished === 0 && row.isPulled !== 1) {
parts.push(h("Tag", { color: "purple" }, () => "定时中"));
parts.push(statusPill(h, C_BLUE, "定时中"));
} else {
parts.push(h("Tag", { color: "success" }, () => "已发布"));
parts.push(statusPill(h, C_GREEN, "已发布"));
}
} else if (row.status === 2) {
parts.push(h("Tag", { color: "warning" }, () => "待审批"));
parts.push(statusPill(h, C_ORANGE, "待审批"));
// 多步进度文本(如「第1/2步 校级审核·会签 0/2」),与徽标重复时不再显示
if (row.approvalStatusDesc && row.approvalStatusDesc !== "待审批") {
parts.push(h("div", { style: "font-size:12px;color:#808695;line-height:1.4;margin-top:2px" }, String(row.approvalStatusDesc)));
}
} else if (row.status === 3) {
parts.push(h("Tag", { color: "error" }, () => "已驳回"));
parts.push(statusPill(h, C_RED, "已驳回"));
} else {
parts.push(h("Tag", {}, () => row.statusDesc || "草稿"));
parts.push(statusPill(h, C_GREY, row.statusDesc || "草稿"));
}
if (row.isPulled === 1) parts.push(h("Tag", { style: "margin-left:4px" }, () => "已撤回"));
if (row.topmost == 1) parts.push(h("Tag", { color: "orange", style: "margin-left:4px" }, () => "置顶"));
if (row.isPulled === 1) parts.push(miniBadge(h, C_ORANGE, "已撤回"));
if (row.topmost == 1) parts.push(miniBadge(h, C_RED, "置顶"));
return h("div", {}, parts);
}
@@ -268,26 +348,17 @@ const columns = computed(() => [
return h("span", { attrs: { title: t } }, t.length > 8 ? `${t.slice(0, 8)}…` : t);
},
},
{ title: "类型", width: 80, align: "center",
render: (h: any, p: any) => h("Tag", { color: p.row.type == 2 ? "error" : "default" }, () => p.row.typeDesc || (p.row.type == 2 ? "重要" : "普通")) },
{ title: "类型", width: 80, align: "center", render: (h: any, p: any) => typeCell(h, p.row) },
{ title: "来源部门", key: "deptName", width: 110, align: "left" },
{ title: "发布人", key: "publisherName", width: 90, align: "left", tooltip: true },
{ title: "状态", width: 190, align: "left", render: (h: any, p: any) => renderStatus(h, p.row) },
{
title: "发布时间",
width: 150,
width: 160,
align: "left",
render: (h: any, p: any) => h("span", {}, String(p.row.timingSendTime || p.row.createTime || "").replace("T", " ").slice(0, 16)),
},
{
title: "阅读情况",
width: 110,
align: "left",
render: (h: any, p: any) =>
p.row.status === 1
? h("span", {}, `${p.row.readCount || 0} / ${p.row.allowCount || 0} 已读`)
: h("span", { style: "color:#999;" }, "—"),
},
{ title: "阅读情况", width: 180, align: "left", render: (h: any, p: any) => readCell(h, p.row) },
{
title: "附件",
width: 70,
@@ -341,9 +412,10 @@ const tableHeight = computed(() => Utils.useTableHeight({ isSearch: true, isOper
<!-- 2. 搜索区 -->
<div class="search-wrap fix">
<div class="search-wrap_date fl">
<DatePicker type="date" placeholder="开始时间" v-model="startDt" @on-change="doSearch"></DatePicker>
<!-- v-model 拿到的是 Date 实例,提交前由 shortDate() 归一化为 yyyy-MM-dd 短日期 -->
<DatePicker type="date" format="yyyy-MM-dd" placeholder="开始时间" v-model="startDt" @on-change="doSearch"></DatePicker>
<span class="ml-8 mr-8">至</span>
<DatePicker type="date" placeholder="结束时间" v-model="endDt" @on-change="doSearch"></DatePicker>
<DatePicker type="date" format="yyyy-MM-dd" placeholder="结束时间" v-model="endDt" @on-change="doSearch"></DatePicker>
</div>
<Select v-model="statusType" class="search-wrap_sel ml-12" clearable placeholder="全部状态" style="width: 110px" @on-change="doSearch">
<Option value="1">已发布</Option>