feat: 添加项目库组件并优化搜索页面样式和功能
新增项目库组件item-project,包含学校、专业、学费等信息的展示 优化搜索页面样式,调整分类项宽度和间距 修复搜索页面滚动定位问题,完善分页和URL参数处理 添加本地开发服务器脚本serve.ps1 更新public.css和public.less样式文件
This commit is contained in:
91
component/item-project/item-project.js
Normal file
91
component/item-project/item-project.js
Normal file
@@ -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: `<div class="item-box item-project"> <div class="tag flexflex"> <img class="tag-item icon" v-if="item.admissionsproject" src="https://app.gter.net/image/miniApp/offer/admissions-icon.png" /> <a class="tag-item blue" href="https://program.gter.net/" target="_blank">港校项目库</a> <div class="tag-item" :class="item.class" v-for="(tag, index) in item.tags" :key="index">{{ tag.name || tag }} </div> </div> <a class="school flexacenter" :href="item.url" target="_blank"> <img class="icon" v-if="item.schoollogo" :src="item.schoollogo" /> <span class="flex1">{{ item.schoolname }}</span> </a> <a class="name flexacenter" :href="item.url" target="_blank">{{ item.name_zh || item.program_zh }}</a> <a class="en flexacenter" :href="item.url" target="_blank">{{ item.name_en || item.program_en }}</a> <a class="introduce flexacenter" :href="item.url" target="_blank"> <span>{{ item.department }}</span> <div class="flexacenter" v-if="item.rank"> <div class="line" v-if="item.department">|</div> 专业排名 <div class="q">{{ item.rank }}</div> </div> <div class="flexacenter" v-if="item.tuition_fee"> <div class="line" v-if="item.department || item.rank">|</div> 学费HK$ <div class="q">{{ item.tuition_fee_text }}</div> </div> </a> <a class="word flexacenter one-line-display" v-if="item.distinctive" :href="item.url" target="_blank">{{ item.distinctive }}</a></div>`,
|
||||
});
|
||||
30
component/item-project/item-project.txt
Normal file
30
component/item-project/item-project.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
<div class="item-box item-project">
|
||||
<div class="tag flexflex">
|
||||
<img class="tag-item icon" v-if="item.admissionsproject" src="https://app.gter.net/image/miniApp/offer/admissions-icon.png" />
|
||||
<a class="tag-item blue" href="https://program.gter.net/" target="_blank">港校项目库</a>
|
||||
<div class="tag-item" :class="item.class" v-for="(tag, index) in item.tags" :key="index">{{ tag.name || tag }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="school flexacenter" :href="item.url" target="_blank">
|
||||
<img class="icon" v-if="item.schoollogo" :src="item.schoollogo" />
|
||||
<span class="flex1">{{ item.schoolname }}</span>
|
||||
</a>
|
||||
|
||||
<a class="name flexacenter" :href="item.url" target="_blank">{{ item.name_zh || item.program_zh }}</a>
|
||||
<a class="en flexacenter" :href="item.url" target="_blank">{{ item.name_en || item.program_en }}</a>
|
||||
<a class="introduce flexacenter" :href="item.url" target="_blank">
|
||||
<span>{{ item.department }}</span>
|
||||
<div class="flexacenter" v-if="item.rank">
|
||||
<div class="line" v-if="item.department">|</div>
|
||||
专业排名 <div class="q">{{ item.rank }}</div>
|
||||
</div>
|
||||
<div class="flexacenter" v-if="item.tuition_fee">
|
||||
<div class="line" v-if="item.department || item.rank">|</div>
|
||||
学费HK$ <div class="q">{{ item.tuition_fee_text }}</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a class="word flexacenter one-line-display" v-if="item.distinctive" :href="item.url" target="_blank">{{
|
||||
item.distinctive }}</a>
|
||||
</div>
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
padding-top: 39px;
|
||||
padding-bottom: 40px;
|
||||
margin-right: 20px;
|
||||
position: sticky;
|
||||
top: 10px;
|
||||
|
||||
.avatar {
|
||||
width: 120px;
|
||||
|
||||
101
css/public.css
101
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 {
|
||||
|
||||
120
css/public.less
120
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
106
js/search.js
106
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);
|
||||
|
||||
159
search.html
159
search.html
@@ -1,84 +1,89 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>搜索</title>
|
||||
<link rel="stylesheet" href="./css/public.css" />
|
||||
<link rel="stylesheet" href="./css/search.css" />
|
||||
<script src="./js/vue.global.js"></script>
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container" id="search" v-cloak>
|
||||
<div class="templateValue" ref="kwValue">香港</div>
|
||||
<div class="templateValue" ref="typeValue"></div>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>搜索</title>
|
||||
<link rel="stylesheet" href="./css/public.css" />
|
||||
<link rel="stylesheet" href="./css/search.css" />
|
||||
<script src="./js/vue.global.js"></script>
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<div class="head-top flexacenter">
|
||||
<img class="logo" src="https://oss.gter.net/logo" alt="" />
|
||||
<div class="flex1"></div>
|
||||
</div>
|
||||
<div class="search-box flexacenter">
|
||||
<input class="search-input flex1" placeholder="请输入搜索关键词" v-model="kw" @keyup.enter="startSearch" />
|
||||
<img class="search-icon" src="./img/search-icon.svg" alt="" @click="startSearch" />
|
||||
</div>
|
||||
<body>
|
||||
<div class="container" id="search" v-cloak>
|
||||
<div class="templateValue" ref="kwValue">香港</div>
|
||||
<div class="templateValue" ref="typeValue"></div>
|
||||
|
||||
<div class="classify flexacenter">
|
||||
<div class="item" :class="{'pitch': key == tabValue}" v-for="(item, key) in tabList" :key="key" @click="cutTab(key)">{{ item }}</div>
|
||||
</div>
|
||||
|
||||
<div class="quantity flexacenter">
|
||||
{{ tabList[tabValue] }}
|
||||
<div class="line"></div>
|
||||
共
|
||||
<div class="num">{{ count }}</div>
|
||||
条
|
||||
</div>
|
||||
|
||||
<div class="matter flexflex">
|
||||
<div class="matter-content flex1" :style="{'top': matterHeight + 'px'}">
|
||||
<div class="list-box" v-if="list.length != 0">
|
||||
<template v-for="(item,index) in list" :key="index">
|
||||
<item-offer v-if=" item.type == 'offer'" :itemdata="item"></item-offer>
|
||||
<item-summary v-else-if="item.type == 'offer_summary'" :itemdata="item"></item-summary>
|
||||
<item-vote v-else-if="item.type == 'vote'" :itemdata="item"></item-vote>
|
||||
<item-mj v-else-if="item.type == 'interviewexperience'" :itemdata="item"></item-mj>
|
||||
<item-tenement v-else-if="item.type == 'tenement'" :itemdata="item"></item-tenement>
|
||||
<item-forum v-else :itemdata="item"></item-forum>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="list.length == 0 && page == null" class="empty flexcenter">
|
||||
<img class="empty-icon" src="./img/empty-icon.png" />
|
||||
<div class="empty-text">- 暂无内容 -</div>
|
||||
</div>
|
||||
|
||||
<div class="pages-box flexcenter" v-if="pagination.length != 0">
|
||||
<img v-if="page == 1" class="arrows" src="./img/arrows-gray-simple.svg" alt="" />
|
||||
<img @click="prevPage" v-else class="arrows rotate180" src="./img/arrows-gray-deep.svg" alt="" />
|
||||
|
||||
<div class="item" :class="{'pitch': item == page }" v-for="(item, index) in pagination" @click="cutPage(item)">{{ item }}</div>
|
||||
|
||||
<img v-if="page == maxPage" class="arrows rotate180" src="./img/arrows-gray-simple.svg" alt="" />
|
||||
<img @click="nextPage" v-else v-else class="arrows" src="./img/arrows-gray-deep.svg" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-box" :style="{'top': sidebarHeight + 'px'}">
|
||||
<hot-search></hot-search>
|
||||
<hot-tag></hot-tag>
|
||||
<slideshow-box></slideshow-box>
|
||||
<latest-list></latest-list>
|
||||
</div>
|
||||
</div>
|
||||
<div class="head-top flexacenter">
|
||||
<img class="logo" src="https://oss.gter.net/logo" alt="" />
|
||||
<div class="flex1"></div>
|
||||
</div>
|
||||
<div class="search-box flexacenter">
|
||||
<input class="search-input flex1" placeholder="请输入搜索关键词" v-model="kw" @keyup.enter="startSearch" />
|
||||
<img class="search-icon" src="./img/search-icon.svg" alt="" @click="startSearch" />
|
||||
</div>
|
||||
|
||||
<script src="./js/axios.min.js"></script>
|
||||
<script src="./js/public.js"></script>
|
||||
<script type="module" src="./js/search.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<div class="classify flexacenter">
|
||||
<div class="item" :class="{'pitch': key == tabValue}" v-for="(item, key) in tabList" :key="key"
|
||||
@click="cutTab(key)">{{ item }}</div>
|
||||
</div>
|
||||
|
||||
<div class="quantity flexacenter">
|
||||
{{ tabList[tabValue] }}
|
||||
<div class="line"></div>
|
||||
共
|
||||
<div class="num">{{ count }}</div>
|
||||
条
|
||||
</div>
|
||||
|
||||
<div class="matter flexflex">
|
||||
<div class="matter-content flex1" :style="{'top': matterHeight + 'px'}">
|
||||
<div class="list-box" v-if="list.length != 0">
|
||||
<template v-for="(item,index) in list" :key="index">
|
||||
<item-project v-if="item.type == 'programs'" :itemdata="item"></item-project>
|
||||
<item-offer v-else-if="item.type == 'offer'" :itemdata="item"></item-offer>
|
||||
<item-summary v-else-if="item.type == 'offer_summary'" :itemdata="item"></item-summary>
|
||||
<item-vote v-else-if="item.type == 'vote'" :itemdata="item"></item-vote>
|
||||
<item-mj v-else-if="item.type == 'interviewexperience'" :itemdata="item"></item-mj>
|
||||
<item-tenement v-else-if="item.type == 'tenement'" :itemdata="item"></item-tenement>
|
||||
<item-forum v-else :itemdata="item"></item-forum>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="list.length == 0 && page == null" class="empty flexcenter">
|
||||
<img class="empty-icon" src="./img/empty-icon.png" />
|
||||
<div class="empty-text">- 暂无内容 -</div>
|
||||
</div>
|
||||
|
||||
<div class="pages-box flexcenter" v-if="pagination.length != 0">
|
||||
<img v-if="page == 1" class="arrows" src="./img/arrows-gray-simple.svg" alt="" />
|
||||
<img @click="prevPage" v-else class="arrows rotate180" src="./img/arrows-gray-deep.svg" alt="" />
|
||||
|
||||
<div class="item" :class="{'pitch': item == page }" v-for="(item, index) in pagination"
|
||||
@click="cutPage(item)">{{ item }}</div>
|
||||
|
||||
<img v-if="page == maxPage" class="arrows rotate180" src="./img/arrows-gray-simple.svg" alt="" />
|
||||
<img @click="nextPage" v-else v-else class="arrows" src="./img/arrows-gray-deep.svg" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-box" :style="{'top': sidebarHeight + 'px'}">
|
||||
<hot-search></hot-search>
|
||||
<hot-tag></hot-tag>
|
||||
<slideshow-box></slideshow-box>
|
||||
<latest-list></latest-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="./js/axios.min.js"></script>
|
||||
<script src="./js/public.js"></script>
|
||||
<script type="module" src="./js/search.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
56
serve.ps1
Normal file
56
serve.ps1
Normal file
@@ -0,0 +1,56 @@
|
||||
$base = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 5501)
|
||||
$listener.Start()
|
||||
Write-Host "http://127.0.0.1:5501/"
|
||||
while ($true) {
|
||||
$client = $listener.AcceptTcpClient()
|
||||
try {
|
||||
$stream = $client.GetStream()
|
||||
$reader = New-Object System.IO.StreamReader($stream, [System.Text.Encoding]::ASCII, $false, 1024, $true)
|
||||
$writer = New-Object System.IO.StreamWriter($stream)
|
||||
$writer.AutoFlush = $true
|
||||
$requestLine = $reader.ReadLine()
|
||||
if (-not $requestLine) { $client.Close(); continue }
|
||||
while ($true) { $line = $reader.ReadLine(); if ($line -eq $null -or $line -eq "") { break } }
|
||||
$rawPath = "/"
|
||||
if ($requestLine -match "^GET\s+([^\s]+)") { $rawPath = $matches[1] }
|
||||
$pathOnly = $rawPath.Split("?")[0]
|
||||
$pathOnly = [System.Uri]::UnescapeDataString($pathOnly)
|
||||
$reqExt = [System.IO.Path]::GetExtension($pathOnly)
|
||||
if ($pathOnly -like "/search/*" -and [string]::IsNullOrEmpty($reqExt)) { $file = Join-Path $base "search.html" }
|
||||
elseif ($pathOnly -eq "/") { $file = Join-Path $base "index.html" }
|
||||
else { $rel = $pathOnly.TrimStart("/"); $file = Join-Path $base $rel }
|
||||
if (-not (Test-Path $file)) {
|
||||
$status = "404 Not Found"
|
||||
$body = [System.Text.Encoding]::UTF8.GetBytes("Not Found")
|
||||
$contentType = "text/plain; charset=utf-8"
|
||||
}
|
||||
else {
|
||||
$status = "200 OK"
|
||||
$body = [System.IO.File]::ReadAllBytes($file)
|
||||
$ext = [System.IO.Path]::GetExtension($file).ToLower()
|
||||
switch ($ext) {
|
||||
".html" { $contentType = "text/html; charset=utf-8" }
|
||||
".css" { $contentType = "text/css" }
|
||||
".js" { $contentType = "application/javascript" }
|
||||
".png" { $contentType = "image/png" }
|
||||
".jpg" { $contentType = "image/jpeg" }
|
||||
".jpeg" { $contentType = "image/jpeg" }
|
||||
".svg" { $contentType = "image/svg+xml" }
|
||||
".json" { $contentType = "application/json" }
|
||||
".ico" { $contentType = "image/x-icon" }
|
||||
default { $contentType = "application/octet-stream" }
|
||||
}
|
||||
}
|
||||
$writer.WriteLine("HTTP/1.1 $status")
|
||||
$writer.WriteLine("Content-Type: $contentType")
|
||||
$writer.WriteLine("Content-Length: " + $body.Length)
|
||||
$writer.WriteLine("Connection: close")
|
||||
$writer.WriteLine("")
|
||||
$stream.Write($body, 0, $body.Length)
|
||||
} catch {
|
||||
try { $client.Close() } catch {}
|
||||
} finally {
|
||||
try { $client.Close() } catch {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user