feat: 实现动态配置和表单自动重置功能

- 添加动态配置支持,包括下载链接、网站标题等
- 表单提交成功后自动重置表单内容
- 优化详情页布局和显示逻辑
- 更新联系方式卡片样式和功能
- 修改vite配置允许所有网络接口访问
master
Swanky 2 months ago
parent 09a3494e77
commit 6a01af9bb1
  1. 4
      index.html
  2. 49
      src/components/AchievementList.vue
  3. 51
      src/components/DemandList.vue
  4. 6
      src/components/SubmitAchievementForm.vue
  5. 7
      src/components/SubmitDemandForm.vue
  6. 100
      src/views/HomeView.vue
  7. 1
      vite.config.js

@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>project-report</title>
<title></title>
</head>
<body>
<div id="app"></div>

@ -3,6 +3,11 @@ import { ref, reactive, onMounted } from 'vue'
import { showToast } from 'vant'
import { getAchievementList } from '../services/api'
//
const props = defineProps({
config: Object
})
//
const searchKey = ref('')
@ -84,8 +89,13 @@ const handleDetail = (item) => {
}
//
const handleDownload = (year) => {
showToast(`${year}清单下载功能开发中`)
const handleDownload = (url) => {
if (url) {
//
window.open(url, '_blank')
} else {
showToast('下载链接无效')
}
}
//
@ -98,7 +108,7 @@ onMounted(() => {
<div class="achievement-list">
<!-- 搜索框 -->
<div class="search-container">
<van-search v-model="searchKey" placeholder="请输入关键词搜索" show-action :show-filter="true" :clearable="false"
<van-search v-model="searchKey" placeholder="请输入关键词搜索" show-action action-text="清除" :show-filter="true" :clearable="false"
wrap-with-form @search="handleSearch" @cancel="handleCancel" shape="round" @click-right-icon="handleSearch">
<template #right-icon>
<a style="text-decoration:none">搜索</a>
@ -108,11 +118,14 @@ onMounted(() => {
<!-- 下载按钮 -->
<div class="download-container">
<van-button type="default" size="small" @click="handleDownload('2025')">
2025年清单下载
</van-button>
<van-button type="default" size="small" @click="handleDownload('2024')">
2024年清单下载
<van-button
v-for="item in props.config?.achievementDownList || []"
:key="item.name"
type="default"
size="small"
@click="handleDownload(item.url)"
>
{{ item.name }}
</van-button>
</div>
@ -143,10 +156,10 @@ onMounted(() => {
<div class="item-tags">
<van-tag v-if="item?.tech_maturity" color="#ff9800" size="small" round>{{ item.tech_maturity
}}</van-tag>
<van-tag v-if="item?.intellectual_property" color="#9c27b0" size="small" round>{{
item.intellectual_property }}</van-tag>
<van-tag v-if="item?.cooperation_mode" color="#9c27b0" size="small" round>{{
item.cooperation_mode }}</van-tag>
</div>
<div class="item-date">{{ item?.create_time || '' }}</div>
<!-- <div class="item-date">{{ item?.create_time || '' }}</div> -->
</div>
<div class="item-intro">{{ item?.achievement_intro || '' }}</div>
</div>
@ -180,7 +193,7 @@ onMounted(() => {
<van-popup v-model:show="showDetail" position="bottom" :style="{ height: '80%', borderRadius: '16px 16px 0 0' }">
<div class="detail-content">
<div class="detail-header">
<h3>{{ currentAchievement?.project_name }}</h3>
<h3>成果详情</h3>
<van-icon name="cross" size="20" color="#999" @click="showDetail = false" />
</div>
@ -190,6 +203,10 @@ onMounted(() => {
<van-icon name="info-o" color="#2196f3" size="18" />
基本信息
</h4>
<div class="detail-item">
<span class="item-label">项目名称</span>
<span class="item-value">{{ currentAchievement?.project_name }}</span>
</div>
<div class="detail-item">
<span class="item-label">所属领域</span>
<span class="item-value">{{ currentAchievement?.field }}</span>
@ -198,10 +215,10 @@ onMounted(() => {
<span class="item-label">技术成熟度</span>
<span class="item-value">{{ currentAchievement?.tech_maturity }}</span>
</div>
<div class="detail-item">
<!-- <div class="detail-item">
<span class="item-label">知识产权情况</span>
<span class="item-value">{{ currentAchievement?.intellectual_property }}</span>
</div>
</div> -->
<div class="detail-item">
<span class="item-label">合作方式</span>
<span class="item-value">{{ currentAchievement?.cooperation_mode }}</span>
@ -225,9 +242,9 @@ onMounted(() => {
</div>
<!-- 发布时间 -->
<div style="font-size: 12px; color: #999; margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee;">
<!-- <div style="font-size: 12px; color: #999; margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee;">
发布时间{{ currentAchievement?.create_time || '未设置' }}
</div>
</div> -->
</div>
</div>
</van-popup>

@ -3,6 +3,11 @@ import { ref, reactive, onMounted } from 'vue'
import { showToast } from 'vant'
import { getDemandList } from '../services/api'
//
const props = defineProps({
config: Object
})
//
const searchKey = ref('')
@ -84,8 +89,13 @@ const handleDetail = (item) => {
}
//
const handleDownload = (year) => {
showToast(`${year}清单下载功能开发中`)
const handleDownload = (url) => {
if (url) {
//
window.open(url, '_blank')
} else {
showToast('下载链接无效')
}
}
//
@ -98,8 +108,9 @@ onMounted(() => {
<div class="demand-list">
<!-- 搜索框 -->
<div class="search-container">
<van-search v-model="searchKey" placeholder="请输入关键词搜索" show-action :show-filter="true" :clearable="false"
wrap-with-form @search="handleSearch" @cancel="handleCancel" shape="round" @click-right-icon="handleSearch">
<van-search v-model="searchKey" placeholder="请输入关键词搜索" action-text="清除" show-action :show-filter="true"
:clearable="false" wrap-with-form @search="handleSearch" @cancel="handleCancel" shape="round"
@click-right-icon="handleSearch">
<template #right-icon>
<a style="text-decoration:none">搜索</a>
</template>
@ -108,11 +119,9 @@ onMounted(() => {
<!-- 下载按钮 -->
<div class="download-container">
<van-button type="default" size="small" @click="handleDownload('2025')">
2025年清单下载
</van-button>
<van-button type="default" size="small" @click="handleDownload('2024')">
2024年清单下载
<van-button v-for="item in props.config?.demandDownList || []" :key="item.name" type="default" size="small"
@click="handleDownload(item.url)">
{{ item.name }}
</van-button>
</div>
@ -135,19 +144,18 @@ onMounted(() => {
<van-loading type="spinner" color="#1989fa" />
</div>
<div v-for="item in demandList" :key="item?.id || item" class="list-item"
@click="item && handleDetail(item)">
<div v-for="item in demandList" :key="item?.id || item" class="list-item" @click="item && handleDetail(item)">
<div class="item-content">
<div class="item-title">{{ item?.project_name || '' }}</div>
<div class="item-info-row">
<!-- <div class="item-info-row">
<div class="item-tags">
<van-tag v-if="item?.tech_maturity" color="#ff9800" size="small" round>{{ item.tech_maturity
}}</van-tag>
}}</van-tag>
<van-tag v-if="item?.intellectual_property" color="#9c27b0" size="small" round>{{
item.intellectual_property }}</van-tag>
</div>
<div class="item-date">{{ item?.create_time || '' }}</div>
</div>
</div> -->
<div class="item-intro">{{ item?.demand_overview || '' }}</div>
</div>
<div class="item-footer">
@ -180,7 +188,7 @@ onMounted(() => {
<van-popup v-model:show="showDetail" position="bottom" :style="{ height: '80%', borderRadius: '16px 16px 0 0' }">
<div class="detail-content">
<div class="detail-header">
<h3>{{ currentDemand?.project_name }}</h3>
<h3>需求详情</h3>
<van-icon name="cross" size="20" color="#999" @click="showDetail = false" />
</div>
@ -190,18 +198,22 @@ onMounted(() => {
<van-icon name="info-o" color="#2196f3" size="18" />
基本信息
</h4>
<div class="detail-item">
<span class="item-label">项目名称</span>
<span class="item-value">{{ currentDemand?.project_name }}</span>
</div>
<div class="detail-item">
<span class="item-label">所属领域</span>
<span class="item-value">{{ currentDemand?.field }}</span>
</div>
<div class="detail-item">
<!-- <div class="detail-item">
<span class="item-label">技术成熟度</span>
<span class="item-value">{{ currentDemand?.tech_maturity }}</span>
</div>
<div class="detail-item">
<span class="item-label">知识产权要求</span>
<span class="item-value">{{ currentDemand?.intellectual_property }}</span>
</div>
</div> -->
<div class="detail-item">
<span class="item-label">预期技术指标</span>
<span class="item-value">{{ currentDemand?.expected_tech_target }}</span>
@ -238,7 +250,7 @@ onMounted(() => {
</h4>
<div class="detail-content-text">{{ currentDemand?.demand_overview }}</div>
</div>
<!-- 发布时间 -->
<div style="font-size: 12px; color: #999; margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee;">
发布时间{{ currentDemand?.create_time || '未设置' }}
@ -513,8 +525,7 @@ onMounted(() => {
padding-left: 0;
}
.download-container {
}
.download-container {}
.list-container {
padding: 12px;

@ -136,6 +136,12 @@ const submitForm = async () => {
//
showSuccessToast('表单提交成功')
console.log('表单提交成功:', response)
//
for (const key in form) {
if (Object.hasOwnProperty.call(form, key)) {
form[key] = ''
}
}
} else {
//
showToast(response.message || '提交失败,请稍后重试')

@ -124,7 +124,12 @@ const submitForm = async () => {
//
if (response.code === 1) {
showSuccessToast('需求提交成功')
//
//
for (const key in form) {
if (Object.hasOwnProperty.call(form, key)) {
form[key] = ''
}
}
} else {
showToast(response.message || '提交失败,请稍后重试')
}

@ -1,5 +1,5 @@
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue'
import { getHomeConfig } from '../services/api'
import { showToast } from 'vant'
import SubmitAchievementForm from '../components/SubmitAchievementForm.vue'
@ -13,10 +13,16 @@ const config = ref({
introduction: '',
serviceHotline: [],
emails: [],
address: []
officialSite: []
})
const loading = ref(true)
//
const introductionParagraphs = computed(() => {
if (!config.value.introduction) return []
return config.value.introduction.split(/\r\n|\r|\n/).filter(paragraph => paragraph.trim())
})
// <van-icon name="balance-list" /><van-icon name="add-square" />
const tabs = [
{ key: 'achievement', name: '成果清单', icon: 'balance-list', color: '#2196f3', activeColor: '#1976d2' },
@ -83,6 +89,10 @@ onMounted(async () => {
const response = await getHomeConfig()
if (response.code === 1) {
config.value = response.data
//
if (response.data?.siteTitle) {
document.title = response.data.siteTitle
}
} else {
showToast('获取数据失败')
}
@ -115,6 +125,7 @@ onUnmounted(() => {
<!-- 顶部标题区域 -->
<div class="header">
<h1 class="title">{{ config.title }}</h1>
<p class="subtitle">{{ config.subTitle }}</p>
</div>
<!-- 内容区 -->
@ -125,7 +136,11 @@ onUnmounted(() => {
<div v-if="loading" class="loading-container">
<van-skeleton title :row="3" animated />
</div>
<div v-else class="intro-content">{{ config.introduction }}</div>
<div v-else class="intro-content">
<p v-for="(paragraph, index) in introductionParagraphs" :key="index" class="intro-paragraph">
{{ paragraph }}
</p>
</div>
</div>
<!-- 切换按钮区域 -->
@ -152,12 +167,12 @@ onUnmounted(() => {
<div class="tab-content">
<!-- 成果清单 -->
<div v-if="activeTab === 'achievement'" class="tab-panel">
<AchievementList />
<AchievementList :config="config" />
</div>
<!-- 需求清单 -->
<div v-else-if="activeTab === 'demand'" class="tab-panel">
<DemandList />
<DemandList :config="config" />
</div>
<!-- 成果填报 -->
@ -183,13 +198,16 @@ onUnmounted(() => {
<!-- 服务热线 -->
<div class="contact-item-card phone-card">
<div class="contact-item-header">
<van-icon name="phone-o" size="18" color="#2196f3" />
<van-icon name="service-o" size="18" color="#2196f3" />
<span class="contact-item-label">服务热线</span>
</div>
<div class="contact-item-content">
<div v-for="(item, index) in config.serviceHotline" :key="index" class="contact-person">
<span class="person-name">{{ item.name }}</span>
<a :href="`tel:${item.phone}`" class="person-phone">{{ item.phone }}</a>
<a :href="`tel:${item.phone}`" class="person-phone">
<van-icon name="phone" size="14" color="#2196f3" />
<span>{{ item.phone }}</span>
</a>
</div>
</div>
</div>
@ -197,7 +215,7 @@ onUnmounted(() => {
<!-- 邮箱咨询 -->
<div class="contact-item-card email-card">
<div class="contact-item-header">
<van-icon name="email-o" size="18" color="#9c27b0" />
<van-icon name="envelop-o" size="18" color="#9c27b0" />
<span class="contact-item-label">邮箱咨询</span>
</div>
<div class="contact-item-content">
@ -207,16 +225,16 @@ onUnmounted(() => {
</div>
</div>
<!-- 办公地址 -->
<div class="contact-item-card address-card">
<!-- 协会官网 -->
<div class="contact-item-card official-site-card">
<div class="contact-item-header">
<van-icon name="location-o" size="18" color="#4caf50" />
<span class="contact-item-label">办公地址</span>
<van-icon name="link-o" size="18" color="#4caf50" />
<span class="contact-item-label">协会官网</span>
</div>
<div class="contact-item-content">
<div v-for="(addr, index) in config.address" :key="index" class="contact-address">
{{ addr }}
</div>
<a v-for="(site, index) in config.officialSite" :key="index" :href="site" class="contact-official-site" target="_blank">
{{ site }}
</a>
</div>
</div>
</div>
@ -249,6 +267,16 @@ onUnmounted(() => {
word-break: break-word;
}
/* 副标题样式 */
.subtitle {
font-size: 16px;
font-weight: 500;
margin: 8px 0 0 0;
line-height: 1.3;
word-break: break-word;
opacity: 0.9;
}
/* 内容区 */
.content {
padding: 15px;
@ -284,12 +312,26 @@ onUnmounted(() => {
padding: 10px 0;
}
/* 简介内容 */
/* 简介内容容器 */
.intro-content {
display: block;
width: 100%;
box-sizing: border-box;
}
/* 简介段落 */
.intro-paragraph {
font-size: 14px;
line-height: 1.7;
color: #555;
text-align: justify;
text-indent: 2em;
margin: 0 0 12px 0;
word-wrap: break-word;
}
.intro-paragraph:last-child {
margin-bottom: 0;
}
/* 正文占位符 */
@ -377,17 +419,17 @@ onUnmounted(() => {
color: #9c27b0;
}
/* 办公地址卡片样式 */
.address-card {
/* 协会官网卡片样式 */
.official-site-card {
background-color: rgba(76, 175, 80, 0.05);
border-left: 4px solid #4caf50;
}
.address-card .contact-item-label {
.official-site-card .contact-item-label {
color: #4caf50;
}
.address-card .contact-address {
.official-site-card .contact-official-site {
color: #4caf50;
}
@ -414,9 +456,9 @@ onUnmounted(() => {
/* 联系人信息 */
.contact-person {
display: flex;
justify-content: space-between;
flex-direction: row;
align-items: center;
margin-bottom: 8px;
margin-bottom: 12px;
font-size: 14px;
}
@ -428,10 +470,14 @@ onUnmounted(() => {
.person-name {
color: #666;
font-weight: 500;
margin-right: 12px;
}
/* 联系人电话 */
.person-phone {
display: flex;
align-items: center;
gap: 6px;
font-weight: 500;
text-decoration: none;
}
@ -444,12 +490,18 @@ onUnmounted(() => {
font-weight: 500;
}
/* 联系地址 */
.contact-address {
/* 协会官网链接 */
.contact-official-site {
display: block;
font-size: 14px;
font-weight: 500;
line-height: 1.6;
text-decoration: none;
word-break: break-all;
}
.contact-official-site:hover {
text-decoration: underline;
}
/* 切换按钮区域 */

@ -16,6 +16,7 @@ export default defineConfig({
// }),
],
server: {
host: '0.0.0.0', // 允许所有网络接口访问
proxy: {
'/pro-report': {
target: 'http://47.109.32.96:9999',

Loading…
Cancel
Save