2026-06-10 11:00:12 +08:00

1688 lines
42 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="plaza-page">
<div class="floating-orb orb-1"></div>
<div class="floating-orb orb-2"></div>
<div class="floating-orb orb-3"></div>
<div class="floating-orb orb-4"></div>
<section class="hero-section">
<div class="hero-glow glow-blue"></div>
<div class="hero-glow glow-cyan"></div>
<div class="hero-glow glow-warm"></div>
<div class="particle-layer">
<span v-for="item in 30" :key="item" :style="getParticleStyle(item)"></span>
</div>
<div class="hero-inner">
<h1>供需<span>广场</span></h1>
<p>
围绕云资源算力网络服务模型及应用企业可灵活发布资源供给或业务需求
通过精准匹配打破信息壁垒助力企业降本增效
</p>
<div class="hero-actions">
<button type="button" class="primary-btn" @click="sendInfo('2')">
<i class="el-icon-plus"></i>
发布需求
</button>
<button type="button" class="secondary-btn" @click="scrollToList">
<i class="el-icon-search"></i>
浏览产品
</button>
<!-- <button type="button" class="secondary-btn" @click="sendInfo('1')">
<i class="el-icon-s-goods"></i>
发布商品
</button> -->
</div>
</div>
</section>
<section id="demand-list" class="list-section">
<div class="section-head">
<h2>热门<span>产品</span></h2>
<p>AI 行业应用领域开元云为您提供完善的产品服务</p>
</div>
<div class="toolbar">
<div class="search-box">
<i class="el-icon-search"></i>
<input v-model.trim="keyword" type="text" placeholder="搜索产品..." @input="handleSearch">
</div>
</div>
<div class="tab-row">
<button
v-for="tab in plazaTabs"
:key="tab.value"
type="button"
:class="['tab-btn', { active: currentPlazaTab === tab.value }]"
:data-color="tab.color"
@click="switchPlazaTab(tab.value)"
>
{{ tab.label }}
</button>
</div>
<div v-if="filteredPlazaDemandList.length" class="card-grid">
<article v-for="item in filteredPlazaDemandList" :key="item.title" class="demand-card">
<div class="card-top">
<span class="type-tag" :style="getTypeStyle(item)">{{ item.type }}</span>
</div>
<h4>{{ item.title }}</h4>
<p>{{ item.desc }}</p>
<div class="tag-list">
<span v-for="tag in item.tags" :key="tag" :style="getTypeStyle(item)">{{ tag }}</span>
</div>
<div class="card-foot static-card-foot">
<button type="button">
查看详情
<span></span>
</button>
</div>
</article>
</div>
<div v-else class="empty-box">
<img src="./img/empty.svg" alt="暂无数据">
<p>暂无匹配的需求信息</p>
</div>
<!-- 原接口返回产品区域先保留按需求隐藏不删除 -->
<div class="interface-product-block is-hidden">
<div class="tab-row">
<button
type="button"
:class="['tab-btn', { active: !activeFirstId }]"
data-color="blue"
@click="selectFirstCategory('')"
>
全部
</button>
<button
v-for="category in firstCategories"
:key="category.id"
type="button"
:class="['tab-btn', { active: activeFirstId === category.id }]"
:data-color="getTabColor(category)"
@click="selectFirstCategory(category.id)"
>
{{ getCategoryName(category) }}
</button>
</div>
<div v-if="secondCategories.length" class="sub-tab-row">
<button
type="button"
:class="['sub-tab-btn', { active: !activeSecondId }]"
@click="selectSecondCategory('')"
>
全部品类
</button>
<button
v-for="category in secondCategories"
:key="category.id"
type="button"
:class="['sub-tab-btn', { active: activeSecondId === category.id }]"
@click="selectSecondCategory(category.id)"
>
{{ getCategoryName(category) }}
</button>
</div>
<div class="publish-type-row">
<button type="button" :class="{ active: publish_type === '1' }" @click="changePublishType('1')">企业商品</button>
<button type="button" :class="{ active: publish_type === '2' }" @click="changePublishType('2')">企业需求</button>
</div>
<div v-if="filteredProductList.length" class="card-grid">
<article v-for="item in filteredProductList" :key="item.id" class="demand-card">
<div class="card-top">
<span class="type-tag" :style="getTypeStyle(item)">{{ getItemType(item) }}</span>
<span v-if="item.favorite === '1'" class="favorite-tag">已收藏</span>
</div>
<h4>{{ item.product_name || item.name || '未命名产品' }}</h4>
<p>{{ item.requirement_summary || item.description || '暂无描述' }}</p>
<div v-if="item.company_name || item.company_type" class="meta-line">
<span v-if="item.company_name">{{ item.company_name }}</span>
<span v-if="item.company_type">{{ item.company_type }}</span>
</div>
<div class="tag-list">
<span v-for="tag in getItemTags(item)" :key="tag" :style="getTypeStyle(item)">{{ tag }}</span>
</div>
<div class="card-foot">
<span class="price">{{ getPriceText(item) }}</span>
<button type="button" @click="openDetail(item)">
查看详情
<i v-if="loadingStates[item.id]" class="el-icon-loading"></i>
<span v-else></span>
</button>
</div>
</article>
</div>
<div v-else class="empty-box">
<img src="./img/empty.svg" alt="暂无数据">
<p>暂无匹配的需求信息</p>
</div>
<el-pagination
v-if="total > page_size"
class="plaza-pagination"
:current-page.sync="current_page"
:page-size="page_size"
layout="total, prev, pager, next"
:total="total"
@current-change="handleCurrentChange"
/>
</div>
</section>
<section class="cta-section">
<div class="cta-card">
<div class="cta-bg"></div>
<div class="cta-content">
<h2>打破信息壁垒助力降本增效</h2>
<p>发布需求精准匹配与行业伙伴共建云服务生态</p>
<div>
<button type="button" class="primary-btn" @click="sendInfo('2')">
<i class="el-icon-plus"></i>
发布需求
</button>
<button type="button" class="secondary-btn" @click="sendInfo('1')">
<i class="el-icon-office-building"></i>
企业入驻
</button>
</div>
</div>
</div>
</section>
<div v-if="publishDemandVisible" class="modal-overlay active" @click.self="closePublishDemand">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>发布需求</h3>
<button type="button" class="modal-close" @click="closePublishDemand">
<i class="el-icon-close"></i>
</button>
</div>
<div class="modal-body">
<div class="form-item">
<label>所属类别 <span>*</span></label>
<el-cascader
v-model="demandForm.product_category"
class="demand-category-cascader"
:options="demandCategoryOptions"
placeholder="请选择所属类别"
@change="handleDemandCategoryChange"
></el-cascader>
</div>
<div class="form-item">
<label>应用场景</label>
<input v-model.trim="demandForm.scenario" class="form-input" type="text" placeholder="请描述应用场景,如:智能客服、金融风控、医疗影像等">
</div>
<div class="form-item">
<label>需求标题 <span>*</span></label>
<input v-model.trim="demandForm.title" class="form-input" type="text" placeholder="请输入需求标题">
</div>
<div class="form-item">
<label>需求描述 <span>*</span></label>
<textarea v-model.trim="demandForm.desc" class="form-input" placeholder="请详细描述您的需求,包括场景、规模、期望交付方式等"></textarea>
</div>
<div class="form-item">
<label>企业名称 <span>*</span></label>
<input v-model.trim="demandForm.company" class="form-input" type="text" placeholder="请输入企业名称">
</div>
<div class="form-item">
<label>联系人</label>
<input v-model.trim="demandForm.contact" class="form-input" type="text" placeholder="请输入联系人姓名">
</div>
<div class="form-item">
<label>联系方式</label>
<input v-model.trim="demandForm.phone" class="form-input" type="text" placeholder="手机号或邮箱">
</div>
<div class="modal-actions">
<button type="button" class="submit-demand-btn" @click="submitDemand">提交需求</button>
<button type="button" class="cancel-demand-btn" @click="closePublishDemand">取消</button>
</div>
</div>
</div>
</div>
<el-dialog
:title="publish_type === '2' ? '发布需求' : '发布商品'"
width="60vw"
top="5vh"
custom-class="publish-dialog"
:visible.sync="sendProductVisible"
>
<sendProduct v-if="publish_type" :isEdit="false" :publish_type="publish_type" @success="sendProductSuccess"></sendProduct>
</el-dialog>
<el-dialog title="温馨提示" :visible.sync="showTip" width="30%">
<span>您还没有完善企业信息完善企业信息审核通过后您可以发布需求与商品</span>
<span slot="footer" class="dialog-footer">
<span>
<span class="tip-text">跳转到</span>
<el-button size="small" type="primary" @click="goInfo">信息完善</el-button>
</span>
</span>
</el-dialog>
</div>
</template>
<script>
import {
reqCompanyCategorySearch,
reqEnterpriseAuditInfoSearch,
reqGetProductCategorySearch,
reqGetProductDetail,
reqGetSupplyAndDemandSquareList,
reqPublishProductAdd,
reqSupplyAndDemandFirstCategory
} from '@/api/ncmatch'
import { buildCaTree } from '@/views/homePage/ncmatch/mainPage/sendProduct/buildCaTree'
import { mapGetters, mapState } from 'vuex'
export default {
name: 'supplyAndDemandSquare',
components: {
sendProduct: () => import('@/views/homePage/ncmatch/mainPage/sendProduct/index.vue')
},
data() {
return {
page_size: 9,
current_page: 1,
publish_type: '1',
selectedCategory: '',
selectedCompanies: [],
companies: [],
productList: [],
total: 0,
firstCategories: [],
secondCategories: [],
demandCategoryOptions: [],
activeFirstId: '',
activeSecondId: '',
showTip: false,
sendProductVisible: false,
publishDemandVisible: false,
useStaticPlaza: true,
keyword: '',
currentPlazaTab: '全部',
loadingStates: {},
demandForm: this.createDemandForm(),
plazaTabs: [
{ label: '全部', value: '全部', color: 'blue' },
{ label: '大模型', value: '大模型', color: 'orange' },
{ label: '智能体', value: '智能体', color: 'purple' },
{ label: 'AI解决方案', value: '行业解决方案', color: 'green' }
],
plazaDemandData: [
{
title: 'AI Ops能源电力监测模型',
company: '星河能源科技',
type: '大模型',
tags: ['能源', '电力监测'],
desc: '面向能源电力场景的AI运维监测模型实现设备状态智能预警与故障诊断'
},
{
title: '投资决策分析大模型',
company: '银河金融科技',
type: '大模型',
tags: ['投资', '决策分析'],
desc: '面向投资场景的决策分析大模型,实现市场趋势预测与风险评估'
},
{
title: '投标智能体',
company: '鼎盛集团',
type: '智能体',
tags: ['投标', '智能分析'],
desc: '面向投标场景的智能助手,实现投标文件智能生成与合规审查'
},
{
title: '招投标大模型引擎',
company: '仁济医疗科技',
type: '大模型',
tags: ['招投标', '智能引擎'],
desc: '面向招投标场景的大模型智能引擎,实现标书智能分析与生成'
},
{
title: '教学助手智能体',
company: '超算数据中心',
type: '智能体',
tags: ['教育', '智能体'],
desc: '面向教育场景的智能教学助手,实现个性化学习辅导与答疑'
},
{
title: '招标评标智能体',
company: '灵犀AI科技',
type: '智能体',
tags: ['招标', '评标'],
desc: '面向招投标场景的智能评标助手,实现标书智能解析与评分推荐'
},
{
title: 'AI智慧农业',
company: '精工制造集团',
type: '行业解决方案',
tags: ['智慧农业', 'AI'],
desc: '面向农业场景的AI智慧解决方案实现农作物监测与精准灌溉'
}
],
steps: [
{ title: '企业入驻', desc: '完成企业认证,入驻供需广场', icon: 'el-icon-office-building', theme: 'blue' },
{ title: '发布供需', desc: '发布资源供给或业务需求信息', icon: 'el-icon-edit-outline', theme: 'cyan' },
{ title: '精准匹配', desc: 'AI 引擎智能推荐最优合作方', icon: 'el-icon-lightning', theme: 'green' },
{ title: '签约合作', desc: '线上签约,安全高效达成合作', icon: 'el-icon-finished', theme: 'teal' }
]
}
},
computed: {
filteredPlazaDemandList() {
const keyword = this.keyword.toLowerCase()
return this.plazaDemandData.filter(item => {
const matchTab = this.currentPlazaTab === '全部' || item.type === this.currentPlazaTab
const matchSearch = !keyword || [
item.title,
item.company,
item.type,
item.desc,
...(item.tags || [])
].some(value => String(value || '').toLowerCase().includes(keyword))
return matchTab && matchSearch
})
},
filteredProductList() {
if (!this.keyword) return this.productList
const keyword = this.keyword.toLowerCase()
return this.productList.filter(item => {
return [
item.product_name,
item.name,
item.company_name,
item.company_type,
item.requirement_summary,
item.description
].some(value => String(value || '').toLowerCase().includes(keyword))
})
},
isNcmatchHome() {
return window.location.href.includes('ncmatchHome')
},
...mapGetters(['sidebar', 'avatar', 'device']),
...mapState({
isShowPanel: state => state.product.showHomeNav,
navIndex: state => state.product.navIndex,
gridObj: state => state.operationAnalysis.gridObj,
mybalance: state => state.user.mybalance,
logoutUrl: state => state.login.logoutUrl,
loginStateVuex: state => state.login.loginState,
logoInfoNew: state => state.product.logoInfoNew
}),
loginState() {
const userId = sessionStorage.getItem('userId')
return this.loginStateVuex || (userId !== null && userId !== 'null' && userId !== '')
}
},
created() {
this.initDemandCategories()
if (!this.useStaticPlaza) {
this.initAllData()
}
},
methods: {
createDemandForm() {
return {
type: '',
product_category: [],
scenario: '',
title: '',
desc: '',
company: '',
contact: '',
phone: ''
}
},
initDemandCategories() {
return reqGetProductCategorySearch({ url_link: window.location.href, to_page: 'publish' }).then(res => {
if (res.status) {
this.demandCategoryOptions = buildCaTree(res.data || [])
}
return res
})
},
sendInfo(type) {
if (!this.loginState) {
this.$router.push('/login')
return
}
reqEnterpriseAuditInfoSearch({
url_link: window.location.href
}).then(res => {
const dataList = res && res.data && res.data.data
const hasAuditInfo = Array.isArray(dataList) && dataList.length !== 0
const roles = sessionStorage.getItem('jueseNew')
const isCustomer = roles ? roles.includes('客户') : true
if (hasAuditInfo && dataList[0]) {
const auditStatus = dataList[0].audit_status
if (auditStatus === 'pending') {
this.$message.warning('您的审核状态为待审核,请等待审核通过后发布~')
} else if (auditStatus === 'rejected') {
this.$message.warning('您的审核状态为驳回,请重新提交~')
} else {
this.openPublishByType(type)
}
} else if (!isCustomer) {
this.openPublishByType(type)
} else {
this.showTip = true
}
})
},
openPublishByType(type) {
this.publish_type = type
if (type === '2') {
this.publishDemandVisible = true
} else {
this.sendProductVisible = true
}
},
closePublishDemand() {
this.publishDemandVisible = false
this.resetDemandForm()
},
resetDemandForm() {
this.demandForm = this.createDemandForm()
},
submitDemand() {
if (!this.demandForm.product_category || !this.demandForm.product_category.length) {
this.$message.warning('请选择所属类别')
return
}
if (!this.demandForm.title) {
this.$message.warning('请输入需求标题')
return
}
if (!this.demandForm.desc) {
this.$message.warning('请输入需求描述')
return
}
if (!this.demandForm.company) {
this.$message.warning('请输入企业名称')
return
}
const formData = new FormData()
formData.append('url_link', window.location.href)
formData.append('publish_type', '2')
formData.append('product_category', this.demandForm.product_category)
formData.append('product_name', this.demandForm.title)
formData.append('requirement_summary', this.demandForm.desc)
formData.append('company_name', this.demandForm.company)
formData.append('contact_person', this.demandForm.contact)
formData.append('phone_number', this.demandForm.phone)
formData.append('application_scenario', this.demandForm.scenario)
const submittedDemand = {
title: this.demandForm.title,
company: this.demandForm.company,
type: this.demandForm.type,
scenario: this.demandForm.scenario,
tags: [this.demandForm.type].filter(Boolean),
desc: this.demandForm.desc
}
reqPublishProductAdd(formData).then(res => {
if (res.status) {
this.plazaDemandData.unshift(submittedDemand)
this.currentPlazaTab = '全部'
this.publishDemandVisible = false
this.resetDemandForm()
this.$message.success(res.msg || '需求发布成功!')
} else {
this.$message.error(res.msg || '需求发布失败')
}
}).catch(() => {
this.$message.error('需求发布失败,请稍后重试')
})
},
handleDemandCategoryChange(value) {
const lastValue = Array.isArray(value) ? value[value.length - 1] : value
const node = this.findCategoryNode(this.demandCategoryOptions, lastValue)
this.demandForm.type = node ? node.label : ''
},
findCategoryNode(options, value) {
for (const item of options || []) {
if (item.value === value) return item
const child = this.findCategoryNode(item.children || [], value)
if (child) return child
}
return null
},
sendProductSuccess() {
this.sendProductVisible = false
this.initData()
},
goInfo() {
this.showTip = false
this.$router.push('/customer/approve')
},
scrollToList() {
const target = document.getElementById('demand-list')
if (target) target.scrollIntoView({ behavior: 'smooth' })
},
handleSearch() {
this.current_page = 1
},
switchPlazaTab(tab) {
this.currentPlazaTab = tab
},
handleCurrentChange(val) {
this.current_page = val
this.initData()
},
initAllData() {
this.init_product_category().then(() => {
this.init_company_category().then(() => {
this.initData()
})
})
},
init_company_category() {
return reqCompanyCategorySearch({ url_link: window.location.href }).then(res => {
if (res.status) {
this.companies = (res.data || []).map(item => ({
label: item.company_category,
value: item.company_category
}))
}
return res
})
},
init_product_category() {
return reqSupplyAndDemandFirstCategory({ to_page: 'show', url_link: window.location.href }).then(res => {
if (res.status) {
this.firstCategories = Array.isArray(res.data) ? res.data : []
this.activeFirstId = ''
this.activeSecondId = ''
this.selectedCategory = ''
this.secondCategories = []
}
return res
})
},
loadSecondCategories(firstId) {
if (!firstId) {
this.secondCategories = []
return Promise.resolve()
}
return reqSupplyAndDemandFirstCategory({ to_page: 'show', url_link: window.location.href, first_level_id: firstId }).then(res => {
this.secondCategories = res.status && Array.isArray(res.data) ? res.data : []
return res
})
},
selectFirstCategory(id) {
if (id === this.activeFirstId) return
this.current_page = 1
this.activeFirstId = id
this.activeSecondId = ''
this.selectedCategory = id || ''
this.loadSecondCategories(id).then(() => {
this.initData()
})
},
selectSecondCategory(id) {
if (id === this.activeSecondId) return
this.current_page = 1
this.activeSecondId = id
this.selectedCategory = id || this.activeFirstId || ''
this.initData()
},
changePublishType(type) {
if (this.publish_type === type) return
this.publish_type = type
this.current_page = 1
this.initData()
},
initData() {
const payload = {
product_category: this.selectedCategory,
to_page: 'square',
url_link: window.location.href,
page_size: this.page_size,
current_page: this.current_page,
company_type: this.selectedCompanies.length > 0 ? this.selectedCompanies.join(',') : '',
publish_type: this.publish_type
}
reqGetSupplyAndDemandSquareList(payload).then(res => {
if (res.status) {
const data = Array.isArray(res.data) ? res.data : []
const first = data[0] || {}
this.productList = first.product_list || []
this.total = Number(first.total_count || 0)
}
})
},
selectCompany(companyId) {
const index = this.selectedCompanies.indexOf(companyId)
if (index > -1) {
this.selectedCompanies.splice(index, 1)
} else {
this.selectedCompanies.push(companyId)
}
this.initData()
},
openDetail(item) {
this.$set(this.loadingStates, item.id, true)
reqGetProductDetail({
id: item.productid ? item.productid : item.id,
from: 'f'
}).then(res => {
if (res.status) {
this.$store.commit('SETPRODUCTDETAIL', res.data)
this.$store.commit('SHOWPRODUCTDETAIL', true)
} else {
this.$message.error(res.msg)
}
}).finally(() => {
this.$set(this.loadingStates, item.id, false)
})
},
getCategoryName(category) {
return category.product_category || category.label || category.name || '未分类'
},
getTabColor(category) {
const name = this.getCategoryName(category)
if (name.includes('模型')) return 'orange'
if (name.includes('智能体')) return 'purple'
if (name.includes('方案') || name.includes('解决')) return 'green'
return 'blue'
},
getItemType(item) {
return item.type || item.company_type || item.product_category || (this.publish_type === '1' ? '企业商品' : '企业需求')
},
getTypeStyle(item) {
const type = this.getItemType(item)
const colors = type.includes('模型')
? { bg: 'rgba(245,158,11,0.08)', text: '#d97706', border: 'rgba(245,158,11,0.15)' }
: type.includes('智能体')
? { bg: 'rgba(139,92,246,0.08)', text: '#7c3aed', border: 'rgba(139,92,246,0.15)' }
: type.includes('方案') || type.includes('解决')
? { bg: 'rgba(16,185,129,0.08)', text: '#059669', border: 'rgba(16,185,129,0.15)' }
: { bg: 'rgba(45,91,255,0.08)', text: '#2d5bff', border: 'rgba(45,91,255,0.15)' }
return {
background: colors.bg,
color: colors.text,
borderColor: colors.border
}
},
getItemTags(item) {
if (Array.isArray(item.tags)) return item.tags
const tags = [
item.company_type,
item.product_category,
item.unit,
item.short_term === '1' ? '可短租' : ''
].filter(Boolean)
return Array.from(new Set(tags)).slice(0, 3)
},
getPriceText(item) {
if (!item.discount_price && item.discount_price !== 0) return '面议'
return `¥${item.discount_price}${item.unit ? ` / ${item.unit}` : ''}`
},
getParticleStyle(index) {
const size = 2 + (index % 4)
return {
width: `${size}px`,
height: `${size}px`,
left: `${(index * 23) % 100}%`,
top: `${(index * 37) % 100}%`,
animationDelay: `${(index % 8) * 0.4}s`,
animationDuration: `${6 + (index % 5)}s`
}
}
}
}
</script>
<style scoped lang="scss">
.plaza-page {
position: relative;
min-height: 100vh;
overflow-x: hidden;
background: linear-gradient(180deg, #f0f7ff 0%, #ffffff 60%, #f5f8ff 100%);
color: #1a1a1a;
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.floating-orb {
position: fixed;
z-index: 0;
border-radius: 50%;
filter: blur(80px);
opacity: 0.08;
pointer-events: none;
}
.orb-1 {
top: -100px;
left: -100px;
width: 400px;
height: 400px;
background: #3b82f6;
animation: float1 20s ease-in-out infinite;
}
.orb-2 {
right: -150px;
bottom: -150px;
width: 500px;
height: 500px;
background: #3b82f6;
animation: float2 25s ease-in-out infinite;
}
.orb-3 {
top: 50%;
left: 50%;
width: 300px;
height: 300px;
background: #ec4899;
animation: float3 18s ease-in-out infinite;
}
.orb-4 {
right: 20%;
bottom: 30%;
width: 350px;
height: 350px;
background: #f59e0b;
animation: float1 22s ease-in-out infinite reverse;
}
.hero-section {
position: relative;
z-index: 1;
overflow: hidden;
padding: 96px 24px 64px;
}
.hero-glow {
position: absolute;
border-radius: 999px;
pointer-events: none;
}
.glow-blue {
top: 80px;
left: 80px;
width: 384px;
height: 384px;
background: rgba(59, 130, 246, 0.2);
filter: blur(120px);
}
.glow-cyan {
right: 80px;
bottom: 80px;
width: 500px;
height: 500px;
background: rgba(59, 130, 246, 0.2);
filter: blur(150px);
}
.glow-warm {
top: 50%;
left: 50%;
width: 600px;
height: 600px;
background: rgba(245, 158, 11, 0.1);
filter: blur(180px);
transform: translate(-50%, -50%);
}
.particle-layer {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
span {
position: absolute;
border-radius: 50%;
background: rgba(45, 91, 255, 0.08);
animation: particleFloat 8s linear infinite;
}
}
.hero-inner {
position: relative;
z-index: 2;
max-width: 960px;
margin: 0 auto;
text-align: center;
h1 {
margin: 0 0 24px;
color: #111827;
font-size: 72px;
font-weight: 800;
line-height: 1;
span {
background: linear-gradient(135deg, #2d5bff 0%, #3b82f6 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
}
p {
max-width: 780px;
margin: 0 auto;
color: #6b7280;
font-size: 20px;
line-height: 1.8;
}
}
.hero-actions,
.cta-content > div {
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
justify-content: center;
margin-top: 40px;
}
.primary-btn,
.secondary-btn {
display: inline-flex;
gap: 8px;
align-items: center;
justify-content: center;
padding: 14px 32px;
border: 0;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
transition: all 0.3s ease;
}
.primary-btn {
position: relative;
overflow: hidden;
color: #fff;
background: linear-gradient(90deg, #2563eb, #1d4ed8);
box-shadow: 0 10px 20px rgba(59, 130, 246, 0.2);
&:hover {
transform: translateY(-2px);
box-shadow: 0 12px 28px rgba(45, 91, 255, 0.3);
}
}
.secondary-btn {
color: #374151;
background: #fff;
border: 1px solid #e5e7eb;
&:hover {
color: #2563eb;
background: #eff6ff;
border-color: #93c5fd;
transform: translateY(-2px);
}
}
.process-section,
.list-section,
.cta-section {
position: relative;
z-index: 1;
max-width: 1280px;
margin: 0 auto;
padding: 0 24px 80px;
}
.section-subtitle {
margin: 0 0 48px;
color: #6b7280;
font-size: 18px;
text-align: center;
}
.step-list {
display: flex;
align-items: stretch;
justify-content: center;
}
.step-wrap {
display: flex;
flex: 1;
align-items: center;
}
.step-card {
flex: 1;
min-height: 178px;
padding: 24px;
text-align: center;
border: 1px solid rgba(59, 130, 246, 0.12);
border-radius: 16px;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.08), rgba(147, 197, 253, 0.12));
box-shadow: 0 4px 20px rgba(59, 130, 246, 0.06);
transition: all 0.3s ease;
&:hover {
border-color: rgba(59, 130, 246, 0.3);
box-shadow: 0 12px 32px rgba(59, 130, 246, 0.15);
transform: translateY(-6px);
}
&.cyan {
background: linear-gradient(135deg, rgba(6, 182, 212, 0.08), rgba(103, 232, 249, 0.12));
}
&.green {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.08), rgba(110, 231, 183, 0.12));
}
&.teal {
background: linear-gradient(135deg, rgba(20, 184, 166, 0.08), rgba(94, 234, 212, 0.12));
}
h4 {
margin: 0 0 8px;
color: #111827;
font-size: 16px;
font-weight: 600;
}
p {
margin: 0;
color: #6b7280;
font-size: 14px;
}
}
.step-icon {
display: flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
margin: 0 auto 16px;
color: #3b82f6;
font-size: 28px;
border: 1px solid rgba(59, 130, 246, 0.3);
border-radius: 16px;
background: rgba(59, 130, 246, 0.1);
}
.step-arrow {
flex: 0 0 40px;
color: #60a5fa;
font-size: 24px;
text-align: center;
}
.section-head {
margin-bottom: 40px;
text-align: center;
h2 {
margin: 0 0 16px;
color: #111827;
font-size: 40px;
font-weight: 700;
span {
background: linear-gradient(135deg, #2d5bff 0%, #3b82f6 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
}
p {
margin: 0;
color: #6b7280;
font-size: 18px;
}
}
.toolbar {
display: flex;
justify-content: center;
margin-bottom: 16px;
}
.search-box {
display: flex;
gap: 12px;
align-items: center;
width: 100%;
max-width: 672px;
padding: 12px 20px;
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 16px;
background: rgba(255, 255, 255, 0.85);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
backdrop-filter: blur(20px);
i {
color: #64748b;
font-size: 18px;
}
input {
flex: 1;
color: #333;
font-size: 14px;
background: transparent;
border: none;
outline: none;
&::placeholder {
color: #94a3b8;
}
}
}
.tab-row,
.sub-tab-row,
.publish-type-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: center;
justify-content: center;
margin-bottom: 24px;
}
.tab-btn,
.sub-tab-btn,
.publish-type-row button {
padding: 8px 20px;
color: #64748b;
font-size: 14px;
font-weight: 500;
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 999px;
background: rgba(255, 255, 255, 0.8);
transition: all 0.2s ease;
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transform: translateY(-1px);
}
&.active {
color: #fff;
background: #2d5bff;
border-color: #2d5bff;
box-shadow: 0 2px 8px rgba(45, 91, 255, 0.25);
}
}
.tab-btn.active[data-color='orange'] {
background: #f59e0b;
border-color: #f59e0b;
}
.tab-btn.active[data-color='purple'] {
background: #8b5cf6;
border-color: #8b5cf6;
}
.tab-btn.active[data-color='green'] {
background: #10b981;
border-color: #10b981;
}
.interface-product-block.is-hidden {
display: none;
}
.sub-tab-row {
margin-top: -8px;
}
.sub-tab-btn {
padding: 6px 16px;
font-size: 13px;
}
.publish-type-row {
margin-top: -8px;
button.active {
background: #111827;
border-color: #111827;
}
}
.card-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 20px;
}
.demand-card {
padding: 24px;
cursor: pointer;
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
transition: all 0.3s ease;
&:hover {
border-color: rgba(45, 91, 255, 0.15);
box-shadow: 0 8px 24px rgba(45, 91, 255, 0.08);
transform: translateY(-3px);
}
h4 {
margin: 0 0 8px;
color: #1a1a1a;
font-size: 16px;
font-weight: 600;
}
p {
display: -webkit-box;
min-height: 66px;
margin: 0 0 12px;
overflow: hidden;
color: #666;
font-size: 14px;
line-height: 1.6;
text-overflow: ellipsis;
word-break: break-all;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
line-clamp: 3;
}
}
.card-top,
.card-foot,
.meta-line {
display: flex;
gap: 10px;
align-items: center;
justify-content: space-between;
}
.card-top {
margin-bottom: 12px;
}
.type-tag,
.favorite-tag,
.tag-list span {
display: inline-block;
padding: 3px 10px;
font-size: 12px;
font-weight: 500;
border: 1px solid;
border-radius: 6px;
}
.favorite-tag {
color: #059669;
background: rgba(16, 185, 129, 0.08);
border-color: rgba(16, 185, 129, 0.15);
}
.meta-line {
justify-content: flex-start;
margin-bottom: 12px;
color: #6b7280;
font-size: 12px;
}
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
min-height: 26px;
margin-bottom: 12px;
}
.card-foot {
.price {
color: #ef4444;
font-size: 14px;
font-weight: 600;
}
button {
display: inline-flex;
gap: 4px;
align-items: center;
padding: 0;
color: #2d5bff;
font-size: 13px;
background: transparent;
border: none;
}
}
.static-card-foot {
justify-content: flex-end;
}
.empty-box {
display: flex;
min-height: 360px;
flex-direction: column;
align-items: center;
justify-content: center;
color: #7a82a0;
font-size: 16px;
img {
width: 150px;
height: 150px;
margin-bottom: 12px;
}
}
.plaza-pagination {
display: flex;
justify-content: center;
margin-top: 32px;
}
.cta-card {
position: relative;
max-width: 1000px;
padding: 56px;
margin: 0 auto;
overflow: hidden;
text-align: center;
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
backdrop-filter: blur(20px);
&::before,
&::after {
position: absolute;
width: 100%;
height: 2px;
content: '';
background: linear-gradient(90deg, transparent, #2d5bff, transparent);
animation: borderMove 3s linear infinite;
}
&::before {
top: 0;
left: -100%;
}
&::after {
right: -100%;
bottom: 0;
animation-direction: reverse;
}
}
.cta-bg {
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(37, 99, 235, 0.1), transparent, rgba(8, 145, 178, 0.1));
}
.cta-content {
position: relative;
z-index: 1;
h2 {
margin: 0 0 16px;
color: #111827;
font-size: 36px;
font-weight: 700;
}
p {
max-width: 576px;
margin: 0 auto 32px;
color: #6b7280;
font-size: 18px;
}
}
.tip-text {
margin-right: 10px;
}
::v-deep .publish-dialog {
overflow: hidden;
background: linear-gradient(135deg, #ffffff, #f8faff);
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 20px;
box-shadow: 0 24px 80px rgba(15, 23, 42, 0.24);
.el-dialog__header {
padding: 24px;
border-bottom: 1px solid #f3f4f6;
}
.el-dialog__title {
color: #111827;
font-size: 20px;
font-weight: 700;
}
.el-dialog__headerbtn {
top: 24px;
right: 24px;
width: 32px;
height: 32px;
background: #f3f4f6;
border-radius: 8px;
transition: all 0.2s ease;
&:hover {
background: #e5e7eb;
}
}
.el-dialog__body {
max-height: 72vh;
padding: 24px;
overflow-y: auto;
}
}
.modal-overlay {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
visibility: hidden;
background: rgba(0, 0, 0, 0.7);
opacity: 0;
backdrop-filter: blur(8px);
transition: all 0.3s ease;
&.active {
visibility: visible;
opacity: 1;
}
}
.modal-content {
width: 90%;
max-width: 600px;
max-height: 85vh;
overflow-y: auto;
background: linear-gradient(135deg, #ffffff, #f8faff);
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 20px;
box-shadow: 0 24px 80px rgba(15, 23, 42, 0.24);
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24px;
border-bottom: 1px solid #f3f4f6;
h3 {
margin: 0;
color: #111827;
font-size: 20px;
font-weight: 700;
}
}
.modal-close {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
color: #9ca3af;
background: #f3f4f6;
border: none;
border-radius: 8px;
transition: all 0.2s ease;
&:hover {
color: #4b5563;
background: #e5e7eb;
}
}
.modal-body {
padding: 24px;
}
.form-item {
margin-bottom: 20px;
label {
display: block;
margin-bottom: 8px;
color: #4b5563;
font-size: 14px;
font-weight: 500;
span {
color: #ef4444;
}
}
}
.form-input,
.form-select {
width: 100%;
padding: 10px 14px;
color: #333;
font-size: 14px;
background: #f5f7fa;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 10px;
outline: none;
transition: border-color 0.2s ease;
&:focus {
border-color: rgba(45, 91, 255, 0.4);
}
&::placeholder {
color: #999;
}
}
.form-select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23666' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10l-5 5z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
}
.demand-category-cascader {
width: 100%;
::v-deep .el-input__inner {
height: 42px;
color: #333;
background: #f5f7fa;
border-color: rgba(0, 0, 0, 0.1);
border-radius: 10px;
&:focus {
border-color: rgba(45, 91, 255, 0.4);
}
}
}
textarea.form-input {
min-height: 100px;
resize: vertical;
}
.modal-actions {
display: flex;
gap: 12px;
align-items: center;
}
.submit-demand-btn,
.cancel-demand-btn {
padding: 12px 24px;
font-size: 14px;
font-weight: 600;
border: none;
border-radius: 12px;
transition: all 0.2s ease;
}
.submit-demand-btn {
flex: 1;
color: #fff;
background: linear-gradient(90deg, #2563eb, #1d4ed8);
box-shadow: 0 10px 15px rgba(59, 130, 246, 0.2);
&:hover {
background: linear-gradient(90deg, #3b82f6, #2563eb);
}
}
.cancel-demand-btn {
color: #4b5563;
background: #f3f4f6;
border: 1px solid #e5e7eb;
&:hover {
background: #e5e7eb;
}
}
@keyframes float1 {
0%,
100% {
transform: translate(0, 0);
}
33% {
transform: translate(50px, -50px);
}
66% {
transform: translate(-30px, 30px);
}
}
@keyframes float2 {
0%,
100% {
transform: translate(0, 0);
}
33% {
transform: translate(-40px, 40px);
}
66% {
transform: translate(60px, -30px);
}
}
@keyframes float3 {
0%,
100% {
transform: translate(-50%, -50%);
}
33% {
transform: translate(calc(-50% + 40px), calc(-50% - 40px));
}
66% {
transform: translate(calc(-50% - 30px), calc(-50% + 30px));
}
}
@keyframes particleFloat {
0% {
opacity: 0;
transform: translateY(0) scale(0);
}
10% {
opacity: 1;
transform: translateY(-20px) scale(1);
}
90% {
opacity: 1;
transform: translateY(-200px) scale(1);
}
100% {
opacity: 0;
transform: translateY(-240px) scale(0.5);
}
}
@keyframes borderMove {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
@media (max-width: 1024px) {
.card-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.step-list {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.step-wrap {
display: block;
}
.step-arrow {
display: none;
}
}
@media (max-width: 768px) {
.hero-inner h1 {
font-size: 48px;
}
.hero-inner p {
font-size: 16px;
}
.process-section,
.list-section,
.cta-section {
padding-right: 16px;
padding-left: 16px;
}
.card-grid,
.step-list {
grid-template-columns: 1fr;
}
.cta-card {
padding: 36px 20px;
}
}
</style>