From 40d06c180fc398c02145b9a45c88313aae216436 Mon Sep 17 00:00:00 2001
From: "DESKTOP-RQ919RC\\Pc" <1300399510@qq.com>
Date: Thu, 20 Nov 2025 19:11:48 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE?=
=?UTF-8?q?=E5=BA=93=E7=BB=84=E4=BB=B6=E5=B9=B6=E4=BC=98=E5=8C=96=E6=90=9C?=
=?UTF-8?q?=E7=B4=A2=E9=A1=B5=E9=9D=A2=E6=A0=B7=E5=BC=8F=E5=92=8C=E5=8A=9F?=
=?UTF-8?q?=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
新增项目库组件item-project,包含学校、专业、学费等信息的展示
优化搜索页面样式,调整分类项宽度和间距
修复搜索页面滚动定位问题,完善分页和URL参数处理
添加本地开发服务器脚本serve.ps1
更新public.css和public.less样式文件
---
component/item-project/item-project.js | 91 ++++++++++++++
component/item-project/item-project.txt | 30 +++++
css/homepage-me.css | 2 +
css/homepage-me.less | 5 +-
css/homepage-other.css | 2 +
css/homepage-other.less | 2 +
css/public.css | 101 ++++++++++++++-
css/public.less | 120 +++++++++++++++++-
css/search.css | 3 +-
css/search.less | 3 +-
js/save.js | 2 +
js/search.js | 106 ++++++++++------
search.html | 159 ++++++++++++------------
serve.ps1 | 56 +++++++++
14 files changed, 558 insertions(+), 124 deletions(-)
create mode 100644 component/item-project/item-project.js
create mode 100644 component/item-project/item-project.txt
create mode 100644 serve.ps1
diff --git a/component/item-project/item-project.js b/component/item-project/item-project.js
new file mode 100644
index 0000000..79afab9
--- /dev/null
+++ b/component/item-project/item-project.js
@@ -0,0 +1,91 @@
+const { defineComponent, ref, onMounted } = Vue;
+
+export const itemProject = defineComponent({
+ name: "item-project",
+ props: {
+ itemdata: {
+ type: Object,
+ default: () => ({}),
+ },
+ },
+ setup(props) {
+ const formatNumberWithSpaces = (number) => {
+ if (Number(number) != number) return;
+ return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+ };
+
+ const judgmentClass = (name) => {
+ const redtag = redtagArr.value || ["26Fall", "26Spring"];
+ let classname = "";
+ if (redtag.includes(name)) classname = "gray semester";
+ else {
+ // 判断 字符串 是以 25/26/27... + Fall/Spring/Summer
+ const regex = /^(2[0-9]|3\d)(Fall|Spring|Summer)$/;
+ classname = regex.test(name) ? "gray" : "";
+ }
+ return {
+ name,
+ class: classname,
+ };
+ };
+
+ onMounted(() => {
+ checkWConfig();
+ });
+
+ let redtagArr = ref([]);
+
+ const checkWConfig = () => {
+ const wConfig = JSON.parse(localStorage.getItem("wConfig")) || {};
+ if (wConfig.time) {
+ const time = new Date(wConfig.time);
+ const now = new Date();
+ if (now - time > 24 * 60 * 60 * 1000) {
+ getWConfig();
+ monitorGetRedTag();
+ } else {
+ const config = wConfig.config || {};
+ redtagArr.value = config.redtag || [];
+ }
+ } else {
+ getWConfig();
+ monitorGetRedTag();
+ }
+ };
+
+ const monitorGetRedTag = () => {
+ let timer = setInterval(() => {
+ const wConfig = JSON.parse(localStorage.getItem("wConfig")) || {};
+ if (wConfig.time) {
+ const config = wConfig.config || {};
+ redtagArr.value = config.redtag;
+ clearInterval(timer);
+ }
+ }, 1000);
+ };
+
+ const getWConfig = () => {
+ if (window.wConfigloading) return;
+ window.wConfigloading = true;
+ ajaxGet("/v2/api/config/website").then((res) => {
+ if (res.code == 200) {
+ let data = res["data"] || {};
+ const config = data.config || {};
+ redtagArr.value = config.redtag;
+ data.time = new Date().toISOString();
+ localStorage.setItem("wConfig", JSON.stringify(data));
+ }
+ window.wConfigloading = false;
+ });
+ };
+
+ let item = ref({ ...props.itemdata });
+
+ item.value["tuition_fee_text"] = formatNumberWithSpaces(item.value.tuition_fee || "");
+
+ item.value["url"] = "https://program.gter.net/details/" + item.value.uniqid;
+
+ return { item };
+ },
+ template: `
`,
+});
diff --git a/component/item-project/item-project.txt b/component/item-project/item-project.txt
new file mode 100644
index 0000000..672dfbc
--- /dev/null
+++ b/component/item-project/item-project.txt
@@ -0,0 +1,30 @@
+
\ No newline at end of file
diff --git a/css/homepage-me.css b/css/homepage-me.css
index cc6e801..fb96fcf 100644
--- a/css/homepage-me.css
+++ b/css/homepage-me.css
@@ -17,6 +17,8 @@
padding-top: 39px;
padding-bottom: 38px;
margin-right: 20px;
+ position: sticky;
+ top: 10px;
}
#homepage-me .matter .card-user .avatar-box {
position: relative;
diff --git a/css/homepage-me.less b/css/homepage-me.less
index 7b80c47..c4e111e 100644
--- a/css/homepage-me.less
+++ b/css/homepage-me.less
@@ -16,6 +16,9 @@
padding-top: 39px;
padding-bottom: 38px;
margin-right: 20px;
+ position: sticky;
+ top: 10px;
+
.avatar-box {
position: relative;
margin-bottom: 20px;
@@ -165,7 +168,7 @@
a {
text-decoration: none;
}
-
+
.bi-url {
text-decoration: underline;
color: #04b0d5;
diff --git a/css/homepage-other.css b/css/homepage-other.css
index 4017d69..a99d61f 100644
--- a/css/homepage-other.css
+++ b/css/homepage-other.css
@@ -38,6 +38,8 @@
padding-top: 39px;
padding-bottom: 40px;
margin-right: 20px;
+ position: sticky;
+ top: 10px;
}
#homepage-other .matter .card-user .avatar {
width: 120px;
diff --git a/css/homepage-other.less b/css/homepage-other.less
index ae17d5f..a76e99f 100644
--- a/css/homepage-other.less
+++ b/css/homepage-other.less
@@ -40,6 +40,8 @@
padding-top: 39px;
padding-bottom: 40px;
margin-right: 20px;
+ position: sticky;
+ top: 10px;
.avatar {
width: 120px;
diff --git a/css/public.css b/css/public.css
index 64f0079..5759dbe 100644
--- a/css/public.css
+++ b/css/public.css
@@ -600,6 +600,103 @@ body {
.item-box.item-tenement .picture .picture-item:not(:last-child) {
margin-right: 10px;
}
+.item-box.item-project {
+ padding-bottom: 18px;
+}
+.item-box.item-project .school {
+ color: #333333;
+ font-size: 14px;
+ margin-top: 2px;
+ margin-bottom: 5px;
+}
+.item-box.item-project .school .icon {
+ width: 18px;
+ height: 20px;
+ margin-right: 8px;
+}
+.item-box.item-project .name {
+ word-break: break-word;
+ font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
+ font-weight: 650;
+ font-style: normal;
+ font-size: 20px;
+ color: #000000;
+ line-height: 36px;
+}
+.item-box.item-project .en {
+ font-size: 14px;
+ color: #7f7f7f;
+ margin-top: 4px;
+ word-break: break-all;
+}
+.item-box.item-project .introduce {
+ font-size: 14px;
+ color: #555555;
+ margin-top: 8px;
+ flex-wrap: wrap;
+}
+.item-box.item-project .introduce .line {
+ color: #d7d7d7;
+ margin: 0 8px;
+}
+.item-box.item-project .introduce .q {
+ font-family: "Arial", "Arial-Black", "Arial Black", sans-serif;
+ font-weight: 900;
+ color: #000000;
+ margin-left: 8px;
+}
+.item-box.item-project .word {
+ font-size: 14px;
+ color: #7f7f7f;
+ padding: 0 10px;
+ height: 46px;
+ line-height: 46px;
+ background-color: #f6f6f6;
+ border-radius: 10px;
+ margin-top: 10px;
+ word-break: break-all;
+ display: inherit;
+}
+.item-box.item-project .tag {
+ flex-wrap: wrap;
+}
+.item-box.item-project .tag .tag-item {
+ font-size: 14px;
+ color: #555555;
+ padding: 0 8px;
+ width: fit-content;
+ height: 24px;
+ line-height: 24px;
+ background-color: #f2f2f2;
+ border-radius: 6px;
+ margin-bottom: 10px;
+}
+.item-box.item-project .tag .tag-item.admissions {
+ background-color: #04b0d5;
+ border: none;
+ color: #fff;
+}
+.item-box.item-project .tag .tag-item.gray {
+ border: none;
+ color: #fff;
+ background-color: #333333;
+}
+.item-box.item-project .tag .tag-item.gray.semester {
+ background-color: #f95d5d;
+}
+.item-box.item-project .tag .tag-item:not(:last-child) {
+ margin-right: 10px;
+}
+.item-box.item-project .tag .tag-item.blue {
+ color: #ffffff;
+ background-color: #04b0d5;
+}
+.item-box.item-project .tag .tag-item.icon {
+ height: 24px;
+ width: 94px;
+ padding: 0;
+ margin-right: 10px;
+}
.item-box .comment {
height: 40px;
background-color: #f6f6f6;
@@ -1123,7 +1220,7 @@ body {
margin-right: 10px;
}
.side-box.newest-side-box .box .item .dot.dot-green {
- background-image: url(/img/dot-green.svg);
+ background-image: url(../img/dot-green.svg);
background-repeat: no-repeat;
}
.side-box.newest-side-box .box .item .text {
@@ -1158,7 +1255,7 @@ body {
width: 6px;
height: 6px;
margin-right: 10px;
- background-image: url(/img/dot-blue.svg);
+ background-image: url(../img/dot-blue.svg);
background-repeat: no-repeat;
}
.side-box.essence-side-box .box .item .text {
diff --git a/css/public.less b/css/public.less
index f4eb389..838a817 100644
--- a/css/public.less
+++ b/css/public.less
@@ -728,6 +728,119 @@ body {
}
}
+ &.item-project {
+ padding-bottom: 18px;
+ .school {
+ .icon {
+ width: 18px;
+ height: 20px;
+ margin-right: 8px;
+ }
+
+ color: #333333;
+ font-size: 14px;
+ margin-top: 2px;
+ margin-bottom: 5px;
+ }
+
+ .name {
+ word-break: break-word;
+ font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
+ font-weight: 650;
+ font-style: normal;
+ font-size: 20px;
+ color: #000000;
+ line-height: 36px;
+ }
+
+ .en {
+ font-size: 14px;
+ color: #7f7f7f;
+ margin-top: 4px;
+ word-break: break-all;
+ }
+
+ .introduce {
+ font-size: 14px;
+ color: #555555;
+ margin-top: 8px;
+ flex-wrap: wrap;
+
+ .line {
+ color: #d7d7d7;
+ margin: 0 8px;
+ }
+
+ .q {
+ font-family: "Arial", "Arial-Black", "Arial Black", sans-serif;
+ font-weight: 900;
+ color: #000000;
+ margin-left: 8px;
+ }
+ }
+
+ .word {
+ font-size: 14px;
+ color: #7f7f7f;
+ padding: 0 10px;
+ height: 46px;
+ line-height: 46px;
+ background-color: #f6f6f6;
+ border-radius: 10px;
+ margin-top: 10px;
+ word-break: break-all;
+ display: inherit;
+ }
+
+ .tag {
+ flex-wrap: wrap;
+
+ .tag-item {
+ &.admissions {
+ background-color: #04b0d5;
+ border: none;
+ color: #fff;
+ }
+
+ &.gray {
+ border: none;
+ color: #fff;
+ background-color: rgba(51, 51, 51, 1);
+
+ &.semester {
+ background-color: #f95d5d;
+ }
+ }
+
+ font-size: 14px;
+ color: #555555;
+ padding: 0 8px;
+ width: fit-content;
+ height: 24px;
+ line-height: 24px;
+ background-color: #f2f2f2;
+ border-radius: 6px;
+ margin-bottom: 10px;
+
+ &:not(:last-child) {
+ margin-right: 10px;
+ }
+
+ &.blue {
+ color: #ffffff;
+ background-color: #04b0d5;
+ }
+
+ &.icon {
+ height: 24px;
+ width: 94px;
+ padding: 0;
+ margin-right: 10px;
+ }
+ }
+ }
+ }
+
.comment {
height: 40px;
background-color: rgba(246, 246, 246, 1);
@@ -1360,7 +1473,7 @@ body {
}
.side-box.newest-side-box .box .item .dot.dot-green {
- background-image: url(/img/dot-green.svg);
+ background-image: url(../img/dot-green.svg);
background-repeat: no-repeat;
}
@@ -1403,7 +1516,7 @@ body {
width: 6px;
height: 6px;
margin-right: 10px;
- background-image: url(/img/dot-blue.svg);
+ background-image: url(../img/dot-blue.svg);
background-repeat: no-repeat;
}
@@ -1831,7 +1944,6 @@ body {
}
}
-
.input {
border: none;
outline: none;
@@ -2160,4 +2272,4 @@ td {
100% {
transform: rotate(360deg);
}
-}
\ No newline at end of file
+}
diff --git a/css/search.css b/css/search.css
index c40eae3..a3407e3 100644
--- a/css/search.css
+++ b/css/search.css
@@ -25,7 +25,8 @@
margin-bottom: 16px;
}
#search .classify .item {
- width: 50px;
+ min-width: 50px;
+ padding: 0 8px;
height: 32px;
line-height: 32px;
text-align: center;
diff --git a/css/search.less b/css/search.less
index 92e1922..55a378b 100644
--- a/css/search.less
+++ b/css/search.less
@@ -27,7 +27,8 @@
.classify {
margin-bottom: 16px;
.item {
- width: 50px;
+ min-width: 50px;
+ padding: 0 8px;
height: 32px;
line-height: 32px;
text-align: center;
diff --git a/js/save.js b/js/save.js
index 91ce106..3ca5e83 100644
--- a/js/save.js
+++ b/js/save.js
@@ -29,6 +29,8 @@ const watchList = {
"../component/hot-tag/hot-tag.txt": "../component/hot-tag/hot-tag.js",
// 监听 hot-search.txt,同步到 hot-search.js
"../component/hot-search/hot-search.txt": "../component/hot-search/hot-search.js",
+ // 监听 item-project.txt,同步到 item-project.js
+ "../component/item-project/item-project.txt": "../component/item-project/item-project.js",
// 监听 bi.txt,同步到 bi.js
"../component/bi/bi.txt": "../component/bi/bi.js",
diff --git a/js/search.js b/js/search.js
index 6c6d6de..180be59 100644
--- a/js/search.js
+++ b/js/search.js
@@ -1,16 +1,17 @@
const { createApp, ref, onMounted, nextTick, onUnmounted, computed, watch, provide } = Vue;
-import { itemForum } from "/component/item-forum/item-forum.js";
-import { itemOffer } from "/component/item-offer/item-offer.js";
-import { itemSummary } from "/component/item-summary/item-summary.js";
-import { itemVote } from "/component/item-vote/item-vote.js";
-import { itemMj } from "/component/item-mj/item-mj.js";
-import { itemTenement } from "/component/item-tenement/item-tenement.js";
-import { headTop } from "/component/head-top/head-top.js";
-import { hotTag } from "/component/hot-tag/hot-tag.js";
-import { hotSearch } from "/component/hot-search/hot-search.js";
-import { slideshowBox } from "/component/slideshow-box/slideshow-box.js";
-import { latestList } from "/component/latest-list/latest-list.js";
-import { loadBox } from "/component/load-box/load-box.js";
+import { itemForum } from "../component/item-forum/item-forum.js";
+import { itemOffer } from "../component/item-offer/item-offer.js";
+import { itemSummary } from "../component/item-summary/item-summary.js";
+import { itemVote } from "../component/item-vote/item-vote.js";
+import { itemMj } from "../component/item-mj/item-mj.js";
+import { itemTenement } from "../component/item-tenement/item-tenement.js";
+import { itemProject } from "../component/item-project/item-project.js";
+import { headTop } from "../component/head-top/head-top.js";
+import { hotTag } from "../component/hot-tag/hot-tag.js";
+import { hotSearch } from "../component/hot-search/hot-search.js";
+import { slideshowBox } from "../component/slideshow-box/slideshow-box.js";
+import { latestList } from "../component/latest-list/latest-list.js";
+import { loadBox } from "../component/load-box/load-box.js";
const appSearch = createApp({
setup() {
@@ -18,7 +19,8 @@ const appSearch = createApp({
let typeValue = ref(null);
let kw = ref("");
onMounted(() => {
- // const params = getUrlParams();
+ const params = getUrlParams();
+ console.log("params", params);
// kw.value = params.kw || "";
// const urlObj = new URL(location.href);
// const pathParts = urlObj.pathname.split("/").filter((part) => part);
@@ -26,9 +28,11 @@ const appSearch = createApp({
kw.value = kwValue.value.innerText;
const tab = typeValue.value.innerText;
if (tab) tabValue.value = tab;
+ if (params.page) page.value = params.page;
+ else page.value = 1;
- page.value = 1;
- getList();
+ if (kw.value) getList();
+ else page.value = null;
getUserInfoWin();
@@ -125,6 +129,8 @@ const appSearch = createApp({
if (loading.value || page.value == null) return;
loading.value = true;
const limit = 20;
+ window.scrollTo(0, 0);
+ updateUrlParams({ page: page.value });
ajaxGet(`/v2/api/forum/topicLists?type=${tabValue.value == "all" ? "" : tabValue.value}&page=${page.value}&limit=${limit}&keyword=${kw.value}`)
.then((res) => {
if (res.code != 200) {
@@ -133,6 +139,38 @@ const appSearch = createApp({
}
let data = res.data;
+ data.data.unshift({
+ id: 20,
+ program_en: "Master of Laws in Arbitration and Dispute Resolution",
+ program_zh: "法学硕士(仲裁及争议解决学)",
+ program_abbr: "LLMARBDR",
+ program_code: "P41",
+ award_en: "Master of Laws in Arbitration and Dispute Resolution",
+ award_zh: "法学硕士(仲裁及争议解决学)",
+ subject_area_id: 9,
+ subject_area_name: "Law",
+ primary_university: "City University of Hong Kong",
+ primary_university_id: 3,
+ status: "ACTIVE",
+ intake_year: 2026,
+ disciplineid: 9,
+ distinctive: "毕业生可参与:当事人、辩护人、专家、仲裁员和调解员",
+ rank: "42",
+ department: "法律学院",
+ admissionsproject: "1",
+ departmentid: 26,
+ schoolalias: "城大",
+ schoolname: "香港城市大学",
+ tags: ["有奖学金", "论文课程", "26fall 提前批", "Top 50", "专业资格认证"],
+ schoolenname: "City University of Hong Kong",
+ intake_month: 9,
+ schoolid: 311,
+ tuition_fee: null,
+ uniqid: "tf1yFYMER8-1bY1t5oLbKaNc2FVhOWM0",
+ type: "programs",
+ schoollogo: "https://oss.x-php.com/school/J6BSwE-VfCFkCb1SBaR7ec6NYmTA4pRcOalNHJRfNzUxNg~~",
+ });
+
list.value = data.data;
if (list.value.length == 0) page.value = null;
@@ -141,7 +179,14 @@ const appSearch = createApp({
maxPage.value = Math.ceil(count.value / limit);
pagination.value = calculatePagination(page.value, maxPage.value);
- updateUrlLastPath(kw.value);
+ let url = `/search/${kw.value}`;
+
+ const hostname = location.hostname;
+ const localHostReg = /^(localhost|127\.0\.0\.1|\[::1\])$/;
+ if (localHostReg.test(hostname)) url = `/search.html`;
+
+ updateUrlLastPath(url);
+ removeQueryQ();
})
.catch((err) => {
err = err.data;
@@ -227,29 +272,13 @@ const appSearch = createApp({
const sidebarFixed = ref(false);
const matterFixed = ref(false);
+ const matterBottom = ref(false);
const handleScroll = () => {
-
- matterHeight.value = -(document.querySelector(".matter-content").offsetHeight - window.innerHeight);
- sidebarHeight.value = -(document.querySelector(".sidebar-box").offsetHeight - window.innerHeight);
-
- // const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
- // const clientHeight = window.innerHeight;
-
- // const sideHeight = sidebarRef.value.offsetHeight;
- // const matterTop = matterRef.value.offsetTop;
- // const matterHeight = matterContentRef.value.offsetHeight;
-
- // console.log("sideHeight", sideHeight);
- // console.log("matterHeight", matterHeight);
- // if (sideHeight < matterHeight) {
- // // 侧边栏滚动固定
- // if (scrollTop >= matterTop + sideHeight - clientHeight) sidebarFixed.value = true;
- // else sidebarFixed.value = false;
- // } else {
- // if (scrollTop >= matterTop + matterHeight - clientHeight) matterFixed.value = true;
- // else matterFixed.value = false;
- // }
+ matterHeight.value = -(matterContentRef.value.offsetHeight - window.innerHeight);
+ sidebarHeight.value = -(sidebarRef.value.offsetHeight - window.innerHeight);
+ if (matterHeight.value > 0) matterHeight.value = 12;
+ if (sidebarHeight.value > 0) sidebarHeight.value = 12;
};
const matterRef = ref(null);
@@ -259,7 +288,7 @@ const appSearch = createApp({
let sidebarHeight = ref(0);
let matterHeight = ref(0);
- return { matterHeight, sidebarHeight, matterFixed, matterContentRef, sidebarFixed, matterRef, sidebarRef, loading, typeValue, kwValue, startSearch, kw, maxPage, prevPage, nextPage, tabValue, cutTab, tabList, count, list, page, pagination, cutPage };
+ return { matterHeight, sidebarHeight, matterBottom, matterFixed, matterContentRef, sidebarFixed, matterRef, sidebarRef, loading, typeValue, kwValue, startSearch, kw, maxPage, prevPage, nextPage, tabValue, cutTab, tabList, count, list, page, pagination, cutPage };
},
});
appSearch.component("item-forum", itemForum);
@@ -268,6 +297,7 @@ appSearch.component("itemSummary", itemSummary);
appSearch.component("itemVote", itemVote);
appSearch.component("itemMj", itemMj);
appSearch.component("itemTenement", itemTenement);
+appSearch.component("itemProject", itemProject);
appSearch.component("head-top", headTop);
appSearch.component("hot-tag", hotTag);
appSearch.component("hot-search", hotSearch);
diff --git a/search.html b/search.html
index 8dd078a..1b5831c 100644
--- a/search.html
+++ b/search.html
@@ -1,84 +1,89 @@
-
-
-
- 搜索
-
-
-
-
-
-
-
-
香港
-
+
+
+
+
搜索
+
+
+
+
+
-
-

-
-
-
-
-

-
+
+
+
香港
+
-
-
-
- {{ tabList[tabValue] }}
-
- 共
-
{{ count }}
- 条
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

-
- 暂无内容 -
-
-
-
-

-

-
-
{{ item }}
-
-

-

-
-
-
-
+
+

+
+
+
-
-
-
-
-
+
+
+
+ {{ tabList[tabValue] }}
+
+ 共
+
{{ count }}
+ 条
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
- 暂无内容 -
+
+
+
+

+

+
+
{{ item }}
+
+

+

+
+
+
+
+
+
+
+
+
+
+
+