style: 修改服务中心样式

This commit is contained in:
zheng020 2026-04-30 01:14:07 +08:00
parent cfeb920294
commit f8ff99786d
14 changed files with 926 additions and 81200 deletions

View File

@ -10,7 +10,8 @@ const path = require('path');
const fs = require('fs'); const fs = require('fs');
const ROOT = __dirname; const ROOT = __dirname;
const WEBS = ['txw-gxzx-web', 'txw-kxtfwzx-web', 'txw-mhzc-web', 'txw-tzzx-web', 'txw-ytzx-web', 'txw-yygl-web']; // const WEBS = ['txw-gxzx-web', 'txw-kxtfwzx-web', 'txw-mhzc-web', 'txw-tzzx-web', 'txw-ytzx-web', 'txw-yygl-web'];
const WEBS = ['txw-mhzc-web'];
const LOCAL_PKGS = [ const LOCAL_PKGS = [
'local-nodemodules/@cssyq/ggzc-web', 'local-nodemodules/@cssyq/ggzc-web',
'local-nodemodules/@gt4/common-front', 'local-nodemodules/@gt4/common-front',
@ -30,7 +31,7 @@ function run(cmd, opts = {}) {
console.log('=== 碳信网 Web 项目初始化 ===\n'); console.log('=== 碳信网 Web 项目初始化 ===\n');
for (const web of WEBS) { for (const web of WEBS) {
const webPath = path.join(ROOT, web); const webPath = path.join(ROOT, '..', web);
if (!fs.existsSync(webPath)) { if (!fs.existsSync(webPath)) {
console.warn(`[跳过] ${web} 目录不存在`); console.warn(`[跳过] ${web} 目录不存在`);
continue; continue;
@ -42,8 +43,10 @@ for (const web of WEBS) {
const pkgName = localPkg.split('/').pop(); const pkgName = localPkg.split('/').pop();
const scope = localPkg.split('/')[1].slice(1); // remove '@' const scope = localPkg.split('/')[1].slice(1); // remove '@'
const scopeDir = path.join(webPath, 'node_modules', `@${scope}`); const scopeDir = path.join(webPath, 'node_modules', `@${scope}`);
const linkTarget = path.join(ROOT, localPkg); // 创建 symlink
const linkPath = path.join(scopeDir, pkgName); const cssyqDir = path.join(webPath, 'node_modules/@cssyq');
const linkTarget = path.join(ROOT, '..', localPkg);
const linkPath = path.join(cssyqDir, 'ggzc-web');
fs.mkdirSync(scopeDir, { recursive: true }); fs.mkdirSync(scopeDir, { recursive: true });
if (fs.existsSync(linkPath)) { if (fs.existsSync(linkPath)) {
@ -73,6 +76,18 @@ for (const web of WEBS) {
console.log(` 链接 @${scope}/${pkgName} -> ${localPkg}`); console.log(` 链接 @${scope}/${pkgName} -> ${localPkg}`);
} }
// 修改 package.json 的 resolutions强制使用本地包
const pkgJsonPath = path.join(webPath, 'package.json');
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
pkgJson.resolutions = pkgJson.resolutions || {};
for (const localPkg of LOCAL_PKGS) {
const pkgName = localPkg.split('/').pop();
const localPath = path.join(ROOT, '..', localPkg);
pkgJson.resolutions[pkgName] = `link:${localPath}`;
}
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
console.log(` 已添加 resolutions`);
// 安装依赖 // 安装依赖
console.log(' 安装依赖...'); console.log(' 安装依赖...');
run(`cd "${webPath}" && yarn install --prefer-offline --ignore-engines`); run(`cd "${webPath}" && yarn install --prefer-offline --ignore-engines`);

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
registry=http://10.23.10.90:4873/
strict-ssl=false

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,6 @@
"@vue/babel-preset-jsx": "1.4.0" "@vue/babel-preset-jsx": "1.4.0"
}, },
"dependencies": { "dependencies": {
"@cssyq/ggzc-web": "^1.0.7",
"@fortawesome/fontawesome-free": "^7.0.1", "@fortawesome/fontawesome-free": "^7.0.1",
"@gt4/common-front": "2.0.113", "@gt4/common-front": "2.0.113",
"@gtff/tdesign-gt-vue": "1.4.0", "@gtff/tdesign-gt-vue": "1.4.0",
@ -100,5 +99,10 @@
"main": "index.js", "main": "index.js",
"repository": "http://10.23.12.27:8089/qyd-znsb/znsb-mhzc.git", "repository": "http://10.23.12.27:8089/qyd-znsb/znsb-mhzc.git",
"author": "wangjianxin@css.com.cn <wangjianxin@css.com.cn>", "author": "wangjianxin@css.com.cn <wangjianxin@css.com.cn>",
"license": "MIT" "license": "MIT",
} "resolutions": {
"ggzc-web": "link:D:\\shanghai\\txw2\\local-nodemodules\\@cssyq\\ggzc-web",
"common-front": "link:D:\\shanghai\\txw2\\local-nodemodules\\@gt4\\common-front",
"tdesign-gt-vue": "link:D:\\shanghai\\txw2\\local-nodemodules\\@gtff\\tdesign-gt-vue"
}
}

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.00008 4.33337L3.66675 6.66671V14.6667" stroke="#D1D9D5" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 1.33337L10.3333 3.66671V8.00004L12.6667 9.66671V14.6667H7V1.33337Z" fill="#D1D9D5" stroke="#D1D9D5" stroke-width="0.666667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.33325 14.6666H14.6666" stroke="#D1D9D5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 546 B

View File

@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.66659 4L1.33325 2V12L5.66659 14L10.3333 12L14.6666 14V4L10.3333 2L5.66659 4Z" fill="#D1D9D5" stroke="#D1D9D5" stroke-width="0.666667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.3333 2V12" stroke="white" stroke-width="0.666667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.66675 4V14" stroke="white" stroke-width="0.666667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.5 3L5.66667 4L10.3333 2L12.5 3" stroke="#D1D9D5" stroke-width="0.666667" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.5 13L5.66667 14L10.3333 12L12.5 13" stroke="#D1D9D5" stroke-width="0.666667" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 794 B

View File

@ -1,28 +1,87 @@
<template> <template>
<div class="fwsc-container"> <div class="fwsc-container">
<!-- 面包屑导航 --> <!-- 面包屑导航 -->
<BreadcrumbNav currentPage="碳服务市场" /> <!-- <BreadcrumbNav currentPage="碳服务市场" /> -->
<!-- 二级菜单 --> <!-- 二级菜单 -->
<div class="secondary-nav"> <div class="secondary-nav">
<div class="secondary-nav-content"> <div class="secondary-nav-content">
<div class="nav-tabs"> <div class="nav-tabs">
<button <button v-for="tab in navTabs" :key="tab.path" :class="['nav-tab', { active: isActiveTab(tab.path) }]"
v-for="tab in navTabs" @click="goToTab(tab.path)">
:key="tab.path"
:class="['nav-tab', { active: isActiveTab(tab.path) }]"
@click="goToTab(tab.path)"
>
{{ tab.label }} {{ tab.label }}
</button> </button>
</div> </div>
<button class="publish-btn" @click="handlePublish">免费发布服务</button> <div class="nav-right">
<span class="list-count"> {{ page.total }} 条服务</span>
<button class="publish-btn" @click="handlePublish">免费发布服务</button>
</div>
</div> </div>
</div> </div>
<main class="fwsc-main"> <main class="fwsc-main">
<div class="content-area"> <div class="content-area">
<!-- 左侧筛选栏 --> <!-- 左侧服务卡片列表 -->
<div class="card-list">
<div class="service-grid">
<div v-for="(card, index) in cardList" :key="card.gxUuid" :data-gx-uuid="card.gxUuid" class="service-card">
<!-- 卡片头部 -->
<div class="card-header">
<div class="card-title-box">
<div class="card-title-text">
<div class="card-title-row">
<div class="card-title-main">{{ card.bt1 }}</div>
<!-- 收藏按钮 -->
<div class="card-collect" @click="handleCollect(card)">
<img v-if="card.scbz === 'Y'" src="../../assets/fwsc/ysc.svg" />
<img v-else src="../../assets/fwsc/wsc.svg" />
</div>
</div>
<div class="card-title-sub">
<div class="card-company">
<img src="../../assets/fwsc/city.svg" />
<span class="company-name">{{ card.qymc }}</span>
</div>
<div class="location" v-if="card.fwfw">
<img src="../../assets/fwsc/map.svg" />
<span class="company-name">{{ card.fwfw }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 卡片内容 -->
<div class="card-content">
<p class="card-desc">{{ card.fwnr }}</p>
<div class="card-tags">
<span v-for="(tag, i) in card.fwlxbqList" :key="i" class="tag">{{ tag }}</span>
</div>
</div>
<!-- 卡片底部 -->
<div class="card-footer">
<div class="card-price-info">
<span class="price-value" v-if="card.gjjg">{{ card.gjjg }}</span>
<span class="price-value" v-else>面议</span>
<!-- <span class="price-unit" v-if="card.gjdwDm">/{{ card.gjdwDm }}</span> -->
</div>
<div class="card-actions">
<span @click="handleContact(card)">立即咨询</span>
</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="cardList.length === 0 && !loading" class="empty-state">
<p>暂无服务信息</p>
</div>
</div>
<!-- 右侧筛选栏 -->
<aside class="filter-sidebar"> <aside class="filter-sidebar">
<div class="filter-toggle" @click="filterCollapsed = !filterCollapsed"> <div class="filter-toggle" @click="filterCollapsed = !filterCollapsed">
<span class="toggle-text">筛选</span> <span class="toggle-text">筛选</span>
@ -32,13 +91,12 @@
<!-- 内容搜索 --> <!-- 内容搜索 -->
<div class="filter-section"> <div class="filter-section">
<div class="filter-title">内容搜索</div> <div class="filter-title">内容搜索</div>
<t-input v-model="filter.nr" placeholder="请输入关键词" @enter="onSearch" /> <t-input v-model="filter.nr" placeholder="请输入关键词" @enter="onSearch">
<div class="filter-buttons"> <template #suffix-icon>
<t-button theme="primary" @click="onSearch">查询</t-button> <SearchIcon />
<t-button theme="default" @click="onReset">重置</t-button> </template>
</div> </t-input>
<!-- 只展示收藏项 --> <!-- 只展示收藏项 -->
<div class="filter-section"> <div class="filter-section">
<t-checkbox v-model="filter.zzsscx" @change="onSearch">只展示收藏项</t-checkbox> <t-checkbox v-model="filter.zzsscx" @change="onSearch">只展示收藏项</t-checkbox>
@ -57,13 +115,8 @@
<!-- 服务企业 --> <!-- 服务企业 -->
<div class="filter-section"> <div class="filter-section">
<div class="filter-title">服务企业</div> <div class="filter-title">服务企业</div>
<t-input <t-input v-model="qySearchKeyword" placeholder="搜索企业名称" clearable class="qy-search-input"
v-model="qySearchKeyword" @change="filterQyOptions">
placeholder="搜索企业名称"
clearable
class="qy-search-input"
@change="filterQyOptions"
>
<template #prefix-icon> <template #prefix-icon>
<SearchIcon /> <SearchIcon />
</template> </template>
@ -77,105 +130,24 @@
</div> </div>
</div> </div>
</aside> </aside>
<!-- 右侧服务卡片列表 -->
<div class="card-list">
<div class="list-header">
<div class="list-title-box">
<span class="list-icon">🛠</span>
<span class="list-title">碳服务市场</span>
</div>
<div class="list-right">
<span class="list-count"><span class="count-dot"></span> {{ page.total }} 条服务</span>
</div>
</div>
<div class="service-grid">
<div
v-for="(card, index) in cardList"
:key="card.gxUuid"
:data-gx-uuid="card.gxUuid"
class="service-card"
>
<!-- 卡片头部 -->
<div class="card-header">
<div class="card-title-box">
<div class="card-title-text">
<div class="card-title-main">{{ card.bt1 }}</div>
<div class="card-title-sub">
<span class="company-name">{{ card.qymc }}</span>
<span class="location" v-if="card.fwfw">
<LocationIcon style="transform: translateY(-1px)" />{{ card.fwfw }}
</span>
</div>
</div>
<!-- 收藏按钮 -->
<div class="card-collect" @click="handleCollect(card)">
<img v-if="card.scbz === 'Y'" src="../../assets/fwsc/ysc.svg" />
<img v-else src="../../assets/fwsc/wsc.svg" />
</div>
</div>
</div>
<!-- 卡片内容 -->
<div class="card-content">
<p class="card-desc">{{ card.fwnr }}</p>
<div class="card-tags">
<span v-for="(tag, i) in card.fwlxbqList" :key="i" class="tag">{{ tag }}</span>
</div>
</div>
<!-- 卡片底部 -->
<div class="card-footer">
<div class="card-price-info">
<span class="price-value" v-if="card.gjjg">{{ card.gjjg }}</span>
<span class="price-value" v-else>面议</span>
<span class="price-unit" v-if="card.gjdwDm">/{{ card.gjdwDm }}</span>
</div>
<div class="card-actions">
<t-button theme="primary" size="small" @click="handleContact(card)">联系服务商</t-button>
</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="cardList.length === 0 && !loading" class="empty-state">
<p>暂无服务信息</p>
</div>
</div>
</div> </div>
<!-- 分页 --> <!-- 分页 -->
<div class="pagination-box" v-if="page.total > 0"> <div class="pagination-box" v-if="page.total > 0">
<t-pagination <div class="pagination-total"> {{ page.total }} 条数据</div>
v-model="page.pageNo" <t-pagination v-model="page.pageNo" :total="page.total" :page-size.sync="page.pageSize" :totalContent='false'
:total="page.total" :page-size-options="[10, 20, 30, 50]" @change="onPageChange" align="center" />
:page-size.sync="page.pageSize"
:page-size-options="[3, 9, 27, 45]"
@change="onPageChange"
/>
</div> </div>
</main> </main>
<Footer /> <Footer />
<!-- 发布服务抽屉 --> <!-- 发布服务抽屉 -->
<FwscPublish <FwscPublish :visible.sync="publishVisible" @success="onPublishSuccess" />
:visible.sync="publishVisible"
@success="onPublishSuccess"
/>
<!-- 联系服务弹窗 --> <!-- 联系服务弹窗 -->
<t-dialog <t-dialog :closeOnOverlayClick="false" header="联系服务" :visible.sync="contactVisible" @confirm="onContactConfirm"
:closeOnOverlayClick="false" :onClose="onContactClose" class="global-dialog" attach="body">
header="联系服务"
:visible.sync="contactVisible"
@confirm="onContactConfirm"
:onClose="onContactClose"
class="global-dialog"
attach="body"
>
<div class="dialog-line"> <div class="dialog-line">
<div class="dialog-line-title">联系人</div> <div class="dialog-line-title">联系人</div>
<div class="dialog-line-text">{{ contactData.lxr }}</div> <div class="dialog-line-text">{{ contactData.lxr }}</div>
@ -191,29 +163,13 @@
</t-dialog> </t-dialog>
<!-- 提示弹窗 --> <!-- 提示弹窗 -->
<t-dialog <t-dialog :closeOnOverlayClick="false" header="提示" body="请先进行企业入驻" :visible.sync="rzVisible" @confirm="onRzConfirm"
:closeOnOverlayClick="false" :onClose="onRzClose" :cancelBtn="null" class="global-dialog" />
header="提示"
body="请先进行企业入驻"
:visible.sync="rzVisible"
@confirm="onRzConfirm"
:onClose="onRzClose"
:cancelBtn="null"
class="global-dialog"
/>
<!-- 发布成功弹窗 --> <!-- 发布成功弹窗 -->
<t-dialog <t-dialog :closeOnOverlayClick="false" body="发布申请成功,请等待审核,是否继续发布?" :visible.sync="publishSuccessVisible"
:closeOnOverlayClick="false" @confirm="onPublishSuccessConfirm" @cancel="onPublishSuccessCancel" :onClose="onPublishSuccessClose"
body="发布申请成功,请等待审核,是否继续发布?" cancelBtn="返回" confirmBtn="继续发布" class="global-dialog" />
:visible.sync="publishSuccessVisible"
@confirm="onPublishSuccessConfirm"
@cancel="onPublishSuccessCancel"
:onClose="onPublishSuccessClose"
cancelBtn="返回"
confirmBtn="继续发布"
class="global-dialog"
/>
</div> </div>
</template> </template>
@ -265,7 +221,7 @@ export default {
// //
page: { page: {
pageNo: 1, pageNo: 1,
pageSize: 9, pageSize: 10,
total: 0, total: 0,
}, },
// //
@ -532,8 +488,7 @@ export default {
// //
.secondary-nav { .secondary-nav {
background: #fff; border-bottom: none;
border-bottom: 1px solid #eee;
} }
.secondary-nav-content { .secondary-nav-content {
@ -541,60 +496,53 @@ export default {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
max-width: 1400px; max-width: 1400px;
padding: 0 20px; padding: 20px 20px 0;
margin: 0 auto; margin: 0 auto;
} }
.nav-tabs { .nav-tabs {
display: flex; display: flex;
gap: 8px; gap: 40px;
width: 596px;
height: 42px;
}
.nav-right {
display: flex;
align-items: center;
gap: 20px;
} }
.nav-tab { .nav-tab {
position: relative; position: relative;
padding: 12px 20px; min-width: max-content;
font-size: 14px; height: 42px;
color: #666; padding: 8px 16px;
font-size: 18px;
font-weight: 500;
color: #003B1A;
cursor: pointer; cursor: pointer;
background: transparent; background: transparent;
border: none; border-radius: 32px;
border-bottom: 2px solid transparent;
transition: all 0.3s; transition: all 0.3s;
&::after {
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 2px;
background: linear-gradient(90deg, #009a29, #48C666);
border-radius: 1px;
content: '';
transform: translateX(-50%);
transition: all 0.3s ease;
}
&:hover { &:hover {
color: #009a29; color: #009a29;
&::after {
width: 60%;
}
} }
&.active { &.active {
color: #009a29; background: #8CFFCE;
box-shadow: inset 0 0 0 1px #00B96B;
&::after {
width: 100%;
}
} }
} }
.publish-btn { .publish-btn {
padding: 8px 24px; width: 220px;
font-size: 14px; height: 42px;
font-weight: 500; line-height: 26px;
padding: 8px 16px;
font-size: 18px;
font-weight: 400;
color: #fff; color: #fff;
cursor: pointer; cursor: pointer;
background: linear-gradient(135deg, #009a29 0%, #48C666 100%); background: linear-gradient(135deg, #009a29 0%, #48C666 100%);
@ -616,6 +564,7 @@ export default {
.content-area { .content-area {
display: flex; display: flex;
gap: 20px; gap: 20px;
align-items: stretch;
} }
// //
@ -623,7 +572,6 @@ export default {
position: sticky; position: sticky;
top: 104px; top: 104px;
width: 220px; width: 220px;
height: fit-content;
flex-shrink: 0; flex-shrink: 0;
} }
@ -682,12 +630,12 @@ export default {
} }
.filter-section { .filter-section {
padding-bottom: 20px; // padding-bottom: 20px;
margin-bottom: 20px; margin-bottom: 20px;
border-bottom: 1px dashed #e0e0e0; border-bottom: 1px dashed #e0e0e0;
&:last-child { &:last-child {
padding-bottom: 0; padding: 10px 0;
margin-bottom: 0; margin-bottom: 0;
border-bottom: none; border-bottom: none;
} }
@ -734,16 +682,6 @@ export default {
text-align: center; text-align: center;
} }
.filter-buttons {
display: flex;
gap: 8px;
margin-top: 16px;
margin-bottom: 16px;
.t-button {
flex: 1;
}
}
// //
.card-list { .card-list {
@ -751,67 +689,16 @@ export default {
min-width: 0; min-width: 0;
} }
.list-header {
display: flex;
padding: 0 8px;
margin-bottom: 20px;
justify-content: space-between;
align-items: center;
}
.list-title-box {
display: flex;
align-items: center;
gap: 10px;
.list-icon {
font-size: 24px;
}
.list-title {
font-size: 22px;
font-weight: 600;
color: #333;
background: linear-gradient(135deg, #333 0%, #009a29 100%);
background-clip: text;
-webkit-text-fill-color: transparent;
}
}
.list-right {
display: flex;
align-items: center;
}
.list-count { .list-count {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px; font-size: 14px;
color: #999; font-weight: 400;
color: #6B8575;
.count-dot {
width: 8px;
height: 8px;
background: linear-gradient(135deg, #009a29, #48C666);
border-radius: 50%;
animation: pulse 2s infinite;
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
} }
.service-grid { .service-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 20px; gap: 24px;
} }
.service-card { .service-card {
@ -828,32 +715,43 @@ export default {
animation: highlight-pulse 3s ease-out; animation: highlight-pulse 3s ease-out;
} }
&::before { // &::before {
position: absolute; // position: absolute;
top: 0; // top: 0;
left: 0; // left: 0;
width: 100%; // width: 100%;
height: 3px; // height: 3px;
background: linear-gradient(90deg, #009a29, #48C666); // background: linear-gradient(90deg, #009a29, #48C666);
content: ''; // content: '';
transform: scaleX(0); // transform: scaleX(0);
transform-origin: left; // transform-origin: left;
transition: transform 0.3s ease; // transition: transform 0.3s ease;
} // }
&:hover { // &:hover {
transform: translateY(-4px); // transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 154, 41, 0.15); // box-shadow: 0 8px 24px rgba(0, 154, 41, 0.15);
&::before { // &::before {
transform: scaleX(1); // transform: scaleX(1);
} // }
} // }
} }
.card-header { .card-header {
position: relative;
padding: 16px; padding: 16px;
border-bottom: 1px solid #eee; height: 70px;
&::after {
position: absolute;
right: 16px;
bottom: -6px;
left: 16px;
height: 1px;
background: #E8F0EC;
content: '';
}
} }
.card-title-box { .card-title-box {
@ -867,34 +765,49 @@ export default {
} }
.card-title-main { .card-title-main {
margin-bottom: 4px; flex: 1;
overflow: hidden; overflow: hidden;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 500;
line-height: 24px; line-height: 24px;
color: #333; color: #003B1A;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.card-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.card-company {
display: flex;
align-items: center;
gap: 8px;
}
.card-title-sub { .card-title-sub {
display: flex; display: flex;
font-size: 14px;
color: #666;
gap: 12px; gap: 12px;
justify-content: space-between;
} }
.company-name { .company-name {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
font-size: 14px;
color: #6B8575;
font-weight: 400;
} }
.location { .location {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 2px; gap: 8px;
flex-shrink: 0;
} }
.card-collect { .card-collect {
@ -931,18 +844,30 @@ export default {
.tag { .tag {
padding: 2px 8px; padding: 2px 8px;
font-size: 12px; font-size: 13px;
color: #2e7d32; font-weight: 400;
background: #dcf9e2; color: #00B96B;
background: #EEFAE2;
border-radius: 4px; border-radius: 4px;
} }
.card-footer { .card-footer {
position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 14px 16px; padding: 14px 16px;
border-top: 1px dashed #979797; height: 64px;
&::before {
position: absolute;
top: 0;
right: 16px;
left: 16px;
height: 1px;
background: #E8F0EC;
content: '';
}
} }
.card-price-info { .card-price-info {
@ -950,10 +875,21 @@ export default {
align-items: baseline; align-items: baseline;
} }
.card-actions {
display: flex;
height: 32px;
padding: 6px 12px;
border-radius: 4px;
border: 1px solid #00b96b;
color: #00b96b;
font-size: 14px;
font-weight: 400;
}
.price-value { .price-value {
font-size: 20px; font-size: 20px;
font-weight: 600; font-weight: 600;
color: #d25f00; color: #FF4D4F;
} }
.price-unit { .price-unit {
@ -992,6 +928,7 @@ export default {
opacity: 0; opacity: 0;
transform: translateY(10px); transform: translateY(10px);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
@ -999,10 +936,28 @@ export default {
} }
// //
.pagination-total {
font-size: 16px;
color: #666;
}
.pagination-box { .pagination-box {
display: flex; display: flex;
align-items: center;
justify-content: center; justify-content: center;
padding-top: 32px; padding-top: 32px;
gap: 10px;
::v-deep .t-pagination {
display: flex !important;
justify-content: center;
flex-wrap: wrap;
width: initial;
.t-input.t-is-readonly {
width: 110px;
}
}
} }
// //
@ -1100,7 +1055,7 @@ export default {
} }
.card-actions { .card-actions {
width: 100%; width: 100%
} }
.pagination-box { .pagination-box {
@ -1153,9 +1108,11 @@ export default {
0% { 0% {
box-shadow: 0 0 0 0 rgba(0, 154, 41, 0.4); box-shadow: 0 0 0 0 rgba(0, 154, 41, 0.4);
} }
50% { 50% {
box-shadow: 0 0 20px 10px rgba(0, 154, 41, 0.2); box-shadow: 0 0 20px 10px rgba(0, 154, 41, 0.2);
} }
100% { 100% {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="jrsc-page"> <div class="jrsc-page">
<!-- 面包屑导航 --> <!-- 面包屑导航 -->
<BreadcrumbNav currentPage="碳金融市场" /> <!-- <BreadcrumbNav currentPage="碳金融市场" /> -->
<!-- 二级菜单 --> <!-- 二级菜单 -->
<div class="secondary-nav"> <div class="secondary-nav">

View File

@ -1,11 +1,11 @@
<template> <template>
<div class="sjlbc-page"> <div class="sjlbc-page">
<!-- 面包屑导航 --> <!-- 面包屑导航 -->
<BreadcrumbNav <!-- <BreadcrumbNav
:currentPage="currentPageName" :currentPage="currentPageName"
secondPage="碳数据市场" secondPage="碳数据市场"
secondLink="/tsjsc" secondLink="/tsjsc"
/> /> -->
<!-- 主内容区 --> <!-- 主内容区 -->
<div class="content-wrapper"> <div class="content-wrapper">

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="sjsc-page"> <div class="sjsc-page">
<!-- 面包屑导航 --> <!-- 面包屑导航 -->
<BreadcrumbNav currentPage="碳数据市场" /> <!-- <BreadcrumbNav currentPage="碳数据市场" /> -->
<!-- 二级菜单 --> <!-- 二级菜单 -->
<div class="secondary-nav"> <div class="secondary-nav">

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="xqsc-container"> <div class="xqsc-container">
<!-- 面包屑导航 --> <!-- 面包屑导航 -->
<BreadcrumbNav currentPage="碳需求市场" /> <!-- <BreadcrumbNav currentPage="碳需求市场" /> -->
<!-- 二级菜单 --> <!-- 二级菜单 -->
<div class="secondary-nav"> <div class="secondary-nav">

File diff suppressed because it is too large Load Diff

660
前端项目文档.md Normal file
View File

@ -0,0 +1,660 @@
# 前端项目文档
## 目录
- [项目概览](#项目概览)
- [目录结构](#目录结构)
- [核心模块详解](#核心模块详解)
- [技术栈](#技术栈)
- [环境变量](#环境变量)
- [启动方式](#启动方式)
- [API 层](#api-层)
- [Mock 数据](#mock-数据)
- [各项目功能对比](#各项目功能对比)
---
## 项目概览
本项目是一个**多模块前后分离架构**的企业级应用,包含 6 个 Vue 前端项目和一个 Java 微服务后端。
### 前端项目列表
| 项目 | 访问路径 | 描述 |
|------|----------|------|
| txw-gxzx-web | /view/gxzx | 可信碳信息网(功能最丰富) |
| txw-mhzc-web | /view/mhzc | 苗栗县综合平台 |
| txw-tzzx-web | /view/tzzx | Topfans 投资资讯 |
| txw-ytzx-web | /view/ytzx | Topfans 媒体中心 |
| txw-yygl-web | /view/yygl | 运营管理系统 |
| txw-kxtfwzx-web | /view/kxtfwzx | 综合服务信息中心 |
### 技术架构
- **框架**: Vue 2.6 + Vue Router 3 + Vuex 3
- **UI 组件库**: TDesign Vue 1.4.0 + @gtff/tdesign-gt-vue
- **构建工具**: Vue CLI 4.5
- **HTTP 客户端**: Axios
- **图表库**: ECharts 5.5
- **样式预处理**: LESS
---
## 目录结构
```
<project>/
├── public/ # 静态资源(不经过 webpack 处理)
│ ├── index.html # 生产环境 HTML 模板
│ └── local.html # 本地开发 HTML 模板
├── src/
│ ├── core/ # 核心初始化模块(所有项目共享)
│ │ ├── index.js # Vue 插件注册、TDesign 组件库初始化
│ │ ├── request.js # Axios 封装(拦截器 + 4 种 fetch 函数)
│ │ └── download.js # 文件下载工具(含 IE 兼容)
│ ├── pages/
│ │ └── index/ # 主页面模块(所有业务代码)
│ │ ├── main.js # 入口文件
│ │ ├── app.vue # 根组件
│ │ ├── api/ # API 接口定义
│ │ ├── assets/ # 模块资源(图片、字体等)
│ │ ├── components/ # 业务组件
│ │ ├── config/ # 顶部/侧边栏配置
│ │ ├── router/ # 路由定义
│ │ │ ├── index.js # VueRouter 实例创建
│ │ │ └── routes.js # 路由表定义(懒加载)
│ │ ├── store/ # Vuex 状态管理
│ │ │ ├── index.js # Store 实例
│ │ │ └── modules/ # 状态模块
│ │ ├── styles/ # 模块级样式
│ │ └── views/ # 页面组件
│ ├── styles/ # 全局共享样式
│ │ ├── index.less
│ │ ├── index.css
│ │ ├── variables.less # LESS 变量(主题色、尺寸等)
│ │ └── span-btn.less
│ └── utils/ # 全局工具函数
│ ├── auth.js # Token 管理
│ ├── calc.js # 税费计算
│ ├── dateUtil.js # 日期工具
│ ├── numberUtils.js # 数字格式化
│ ├── eventBus.js # 事件总线
│ └── urlParams.js # URL 参数解析
├── mock/ # Mock 数据(开发环境)
│ ├── index.js # Mock 入口
│ ├── get/ # GET 请求 Mock
│ └── post/ # POST 请求 Mock
├── .env # 默认环境变量
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── .env.test # 测试环境变量
├── babel.config.js # Babel 转译配置
├── vue.config.js # Vue CLI Webpack 配置
├── proxy.js # 开发服务器代理配置
├── package.json
└── README.md
```
### 目录用途说明
| 目录/文件 | 用途 |
|-----------|------|
| `public/` | 不经过 webpack 处理的静态资源,直接复制到输出目录 |
| `src/core/` | 核心初始化代码,包括 Axios 封装、下载工具 |
| `src/pages/index/` | 所有业务代码API、组件、视图、路由、状态 |
| `src/styles/` | 全局样式和 LESS 变量 |
| `src/utils/` | 与业务无关的纯工具函数 |
| `mock/` | 前端 Mock 数据,用于独立开发 |
| `.env.*` | 环境差异化配置 |
---
## 核心模块详解
### 1. 入口文件 (`src/pages/index/main.js`)
```javascript
// 1. Polyfill 引入
import 'core-js/stable';
import 'regenerator-runtime/runtime';
// 2. 核心模块初始化
import core from '@/core/index.js';
// 3. Vue 实例创建
import Vue from 'vue';
import App from './app.vue';
// 4. 路由和状态管理
import router from './router/index.js';
import store from './store/index.js';
// 5. 创建并挂载 Vue 实例
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');
```
**主要职责**
- 初始化 polyfill
- 注册 Vue 插件和全局组件
- 整合 Router 和 Vuex Store
- 设置文档标题
- 注册全局事件(登录/登出)
### 2. Axios 封装 (`src/core/request.js`)
提供 4 种请求方法:
| 函数 | 用途 | BaseURL | 特殊 Header |
|------|------|---------|-------------|
| `fetch` | 普通业务 API | `API_PREFIX` | 无 |
| `fetchSso` | SSO 服务 | `SSO_PREFIX` | `TOKEN-TYPE: YW` |
| `fetchSso1` | SSO 服务 | `SSO_PREFIX` | `TOKEN-TYPE: STAR` |
| `fetchNoPrefix` | 无前缀调用 | 空 | 无 |
**请求拦截器**
- 自动添加时间戳防止缓存
- GET 请求参数映射到 URL query string
**响应拦截器**
- `code !== 1` 时显示错误信息
- `code === 401` 时跳转登录页
- 统一错误处理
### 3. 路由 (`src/pages/index/router/`)
**`index.js`** - VueRouter 实例:
```javascript
const router = new VueRouter({
mode: 'history', // 使用 History 模式
base: `${window.STATIC_ENV_CONFIG.ROUTER_PREFIX}/`, // 基础路径
routes: require('./routes').default
});
```
**`routes.js`** - 路由表(懒加载):
```javascript
export default [
{
path: '/login',
name: 'login',
component: () => import(/* webpackChunkName: "login" */ '@/pages/index/views/login/login.vue')
},
// ...其他路由
];
```
### 4. 状态管理 (`src/pages/index/store/`)
**`index.js`** - Store 实例创建:
```javascript
import Vue from 'vue';
import Vuex from 'vuex';
import user from './modules/user.js';
import mxuser from './modules/mxuser.js';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
user,
mxuser
}
});
```
**模块结构**
- `modules/user.js` - 用户信息、登录状态
- `modules/mxuser.js` - MX 用户状态
- 各业务模块的 store 在各自目录下
### 5. 文件下载 (`src/core/download.js`)
```javascript
// 普通浏览器下载
download(url, filename) {
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
}
// IE 兼容下载
downloadForIE(url, filename) {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
}
```
---
## 技术栈
### 核心依赖
| 技术 | 版本 | 用途 |
|------|------|------|
| vue | ~2.6.11 | 前端框架 |
| vue-router | 3.2.0 | 路由管理 |
| vuex | ^3.4.0 | 状态管理 |
| tdesign-vue | 1.4.0 | TDesign UI 组件库 |
| @gtff/tdesign-gt-vue | 1.4.0 | 定制 TDesign 组件 |
| @gt4/common-front | 2.0.113 | 公共前端工具库 |
### HTTP 与数据处理
| 技术 | 版本 | 用途 |
|------|------|------|
| axios | ^0.21.1 | HTTP 客户端 |
| dayjs | ^1.10.4 | 日期处理(轻量级) |
| lodash-es | ^4.17.21 | JavaScript 工具库 |
### 可视化
| 技术 | 版本 | 用途 |
|------|------|------|
| echarts | ^5.5.0 | 图表库 |
| vue-echarts | ^6.6.9 | ECharts Vue 封装 |
| vue-pdf | ^4.3.0 | PDF 预览 |
### 构建与开发
| 技术 | 版本 | 用途 |
|------|------|------|
| @vue/cli-service | ~4.5.0 | Vue CLI 服务 |
| @wecity/static-env-config | 1.0.32 | 环境配置注入 |
| mockjs | ^1.1.0 | Mock 数据生成 |
| less | ^4.1.3 | LESS 预处理器 |
### 图标
| 技术 | 版本 | 用途 |
|------|------|------|
| tdesign-icons-vue | 0.0.8 | TDesign 图标 |
| @fortawesome/fontawesome-free | ^7.0.1 | Font Awesome 图标 |
---
## 环境变量
### `.env` 默认配置
```
VUE_APP_ENV=dev
VUE_APP_MODEL=local
VUE_APP_CDN_PATH=/view/<project>
VUE_APP_ROUTER_BASE=/view/<project>
VUE_APP_API_BASE_URL=
VUE_APP_API_TIMEOUT=15000
VUE_APP_CSS_EXTRACT=false
```
### `.env.development` 开发配置
```
VUE_APP_ENV=dev
VUE_APP_MODEL=local
VUE_APP_CDN_PATH=/view/<project>
VUE_APP_ROUTER_BASE=/view/<project>
VUE_APP_API_BASE_URL=
VUE_APP_DEV_SERVER_PORT=9002
VUE_APP_MOCK=true
VUE_APP_AUTO_ROUTER=false
```
### `.env.production` 生产配置
```
VUE_APP_ENV=prod
VUE_APP_MODEL=online
VUE_APP_CDN_PATH=/view/<project>
VUE_APP_ROUTER_BASE=/view/<project>
VUE_APP_API_BASE_URL=
VUE_APP_AUTO_ROUTER=false
```
### `.env.test` 测试配置
```
NODE_ENV=production
VUE_APP_ENV=beta
VUE_APP_MODEL=online
VUE_APP_CDN_PATH=/test
VUE_APP_ROUTER_BASE=/test
VUE_APP_API_BASE_URL=/test/api
VUE_APP_API_BASE_URL2=/test/api
```
### 变量说明
| 变量 | 说明 | 示例值 |
|------|------|--------|
| `VUE_APP_ENV` | 环境标识 | dev / prod / beta |
| `VUE_APP_MODEL` | 运行模式 | local本地/ online线上 |
| `VUE_APP_CDN_PATH` | 静态资源基础路径 | /view/gxzx |
| `VUE_APP_ROUTER_BASE` | Vue Router 基础路径 | /view/gxzx |
| `VUE_APP_API_BASE_URL` | API 请求前缀 | 空(使用网关) |
| `VUE_APP_DEV_SERVER_PORT` | 开发服务器端口 | 9002 |
| `VUE_APP_MOCK` | 是否启用 Mock 数据 | true / false |
| `VUE_APP_AUTO_ROUTER` | 是否启用自动路由 | false |
---
## 启动方式
### 前置条件
- Node.js >= 14.x
- Yarn 或 npm
### 安装依赖
```bash
cd txw-gxzx-web # 进入项目目录
yarn install # 安装依赖(推荐)
# 或 npm install
```
### 开发模式
```bash
# 普通开发模式
yarn dev
# 启用 Mock 数据模式(无需后端启动)
yarn dev:mock
# 使用 npm
npm run serve
```
### 构建打包
```bash
# 生产环境打包
yarn build
# 开发环境打包
yarn build:site:dev
# 测试环境打包
yarn build:site:test
# 指定环境打包
yarn build:site --mode development
```
### 代码检查
```bash
# ESLint 检查
yarn lint
# 样式检查
yarn lint:style
```
### 开发服务器
默认端口:`9002`(可通过 `VUE_APP_DEV_SERVER_PORT` 修改)
代理配置(`proxy.js`
```javascript
proxy: {
'/sso': { target: 'http://10.23.20.13:94/' },
'/mhzc': { target: 'http://10.23.20.13:94/' },
}
```
---
## API 层
### API 定义示例
`src/pages/index/api/` 目录下定义接口:
```javascript
// login.js
import { fetch, fetchSso } from '@/core/request.js';
export const login = (data) => fetch.post('/user/login', data);
export const logout = () => fetchSso.post('/sso/logout');
export const getUserInfo = () => fetch.get('/user/info');
```
### API 调用方式
```javascript
import { login, getUserInfo } from '../api/login.js';
// 登录
login({ username: 'admin', password: '123456' })
.then(res => {
if (res.code === 1) {
// 登录成功
}
});
// 获取用户信息
getUserInfo()
.then(res => console.log(res.data));
```
### 各项目 API 文件
| 项目 | API 文件 |
|------|----------|
| txw-gxzx-web | login.js, common.js, cjjpwh.js, ggwhglHtgl/index.js, gxzx/index.js, htgl.js, lsjr.js, lsjy.js, tjzsym.js, yhjfzsym.js |
| txw-mhzc-web | login.js, ggwhglHtgl/index.js, gxzx/index.js, htgl.js, yhzx/index.js |
| txw-tzzx-web | login.js, cjjpwh.js, common.js, ggwhglHtgl/index.js, htgl.js, tjzsym.js, yhjfzsym.js |
| txw-ytzx-web | login.js, cjjpwh.js, common.js, ggwhglHtgl/index.js, htgl.js, tjzsym.js, yhjfzsym.js |
| txw-yygl-web | login.js, cjjpwh.js, common.js, gggl/index.js, ggwhglHtgl/index.js, gxzx/index.js, htgl.js, lscp.js, tjzsym.js, yhjfzsym.js |
| txw-kxtfwzx-web | login.js, cjjpwh.js, common.js, ggwhglHtgl/index.js, htgl.js, tjzsym.js, yhjfzsym.js |
---
## Mock 数据
### Mock 目录结构
```
mock/
├── index.js # Mock 入口
├── get/ # GET 请求 Mock
│ ├── user_info.json # -> GET /user/info
│ └── user_list.json # -> GET /user/list
└── post/ # POST 请求 Mock
├── user_login.json # -> POST /user/login
└── user_logout.json # -> POST /user/logout
```
### 文件命名规则
Mock 文件名中的双下划线 `__` 会被映射为路径斜杠 `/`
| 文件名 | 映射路径 |
|--------|----------|
| `user_info.json` | `/user/info` |
| `user_bulk__delete.json` | `/user/bulk_delete` |
| `admin_role_list.json` | `/admin/role/list` |
### 启用 Mock
方式一:修改 `.env.development`
```
VUE_APP_MOCK=true
```
方式二:使用 dev:mock 命令
```bash
yarn dev:mock
```
### Mock 数据示例
```javascript
// mock/post/user_login.json
{
"code": 1,
"msg": "登录成功",
"data": {
"token": "mock_token_12345",
"userId": 1001,
"username": "admin"
}
}
```
---
## 各项目功能对比
| 项目 | 主要功能模块 | 特点 |
|------|-------------|------|
| **txw-gxzx-web** | 可信碳信息、绿色金融(lsjr)、绿色交易(lsjy)、绿色信贷(lsxd)、企业出海(qych)、碳能力平台(gxnlpt) | 功能最丰富,业务模块最多 |
| **txw-mhzc-web** | 登录、首页(mhNewMain/home/home2)、用户中心(yhzx)、新闻中心、授权(authorize)、论坛(zxym)、企业入驻(qyrz/qy-rz) | 多首页适配,屏幕适配(1895x953) |
| **txw-tzzx-web** | 登录、首页 | 简单结构,仅基础功能 |
| **txw-ytzx-web** | 登录、首页 | 简单结构,仅基础功能 |
| **txw-yygl-web** | 登录、用户中心、公告管理(gggl)、轮播图(banner)、企业入驻(qyrz/qysp)、历史产品(lscp)、数据概览(tjzsym) | 运营管理功能 |
| **txw-kxtfwzx-web** | 登录、首页 | 简单结构,仅基础功能 |
### 各项目 Views 目录结构
**txw-gxzx-web**:
```
views/gxzx/ # 可信碳信息
views/lsjr/ # 绿色金融
views/lsjy/ # 绿色交易
views/qych/ # 企业出海
views/transfer/ # 转让
```
**txw-mhzc-web**:
```
views/dddl/ # 订单
views/ggwhglHtgl/ # 公共管理
views/glxtSy/ # 管理平台首页
views/gxfb/ # 信息发布
views/gxnlpt/ # 能力平台
views/home/ # 首页
views/hyzt/ # 行业状态
views/login/ # 登录
views/lsjy/ # 历史交易
views/qy-rz/ # 企业入驻
views/qych/ # 企业出海
views/qyrz/ # 企业认证
views/zx/ # 资讯
```
**txw-yygl-web**:
```
views/banner/ # 轮播图管理
views/ggwhglHtgl/ # 公共管理
views/glxtSy/ # 管理平台首页
views/gxfb/ # 信息发布
views/login/ # 登录
views/lscp/ # 历史产品
views/lsjy/ # 历史交易
views/qyrz/ # 企业入驻
views/qysp/ # 企业审批
views/yhgl/ # 用户管理
```
---
## 公共组件
| 组件 | 路径 | 用途 |
|------|------|------|
| AddEditTable | `components/AddEditTable/` | 表格增删改一体 |
| all-select | `components/all-select/` | 自定义多选组件 |
| common-breadcrumb | `components/common-breadcrumb/` | 面包屑导航 |
| common-confirm | `components/common-confirm/` | 确认对话框 |
| commonCard | `components/commonCard/` | 卡片包装组件 |
| empty-placeholder | `components/empty-placeholder/` | 空状态占位 |
| footer | `components/footer/` | 页脚 |
| header-search | `components/header-search/` | 头部搜索 |
| ImportDialog | `components/ImportDialog/` | 文件导入弹窗 |
| nav | `components/nav/` | 导航组件 |
| pdf | `components/pdf/` | PDF 预览组件 |
| sbbBottomSubmit | `components/sbbBottomSubmit/` | 底部提交按钮 |
| search-control-panel | `components/search-control-panel/` | 搜索面板 |
| workflow | `components/workflow/` | 工作流组件 |
---
## 工具函数
| 文件 | 函数 | 用途 |
|------|------|------|
| **auth.js** | `getAccessToken()` | 获取 Access Token |
| | `setToken(token)` | 设置 Token |
| | `removeToken()` | 清除 Token |
| **dateUtil.js** | `getQuarter(date)` | 获取日期所在季度 |
| | `getMonthStartEndDay(date)` | 获取月份开始结束日期 |
| | `getThisMonthRange()` | 获取本月日期范围 |
| **calc.js** | 税费计算相关 | 税费计算工具 |
| **numberUtils.js** | 数字处理相关 | 数字格式化工具 |
| **eventBus.js** | `EventBus.$on()` | 监听事件 |
| | `EventBus.$emit()` | 触发事件 |
| **urlParams.js** | URL 参数解析 | 获取 URL 参数 |
---
## 部署说明
### 静态资源路径
每个项目部署后通过不同路径访问:
- `/view/gxzx` -> txw-gxzx-web
- `/view/mhzc` -> txw-mhzc-web
- `/view/tzzx` -> txw-tzzx-web
- `/view/ytzx` -> txw-ytzx-web
- `/view/yygl` -> txw-yygl-web
- `/view/kxtfwzx` -> txw-kxtfwzx-web
### 环境配置注入
构建时通过 `@wecity/static-env-config` 插件注入环境变量,生成 `env.config.js` 文件:
- 开发环境: `/view/<project>/env.config.js`
- 生产环境: `/view/<project>/env.config.js`
### Nginx 配置示例
```nginx
location /view/gxzx {
alias /usr/share/nginx/html/gxzx;
try_files $uri $uri/ /view/gxzx/index.html;
}
location /view/mhzc {
alias /usr/share/nginx/html/mhzc;
try_files $uri $uri/ /view/mhzc/index.html;
}
```
---
## 常见问题
### 1. 跨域问题
开发环境通过 `proxy.js` 配置代理解决。生产环境通过网关路由解决。
### 2. Mock 数据与实际接口切换
修改 `VUE_APP_MOCK` 环境变量即可切换。
### 3. 路由 404 问题
确保 Nginx 配置了 `try_files $uri $uri/ /index.html`,支持 History 模式的路由。
### 4. IE 浏览器兼容
已引入 `@wecity/tdesign-vue-ie` 和相关 polyfill但部分功能可能受限。
---
*文档生成时间: 2026-04-03*