| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493 |
- <template>
- <div class="wta-page">
- <!-- ══════════════════════════════════════
- HERO BANNER
- ══════════════════════════════════════ -->
- <div class="wta-hero">
- <!-- 背景装饰层 -->
- <div class="wta-hero__grid"></div>
- <div class="wta-hero__orb wta-hero__orb--1"></div>
- <div class="wta-hero__orb wta-hero__orb--2"></div>
- <div class="wta-hero__orb wta-hero__orb--3"></div>
- <!-- 面包屑 -->
- <div class="container wta-hero__breadcrumb-wrap">
- <Breadcrumb />
- </div>
- <!-- 底部波浪 Canvas(放在内容前,z-index 低于内容)-->
- <canvas ref="waveCanvasRef" class="wta-hero__wave-canvas"></canvas>
- <!-- 主标题区 -->
- <div class="container wta-hero__content">
- <div class="wta-hero__badge">
- <span class="wta-hero__badge-dot"></span>
- <span>{{ $t('workflowTrade.heroBadge') }}</span>
- </div>
- <h1 class="wta-hero__title">{{ t('workflowTradeAdd.publishDemand') }}</h1>
- <p class="wta-hero__subtitle">{{ t('workflowTradeAdd.publishDemandTip') }}</p>
- <!-- 统计数字装饰 -->
- <div class="wta-hero__stats">
- <div class="wta-hero__stat">
- <span class="wta-hero__stat-num">12,847</span>
- <span class="wta-hero__stat-label">{{ $t('workflowTrade.statPublished') }}</span>
- </div>
- <div class="wta-hero__stat-divider"></div>
- <div class="wta-hero__stat">
- <span class="wta-hero__stat-num">9,632</span>
- <span class="wta-hero__stat-label">{{ $t('workflowTrade.statSuccess') }}</span>
- </div>
- <div class="wta-hero__stat-divider"></div>
- <div class="wta-hero__stat">
- <span class="wta-hero__stat-num">98.6%</span>
- <span class="wta-hero__stat-label">{{ $t('workflowTrade.statRate') }}</span>
- </div>
- </div>
- </div>
- </div>
- <!-- ══════════════════════════════════════
- 步骤进度条(sticky sentinel + 步骤条)
- ══════════════════════════════════════ -->
- <!-- Sentinel:用于检测步骤条是否滚出视口 -->
- <div ref="stepsSentinelRef" class="wta-steps-sentinel"></div>
- <div class="wta-steps-wrap" :class="{ 'wta-steps-wrap--sticky': isSticky }">
- <div class="container">
- <div class="wta-steps">
- <div v-for="(step, idx) in steps" :key="idx"
- class="wta-step"
- :class="{ 'wta-step--active': idx === currentStep, 'wta-step--done': idx < currentStep }"
- style="cursor: pointer"
- @click="scrollToSection(idx)">
- <div class="wta-step__circle">
- <svg v-if="idx < currentStep" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>
- <span v-else>{{ idx + 1 }}</span>
- </div>
- <span class="wta-step__label">{{ step }}</span>
- <div v-if="idx < steps.length - 1" class="wta-step__line"
- :class="{ 'wta-step__line--done': idx < currentStep }"></div>
- </div>
- </div>
- </div>
- </div>
- <!-- ══════════════════════════════════════
- 主体内容
- ══════════════════════════════════════ -->
- <div class="container wta-body">
- <div class="wta-layout">
- <!-- 左侧表单 -->
- <div class="wta-form-col">
- <el-form :model="ruleForm" :rules="rules" ref="ruleFormRef" label-position="top">
- <!-- ── 基本信息 ── -->
- <div class="wta-card" id="section-basic">
- <div class="wta-card__header">
- <div class="wta-card__header-icon">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
- </div>
- <div>
- <div class="wta-card__title">{{ t('workflowTradeAdd.basicInfo') }}</div>
- <div class="wta-card__subtitle">{{ t('workflowTradeAdd.basicInfoSubtitle') }}</div>
- </div>
- <div class="wta-card__step-badge">Step 1</div>
- </div>
- <div class="wta-card__body">
- <!-- 需求标题 -->
- <el-form-item prop="title">
- <template #label>
- <div class="wta-field-label">
- <span class="wta-required">*</span>
- <span class="wta-label-text">{{ t('workflowTradeAdd.demandTitle') }}</span>
- <el-tooltip content="一个好的标题能吸引更多专业人才关注" placement="top">
- <span class="wta-label-info-icon">
- <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
- </span>
- </el-tooltip>
- </div>
- </template>
- <div class="wta-input-wrap">
- <span class="wta-input-prefix-icon">
- <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
- </span>
- <el-input
- v-model="ruleForm.title"
- :placeholder="t('workflowTradeAdd.placeholderDemandTitle')"
- maxlength="50"
- show-word-limit
- class="wta-input wta-input--has-prefix"
- @focus="scrollToSection(0)"
- />
- </div>
- <!-- 字数进度条 -->
- <div class="wta-char-progress">
- <div class="wta-char-progress__bar">
- <div class="wta-char-progress__fill" :style="{ width: (ruleForm.title.length / 50 * 100) + '%', background: ruleForm.title.length > 40 ? '#f59e0b' : 'linear-gradient(90deg,#6366f1,#a855f7)' }"></div>
- </div>
- <span class="wta-char-progress__text" :class="{ 'wta-char-progress__text--warn': ruleForm.title.length > 40 }">{{ ruleForm.title.length }}/50</span>
- </div>
- </el-form-item>
- <!-- 工作流类型 -->
- <el-form-item prop="categoryId3">
- <template #label>
- <div class="wta-field-label">
- <span class="wta-required">*</span>
- <span class="wta-label-text">{{ t('workflowTradeAdd.workflowType') }}</span>
- <span class="wta-label-hint">{{ t('workflowTradeAdd.workflowTypeTip') }}</span>
- </div>
- </template>
- <div class="wta-input-wrap">
- <span class="wta-input-prefix-icon">
- <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
- </span>
- <el-cascader
- v-model="categoryIdList"
- :options="categoryListTree"
- :placeholder="t('workflowTradeAdd.placeholderWorkflowType')"
- style="width:100%"
- class="wta-cascader wta-input--has-prefix"
- :props="{ label: 'categoryName', value: 'categoryId', children: 'children' }"
- />
- </div>
- </el-form-item>
- </div>
- </div>
- <!-- ── 详细内容 ── -->
- <div class="wta-card mt20" id="section-detail">
- <div class="wta-card__header">
- <div class="wta-card__header-icon wta-card__header-icon--purple">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
- </div>
- <div>
- <div class="wta-card__title">{{ t('workflowTradeAdd.detailInfo') }}</div>
- <div class="wta-card__subtitle">{{ t('workflowTradeAdd.detailInfoSubtitle') }}</div>
- </div>
- <div class="wta-card__step-badge wta-card__step-badge--purple">Step 2</div>
- </div>
- <div class="wta-card__body">
- <!-- 需求背景 -->
- <el-form-item :label="$t('workflowTradeAdd.background')" prop="background">
- <template #label>
- <div class="wta-field-label">
- <span class="wta-required">*</span>
- <span class="wta-label-text">{{ $t('workflowTradeAdd.background') }}</span>
- </div>
- </template>
- <el-input
- v-model="ruleForm.background"
- :placeholder="t('workflowTradeAdd.placeholderBackground')"
- maxlength="500"
- type="textarea"
- show-word-limit
- :rows="6"
- class="wta-textarea"
- @focus="scrollToSection(1)"
- />
- <!-- 字数进度条 -->
- <div class="wta-char-progress">
- <div class="wta-char-progress__bar">
- <div class="wta-char-progress__fill" :style="{ width: (ruleForm.background.length / 500 * 100) + '%' }"></div>
- </div>
- <span class="wta-char-progress__text">{{ ruleForm.background.length }}/500</span>
- </div>
- </el-form-item>
- <!-- 具体需求 -->
- <el-form-item prop="requirements">
- <template #label>
- <div class="wta-field-label">
- <span class="wta-required">*</span>
- <span class="wta-label-text">{{ $t('workflowTradeAdd.requirements') }}</span>
- <el-tooltip content="建议包含:功能需求、技术要求、交付标准" placement="top">
- <span class="wta-label-info-icon">
- <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
- </span>
- </el-tooltip>
- </div>
- </template>
- <!-- 写作提示卡片 -->
- <div class="wta-writing-tips">
- <div class="wta-writing-tips__item" v-for="tip in writingTips" :key="tip">
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
- <span>{{ tip }}</span>
- </div>
- </div>
- <el-input
- v-model="ruleForm.requirements"
- :rows="8"
- :placeholder="t('workflowTradeAdd.requirementsTip') + t('workflowTradeAdd.requirementsTip1')"
- maxlength="2000"
- type="textarea"
- show-word-limit
- class="wta-textarea"
- />
- <div class="wta-char-progress">
- <div class="wta-char-progress__bar">
- <div class="wta-char-progress__fill" :style="{ width: (ruleForm.requirements.length / 2000 * 100) + '%' }"></div>
- </div>
- <span class="wta-char-progress__text">{{ ruleForm.requirements.length }}/2000</span>
- </div>
- </el-form-item>
- </div>
- </div>
- <!-- ── 项目信息 ── -->
- <div class="wta-card mt20" id="section-project">
- <div class="wta-card__header">
- <div class="wta-card__header-icon wta-card__header-icon--green">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
- </div>
- <div>
- <div class="wta-card__title">{{ $t('workflowTradeAdd.projectInfo') }}</div>
- <div class="wta-card__subtitle">{{ t('workflowTradeAdd.projectInfoSubtitle') }}</div>
- </div>
- <div class="wta-card__step-badge wta-card__step-badge--green">Step 3</div>
- </div>
- <div class="wta-card__body">
- <!-- 预算范围 -->
- <div class="wta-budget-row">
- <div class="wta-budget-item">
- <div class="wta-field-label mb8">
- <span class="wta-label-text">{{ $t('workflowTradeAdd.budgetLowerLimit') }}</span>
- <span class="wta-currency-badge">¥ RMB</span>
- </div>
- <el-form-item prop="budgetMin" style="margin-bottom:0">
- <div class="wta-input-wrap">
- <span class="wta-input-prefix-icon">
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
- </span>
- <el-input v-model="ruleForm.budgetMin" :placeholder="$t('workflowTradeAdd.placeholderBudgetLowerLimit')" maxlength="11" type="number" class="wta-input wta-input--has-prefix" @focus="scrollToSection(2)" />
- </div>
- </el-form-item>
- </div>
- <div class="wta-budget-range-icon">
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#a5b4fc" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
- </div>
- <div class="wta-budget-item">
- <div class="wta-field-label mb8">
- <span class="wta-label-text">{{ $t('workflowTradeAdd.budgetUpperLimit') }}</span>
- <span class="wta-currency-badge">¥ RMB</span>
- </div>
- <el-form-item prop="budgetMax" style="margin-bottom:0">
- <div class="wta-input-wrap">
- <span class="wta-input-prefix-icon">
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
- </span>
- <el-input v-model="ruleForm.budgetMax" :placeholder="$t('workflowTradeAdd.placeholderBudgetUpperLimit')" maxlength="11" type="number" class="wta-input wta-input--has-prefix" @focus="scrollToSection(2)" />
- </div>
- </el-form-item>
- </div>
- </div>
- <div class="wta-budget-hint">
- <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#6366f1" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
- <span>{{ $t('workflowTradeAdd.budgetLowerLimitTip') }}</span>
- </div>
- <!-- 报名截止日期 -->
- <el-form-item :label="$t('common.signUpDeadline')" prop="deadline" class="mt16">
- <template #label>
- <div class="wta-field-label">
- <span class="wta-required">*</span>
- <span class="wta-label-text">{{ $t('common.signUpDeadline') }}</span>
- </div>
- </template>
- <div class="wta-input-wrap">
- <span class="wta-input-prefix-icon">
- <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
- </span>
- <el-date-picker
- v-model="ruleForm.deadline"
- type="date"
- value-format="YYYY-MM-DD"
- :placeholder="$t('workflowTradeAdd.selectSignUpDeadline')"
- :disabled-date="time => time.getTime() < Date.now() - 8.64e7"
- style="width:100%"
- class="wta-datepicker wta-input--has-prefix"
- />
- </div>
- </el-form-item>
- </div>
- </div>
- <!-- ── 联系方式 ── -->
- <div class="wta-card mt20" id="section-contact">
- <div class="wta-card__header">
- <div class="wta-card__header-icon wta-card__header-icon--pink">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.6 1.18h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 8.73a16 16 0 0 0 6.29 6.29l.91-.91a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
- </div>
- <div>
- <div class="wta-card__title">{{ $t('workflowTradeAdd.contactInfo') }}</div>
- <div class="wta-card__subtitle">{{ t('workflowTradeAdd.contactInfoSubtitle') }}</div>
- </div>
- <div class="wta-card__step-badge wta-card__step-badge--pink">Step 4</div>
- </div>
- <div class="wta-card__body">
- <div class="wta-contact-grid">
- <!-- 手机号 -->
- <el-form-item prop="phone">
- <template #label>
- <div class="wta-field-label">
- <span class="wta-required">*</span>
- <span class="wta-label-text">{{ $t('workflowTradeAdd.phoneNumber') }}</span>
- </div>
- </template>
- <div class="wta-input-wrap">
- <span class="wta-input-prefix-icon">
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.6 1.18h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 8.73a16 16 0 0 0 6.29 6.29l.91-.91a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
- </span>
- <el-input v-model="ruleForm.phone" :placeholder="$t('workflowTradeAdd.placeholderPhoneNumber')" maxlength="11" class="wta-input wta-input--has-prefix" @focus="scrollToSection(3)" />
- </div>
- </el-form-item>
- <!-- 微信 -->
- <el-form-item prop="wechat">
- <template #label>
- <div class="wta-field-label">
- <span class="wta-label-text">{{ $t('workflowTradeAdd.wechat') }}</span>
- </div>
- </template>
- <div class="wta-input-wrap">
- <span class="wta-input-prefix-icon wta-input-prefix-icon--wechat">
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
- </span>
- <el-input v-model="ruleForm.wechat" :placeholder="$t('workflowTradeAdd.placeholderWechat')" maxlength="50" class="wta-input wta-input--has-prefix" @focus="scrollToSection(3)" />
- </div>
- </el-form-item>
- <!-- 邮箱 -->
- <el-form-item prop="email">
- <template #label>
- <div class="wta-field-label">
- <span class="wta-required">*</span>
- <span class="wta-label-text">{{ $t('workflowTradeAdd.email') }}</span>
- </div>
- </template>
- <div class="wta-input-wrap">
- <span class="wta-input-prefix-icon wta-input-prefix-icon--email">
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
- </span>
- <el-input v-model="ruleForm.email" :placeholder="$t('workflowTradeAdd.placeholderEmail')" maxlength="50" class="wta-input wta-input--has-prefix" @focus="scrollToSection(3)" />
- </div>
- </el-form-item>
- </div>
- <!-- 隐私提示 -->
- <div class="wta-privacy-tip">
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#6366f1" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
- <span>{{ t('workflowTradeAdd.privacyTip') }}</span>
- </div>
- </div>
- </div>
- <!-- ── 操作按钮 ── -->
- <div class="wta-actions mt24">
- <button
- class="wta-btn-primary"
- :class="{ 'wta-btn--loading': isSubmiting }"
- @click="submitForm"
- :disabled="isSubmiting"
- >
- <span v-if="isSubmiting" class="wta-btn-spinner"></span>
- <el-icon v-else><Promotion /></el-icon>
- <span>{{ isSubmiting ? t('workflowTradeAdd.publishing') : $t('common.fabuxuqiu') }}</span>
- </button>
- <button class="wta-btn-cancel" @click.stop.prevent="goBack">
- <el-icon><Back /></el-icon>
- <span>{{ $t('common.back') }}</span>
- </button>
- <span class="wta-actions__tip">提交即表示同意 <a href="#" class="wta-link">{{ t('workflowTradeAdd.serviceAgreement') }}</a></span>
- </div>
- </el-form>
- </div>
- <!-- ══════════════════════════════════════
- 右侧侧边栏
- ══════════════════════════════════════ -->
- <div class="wta-sidebar">
- <div class="wta-sidebar__sticky">
- <!-- 发布提示卡片 -->
- <div class="wta-sidebar-card wta-sidebar-card--tip">
- <div class="wta-sidebar-card__header">
- <div class="wta-sidebar-card__icon-wrap wta-sidebar-card__icon-wrap--tip">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
- </div>
- <span class="wta-sidebar-card__title">{{ $t('workflowTradeAdd.publishTip') }}</span>
- </div>
- <div class="ql-container">
- <div class="ql-editor wta-sidebar-content">
- <div v-html="sanitizedHint"></div>
- </div>
- </div>
- </div>
- <!-- 发布规则卡片 -->
- <div class="wta-sidebar-card wta-sidebar-card--rules mt16">
- <div class="wta-sidebar-card__header">
- <div class="wta-sidebar-card__icon-wrap wta-sidebar-card__icon-wrap--rules">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
- </div>
- <span class="wta-sidebar-card__title">{{ $t('workflowTradeAdd.publishRules') }}</span>
- </div>
- <div class="editor ql-container">
- <div class="ql-editor wta-sidebar-content">
- <div v-html="sanitizedRules"></div>
- </div>
- </div>
- </div>
- <!-- 平台保障卡片 -->
- <div class="wta-sidebar-card wta-sidebar-card--guarantee mt16">
- <div class="wta-sidebar-card__header">
- <div class="wta-sidebar-card__icon-wrap wta-sidebar-card__icon-wrap--guarantee">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
- </div>
- <span class="wta-sidebar-card__title">{{ t('workflowTradeAdd.platformGuarantee') }}</span>
- </div>
- <div class="wta-guarantee-list">
- <div class="wta-guarantee-item" v-for="g in guarantees" :key="g.text">
- <div class="wta-guarantee-item__icon" :style="{ background: g.bg }">
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" :stroke="g.color" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
- </div>
- <span>{{ g.text }}</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import DGTMessage from '@/utils/message'
- import { sanitizeHtml } from '@/utils/sanitize.js'
- import { questAdd, getQuestDetail, questEdit } from '@/api/workflowTrade.js'
- import { getCategoryListTree } from '@/api/category.js'
- import { getAgreementType } from '@/api/common.js'
- import { useI18n } from 'vue-i18n'
- const { t } = useI18n()
- import { useRouter, useRoute } from 'vue-router'
- const router = useRouter()
- const route = useRoute()
- import { ref, computed, reactive, onMounted, onUnmounted, watchEffect, nextTick } from 'vue'
- import { useAppStore } from '@/pinia/appStore'
- const appStore = useAppStore()
- const isSubmiting = ref(false)
- const query = route.query
- const questId = ref(query.id || '')
- const categoryListTree = ref([])
- const categoryIdList = ref([])
- const ruleFormRef = ref(null)
- const currentStep = ref(0)
- const isSticky = ref(false)
- const waveCanvasRef = ref(null)
- const stepsSentinelRef = ref(null)
- const steps = [t('workflowTradeAdd.stepBasicInfo'), t('workflowTradeAdd.stepDetailDesc'), t('workflowTradeAdd.stepProjectBudget'), t('workflowTradeAdd.stepContactInfo'), t('workflowTradeAdd.stepConfirmPublish')]
- const writingTips = [t('workflowTradeAdd.writingTip1'), t('workflowTradeAdd.writingTip2'), t('workflowTradeAdd.writingTip3'), t('workflowTradeAdd.writingTip4')]
- const guarantees = [
- { text: t('workflowTradeAdd.guarantee1'), bg: '#ede9fe', color: '#7c3aed' },
- { text: t('workflowTradeAdd.guarantee2'), bg: '#dbeafe', color: '#2563eb' },
- { text: t('workflowTradeAdd.guarantee3'), bg: '#d1fae5', color: '#059669' },
- { text: t('workflowTradeAdd.guarantee4'), bg: '#fef3c7', color: '#d97706' },
- ]
- const ruleForm = reactive({
- questId: '',
- title: '',
- categoryId1: '',
- categoryId2: '',
- categoryId3: '',
- background: '',
- requirements: '',
- budgetMin: '',
- budgetMax: '',
- deadline: '',
- phone: '',
- wechat: '',
- email: '',
- })
- watchEffect(() => {
- ruleForm.categoryId1 = categoryIdList.value[0] || ''
- ruleForm.categoryId2 = categoryIdList.value[1] || ''
- ruleForm.categoryId3 = categoryIdList.value[2] || ''
- })
- const rules = reactive({
- title: [{ required: true, message: t('workflowTradeAdd.placeholderDemandTitle'), trigger: 'blur' }],
- categoryId3: [{ required: true, message: t('workflowTradeAdd.placeholderWorkflowType'), trigger: 'blur' }],
- background: [{ required: true, message: t('workflowTradeAdd.placeholderBackground'), trigger: 'blur' }],
- requirements: [{ required: true, message: t('workflowTradeAdd.placeholderRequirements'), trigger: 'blur' }],
- budgetMin: [{ validator: (rule, value, callback) => /^\d+(\.\d{1,2})?$/.test(value), message: t('workflowTradeAdd.pleaseInputRightBudgetLowerLimit'), trigger: 'blur' }],
- budgetMax: [{ validator: (rule, value, callback) => /^\d+(\.\d{1,2})?$/.test(value), message: t('workflowTradeAdd.pleaseInputRightBudgetUpperLimit'), trigger: 'blur' }],
- deadline: [{ required: true, message: t('workflowTradeAdd.selectSignUpDeadline'), trigger: 'change' }],
- phone: [
- { required: true, message: t('workflowTradeAdd.placeholderPhoneNumber'), trigger: 'blur' },
- { validator: (rule, value, callback) => /^1[3456789]\d{9}$/.test(value), message: t('common.pleaseInputRightPhoneNumber'), trigger: 'blur' },
- ],
- email: [
- { required: true, message: t('workflowTradeAdd.placeholderEmail'), trigger: 'blur' },
- { validator: (rule, value, callback) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), message: t('common.pleaseInputRightEmail'), trigger: 'blur' },
- ],
- })
- // ── 波浪 Canvas 动画 ──────────────────────────────────────
- let waveAnimId = null
- function initWaveCanvas() {
- const canvas = waveCanvasRef.value
- if (!canvas) return
- const ctx = canvas.getContext('2d')
- let W = 0, H = 0
- function resize() {
- W = canvas.width = canvas.offsetWidth
- H = canvas.height = canvas.offsetHeight
- }
- resize()
- window.addEventListener('resize', resize)
- const waves = [
- { amp: 40, freq: 0.004, speed: 0.0012, phase: 0, color: 'rgba(99,102,241,0.28)' },
- { amp: 28, freq: 0.006, speed: 0.0025, phase: 2.1, color: 'rgba(167,139,250,0.22)' },
- { amp: 18, freq: 0.008, speed: 0.004, phase: 4.3, color: 'rgba(255,255,255,1)' },
- ]
- function draw() {
- ctx.clearRect(0, 0, W, H)
- waves.forEach(w => {
- w.phase += w.speed
- ctx.beginPath()
- ctx.moveTo(0, H)
- for (let x = 0; x <= W; x += 2) {
- // Gemini 建议:起始位置改为 60%,让波浪基线更靠下,波浪紧贴步骤条
- const y = H * 0.60 + Math.sin(x * w.freq + w.phase) * w.amp
- ctx.lineTo(x, y)
- }
- ctx.lineTo(W, H)
- ctx.closePath()
- ctx.fillStyle = w.color
- ctx.fill()
- })
- waveAnimId = requestAnimationFrame(draw)
- }
- draw()
- }
- // ── 点击步骤跳转 ────────────────────────────────────────
- const allSectionIds = ['section-basic', 'section-detail', 'section-project', 'section-contact']
- let isScrollingToSection = false
- function scrollToSection(idx) {
- // 第5步(确认发布)没有对应 section,跳到底部
- const targetId = allSectionIds[idx]
- if (!targetId) {
- window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' })
- currentStep.value = idx
- return
- }
- const el = document.getElementById(targetId)
- if (!el) return
- // 动态获取步骤条实际高度(sticky 激活时为 61px,未激活时为 0)
- const stepsWrap = document.querySelector('.wta-steps-wrap')
- const stepsH = isSticky.value ? (stepsWrap?.offsetHeight || 61) : 0
- // 顶部导航栏高度(固定 60px)
- const navH = 60
- const offset = navH + stepsH + 16
- const top = el.getBoundingClientRect().top + window.scrollY - offset
- // 暂停 scroll 联动,避免滚动过程中步骤被重置
- isScrollingToSection = true
- currentStep.value = idx
- window.scrollTo({ top: Math.max(0, top), behavior: 'smooth' })
- // 1秒后恢复 scroll 联动
- setTimeout(() => { isScrollingToSection = false }, 1000)
- }
- // ── 步骤条悬浮 + 滚动联动 ──────────────────────────────
- const sectionIds = ['section-basic', 'section-detail', 'section-project', 'section-contact']
- let scrollHandler = null
- function initStickyAndSync() {
- // IntersectionObserver 监测 sentinel 是否离开视口
- const sentinel = stepsSentinelRef.value
- if (sentinel) {
- const observer = new IntersectionObserver(
- ([entry]) => { isSticky.value = !entry.isIntersecting },
- { root: null, threshold: 0 }
- )
- observer.observe(sentinel)
- }
- // 滚动联动:找哪个 section 顶部最接近视口 35% 参考线
- scrollHandler = () => {
- // 点击跳转期间暂停联动,避免步骤被重置
- if (isScrollingToSection) return
- const ref35 = window.innerHeight * 0.35
- let best = 0, bestDist = Infinity
- sectionIds.forEach((id, i) => {
- const el = document.getElementById(id)
- if (!el) return
- const dist = Math.abs(el.getBoundingClientRect().top - ref35)
- if (dist < bestDist) { bestDist = dist; best = i }
- })
- currentStep.value = best
- }
- window.addEventListener('scroll', scrollHandler, { passive: true })
- }
- onMounted(() => {
- getCategoryListTreeFn()
- getAgreementTypeFn()
- if (questId.value) getDetail()
- nextTick(() => {
- initWaveCanvas()
- initStickyAndSync()
- })
- })
- onUnmounted(() => {
- if (waveAnimId) cancelAnimationFrame(waveAnimId)
- if (scrollHandler) window.removeEventListener('scroll', scrollHandler)
- })
- const submitForm = async () => {
- await ruleFormRef.value.validate(async (valid, fields) => {
- if (!valid) {
- DGTMessage.warning(fields[Object.keys(fields)[0]][0].message)
- return
- }
- let req = questId.value ? questEdit : questAdd
- isSubmiting.value = true
- await req(ruleForm).then(res => {
- if (res.code === 200) {
- DGTMessage.success(t('workflowTrade.publishSuccess'))
- goBack()
- }
- }).finally(() => {
- setTimeout(() => { isSubmiting.value = false }, 1000)
- })
- })
- }
- const goBack = () => window.history.back()
- const getCategoryListTreeFn = () => {
- getCategoryListTree().then(res => { categoryListTree.value = res.rows || [] })
- }
- const getDetail = () => {
- getQuestDetail({ id: questId.value }).then(res => {
- const detail = res.data || {}
- for (let key in ruleForm) ruleForm[key] = detail[key]
- nextTick(() => {
- if (ruleForm.categoryId1) categoryIdList.value = [ruleForm.categoryId1, ruleForm.categoryId2, ruleForm.categoryId3]
- })
- })
- }
- const release_hint = ref([])
- const release_rules = ref([])
- const sanitizedHint = computed(() => sanitizeHtml(release_hint.value))
- const sanitizedRules = computed(() => sanitizeHtml(release_rules.value))
- const getAgreementTypeFn = () => {
- getAgreementType({ agreementType: 'release_hint' }).then(res => { release_hint.value = res.data.content || '' })
- getAgreementType({ agreementType: 'release_rules' }).then(res => { release_rules.value = res.data.content || '' })
- }
- </script>
- <style scoped lang="scss">
- .container {
- // max-width: 1200px;
- width: 100%;
- min-width: auto;
- margin: 0 auto;
- padding: 0 24px;
- position: relative; z-index: 1;
- }
- /* ══════════════════════════════════════
- CSS 变量
- ══════════════════════════════════════ */
- :root {
- --wta-indigo: #4f46e5;
- --wta-violet: #7c3aed;
- --wta-purple: #a855f7;
- --wta-pink: #ec4899;
- }
- /* ══════════════════════════════════════
- 页面整体
- ══════════════════════════════════════ */
- .wta-page {
- min-height: calc(100vh - 60px);
- background: #f4f5ff;
- margin-bottom: 60px;
- }
- /* ══════════════════════════════════════
- HERO BANNER
- ══════════════════════════════════════ */
- .wta-hero {
- position: relative;
- overflow: hidden;
- background: linear-gradient(135deg, #312e81 0%, #4f46e5 25%, #7c3aed 55%, #a855f7 80%, #ec4899 100%);
- /* Gemini 审查建议:减少 padding-bottom,配合更小的 canvas 高度 */
- padding-bottom: 80px;
- }
- /* 网格背景 */
- .wta-hero__grid {
- position: absolute;
- inset: 0;
- background-image:
- linear-gradient(rgba(255,255,255,0.06) 1px, transparent 1px),
- linear-gradient(90deg, rgba(255,255,255,0.06) 1px, transparent 1px);
- background-size: 40px 40px;
- pointer-events: none;
- }
- /* 光晕球 */
- .wta-hero__orb {
- position: absolute;
- border-radius: 50%;
- filter: blur(70px);
- pointer-events: none;
- }
- .wta-hero__orb--1 {
- width: 500px; height: 500px;
- background: radial-gradient(circle, rgba(129,140,248,0.4), transparent 70%);
- top: -150px; right: 5%;
- }
- .wta-hero__orb--2 {
- width: 350px; height: 350px;
- background: radial-gradient(circle, rgba(240,171,252,0.35), transparent 70%);
- bottom: -100px; left: 3%;
- }
- .wta-hero__orb--3 {
- width: 200px; height: 200px;
- background: radial-gradient(circle, rgba(251,207,232,0.3), transparent 70%);
- top: 30px; left: 40%;
- }
- .wta-hero__breadcrumb-wrap {
- position: relative;
- z-index: 2;
- padding-top: 18px;
- :deep(.el-breadcrumb__inner),
- :deep(.el-breadcrumb__separator) { color: rgba(255,255,255,0.65) !important; }
- :deep(.el-breadcrumb__inner.is-link:hover) { color: #fff !important; }
- :deep(.el-breadcrumb__item:last-child .el-breadcrumb__inner) { color: #fff !important; font-weight: 600; }
- }
- .wta-hero__content {
- position: relative;
- z-index: 2;
- text-align: center;
- padding: 8px 20px 0;
- }
- .wta-hero__badge {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- background: rgba(255,255,255,0.12);
- backdrop-filter: blur(10px);
- border: 1px solid rgba(255,255,255,0.2);
- border-radius: 100px;
- padding: 6px 18px;
- font-size: 13px;
- color: rgba(255,255,255,0.9);
- font-weight: 500;
- margin-bottom: 18px;
- }
- .wta-hero__badge-dot {
- width: 7px; height: 7px;
- border-radius: 50%;
- background: #a5f3fc;
- box-shadow: 0 0 10px #a5f3fc;
- animation: wta-pulse 2s ease-in-out infinite;
- }
- @keyframes wta-pulse {
- 0%, 100% { opacity: 1; transform: scale(1); }
- 50% { opacity: 0.5; transform: scale(0.8); }
- }
- .wta-hero__title {
- font-size: 40px;
- font-weight: 900;
- color: #fff;
- letter-spacing: -1px;
- margin-bottom: 12px;
- line-height: 1.15;
- text-shadow: 0 4px 24px rgba(0,0,0,0.2);
- }
- .wta-hero__subtitle {
- font-size: 16px;
- color: rgba(255,255,255,0.75);
- max-width: 480px;
- margin: 0 auto 28px;
- line-height: 1.6;
- }
- .wta-hero__stats {
- display: inline-flex;
- align-items: center;
- gap: 0;
- background: rgba(255,255,255,0.1);
- backdrop-filter: blur(12px);
- border: 1px solid rgba(255,255,255,0.18);
- border-radius: 16px;
- padding: 14px 28px;
- gap: 24px;
- }
- .wta-hero__stat {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 2px;
- }
- .wta-hero__stat-num {
- font-size: 22px;
- font-weight: 800;
- color: #fff;
- letter-spacing: -0.5px;
- }
- .wta-hero__stat-label {
- font-size: 11px;
- color: rgba(255,255,255,0.65);
- font-weight: 500;
- }
- .wta-hero__stat-divider {
- width: 1px;
- height: 32px;
- background: rgba(255,255,255,0.2);
- }
- /* ══════════════════════════════════════
- 波浪 Canvas
- ══════════════════════════════════════ */
- .wta-hero__wave-canvas {
- position: absolute;
- bottom: 0;
- left: 0;
- width: 100%;
- /* Gemini 审查建议:缩小 canvas 高度,让波浪紧贴步骤条 */
- height: 100px;
- z-index: 1;
- pointer-events: none;
- display: block;
- }
- /* ══════════════════════════════════════
- 步骤进度条
- ══════════════════════════════════════ */
- .wta-steps-sentinel { height: 1px; }
- .wta-steps-wrap {
- background: #fff;
- border-bottom: 1px solid #e8eaf6;
- box-shadow: 0 2px 12px rgba(79,70,229,0.06);
- transition: box-shadow 0.3s;
- }
- .wta-steps-wrap--sticky {
- position: fixed;
- top: 60px;
- left: 0;
- right: 0;
- z-index: 100;
- box-shadow: 0 4px 24px rgba(79,70,229,0.13);
- backdrop-filter: blur(16px);
- background: rgba(255,255,255,0.95);
- }
- .wta-steps {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 16px 0;
- overflow-x: auto;
- }
- .wta-step {
- display: flex;
- align-items: center;
- gap: 8px;
- flex-shrink: 0;
- }
- .wta-step__circle {
- width: 28px; height: 28px;
- border-radius: 50%;
- border: 2px solid #e0e0f0;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 12px;
- font-weight: 700;
- color: #9ca3af;
- background: #fff;
- transition: all 0.3s;
- flex-shrink: 0;
- }
- .wta-step__label {
- font-size: 13px;
- color: #9ca3af;
- font-weight: 500;
- white-space: nowrap;
- transition: color 0.3s;
- }
- .wta-step__line {
- width: 40px;
- height: 2px;
- background: #e0e0f0;
- margin: 0 4px;
- border-radius: 2px;
- transition: background 0.3s;
- }
- .wta-step__line--done { background: linear-gradient(90deg, #6366f1, #a855f7); }
- .wta-step--active .wta-step__circle {
- border-color: #6366f1;
- background: linear-gradient(135deg, #6366f1, #a855f7);
- color: #fff;
- box-shadow: 0 0 0 4px rgba(99,102,241,0.15);
- }
- .wta-step--active .wta-step__label { color: #4f46e5; font-weight: 700; }
- .wta-step--done .wta-step__circle {
- border-color: #6366f1;
- background: linear-gradient(135deg, #6366f1, #a855f7);
- color: #fff;
- }
- .wta-step--done .wta-step__label { color: #6366f1; }
- /* ══════════════════════════════════════
- 主体布局
- ══════════════════════════════════════ */
- .wta-body {
- padding: 28px 0 0;
- /* 覆盖全局 container 的 min-width,确保布局不溢出视口,不影响 sticky */
- &.container { min-width: unset; }
- }
- .wta-layout {
- display: flex;
- gap: 24px;
- align-items: flex-start;
- /* 确保不被 overflow 截断,保证 sticky 正常工作 */
- overflow: visible;
- }
- .wta-form-col { flex: 1; min-width: 0; }
- /* ══════════════════════════════════════
- 表单卡片
- ══════════════════════════════════════ */
- .wta-card {
- background: #fff;
- border-radius: 20px;
- border: 1px solid #eaecf8;
- box-shadow: 0 2px 16px rgba(79,70,229,0.06), 0 1px 3px rgba(0,0,0,0.03);
- overflow: hidden;
- transition: box-shadow 0.25s, transform 0.25s;
- &:hover {
- box-shadow: 0 8px 32px rgba(79,70,229,0.1), 0 2px 8px rgba(0,0,0,0.05);
- transform: translateY(-1px);
- }
- }
- .wta-card__header {
- display: flex;
- align-items: center;
- gap: 14px;
- padding: 20px 24px 16px;
- border-bottom: 1px solid #f0f0fa;
- background: linear-gradient(to right, #fafafe 0%, #fff 100%);
- position: relative;
- }
- .wta-card__header-icon {
- width: 40px; height: 40px;
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- background: linear-gradient(135deg, #ede9fe, #ddd6fe);
- color: #6366f1;
- flex-shrink: 0;
- }
- .wta-card__header-icon--purple { background: linear-gradient(135deg, #f3e8ff, #e9d5ff); color: #9333ea; }
- .wta-card__header-icon--green { background: linear-gradient(135deg, #d1fae5, #a7f3d0); color: #059669; }
- .wta-card__header-icon--pink { background: linear-gradient(135deg, #fce7f3, #fbcfe8); color: #db2777; }
- .wta-card__title {
- font-size: 17px;
- font-weight: 800;
- color: #1e1b4b;
- letter-spacing: 0.2px;
- line-height: 1.2;
- }
- .wta-card__subtitle {
- font-size: 12px;
- color: #9ca3af;
- margin-top: 2px;
- font-weight: 400;
- }
- .wta-card__step-badge {
- margin-left: auto;
- padding: 4px 12px;
- border-radius: 100px;
- font-size: 11px;
- font-weight: 700;
- background: linear-gradient(135deg, #ede9fe, #ddd6fe);
- color: #6366f1;
- letter-spacing: 0.5px;
- }
- .wta-card__step-badge--purple { background: linear-gradient(135deg, #f3e8ff, #e9d5ff); color: #9333ea; }
- .wta-card__step-badge--green { background: linear-gradient(135deg, #d1fae5, #a7f3d0); color: #059669; }
- .wta-card__step-badge--pink { background: linear-gradient(135deg, #fce7f3, #fbcfe8); color: #db2777; }
- .wta-card__body { padding: 22px 24px; }
- /* ══════════════════════════════════════
- 字段标签
- ══════════════════════════════════════ */
- .wta-field-label {
- display: flex;
- align-items: center;
- gap: 5px;
- flex-wrap: wrap;
- margin-bottom: 8px;
- }
- .wta-required {
- color: #ef4444;
- font-size: 16px;
- font-weight: 800;
- line-height: 1;
- }
- .wta-label-text {
- font-size: 14px;
- font-weight: 700;
- color: #1e1b4b;
- }
- .wta-label-hint {
- font-size: 12px;
- color: #9ca3af;
- font-weight: 400;
- }
- .wta-label-info-icon {
- color: #a5b4fc;
- cursor: pointer;
- display: flex;
- align-items: center;
- transition: color 0.2s;
- &:hover { color: #6366f1; }
- }
- /* ══════════════════════════════════════
- 输入框带前缀图标
- ══════════════════════════════════════ */
- .wta-input-wrap {
- position: relative;
- width: 100%;
- }
- .wta-input-prefix-icon {
- position: absolute;
- left: 12px;
- top: 50%;
- transform: translateY(-50%);
- z-index: 10;
- color: #a5b4fc;
- display: flex;
- align-items: center;
- pointer-events: none;
- transition: color 0.2s;
- }
- .wta-input-prefix-icon--wechat { color: #22c55e; }
- .wta-input-prefix-icon--email { color: #f59e0b; }
- .wta-input {
- width: 100%;
- :deep(.el-input__wrapper) {
- border-radius: 12px;
- border: 1.5px solid #e0e0f0;
- box-shadow: none !important;
- background: #fafafe;
- transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
- &:hover { border-color: #a5b4fc; background: #fff; }
- &.is-focus {
- border-color: #6366f1;
- box-shadow: 0 0 0 4px rgba(99,102,241,0.1) !important;
- background: #fff;
- }
- }
- }
- .wta-input--has-prefix {
- :deep(.el-input__wrapper) { padding-left: 36px; }
- }
- .wta-textarea {
- width: 100%;
- :deep(.el-textarea__inner) {
- border-radius: 12px;
- border: 1.5px solid #e0e0f0;
- box-shadow: none !important;
- background: #fafafe;
- transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
- resize: vertical;
- font-size: 14px;
- line-height: 1.7;
- &:hover { border-color: #a5b4fc; background: #fff; }
- &:focus {
- border-color: #6366f1;
- box-shadow: 0 0 0 4px rgba(99,102,241,0.1) !important;
- background: #fff;
- }
- }
- }
- /* 字数进度条 */
- .wta-char-progress {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-top: 6px;
- }
- .wta-char-progress__bar {
- flex: 1;
- height: 3px;
- background: #e8eaf6;
- border-radius: 3px;
- overflow: hidden;
- }
- .wta-char-progress__fill {
- height: 100%;
- border-radius: 3px;
- background: linear-gradient(90deg, #6366f1, #a855f7);
- transition: width 0.3s ease;
- }
- .wta-char-progress__text {
- font-size: 11px;
- color: #9ca3af;
- white-space: nowrap;
- font-weight: 500;
- }
- .wta-char-progress__text--warn { color: #f59e0b; }
- /* 写作提示 */
- .wta-writing-tips {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- margin-bottom: 10px;
- }
- .wta-writing-tips__item {
- display: inline-flex;
- align-items: center;
- gap: 4px;
- padding: 3px 10px;
- border-radius: 100px;
- background: linear-gradient(135deg, #ede9fe, #e0e7ff);
- font-size: 11px;
- color: #6366f1;
- font-weight: 600;
- svg { color: #6366f1; flex-shrink: 0; }
- }
- /* ══════════════════════════════════════
- 预算区域
- ══════════════════════════════════════ */
- .wta-budget-row {
- display: flex;
- align-items: flex-end;
- gap: 12px;
- margin-bottom: 8px;
- }
- .wta-budget-item { flex: 1; }
- .wta-budget-range-icon {
- padding-bottom: 10px;
- flex-shrink: 0;
- }
- .wta-currency-badge {
- font-size: 11px;
- font-weight: 700;
- color: #059669;
- background: #d1fae5;
- border-radius: 6px;
- padding: 2px 7px;
- margin-left: 4px;
- }
- .wta-budget-hint {
- display: flex;
- align-items: center;
- gap: 5px;
- font-size: 12px;
- color: #6366f1;
- background: #f0f0ff;
- border-radius: 8px;
- padding: 7px 12px;
- margin-bottom: 4px;
- }
- /* ══════════════════════════════════════
- 联系方式
- ══════════════════════════════════════ */
- .wta-contact-grid {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- gap: 16px;
- }
- .wta-privacy-tip {
- display: flex;
- align-items: center;
- gap: 6px;
- font-size: 12px;
- color: #6366f1;
- background: linear-gradient(135deg, #f0f0ff, #f5f3ff);
- border: 1px solid #e0e0f8;
- border-radius: 10px;
- padding: 8px 14px;
- margin-top: 4px;
- }
- /* ══════════════════════════════════════
- 操作按钮
- ══════════════════════════════════════ */
- .wta-actions {
- display: flex;
- align-items: center;
- gap: 12px;
- flex-wrap: wrap;
- /* Gemini 建议:增加底部留白,避免与 footer 分割线太紧 */
- margin-bottom: 60px;
- }
- .mt24 { margin-top: 24px; }
- .wta-btn-primary {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- padding: 13px 36px;
- border-radius: 14px;
- border: none;
- background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #a855f7 100%);
- color: #fff;
- font-size: 15px;
- font-weight: 800;
- cursor: pointer;
- box-shadow: 0 4px 20px rgba(99,102,241,0.4), 0 1px 4px rgba(0,0,0,0.1);
- transition: all 0.25s;
- letter-spacing: 0.3px;
- position: relative;
- overflow: hidden;
- &::before {
- content: '';
- position: absolute;
- inset: 0;
- background: linear-gradient(135deg, rgba(255,255,255,0.15), transparent);
- opacity: 0;
- transition: opacity 0.25s;
- }
- &:hover:not(:disabled) {
- transform: translateY(-2px);
- box-shadow: 0 8px 28px rgba(99,102,241,0.5), 0 2px 8px rgba(0,0,0,0.12);
- &::before { opacity: 1; }
- }
- &:active:not(:disabled) {
- transform: translateY(0);
- box-shadow: 0 2px 12px rgba(99,102,241,0.3);
- }
- &:disabled, &.wta-btn--loading {
- opacity: 0.7;
- cursor: not-allowed;
- transform: none;
- }
- }
- .wta-btn-spinner {
- width: 16px; height: 16px;
- border: 2px solid rgba(255,255,255,0.4);
- border-top-color: #fff;
- border-radius: 50%;
- animation: wta-spin 0.7s linear infinite;
- }
- @keyframes wta-spin { to { transform: rotate(360deg); } }
- .wta-btn-cancel {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- padding: 13px 24px;
- border-radius: 14px;
- border: 1.5px solid #e0e0f0;
- background: #fff;
- color: #6b7280;
- font-size: 15px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s;
- &:hover {
- border-color: #a5b4fc;
- color: #4f46e5;
- background: #f5f3ff;
- transform: translateY(-1px);
- }
- }
- .wta-actions__tip {
- font-size: 12px;
- color: #9ca3af;
- margin-left: 4px;
- }
- .wta-link {
- color: #6366f1;
- text-decoration: none;
- font-weight: 600;
- &:hover { text-decoration: underline; }
- }
- /* ══════════════════════════════════════
- 右侧侧边栏
- ══════════════════════════════════════ */
- .wta-sidebar {
- width: 320px;
- flex-shrink: 0;
- /* 让侧边栏拉伸到与左侧表单列等高,为 sticky 提供滚动空间 */
- align-self: stretch;
- }
- .wta-sidebar__sticky {
- position: sticky;
- top: 136px; /* 导航60px + 步骤条61px + 间距15px */
- }
- .wta-sidebar-card {
- background: #fff;
- border-radius: 18px;
- border: 1px solid #eaecf8;
- box-shadow: 0 2px 14px rgba(79,70,229,0.07);
- overflow: hidden;
- }
- .wta-sidebar-card__header {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 16px 18px 12px;
- border-bottom: 1px solid #f0f0fa;
- }
- .wta-sidebar-card__icon-wrap {
- width: 32px; height: 32px;
- border-radius: 9px;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
- .wta-sidebar-card__icon-wrap--tip { background: linear-gradient(135deg, #ede9fe, #ddd6fe); color: #7c3aed; }
- .wta-sidebar-card__icon-wrap--rules { background: linear-gradient(135deg, #dbeafe, #bfdbfe); color: #2563eb; }
- .wta-sidebar-card__icon-wrap--guarantee { background: linear-gradient(135deg, #d1fae5, #a7f3d0); color: #059669; }
- .wta-sidebar-card__title {
- font-size: 14px;
- font-weight: 800;
- color: #1e1b4b;
- }
- .wta-sidebar-content {
- padding: 14px 18px;
- color: #4b5563;
- line-height: 1.75;
- font-size: 13px;
- :deep(ol), :deep(ul) {
- padding-left: 18px;
- li {
- margin-bottom: 6px;
- &::marker { color: #6366f1; }
- }
- }
- }
- /* 平台保障列表 */
- .wta-guarantee-list {
- padding: 14px 18px;
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
- .wta-guarantee-item {
- display: flex;
- align-items: center;
- gap: 10px;
- font-size: 13px;
- color: #374151;
- font-weight: 500;
- }
- .wta-guarantee-item__icon {
- width: 22px; height: 22px;
- border-radius: 6px;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
- /* ══════════════════════════════════════
- Element Plus 覆盖
- ══════════════════════════════════════ */
- :deep(.el-form-item__label) {
- font-size: 14px;
- font-weight: 700;
- color: #1e1b4b;
- padding-bottom: 4px;
- /* 隐藏 Element Plus 自动注入的必填星号 */
- &::before { display: none !important; content: '' !important; }
- }
- :deep(.el-form-item) { margin-bottom: 20px; }
- :deep(.el-cascader) {
- width: 100%;
- .el-input__wrapper {
- border-radius: 12px;
- border: 1.5px solid #e0e0f0;
- box-shadow: none !important;
- background: #fafafe;
- padding-left: 36px;
- &:hover { border-color: #a5b4fc; background: #fff; }
- &.is-focus {
- border-color: #6366f1;
- box-shadow: 0 0 0 4px rgba(99,102,241,0.1) !important;
- background: #fff;
- }
- }
- }
- :deep(.el-date-editor) {
- width: 100% !important;
- .el-input__wrapper {
- border-radius: 12px;
- border: 1.5px solid #e0e0f0;
- box-shadow: none !important;
- background: #fafafe;
- padding-left: 36px;
- &:hover { border-color: #a5b4fc; background: #fff; }
- &.is-focus {
- border-color: #6366f1;
- box-shadow: 0 0 0 4px rgba(99,102,241,0.1) !important;
- background: #fff;
- }
- }
- }
- /* 辅助间距 */
- .mt16 { margin-top: 16px; }
- .mt20 { margin-top: 20px; }
- .mb8 { margin-bottom: 8px; }
- </style>
|