|
|
@@ -26,7 +26,7 @@
|
|
|
<div class="policy-item-action" @click.stop="openDetailByItem(item)">查看详情</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="view-all-btn" @click.stop="openList">更多 >></div>
|
|
|
+ <div class="view-all-btn" @click.stop="openPolicyList">更多 >></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -43,7 +43,7 @@
|
|
|
{{ panelMode === "list" ? "智能政策匹配" : "智能政策匹配" }}
|
|
|
</div>
|
|
|
<div class="header-actions">
|
|
|
- <button v-if="panelMode === 'detail' && !recordMode" class="header-back" @click.stop="openList">
|
|
|
+ <button v-if="panelMode === 'detail' && !recordMode" class="header-back" @click.stop="openPolicyList">
|
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
<path d="M15 18L9 12L15 6" stroke="white" stroke-width="2" stroke-linecap="round"
|
|
|
stroke-linejoin="round" />
|
|
|
@@ -216,11 +216,12 @@
|
|
|
|
|
|
<div v-else class="panel-scroll detail-body" :class="{ 'detail-body-record': recordMode }">
|
|
|
<div class="record-header-section" v-if="recordMode">
|
|
|
- <div class="record-back-btn" @click.stop="openList">
|
|
|
+ <!-- 只有惠企政策才显示「返回政策事项列表」——它通向惠企政策库 -->
|
|
|
+ <div class="record-back-btn" v-if="selectedIsPolicyLibrary" @click.stop="openPolicyList">
|
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
|
<path d="M10.5 12.5L6 8L10.5 3.5L9.5 2.5L4 8L9.5 13.5L10.5 12.5Z" fill="#3525CD" />
|
|
|
</svg>
|
|
|
- <span>返回政策列表</span>
|
|
|
+ <span>返回政策事项列表</span>
|
|
|
</div>
|
|
|
<div class="record-main-title">{{ selectedPolicyItem?.declaration_item ||
|
|
|
'建立重点产品碳足迹背景数据库,构建碳足迹因子核算和发布工作机制' }}</div>
|
|
|
@@ -454,6 +455,12 @@
|
|
|
import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
|
import { parsePolicyTableContent } from "@/utils/stream-xml-filter";
|
|
|
import { isZhaoshangLLM as isZhaoshangLLMModel } from "@/utils";
|
|
|
+import {
|
|
|
+ buildLibraryNameSet,
|
|
|
+ collectMatchedLibraryNames,
|
|
|
+ isPolicyLibraryItem,
|
|
|
+ pinMatchedFirst,
|
|
|
+} from "./policy-library-utils";
|
|
|
import {
|
|
|
buildPolicyListSearchParams,
|
|
|
buildPolicyListUrl,
|
|
|
@@ -851,6 +858,70 @@ const listPolicyItems = computed(() => {
|
|
|
* 顺序与卡片完全一致(不再另行拉取与卡片无关的公共政策库),
|
|
|
* 部门筛选也一定能筛到内容。
|
|
|
*/
|
|
|
+/* ──────────────────────────────────────────────────────────────────────────
|
|
|
+ * 惠企政策库相关
|
|
|
+ *
|
|
|
+ * 惠企政策 = policies_public.v1.json 里的那些政策(262 条申报事项 / 38 个政策,
|
|
|
+ * 由 index.html 预加载到 globalThis.policiesPublic)。
|
|
|
+ * 判定优先用接口下发的 is_policy_library 字段,缺失时回退到"标题能在库里找到"。
|
|
|
+ * ────────────────────────────────────────────────────────────────────────── */
|
|
|
+
|
|
|
+/**
|
|
|
+ * ⚠️ 这里是**函数**不是 computed,是刻意的。
|
|
|
+ *
|
|
|
+ * `globalThis.policiesPublic` 由 index.html 异步 fetch 加载(失败时退到本地兜底),
|
|
|
+ * 且应用**不等待**它——不是响应式依赖。写成 computed 会把首次读到的值
|
|
|
+ * (很可能是空数组)缓存住,加载完成后也不会更新。
|
|
|
+ * 所以每次调用时现读。
|
|
|
+ */
|
|
|
+const getLibraryPolicies = (): RawPolicyItem[] =>
|
|
|
+ Array.isArray(globalThis.policiesPublic) ? globalThis.policiesPublic : [];
|
|
|
+
|
|
|
+const getLibraryNameSet = (): Set<string> => buildLibraryNameSet(getLibraryPolicies());
|
|
|
+
|
|
|
+/** 本轮卡片里匹配到的惠企政策名(用于在库列表里置顶) */
|
|
|
+const matchedLibraryNames = computed(() =>
|
|
|
+ collectMatchedLibraryNames(policies.value, getLibraryNameSet())
|
|
|
+);
|
|
|
+
|
|
|
+/** 当前详情页正在看的政策是否惠企政策——决定是否显示"返回政策列表" */
|
|
|
+const selectedIsPolicyLibrary = computed(() =>
|
|
|
+ isPolicyLibraryItem(selectedPolicyItem.value, getLibraryNameSet())
|
|
|
+);
|
|
|
+
|
|
|
+/**
|
|
|
+ * 构建惠企政策库列表(从 policies_public.v1.json)。
|
|
|
+ * 本轮匹配到的惠企政策置顶,其余保持库内原顺序。
|
|
|
+ */
|
|
|
+const buildLibraryPolicyList = () => {
|
|
|
+ const keyword = searchKeyword.value.trim().toLowerCase();
|
|
|
+ const dept = currentDeptFilter.value === "全部部门" ? "" : currentDeptFilter.value;
|
|
|
+ const matched = matchedLibraryNames.value;
|
|
|
+
|
|
|
+ const filtered = getLibraryPolicies().map(normalizePolicy).filter((item) => {
|
|
|
+ // 与卡片列表保持同一套筛选逻辑
|
|
|
+ if (dept && String(item.source || "") !== dept) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ const autoGranted = !!item.originalData?.auto_granted;
|
|
|
+ if (currentFilterOption.value === "只看免申即享" && !autoGranted) return false;
|
|
|
+ if (currentFilterOption.value === "非免申即享" && autoGranted) return false;
|
|
|
+ if (keyword) {
|
|
|
+ const haystack = [item.title, item.declaration_item, item.source, item.desc]
|
|
|
+ .join(" ")
|
|
|
+ .toLowerCase();
|
|
|
+ if (!haystack.includes(keyword)) return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 置顶:本轮匹配到的惠企政策在前,其余保持原顺序
|
|
|
+ const ordered = pinMatchedFirst(filtered, matched, (item) => item.title);
|
|
|
+ const start = (currentPage.value - 1) * itemsPerPage;
|
|
|
+ policyListItems.value = ordered.slice(start, start + itemsPerPage);
|
|
|
+ policyListTotal.value = ordered.length;
|
|
|
+};
|
|
|
+
|
|
|
const buildRecordPolicyList = () => {
|
|
|
const keyword = searchKeyword.value.trim().toLowerCase();
|
|
|
const dept = currentDeptFilter.value === "全部部门" ? "" : currentDeptFilter.value;
|
|
|
@@ -907,7 +978,13 @@ let policyListRequestId = 0;
|
|
|
const fetchPolicyList = async () => {
|
|
|
if (!props.recordMode || !panelVisible.value || panelMode.value !== "list") return;
|
|
|
|
|
|
- // 有卡片数据时一律本地筛选,保证“更多”与卡片同源同序
|
|
|
+ // 政策事项列表统一来自惠企政策库(policies_public.v1.json)
|
|
|
+ if (getLibraryPolicies().length) {
|
|
|
+ buildLibraryPolicyList();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 兜底:库还没加载完时,先列出本轮匹配到的卡片,避免列表空白
|
|
|
if (policies.value.length) {
|
|
|
buildRecordPolicyList();
|
|
|
return;
|
|
|
@@ -1113,7 +1190,14 @@ const openDetailByItem = async (item: { originalData?: RawPolicyItem; support_ob
|
|
|
emitPanelWidth();
|
|
|
};
|
|
|
|
|
|
-const openList = async () => {
|
|
|
+/**
|
|
|
+ * 打开「政策事项列表」。
|
|
|
+ *
|
|
|
+ * 卡片区「更多 >>」与惠企政策详情的「返回政策事项列表」**走同一条路径、同一个列表**——
|
|
|
+ * 不因入口不同而内容不同。列表数据统一来自惠企政策库(policies_public.v1.json),
|
|
|
+ * 本轮匹配到的惠企政策置顶。
|
|
|
+ */
|
|
|
+const openPolicyList = async () => {
|
|
|
selectedPolicyOverride.value = null;
|
|
|
panelMode.value = "list";
|
|
|
const openTicket = modalMutex?.requestOpen(policyModalId);
|