| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392 |
- <template>
- <div class="my-dynamic-container">
- <!-- 用户信息卡片 -->
- <div class="user-card">
- <div class="user-header">
- <div class="user-avatar-section">
- <div class="user-avatar-large">
- <img v-if="cachedHeadImg" :src="cachedHeadImg" :alt="userInfo.name" />
- <el-icon v-else :size="60">
- <Avatar />
- </el-icon>
- </div>
- <div class="user-info-text">
- <div class="user-name">
- {{ userStore.userInfo.nickName }}
- </div>
- <div class="user-stats">
- <span class="stat-item" @click="openRelationDialog('friend')">{{ userInfo.followCount }} 好友</span>
- <span class="stat-divider">|</span>
- <span class="stat-item" @click="openRelationDialog('follow')">{{ userInfo.fansCount }} 关注</span>
- <span class="stat-divider">|</span>
- <span class="stat-item" @click="openRelationDialog('fans')">{{ userInfo.likeCount }} 粉丝</span>
- </div>
- </div>
- <el-button type="primary" size="small" @click="addFriend" class="add-friend-btn">
- <el-icon class="btn-icon">
- <Plus />
- </el-icon>
- 添加好友
- </el-button>
- </div>
- </div>
- <div class="user-bio">
- {{ userStore.userInfo.accountBlurb }}
- </div>
- </div>
- <!-- 标签页 -->
- <div class="tabs-section">
- <el-tabs v-model="activeTab" @tab-click="handleTabClick">
- <el-tab-pane label="动态" name="dynamic" />
- <el-tab-pane label="赞过" name="liked" />
- </el-tabs>
- </div>
- <!-- 内容区域 -->
- <div v-if="contentList.length > 0 && activeTab === 'dynamic'" class="content-section">
- <!-- 动态卡片网格 -->
- <div class="content-grid">
- <div class="draft-card" @click="handleDraftClick">
- <el-icon :size="48" color="#999">
- <Edit />
- </el-icon>
- <div class="draft-title">本地草稿</div>
- <div class="draft-count">有{{ draftCount }}篇动态待发布</div>
- </div>
- <div v-for="item in contentList" :key="item.id" class="content-card" @click="handleCardClick(item)">
- <!-- 封面图片/视频区域 -->
- <div class="content-cover-wrapper">
- <img v-if="item.imagePath" :src="getCoverImage(item)" :alt="item.title" class="content-cover" />
- <div v-else class="cover-placeholder">
- <el-icon :size="48" color="#999">
- <Picture />
- </el-icon>
- </div>
- <!-- 视频播放按钮 -->
- <div v-if="isVideoItem(item)" class="play-overlay">
- <el-icon :size="40" color="#fff">
- <VideoPlay />
- </el-icon>
- </div>
- <!-- 标题标签(仅第一个显示) -->
- <div v-if="item.showLabel" class="content-label">
- {{ item.labelText }}
- </div>
- </div>
- <!-- 底部信息 -->
- <div class="content-footer">
- <div class="footer-left">
- <el-icon :size="14" color="#666">
- <View />
- </el-icon>
- <span class="view-count">{{ item.liulanCount }}</span>
- </div>
- <div class="footer-right">
- <i class="iconfont icon-dianzanb" :style="{ fontSize: '14px', color: item.isLike == '1' ? '#f56c6c' : '#666' }" />
- <span class="like-count">{{ item.dianzanCount || 0 }}</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 赞过 -->
- <div class="content-section" v-if="dianZanList.length > 0 && activeTab === 'liked'">
- <div class="content-grid">
- <div v-for="item in dianZanList" :key="item.id" class="content-card" @click="handleCardClick(item)">
- <!-- 封面图片/视频区域 -->
- <div class="content-cover-wrapper">
- <img v-if="item.imagePath" :src="getCoverImage(item)" :alt="item.title" class="content-cover" />
- <div v-else class="cover-placeholder">
- <el-icon :size="48" color="#999">
- <Picture />
- </el-icon>
- </div>
- <!-- 视频播放按钮 -->
- <div v-if="isVideoItem(item)" class="play-overlay">
- <el-icon :size="40" color="#fff">
- <VideoPlay />
- </el-icon>
- </div>
- <!-- 标题标签(仅第一个显示) -->
- <div v-if="item.showLabel" class="content-label">
- {{ item.labelText }}
- </div>
- </div>
- <!-- 底部信息 -->
- <div class="content-footer">
- <div class="footer-left">
- <el-icon :size="14" color="#666">
- <View />
- </el-icon>
- <span class="view-count">{{ item.liulanCount || 0 }}</span>
- </div>
- <div class="footer-right">
- <i class="iconfont icon-dianzanb" :style="{ fontSize: '14px', color: item.isLike == '1' ? '#f56c6c' : '#666' }" />
- <span class="like-count">{{ item.dianzanCount || 0 }}</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 动态详情 Drawer -->
- <el-drawer
- v-model="detailDrawerVisible"
- direction="rtl"
- size="90%"
- :show-close="false"
- destroy-on-close
- class="detail-drawer"
- >
- <template #header>
- <div class="drawer-header">
- <el-button class="close-btn" text @click="handleCloseDetail">
- <el-icon :size="24">
- <Close />
- </el-icon>
- </el-button>
- </div>
- </template>
- <div v-if="currentDetail" class="detail-content">
- <!-- 主内容区域 -->
- <div class="detail-main">
- <!-- 图片/视频轮播展示 -->
- <div class="media-container">
- <!-- 多媒体轮播 -->
- <el-carousel
- v-if="currentDetail.mediaList && currentDetail.mediaList.length > 0"
- :autoplay="false"
- :loop="false"
- indicator-position="outside"
- arrow="always"
- height="100%"
- class="media-carousel"
- @change="handleCarouselChange"
- >
- <el-carousel-item v-for="(media, index) in currentDetail.mediaList" :key="index">
- <!-- 视频 -->
- <video
- v-if="media.type === 'video'"
- :ref="el => setVideoRef(el, index)"
- :src="media.url"
- class="detail-media detail-video"
- controls
- preload="metadata"
- @play="handleVideoPlay(index)"
- />
- <!-- 图片 -->
- <img v-else :src="media.url" :alt="currentDetail.title" class="detail-media detail-image" />
- </el-carousel-item>
- </el-carousel>
- <!-- 占位符 -->
- <div v-else class="media-placeholder">
- <el-icon :size="80" color="#dcdfe6">
- <Picture />
- </el-icon>
- </div>
- <!-- 媒体计数指示器 -->
- <div v-if="currentDetail.mediaList && currentDetail.mediaList.length > 1" class="media-counter">
- {{ currentCarouselIndex + 1 }} / {{ currentDetail.mediaList.length }}
- </div>
- </div>
- <!-- 底部信息 -->
- <div class="detail-info">
- <div class="author-info">
- <div class="author-avatar">
- <img
- v-if="currentDetail.author?.avatar || currentDetail.userAvatar"
- :src="currentDetail.author?.avatar || currentDetail.userAvatar"
- :alt="currentDetail.author?.name || currentDetail.userName"
- />
- <el-icon v-else :size="32">
- <Avatar />
- </el-icon>
- </div>
- <div class="author-details">
- <div class="author-name">@{{ currentDetail.author?.name || currentDetail.userName }}</div>
- <div class="publish-time">
- {{ currentDetail.publishTime }}
- </div>
- </div>
- </div>
- <div style=" padding-bottom: 10px;color: #ffffff">
- {{ currentDetail.title }}
- </div>
- <div class="detail-description">
- <p
- v-if="isDescriptionExpanded || (currentDetail.context && currentDetail.context.length >= 20)"
- :class="{ 'text-ellipsis': !isDescriptionExpanded }"
- >
- {{ currentDetail.context }}
- </p>
- <span v-if="currentDetail.context" class="expand-btn" @click="toggleDescription">
- {{ isDescriptionExpanded ? "收起" : "展开" }}
- </span>
- </div>
- </div>
- </div>
- <!-- 右侧操作栏 -->
- <div class="action-bar">
- <!-- 作者头像 -->
- <div class="action-item author-action">
- <div class="action-avatar" @click="handleViewUserProfile">
- <img
- v-if="currentDetail.author?.avatar || currentDetail.userAvatar"
- :src="currentDetail.author?.avatar || currentDetail.userAvatar"
- :alt="currentDetail.author?.name || currentDetail.userName"
- />
- <el-icon v-else :size="40" color="#fff">
- <Avatar />
- </el-icon>
- </div>
- </div>
- <!-- 点赞 -->
- <div class="action-item" @click="handleDetailLike">
- <div class="action-icon">
- <i
- class="iconfont icon-dianzanb"
- :style="{ fontSize: '28px', color: currentDetail.isLike == '1' ? '#f56c6c' : '#fff' }"
- />
- </div>
- <div class="action-count">
- {{ currentDetail.dianzanCount }}
- </div>
- </div>
- <!-- 评论 -->
- <div class="action-item" @click="handleShowComments">
- <div class="action-icon">
- <el-icon :size="28" color="#fff">
- <ChatDotRound />
- </el-icon>
- </div>
- <div class="action-count">
- {{ currentDetail.commentCount }}
- </div>
- </div>
- <!-- 分享 -->
- <div class="action-item" @click="handleShare">
- <div class="action-icon">
- <el-icon :size="28" color="#fff">
- <Share />
- </el-icon>
- </div>
- <div class="action-count">分享</div>
- </div>
- <!-- 更多 -->
- <el-popover placement="left" :width="120" trigger="click" popper-class="more-actions-popover">
- <template #reference>
- <div class="action-item">
- <div class="action-icon">
- <el-icon :size="28" color="#fff">
- <MoreFilled />
- </el-icon>
- </div>
- </div>
- </template>
- <div class="more-actions-menu">
- <!-- 如果是当前用户的动态,显示删除 -->
- <template v-if="isMyDynamic">
- <!-- <div class="menu-item" @click="handleEditDynamic">
- <el-icon :size="18">
- <Edit />
- </el-icon>
- <span>编辑</span>
- </div> -->
- <div class="menu-item" style="display: flex; cursor: pointer" @click="handleDeleteDynamic">
- <el-icon :size="18">
- <Delete /> </el-icon
- >
- <span>删除</span>
- </div>
- </template>
- <!-- 如果不是当前用户的动态,显示举报和拉黑 -->
- <template v-else>
- <div class="menu-item" @click="handleReportDynamic">
- <el-icon :size="18">
- <Warning />
- </el-icon>
- <span>举报</span>
- </div>
- <div class="menu-item" @click="handleBlockUserClick">
- <el-icon :size="18">
- <CircleClose />
- </el-icon>
- <span>拉黑</span>
- </div>
- </template>
- </div>
- </el-popover>
- </div>
- </div>
- </el-drawer>
- <!-- 评论侧边栏 -->
- <el-drawer v-model="commentDrawerVisible" title="评论" direction="rtl" size="400px" destroy-on-close>
- <!-- 评论列表 -->
- <div class="comment-list-container">
- <div v-if="commentListData.length > 0" class="comment-list">
- <div v-for="comment in commentListData" :key="comment.id" class="comment-item">
- <div class="comment-avatar">
- <img v-if="comment.userAvatar" :src="comment.userAvatar" :alt="comment.userName" />
- <el-icon v-else :size="32">
- <Avatar />
- </el-icon>
- </div>
- <div class="comment-content-wrapper">
- <div class="comment-header">
- <span class="comment-user-name">{{ comment.userName }}</span>
- </div>
- <div class="comment-text">
- {{ comment.commentContent }}
- </div>
- <div class="comment-actions">
- <span class="comment-action-item" @click="handleLikeComment(comment)">
- <i class="iconfont icon-dianzanb" :style="{ fontSize: '16px', color: comment.isLiked ? '#f56c6c' : '#999' }" />
- <span>{{ comment.likeCount || 0 }}</span>
- </span>
- <span class="comment-action-item" @click="handleReplyComment(comment)">
- <el-icon :size="16">
- <ChatDotRound />
- </el-icon>
- <span>回复</span>
- </span>
- </div>
- <!-- 商家回复 -->
- <div v-for="item in comment.storeComment" :key="item.id" class="store-comment-wrapper">
- <div class="store-comment-item">
- <div class="store-comment-avatar">
- <img v-if="item.userImage" :src="item.userImage" :alt="item.userName" />
- <el-icon v-else :size="24">
- <Avatar />
- </el-icon>
- </div>
- <div class="store-comment-content">
- <div class="store-comment-header">
- <span class="store-comment-user-name">{{ item.userName || "商家" }}</span>
- <span class="store-comment-time">{{ item.createdTime || item.createDate }}</span>
- </div>
- <div class="store-comment-text">
- {{ item.commentContent }}
- <span
- class="comment-action-item"
- @click="handleLikeComment(item)"
- style="display: flex; align-items: center"
- >
- <i
- class="iconfont icon-dianzanb"
- :style="{ fontSize: '16px', color: item.isLiked ? '#f56c6c' : '#999' }"
- />
- <span>{{ item.likeCount || 0 }}</span>
- </span>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <el-empty v-else description="暂无评论" />
- </div>
- <!-- 评论输入框 -->
- <div class="comment-input-wrapper">
- <!-- 回复提示 -->
- <div v-if="replyingComment" class="reply-hint">
- <span class="reply-text">回复 @{{ replyingComment.userName }}</span>
- <el-icon class="cancel-reply" @click="handleCancelReply">
- <Close />
- </el-icon>
- </div>
- <el-input
- v-model="commentInput"
- type="textarea"
- :rows="3"
- :placeholder="replyingComment ? '输入回复内容...' : '你要评论点什么呢~'"
- maxlength="500"
- show-word-limit
- />
- <el-button type="primary" :loading="commentSubmitting" @click="handleSubmitComment">
- {{ replyingComment ? "回复" : "发送" }}
- </el-button>
- </div>
- </el-drawer>
- <!-- 好友/关注/粉丝对话框 -->
- <el-dialog
- v-model="relationDialogVisible"
- :title="relationDialogTitle"
- width="600px"
- destroy-on-close
- @close="handleCloseRelationDialog"
- >
- <div class="relation-dialog-content">
- <!-- 标签页 -->
- <el-tabs v-model="relationActiveTab" @tab-click="handleRelationTabClick">
- <el-tab-pane label="好友" name="friend" value="friend" />
- <el-tab-pane label="关注" name="follow" value="follow" />
- <el-tab-pane label="粉丝" name="fans" value="fans" />
- </el-tabs>
- <!-- 搜索框 -->
- <div class="search-box">
- <el-input v-model="relationSearch" placeholder="请输入手机号或昵称" clearable @input="handleRelationSearch">
- <template #prefix>
- <el-icon>
- <Search />
- </el-icon>
- </template>
- </el-input>
- </div>
- <!-- 用户列表(一页5条,滚动到底自动加载) -->
- <div ref="relationListScrollRef" class="relation-list" @scroll="onRelationListScroll">
- <div v-for="user in filteredRelationList" :key="user.id" class="relation-item">
- <div class="user-info-row">
- <div class="user-avatar-small">
- <img v-if="user.avatar" :src="user.avatar" :alt="user.name" />
- <el-icon v-else :size="40">
- <Avatar />
- </el-icon>
- </div>
- <div class="user-details">
- <div class="user-name-text">
- {{ user.name }}
- </div>
- <div class="user-desc">
- {{ user.description }}
- </div>
- </div>
- </div>
- <div class="action-button">
- <el-button
- v-if="user.relationStatus === 'following'"
- type="primary"
- plain
- size="small"
- @click="handleUnfollow(user)"
- >
- 已关注
- </el-button>
- <el-button v-else-if="user.relationStatus === 'mutual'" type="primary" size="small" @click="handleUnfollow(user)">
- 互相关注
- </el-button>
- <el-button v-else type="primary" plain size="small" @click="handleFollow(user)"> 关注 </el-button>
- </div>
- </div>
- <!-- 触底加载哨兵(IntersectionObserver 检测可见时加载更多) -->
- <div v-if="relationHasMore" ref="relationLoadMoreSentinelRef" class="relation-load-sentinel" />
- <!-- 加载更多按钮(点击或滚到底都会触发) -->
- <div v-if="relationHasMore" class="relation-load-more">
- <el-button :loading="relationLoading" text type="primary" @click="loadMoreRelationList">
- {{ relationLoading ? "加载中..." : "点击或滚动到底加载更多" }}
- </el-button>
- </div>
- <!-- 空状态 -->
- <el-empty v-if="!relationLoading && filteredRelationList.length === 0" description="暂无数据" />
- </div>
- </div>
- </el-dialog>
- <!-- 添加好友对话框 -->
- <el-dialog
- v-model="addFriendDialogVisible"
- title="添加好友"
- width="500px"
- destroy-on-close
- @close="handleCloseAddFriendDialog"
- >
- <div class="add-friend-dialog-content">
- <!-- 搜索框 -->
- <div class="search-box">
- <el-input
- v-model="addFriendSearchKeyword"
- placeholder="请输入手机号或昵称"
- clearable
- @keyup.enter="handleSearchFriend"
- @input="handleSearchInput"
- @clear="handleClearSearch"
- >
- <template #prefix>
- <el-icon>
- <Search />
- </el-icon>
- </template>
- </el-input>
- </div>
- <!-- 搜索结果 -->
- <div v-if="addFriendSearchKeyword" class="search-results">
- <div v-if="addFriendSearching" class="search-loading">
- <el-icon class="is-loading" :size="20">
- <Loading />
- </el-icon>
- <span>搜索中...</span>
- </div>
- <div v-else-if="addFriendSearchResults.length > 0" class="search-results-list">
- <div class="search-results-count">共{{ addFriendSearchResults.length }}个搜索结果</div>
- <div v-for="user in addFriendSearchResults" :key="user.phoneId || user.id" class="search-result-item">
- <div class="user-info-row">
- <div class="user-avatar-small">
- <img v-if="user.imgUrl" :src="user.imgUrl || user.userAvatar" :alt="user.name || user.userName" />
- <el-icon v-else :size="40">
- <Avatar />
- </el-icon>
- </div>
- <div class="user-details">
- <div class="user-name-text">
- {{ user.storeUserName || "—" }}
- </div>
- <div class="user-desc">
- {{ user.blurb || "—" }}
- </div>
- </div>
- </div>
- <div class="action-button">
- <el-button v-if="user.isFollowThis == 1" type="primary" plain size="small" @click="handleUnfollowInSearch(user)">
- 已关注
- </el-button>
- <el-button v-else type="primary" plain size="small" @click="handleFollowInSearch(user)"> 关注 </el-button>
- </div>
- </div>
- </div>
- <div v-else class="search-empty">
- <el-empty description="暂无搜索结果" />
- </div>
- </div>
- <div v-else class="search-placeholder">
- <el-empty description="请输入手机号或昵称进行搜索" />
- </div>
- </div>
- </el-dialog>
- <!-- 举报对话框 -->
- <el-dialog v-model="reportDialogVisible" title="举报理由" width="500px" destroy-on-close @close="handleCloseReportDialog">
- <div class="report-dialog-content">
- <div class="report-tip">请选择最符合的原因,以便于我们进行的处理</div>
- <!-- 举报原因选项 -->
- <div class="report-reasons">
- <el-radio-group v-model="reportForm.reason">
- <el-radio label="用户头像"> 用户头像 </el-radio>
- <el-radio label="名称/昵称"> 名称/昵称 </el-radio>
- <el-radio label="违法违规"> 违法违规 </el-radio>
- <el-radio label="低俗色情、暴力恐怖、政治谣言"> 低俗色情、暴力恐怖、政治谣言 </el-radio>
- <el-radio label="涉嫌诈骗"> 涉嫌诈骗 </el-radio>
- <el-radio label="人身攻击"> 人身攻击 </el-radio>
- <el-radio label="侵犯版权"> 侵犯版权 </el-radio>
- <el-radio label="恶意骚扰"> 恶意骚扰 </el-radio>
- <el-radio label="虚假/过度宣传"> 虚假/过度宣传 </el-radio>
- <el-radio label="诱导点赞分享"> 诱导点赞分享 </el-radio>
- <el-radio label="传播人身安全"> 传播人身安全 </el-radio>
- <el-radio label="侵权举报"> 侵权举报 </el-radio>
- <el-radio label="其他举报"> 其他举报 </el-radio>
- </el-radio-group>
- </div>
- <!-- 详细描述 -->
- <div class="report-description">
- <el-input
- v-model="reportForm.description"
- type="textarea"
- :rows="4"
- placeholder="请描述任何涉及举报内容的其体情况,我们会综合一判断合举政采!(必填)"
- maxlength="300"
- show-word-limit
- />
- </div>
- <!-- 上传凭证 -->
- <div class="report-upload">
- <div class="upload-title">上传凭证</div>
- <el-upload
- v-model:file-list="reportForm.fileList"
- list-type="picture-card"
- :limit="9"
- :on-preview="handleReportPreview"
- :on-remove="handleReportRemove"
- :before-upload="beforeReportUpload"
- :http-request="handleReportUpload"
- accept="image/*"
- multiple
- >
- <el-icon :size="24">
- <Plus />
- </el-icon>
- </el-upload>
- </div>
- <!-- 同意协议 -->
- <div class="report-agreement">
- <el-checkbox v-model="reportForm.agreed"> 同意发票用户协议 </el-checkbox>
- </div>
- </div>
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="reportDialogVisible = false"> 取消 </el-button>
- <el-button type="primary" :loading="reportSubmitting" @click="handleSubmitReport"> 提交 </el-button>
- </div>
- </template>
- </el-dialog>
- <!-- 分享对话框 -->
- <el-dialog v-model="shareDialogVisible" title="分享给好友" width="500px" destroy-on-close @close="handleCloseShareDialog">
- <div class="share-dialog-content">
- <!-- 好友列表 -->
- <div class="share-friend-list">
- <div v-if="filteredShareFriendList.length > 0">
- <div
- v-for="friend in filteredShareFriendList"
- :key="friend.id"
- class="share-friend-item"
- @click="handleSelectFriend(friend)"
- >
- <div class="friend-info">
- <div class="friend-avatar">
- <img v-if="friend.avatar" :src="friend.avatar" :alt="friend.name" />
- <el-icon v-else :size="40">
- <Avatar />
- </el-icon>
- </div>
- <div class="friend-name">
- {{ friend.name }}
- </div>
- </div>
- <el-icon v-if="selectedFriends.includes(friend.id)" :size="20" color="#409eff">
- <CircleCheck />
- </el-icon>
- </div>
- </div>
- <el-empty v-else description="暂无好友" />
- </div>
- <!-- 已选择的好友 -->
- <div v-if="selectedFriends.length > 0" class="selected-friends">
- <div class="selected-title">已选择 {{ selectedFriends.length }} 位好友</div>
- <div class="selected-list">
- <el-tag v-for="friendId in selectedFriends" :key="friendId" closable @close="handleRemoveFriend(friendId)">
- {{ shareFriendList.find(f => f.id === friendId)?.name }}
- </el-tag>
- </div>
- </div>
- </div>
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="shareDialogVisible = false"> 取消 </el-button>
- <el-button
- type="primary"
- :loading="shareSubmitting"
- :disabled="selectedFriends.length === 0"
- @click="handleConfirmShare"
- >
- 确认分享
- </el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts" name="myDynamic">
- import { ref, reactive, computed, onMounted, nextTick, watch } from "vue";
- import { useRouter } from "vue-router";
- import { ElMessage, ElMessageBox } from "element-plus";
- import {
- Avatar,
- Picture,
- VideoPlay,
- View,
- Search,
- Close,
- ChatDotRound,
- Share,
- MoreFilled,
- Edit,
- Delete,
- Warning,
- Plus,
- CircleCheck,
- CircleClose,
- Loading
- } from "@element-plus/icons-vue";
- import {
- deleteDynamicsById,
- getUserDynamics,
- getUserDraftDynamics,
- getHomePageInfo,
- getDianZanList,
- saveComment,
- commentList,
- getMutualAttention,
- getMyFollowed,
- addTransferCount,
- getMyUserFans,
- getMyStoreFans,
- blockUser,
- likeDynamicNew,
- unlikeDynamicNew,
- getStoreAndUserByName,
- toggleFollowUser,
- cancelFollewed
- } from "@/api/modules/newLoginApi";
- import {} from "@/api/modules/dynamicManagement";
- import { useUserStore } from "@/stores/modules/user";
- import FriendCoupon from "./friendCoupon.vue";
- import { localGet } from "@/utils";
- const router = useRouter();
- const userStore = useUserStore();
- // 接口定义
- interface UserInfo {
- name: string;
- avatar: string;
- bio: string;
- followCount: number;
- fansCount: number;
- likeCount: number;
- }
- interface MediaItem {
- url: string;
- type: "image" | "video";
- }
- interface ContentItem {
- id: number;
- title: string;
- coverUrl: string;
- imagePath?: string;
- type: "image" | "video";
- viewCount: number;
- liulanCount?: number;
- isLike?: string;
- dianzanCount?: number;
- showLabel?: boolean;
- labelText?: string;
- createTime: string;
- mediaList?: MediaItem[];
- }
- interface DetailItem extends ContentItem {
- author?: {
- id: number;
- name: string;
- avatar: string;
- };
- userAvatar?: string;
- userName?: string;
- description: string;
- publishTime: string;
- likeCount: number;
- commentCount: number;
- isLiked: boolean;
- isLike?: string; // 点赞状态:'0'未点赞,'1'已点赞
- dianzanCount?: number; // 点赞数量
- userId?: string | number; // 发布者ID
- phoneId?: string; // 发布者店铺ID
- storeUserId?: string | number; // 小店用户ID(用于举报和拉黑)
- userType?: number; // 发布者用户类型:1商家,2用户
- imagePath?: string; // 图片路径
- }
- interface RelationUser {
- id: number;
- name: string;
- avatar: string;
- description: string;
- relationStatus: "following" | "mutual" | "none"; // following: 已关注, mutual: 互相关注, none: 未关注
- phoneId?: string; // 用户的 phoneId,用于后续操作
- }
- // 响应式数据
- const activeTab = ref("dynamic");
- const contentList = ref<ContentItem[]>([]);
- const draftCount = ref(0); // 草稿数量
- // 详情 Drawer 相关
- const detailDrawerVisible = ref(false);
- const currentDetail = ref<DetailItem | null>(null);
- // 描述展开/收起状态
- const isDescriptionExpanded = ref(false);
- // 视频轮播相关
- const videoRefs = ref<HTMLVideoElement[]>([]);
- const currentCarouselIndex = ref(0);
- // 切换描述展开/收起
- const toggleDescription = () => {
- isDescriptionExpanded.value = !isDescriptionExpanded.value;
- };
- // 评论相关
- const commentDrawerVisible = ref(false);
- const commentListData = ref<any[]>([]);
- const commentInput = ref("");
- const commentSubmitting = ref(false);
- const replyingComment = ref<any>(null);
- // 关系对话框相关
- const relationDialogVisible = ref(false);
- const relationActiveTab = ref("friend");
- const relationSearch = ref("");
- const relationList = ref<RelationUser[]>([]);
- const relationPage = ref(1);
- const relationPageSize = 5;
- const relationTotal = ref(0);
- const relationLoading = ref(false);
- const relationHasMore = computed(() => relationList.value.length < relationTotal.value && !relationLoading.value);
- const relationListScrollRef = ref<HTMLElement | null>(null);
- const relationScrollTargets: HTMLElement[] = [];
- const onRelationListScroll = (e: Event) => {
- const el = e.target as HTMLElement;
- if (!el || !relationHasMore.value) return;
- const { scrollTop, scrollHeight, clientHeight } = el;
- const distanceToBottom = scrollHeight - scrollTop - clientHeight;
- if (distanceToBottom < 80) {
- loadMoreRelationList();
- }
- };
- const bindRelationScroll = () => {
- nextTick(() => {
- setTimeout(() => {
- const listEl = relationListScrollRef.value;
- if (!listEl) return;
- unbindRelationScroll();
- listEl.addEventListener("scroll", onRelationListScroll, { passive: true });
- relationScrollTargets.push(listEl);
- const dialogBody = listEl.closest(".el-dialog")?.querySelector(".el-dialog__body") as HTMLElement | null;
- if (dialogBody && dialogBody !== listEl) {
- dialogBody.addEventListener("scroll", onRelationListScroll, { passive: true });
- relationScrollTargets.push(dialogBody);
- }
- }, 100);
- });
- };
- const unbindRelationScroll = () => {
- relationScrollTargets.splice(0, relationScrollTargets.length).forEach(el => {
- el.removeEventListener("scroll", onRelationListScroll);
- });
- };
- watch(relationDialogVisible, visible => {
- if (visible) bindRelationScroll();
- else unbindRelationScroll();
- });
- //点击本地草稿
- const handleDraftClick = () => {
- router.push({
- path: "/dynamicManagement/draftDynamic"
- });
- };
- // 举报对话框相关
- const reportDialogVisible = ref(false);
- const reportSubmitting = ref(false);
- const reportForm = reactive({
- reason: "",
- description: "",
- fileList: [] as any[],
- agreed: false
- });
- // 分享对话框相关
- interface ShareFriend {
- id: number;
- name: string;
- avatar: string;
- phoneId?: string;
- }
- const shareDialogVisible = ref(false);
- const shareSubmitting = ref(false);
- const shareSearch = ref("");
- const shareFriendList = ref<ShareFriend[]>([]);
- const selectedFriends = ref<number[]>([]);
- // 添加好友对话框相关
- const addFriendDialogVisible = ref(false);
- const addFriendSearchKeyword = ref("");
- const addFriendSearchResults = ref<any[]>([]);
- const addFriendSearching = ref(false);
- // 过滤后的好友列表
- const filteredShareFriendList = computed(() => {
- if (shareSearch.value == null || shareSearch.value === "") {
- return shareFriendList.value;
- }
- const keyword = String(shareSearch.value).toLowerCase();
- return shareFriendList.value.filter(friend => (friend.name ?? "").toLowerCase().includes(keyword));
- });
- // 用户信息
- const userInfo = reactive<UserInfo>({
- name: "白己的流浪主页",
- avatar: "",
- bio: "一家好吃的火锅店",
- followCount: 19,
- fansCount: 10,
- likeCount: 10
- });
- // 头像优先取缓存 geeker-user 的 headImg
- const cachedHeadImg = computed(() => localGet("geeker-user")?.userInfo?.headImg || userInfo.avatar || "");
- // 对话框标题
- const relationDialogTitle = computed(() => {
- const titles = {
- friend: "好友",
- follow: "关注",
- fans: "粉丝"
- };
- return titles[relationActiveTab.value as keyof typeof titles];
- });
- // 过滤后的关系列表
- const filteredRelationList = computed(() => {
- if (relationSearch.value == null || relationSearch.value === "") {
- return relationList.value;
- }
- const keyword = String(relationSearch.value).toLowerCase();
- return relationList.value.filter(
- user => (user.name ?? "").toLowerCase().includes(keyword) || (user.description ?? "").toLowerCase().includes(keyword)
- );
- });
- // 判断当前详情是否是当前用户的动态
- const isMyDynamic = computed(() => {
- if (!currentDetail.value) return false;
- // 获取当前用户的 storeId
- const currentUserStoreId = userStore.userInfo?.storeId;
- // 获取动态的 storeUserId 或 userId
- const dynamicStoreUserId = currentDetail.value.storeUserId || currentDetail.value.userId;
- // 通过 storeId 判断是否是当前用户的动态
- const result = currentUserStoreId == dynamicStoreUserId;
- console.log("是否是自己发布的作品:", result, {
- currentUserStoreId,
- dynamicStoreUserId
- });
- return result;
- });
- // 标签切换
- const handleTabClick = (pane: any) => {
- const tabName = pane.props.name;
- if (tabName === "liked") {
- getDianZan();
- } else if (tabName === "dynamic") {
- getDyanmicList();
- }
- };
- // 点击卡片
- const handleCardClick = async (item: any) => {
- try {
- // 重置描述展开状态
- isDescriptionExpanded.value = false;
- // 解析媒体列表(支持多张图片和视频)
- const mediaUrl = item.imagePath || "";
- const mediaUrls = mediaUrl
- .split(",")
- .map((url: string) => url.trim())
- .filter((url: string) => url);
- const mediaList: MediaItem[] = mediaUrls.map((url: string) => ({
- url,
- type: url.toLowerCase().endsWith(".mp4") ? ("video" as const) : ("image" as const)
- }));
- const firstUrl = mediaUrls[0] || "";
- const isVideo = firstUrl.toLowerCase().endsWith(".mp4");
- currentDetail.value = {
- ...item,
- mediaList,
- coverUrl: firstUrl,
- type: isVideo ? "video" : "image",
- author: {
- id: item.userId || 0,
- name: item.userName || "用户",
- avatar: item.userAvatar || item.userImage || ""
- },
- userAvatar: item.userAvatar || item.userImage || "",
- userName: item.userName || "用户",
- description: item.context || item.title || "",
- publishTime: item.createTime || item.createdTime || "",
- likeCount: item.likeCount || item.praiseNum || 0,
- commentCount: item.commentCount || 0,
- isLiked: item.isLiked || false
- };
- detailDrawerVisible.value = true;
- // 重置轮播索引
- currentCarouselIndex.value = 0;
- } catch (error) {
- console.error("加载详情失败:", error);
- ElMessage.error("加载详情失败");
- }
- };
- // 关闭详情
- const handleCloseDetail = () => {
- detailDrawerVisible.value = false;
- // 暂停所有视频
- videoRefs.value.forEach(video => {
- if (video && !video.paused) {
- video.pause();
- }
- });
- setTimeout(() => {
- currentDetail.value = null;
- videoRefs.value = [];
- }, 300);
- };
- // 设置视频引用
- const setVideoRef = (el: any, index: number) => {
- if (el) {
- videoRefs.value[index] = el as HTMLVideoElement;
- }
- };
- // 轮播切换
- const handleCarouselChange = (newIndex: number) => {
- // 暂停所有视频
- videoRefs.value.forEach(video => {
- if (video && !video.paused) {
- video.pause();
- }
- });
- currentCarouselIndex.value = newIndex;
- };
- // 视频播放
- const handleVideoPlay = (index: number) => {
- // 暂停其他视频
- videoRefs.value.forEach((video, i) => {
- if (i !== index && video && !video.paused) {
- video.pause();
- }
- });
- };
- // 查看用户主页
- const handleViewUserProfile = () => {
- // ElMessage.info("查看用户主页功能开发中");
- };
- // 详情页点赞
- const handleDetailLike = async () => {
- if (!currentDetail.value) return;
- try {
- // 获取当前用户的手机号,并在前面拼接 "store_"
- const phone = userStore.userInfo?.phone || "";
- const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
- const params = {
- userId: currentUserPhoneId, // 当前用户phoneId
- huifuId: currentDetail.value.id, // 动态ID
- type: 2 // 2表示点赞动态
- };
- // 根据当前点赞状态调用不同的接口
- if (currentDetail.value.isLike == "1") {
- // 已点赞,调用取消点赞接口
- await unlikeDynamicNew(params);
- currentDetail.value.isLike = "0";
- currentDetail.value.dianzanCount = Math.max(0, (currentDetail.value.dianzanCount || 1) - 1);
- } else {
- // 未点赞,调用点赞接口
- await likeDynamicNew(params);
- currentDetail.value.isLike = "1";
- currentDetail.value.dianzanCount = (currentDetail.value.dianzanCount || 0) + 1;
- }
- // 同步更新列表中的数据
- const listItem = contentList.value.find(item => item.id === currentDetail.value?.id);
- if (listItem) {
- listItem.isLike = currentDetail.value.isLike;
- listItem.dianzanCount = currentDetail.value.dianzanCount;
- }
- // 同步更新点赞列表中的数据
- const dianZanItem = dianZanList.value.find(item => item.id === currentDetail.value?.id);
- if (dianZanItem) {
- dianZanItem.isLike = currentDetail.value.isLike;
- dianZanItem.dianzanCount = currentDetail.value.dianzanCount;
- }
- ElMessage.success(currentDetail.value.isLike == "1" ? "点赞成功" : "取消点赞");
- } catch (error) {
- console.error("点赞操作失败:", error);
- ElMessage.error("操作失败");
- }
- };
- // 分享
- const handleShare = async () => {
- shareDialogVisible.value = true;
- await loadShareFriendList();
- };
- // 加载好友列表
- const loadShareFriendList = async () => {
- try {
- // 获取当前用户的手机号,并在前面拼接 "store_"
- const phone = userStore.userInfo?.phone || "";
- const fansId = phone.startsWith("store_") ? phone : `store_${phone}`;
- const res: any = await getMutualAttention({
- page: 1,
- size: 1000,
- fansId: fansId,
- name: ""
- });
- if (res.code === 200) {
- const dataList = res.data?.records || res.data?.list || res.data || [];
- shareFriendList.value = dataList.map((item: any) => ({
- id: item.id || item.userId,
- name: item.userName || item.nickname || item.name || "用户",
- avatar: item.userImage || item.avatar || item.headImg || "",
- phoneId: item.phoneId || item.fansId || ""
- }));
- console.log("加载好友列表成功:", shareFriendList.value);
- }
- } catch (error) {
- console.error("加载好友列表失败:", error);
- ElMessage.error("加载好友列表失败");
- shareFriendList.value = [];
- }
- };
- // 搜索好友
- const handleShareSearch = () => {
- // 搜索由计算属性自动处理
- };
- // 选择好友
- const handleSelectFriend = (friend: ShareFriend) => {
- const index = selectedFriends.value.indexOf(friend.id);
- if (index > -1) {
- // 已选择,取消选择
- selectedFriends.value.splice(index, 1);
- } else {
- // 未选择,添加选择
- selectedFriends.value.push(friend.id);
- }
- };
- // 移除已选择的好友
- const handleRemoveFriend = (friendId: number) => {
- const index = selectedFriends.value.indexOf(friendId);
- if (index > -1) {
- selectedFriends.value.splice(index, 1);
- }
- };
- // 确认分享
- const handleConfirmShare = async () => {
- if (selectedFriends.value.length === 0) {
- ElMessage.warning("请选择要分享的好友");
- return;
- }
- if (!currentDetail.value) {
- ElMessage.error("动态信息不存在");
- return;
- }
- try {
- shareSubmitting.value = true;
- // 调用 addTransferCount 接口,传递动态 id
- const res: any = await addTransferCount({
- id: currentDetail.value.id
- });
- if (res.code === 200) {
- ElMessage.success(`已分享给 ${selectedFriends.value.length} 位好友`);
- shareDialogVisible.value = false;
- // 可以在这里更新动态的分享数(如果需要的话)
- console.log("分享成功,动态ID:", currentDetail.value.id);
- } else {
- ElMessage.error(res.message || "分享失败");
- }
- } catch (error) {
- console.error("分享失败:", error);
- ElMessage.error("分享失败");
- } finally {
- shareSubmitting.value = false;
- }
- };
- // 关闭分享对话框
- const handleCloseShareDialog = () => {
- shareSearch.value = "";
- selectedFriends.value = [];
- shareFriendList.value = [];
- };
- // 显示评论
- const handleShowComments = async () => {
- if (!currentDetail.value) return;
- commentDrawerVisible.value = true;
- await loadCommentList();
- };
- // 加载评论列表
- const loadCommentList = async () => {
- if (!currentDetail.value) return;
- try {
- const phone = userStore.userInfo?.phone || "";
- const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
- let params = {
- businessId: String(currentDetail.value.id),
- businessType: "2",
- replyStatus: 0,
- pageNum: 1,
- pageSize: 100,
- commentType: 0,
- days: "",
- phoneId: phoneId
- };
- const res: any = await commentList(params);
- if (res.code === 200) {
- commentListData.value = res.data.records || [];
- // 更新评论总数(包括回复数)
- if (currentDetail.value) {
- const baseCommentCount = res.data.total || 0;
- // 计算所有回复的总数
- const replyCount = commentListData.value.reduce((total, comment) => {
- return total + (comment.storeComment?.length || 0);
- }, 0);
- // 评论总数 = 评论数 + 回复数
- currentDetail.value.commentCount = baseCommentCount + replyCount;
- }
- console.log("评论列表:", commentListData.value);
- console.log("评论总数:", res.data.total);
- }
- } catch (error) {
- console.error("加载评论列表失败:", error);
- }
- };
- // 点赞评论
- const handleLikeComment = async (comment: any) => {
- console.log(comment);
- try {
- // 获取当前用户的手机号,并在前面拼接 "store_"
- const phone = userStore.userInfo?.phone || "";
- const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
- const params = {
- userId: currentUserPhoneId, // 当前用户phoneId
- huifuId: comment.id, // 评论ID
- type: 1 // 1表示点赞评论
- };
- // 根据当前点赞状态调用不同的接口
- if (comment.isLiked) {
- // 已点赞,调用取消点赞接口
- await unlikeDynamicNew(params);
- } else {
- // 未点赞,调用点赞接口
- await likeDynamicNew(params);
- }
- // 切换点赞状态
- comment.isLiked = !comment.isLiked;
- comment.likeCount = comment.isLiked ? (comment.likeCount || 0) + 1 : Math.max(0, (comment.likeCount || 1) - 1);
- ElMessage.success(comment.isLiked ? "点赞成功" : "取消点赞");
- } catch (error) {
- console.error("点赞评论失败:", error);
- ElMessage.error("操作失败");
- }
- };
- // 回复评论
- const handleReplyComment = (comment: any) => {
- replyingComment.value = comment;
- commentInput.value = ``;
- // 聚焦到输入框
- setTimeout(() => {
- const textarea = document.querySelector(".comment-input-wrapper textarea") as HTMLTextAreaElement;
- if (textarea) {
- textarea.focus();
- }
- }, 100);
- };
- // 取消回复
- const handleCancelReply = () => {
- replyingComment.value = null;
- commentInput.value = "";
- };
- // 提交评论
- const handleSubmitComment = async () => {
- if (!commentInput.value.trim()) {
- ElMessage.warning("请输入评论内容");
- return;
- }
- if (!currentDetail.value) {
- ElMessage.error("动态信息不存在");
- return;
- }
- try {
- commentSubmitting.value = true;
- const phone = userStore.userInfo?.phone || "";
- const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
- // 判断是回复评论还是评论动态
- const isReply = !!replyingComment.value;
- const params: any = {
- replyId: isReply ? replyingComment.value.id : "", // 回复评论时传评论ID,否则为空
- commentContent: commentInput.value,
- businessType: "2",
- businessId: String(currentDetail.value.id),
- storeId: userStore.userInfo?.storeId || userStore.userInfo?.createdId,
- commentStar: "",
- phoneId: phoneId
- };
- const res: any = await saveComment(params);
- if (res.code === 200) {
- ElMessage.success(isReply ? "回复成功" : "评论成功");
- commentInput.value = "";
- replyingComment.value = null;
- await loadCommentList();
- } else {
- ElMessage.error(res.message || (isReply ? "回复失败" : "评论失败"));
- }
- } catch (error) {
- console.error("提交评论失败:", error);
- ElMessage.error(replyingComment.value ? "回复失败" : "评论失败");
- } finally {
- commentSubmitting.value = false;
- }
- };
- // 编辑动态
- const handleEditDynamic = () => {
- if (!currentDetail.value) return;
- // 关闭详情页
- detailDrawerVisible.value = false;
- // 跳转到编辑页面
- router.push({
- path: "/dynamicManagement/publishDynamic",
- query: { id: currentDetail.value.id }
- });
- };
- // 删除动态
- const handleDeleteDynamic = async () => {
- if (!currentDetail.value) return;
- try {
- await ElMessageBox.confirm("确定要删除这条动态吗?删除后将无法恢复。", "删除确认", {
- confirmButtonText: "确定删除",
- cancelButtonText: "取消",
- type: "warning",
- confirmButtonClass: "el-button--danger"
- }).then(async () => {
- if (!currentDetail.value) return;
- const res: any = await deleteDynamicsById({ id: currentDetail.value.id });
- if (res.code === 200) {
- ElMessage.success("删除成功");
- detailDrawerVisible.value = false;
- const index = contentList.value.findIndex(item => item.id === currentDetail.value?.id);
- if (index > -1) {
- contentList.value.splice(index, 1);
- }
- }
- });
- } catch {
- // 用户取消删除
- }
- };
- // 举报动态
- const handleReportDynamic = () => {
- reportDialogVisible.value = true;
- };
- // 拉黑用户(点击菜单项)
- const handleBlockUserClick = () => {
- handleBlockUser(false);
- };
- // 拉黑用户
- const handleBlockUser = async (skipConfirm: boolean = false) => {
- if (!currentDetail.value) return;
- try {
- // 如果不跳过确认,显示确认对话框
- if (!skipConfirm) {
- await ElMessageBox.confirm("拉黑后将不再看到该用户的动态,确定要拉黑吗?", "拉黑确认", {
- confirmButtonText: "确定拉黑",
- cancelButtonText: "取消",
- type: "warning"
- });
- }
- // 获取当前用户信息
- const currentUserId = userStore.userInfo?.id || userStore.userInfo?.userId || userStore.userInfo?.storeId || "";
- const currentUserType = userStore.userInfo?.userType || userStore.userInfo?.type || 1; // 用户类型:1商家,2用户,默认1
- // 获取被拉黑用户类型(从 phoneId 解析)
- const getUserTypeFromPhoneId = (phoneId: string | undefined): number => {
- if (!phoneId) return 1; // 默认商家
- const prefix = phoneId.split("_")[0]; // 截取 "_" 之前的文字
- return prefix === "store" ? 1 : 2; // store = 商家(1), 其他 = 用户(2)
- };
- const blockedUserType = getUserTypeFromPhoneId(currentDetail.value.phoneId) || 1;
- console.log("当前用户信息:", userStore.userInfo);
- console.log("当前用户类型:", currentUserType);
- console.log("被拉黑用户类型:", blockedUserType);
- console.log("动态详情:", currentDetail.value);
- // 调用拉黑接口
- await blockUser({
- blockerType: currentUserType, // 拉黑者类型(当前登录用户类型)
- blockedType: blockedUserType, // 被拉黑者类型(从 phoneId 解析)
- blockerId: currentUserId, // 拉黑者ID(当前登录用户)
- blockedId: currentDetail.value.storeUserId || currentDetail.value.userId || "" // 被拉黑者ID
- });
- if (!skipConfirm) {
- ElMessage.success("已拉黑该用户");
- }
- detailDrawerVisible.value = false;
- // 从列表中移除该用户的动态
- const index = contentList.value.findIndex(item => item.id === currentDetail.value?.id);
- if (index > -1) {
- contentList.value.splice(index, 1);
- }
- } catch (error) {
- if (!skipConfirm) {
- // 单独拉黑时,用户取消操作或接口失败
- console.error("拉黑失败:", error);
- if (error !== "cancel") {
- ElMessage.error("拉黑失败");
- }
- } else {
- // 举报后自动拉黑失败,抛出错误
- throw error;
- }
- }
- };
- // 举报图片预览
- const handleReportPreview = (uploadFile: any) => {
- // 可以添加图片预览功能
- console.log("预览图片", uploadFile);
- };
- // 移除举报图片
- const handleReportRemove = (uploadFile: any, uploadFiles: any[]) => {
- // 已由 v-model:file-list 自动处理
- };
- // 举报图片上传前验证
- const beforeReportUpload = (file: File) => {
- const isImage = file.type.startsWith("image/");
- const isLt5M = file.size / 1024 / 1024 < 5;
- if (!isImage) {
- ElMessage.error("只能上传图片文件!");
- return false;
- }
- if (!isLt5M) {
- ElMessage.error("图片大小不能超过 5MB!");
- return false;
- }
- return true;
- };
- // 自定义举报图片上传
- const handleReportUpload = async (options: any) => {
- const { file } = options;
- try {
- // TODO: 集成真实上传接口时,取消下面的注释
- // const uploadFormData = new FormData();
- // uploadFormData.append('file', file);
- // const res = await uploadDynamicImage(uploadFormData);
- // // 上传成功后更新文件列表
- // return res;
- // 临时方案:使用 FileReader 模拟上传
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = e => {
- resolve({
- url: e.target?.result
- });
- };
- reader.onerror = reject;
- reader.readAsDataURL(file);
- });
- } catch (error) {
- ElMessage.error("图片上传失败");
- throw error;
- }
- };
- // 提交举报
- const handleSubmitReport = async () => {
- // 验证表单
- if (!reportForm.reason) {
- ElMessage.warning("请选择举报原因");
- return;
- }
- if (!reportForm.description.trim()) {
- ElMessage.warning("请填写详细描述");
- return;
- }
- if (!reportForm.agreed) {
- ElMessage.warning("请同意发票用户协议");
- return;
- }
- reportSubmitting.value = true;
- try {
- // TODO: 集成真实接口时,取消下面的注释
- // await reportDynamic({
- // dynamicId: currentDetail.value?.id,
- // reason: reportForm.reason,
- // description: reportForm.description,
- // images: reportForm.fileList.map(f => f.url)
- // });
- // 模拟提交延迟
- await new Promise(resolve => setTimeout(resolve, 1000));
- ElMessage.success("举报提交成功,我们会尽快处理");
- reportDialogVisible.value = false;
- } catch (error) {
- ElMessage.error("举报提交失败");
- } finally {
- reportSubmitting.value = false;
- }
- };
- // 关闭举报对话框
- const handleCloseReportDialog = () => {
- reportForm.reason = "";
- reportForm.description = "";
- reportForm.fileList = [];
- reportForm.agreed = false;
- };
- // 加载用户主页信息
- const loadUserInfo = async () => {
- try {
- // 获取当前用户的手机号,并在前面拼接 "store_"
- const phone = userStore.userInfo?.phone || "";
- const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
- // 调用接口获取主页信息
- const res: any = await getHomePageInfo({
- phoneId: phoneId
- });
- if (res.code === 200 && res.data) {
- userInfo.followCount = res.data.friendNum;
- userInfo.fansCount = res.data.followNum;
- userInfo.likeCount = res.data.fansNum;
- console.log("加载用户信息成功:", userInfo);
- }
- } catch (error) {
- console.error("加载用户信息失败:", error);
- }
- };
- // 判断是否是视频项
- const isVideoItem = (item: any) => {
- if (!item.imagePath) return false;
- const files = item.imagePath
- .split(",")
- .map((url: string) => url.trim())
- .filter((url: string) => url);
- const firstFile = files[0] || "";
- return firstFile.toLowerCase().endsWith(".mp4") || firstFile.toLowerCase().includes(".mp4");
- };
- // 获取封面图片
- const getCoverImage = (item: any) => {
- console.log(item.imagePath);
- if (!item.imagePath) return "";
- const files = item.imagePath
- .split(",")
- .map((url: string) => url.trim())
- .filter((url: string) => url);
- // 检查第一个文件是否是视频
- const firstFile = files[0] || "";
- const isVideo = firstFile.toLowerCase().endsWith(".mp4") || firstFile.toLowerCase().includes(".mp4");
- // 如果第一个文件是视频,取第二个文件(jpg缩略图)
- if (isVideo && files.length > 1) {
- return files[1]; // 第二个是jpg缩略图
- }
- // 如果是图片或只有一个文件,取第一个
- return files[0] || "";
- };
- // 加载草稿数量
- const loadDraftCount = async () => {
- try {
- // 获取当前用户的手机号,并在前面拼接 "store_"
- const phone = userStore.userInfo?.phone || "";
- const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
- // 调用接口获取草稿列表
- const res: any = await getUserDraftDynamics({
- phoneId: phoneId,
- page: 1,
- size: 1000
- });
- if (res.code === 200) {
- const draftList = res.data?.records || res.data?.list || [];
- draftCount.value = draftList.length;
- console.log("加载草稿数量成功:", draftCount.value);
- }
- } catch (error) {
- console.error("加载草稿数量失败:", error);
- }
- };
- const dianZanList = ref<any[]>([]);
- const getDianZan = async () => {
- const phone = userStore.userInfo?.phone || "";
- const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
- const res: any = await getDianZanList({
- phoneId: phoneId
- });
- if (res.code === 200) {
- dianZanList.value = res.data;
- }
- };
- const getDyanmicList = async () => {
- const phone = userStore.userInfo?.phone || "";
- const phoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
- const res: any = await getUserDynamics({
- phoneId: phoneId,
- type: 2, // 2表示动态类型
- myself: 1, // 1表示自己的动态
- page: 1,
- size: 1000
- });
- if (res.code === 200) {
- const dataList = res.data?.records || res.data?.list || [];
- contentList.value = dataList.map((item: any) => ({
- id: item.id,
- title: item.title || item.context || "",
- coverUrl: item.imagePath ? item.imagePath.split(",")[0] : "", // 取第一张图片作为封面
- type: item.imagePath && item.imagePath.toLowerCase().includes(".mp4") ? "video" : "image",
- viewCount: item.viewCount || item.browseNum || 0,
- showLabel: false,
- labelText: "",
- createTime: item.createTime || item.createdTime || new Date().toISOString(),
- // 保留原始数据,用于详情展示
- ...item
- }));
- }
- };
- // 打开关系对话框
- const openRelationDialog = (type: "friend" | "follow" | "fans") => {
- relationActiveTab.value = type;
- if (relationActiveTab.value == "friend") {
- handleFriendList();
- } else if (relationActiveTab.value == "follow") {
- handleFollowList();
- } else {
- handleFansList();
- }
- relationDialogVisible.value = true;
- };
- // 关系标签切换
- const handleRelationTabClick = (tab: any) => {
- relationSearch.value = "";
- // 从 tab 对象中获取当前点击的标签名称
- const tabName = tab?.props?.name || tab?.paneName;
- console.log("切换标签:", tabName);
- if (tabName === "friend") {
- handleFriendList();
- } else if (tabName === "follow") {
- handleFollowList();
- } else if (tabName === "fans") {
- handleFansList();
- }
- };
- //好友列表
- const mapRecordToRelationUser = (item: any, defaultStatus: "following" | "mutual" | "none" = "none"): RelationUser => ({
- id: item.id || item.userId,
- name: item.userName || item.nickname || item.name || "用户",
- avatar: item.userImage || item.avatar || item.headImg || "",
- description: item.description || item.bio || item.signature || "",
- relationStatus: item.isFollowThis === 1 ? ("mutual" as const) : defaultStatus,
- phoneId: item.phoneId || item.fansId || ""
- });
- const handleFriendList = async (append = false) => {
- if (!append) {
- relationPage.value = 1;
- relationList.value = [];
- }
- const phone = userStore.userInfo?.phone || "";
- const fansId = phone.startsWith("store_") ? phone : `store_${phone}`;
- relationLoading.value = true;
- try {
- const res: any = await getMutualAttention({
- page: relationPage.value,
- size: relationPageSize,
- fansId,
- name: relationSearch.value || ""
- });
- if (res.code == 200) {
- const data = res.data;
- const dataList = data?.records || data?.list || data || [];
- const mapped = dataList.map((item: any) => mapRecordToRelationUser(item, "mutual"));
- if (append) relationList.value.push(...mapped);
- else relationList.value = mapped;
- const currentLen = relationList.value.length;
- if (dataList.length === 0) relationTotal.value = currentLen;
- else if (typeof data?.total === "number" && currentLen >= data.total) relationTotal.value = data.total;
- else relationTotal.value = currentLen + 1;
- }
- } finally {
- relationLoading.value = false;
- }
- };
- const handleFollowList = async (append = false) => {
- if (!append) {
- relationPage.value = 1;
- relationList.value = [];
- }
- const phone = userStore.userInfo?.phone || "";
- const fansId = phone.startsWith("store_") ? phone : `store_${phone}`;
- relationLoading.value = true;
- try {
- const res: any = await getMyFollowed({
- page: relationPage.value,
- size: relationPageSize,
- fansId,
- name: relationSearch.value || ""
- });
- if (res.code === 200) {
- const data = res.data;
- const dataList = data?.records || data?.list || data || [];
- const mapped = dataList.map((item: any) => mapRecordToRelationUser(item, "following"));
- if (append) relationList.value.push(...mapped);
- else relationList.value = mapped;
- const currentLen = relationList.value.length;
- if (dataList.length === 0) relationTotal.value = currentLen;
- else if (typeof data?.total === "number" && currentLen >= data.total) relationTotal.value = data.total;
- else relationTotal.value = currentLen + 1;
- }
- } finally {
- relationLoading.value = false;
- }
- };
- const handleFansList = async (append = false) => {
- if (!append) {
- relationPage.value = 1;
- relationList.value = [];
- }
- const phone = userStore.userInfo?.phone || "";
- const fansId = phone.startsWith("store_") ? phone : `store_${phone}`;
- const name = relationSearch.value || "";
- const listParams = { page: append ? relationPage.value : 1, size: append ? relationPageSize : 1000, fansId, name };
- relationLoading.value = true;
- try {
- let dataList: any[] = [];
- if (!append) {
- const res1: any = await getMyStoreFans(listParams);
- const res2: any = await getMyUserFans(listParams);
- const records1 = res1?.code === 200 ? res1.data?.records || res1.data?.list || res1.data || [] : [];
- const records2 = res2?.code === 200 ? res2.data?.records || res2.data?.list || res2.data || [] : [];
- dataList = [...records1, ...records2];
- const createdId = localGet("createdId") || userStore.userInfo?.storeId || "";
- if (createdId) dataList = dataList.filter((item: any) => String(item.id) !== String(createdId));
- } else {
- const res: any = await getMyUserFans(listParams);
- if (res.code === 200) {
- const data = res.data;
- dataList = data?.records || data?.list || data || [];
- const createdId = localGet("createdId") || userStore.userInfo?.storeId || "";
- if (createdId) dataList = dataList.filter((item: any) => String(item.id) !== String(createdId));
- }
- }
- if (!append || dataList.length > 0) {
- const mapped = dataList.map((item: any) => mapRecordToRelationUser(item, "none"));
- if (append) relationList.value.push(...mapped);
- else relationList.value = mapped;
- const currentLen = relationList.value.length;
- if (dataList.length === 0) relationTotal.value = currentLen;
- else if (!append) relationTotal.value = currentLen;
- else relationTotal.value = currentLen + 1;
- }
- } finally {
- relationLoading.value = false;
- }
- };
- const loadMoreRelationList = () => {
- if (!relationHasMore.value) return;
- relationPage.value++;
- if (relationActiveTab.value === "friend") handleFriendList(true);
- else if (relationActiveTab.value === "follow") handleFollowList(true);
- else handleFansList(true);
- };
- // 搜索关系列表
- const handleRelationSearch = () => {
- if (relationActiveTab.value === "friend") {
- handleFriendList();
- } else if (relationActiveTab.value === "follow") {
- handleFollowList();
- } else if (relationActiveTab.value === "fans") {
- handleFansList();
- }
- };
- // 关注用户
- const handleFollow = async (user: RelationUser) => {
- try {
- // TODO: 集成真实接口时,取消下面的注释
- // await followUser({ userId: user.id });
- user.relationStatus = "following";
- ElMessage.success("关注成功");
- // 更新用户统计信息
- if (relationActiveTab.value === "fans") {
- userInfo.followCount++;
- }
- } catch (error) {
- ElMessage.error("关注失败");
- }
- };
- // 取消关注用户
- const handleUnfollow = async (user: RelationUser) => {
- try {
- await ElMessageBox.confirm("确定要取消关注吗?", "提示", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning"
- });
- // TODO: 集成真实接口时,取消下面的注释
- // await unfollowUser({ userId: user.id });
- user.relationStatus = "none";
- ElMessage.success("已取消关注");
- // 更新用户统计信息
- if (relationActiveTab.value === "follow") {
- userInfo.followCount--;
- }
- } catch {
- // 用户取消操作
- }
- };
- // 关闭关系对话框
- const handleCloseRelationDialog = () => {
- relationSearch.value = "";
- relationList.value = [];
- relationPage.value = 1;
- relationTotal.value = 0;
- unbindRelationScroll();
- };
- // 打开添加好友对话框
- const addFriend = () => {
- addFriendDialogVisible.value = true;
- };
- // 关闭添加好友对话框
- const handleCloseAddFriendDialog = () => {
- // 清空防抖定时器
- if (searchDebounceTimer) {
- clearTimeout(searchDebounceTimer);
- searchDebounceTimer = null;
- }
- addFriendSearchKeyword.value = "";
- addFriendSearchResults.value = [];
- addFriendSearching.value = false;
- };
- // 搜索输入防抖定时器
- let searchDebounceTimer: NodeJS.Timeout | null = null;
- // 搜索输入处理(带防抖)
- const handleSearchInput = () => {
- // 清空之前的定时器
- if (searchDebounceTimer) {
- clearTimeout(searchDebounceTimer);
- }
- // 如果输入框为空,清空搜索结果
- if (!addFriendSearchKeyword.value.trim()) {
- addFriendSearchResults.value = [];
- return;
- }
- // 设置防抖,500ms后执行搜索
- searchDebounceTimer = setTimeout(() => {
- handleSearchFriend();
- }, 200);
- };
- // 搜索好友
- const handleSearchFriend = async () => {
- if (!addFriendSearchKeyword.value.trim()) {
- addFriendSearchResults.value = [];
- return;
- }
- addFriendSearching.value = true;
- addFriendSearchResults.value = [];
- try {
- const phone = userStore.userInfo?.phone || "";
- const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
- const userId = userStore.userInfo?.id || userStore.userInfo?.userId || "";
- const res: any = await getStoreAndUserByName({
- page: 1,
- size: 100,
- phoneId: currentUserPhoneId,
- userId: userId,
- searchName: addFriendSearchKeyword.value.trim()
- });
- if (res && res.code === 200) {
- // 处理搜索结果数据
- const dataList = res.data?.records || [];
- addFriendSearchResults.value = dataList;
- } else {
- ElMessage.error(res?.msg || res?.message || "搜索失败");
- }
- } catch (error: any) {
- console.error("搜索好友失败:", error);
- ElMessage.error(error?.message || "搜索失败,请重试");
- } finally {
- addFriendSearching.value = false;
- }
- };
- // 清空搜索
- const handleClearSearch = () => {
- // 清空防抖定时器
- if (searchDebounceTimer) {
- clearTimeout(searchDebounceTimer);
- searchDebounceTimer = null;
- }
- addFriendSearchResults.value = [];
- };
- // 在搜索结果中关注用户
- const handleFollowInSearch = async (user: any) => {
- try {
- const phone = userStore.userInfo?.phone || "";
- const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
- if (!user.phoneId) {
- ElMessage.error("用户信息不完整");
- return;
- }
- await toggleFollowUser({
- followedId: user.phoneId,
- fansId: currentUserPhoneId,
- fansType: 2
- });
- // 更新关注状态
- user.isFollowed = 1;
- user.isFollowThis = 1;
- ElMessage.success("关注成功");
- } catch (error: any) {
- console.error("关注失败:", error);
- ElMessage.error(error?.message || "关注失败,请重试");
- }
- };
- // 在搜索结果中取消关注用户
- const handleUnfollowInSearch = async (user: any) => {
- try {
- await ElMessageBox.confirm("确定要取消关注吗?", "提示", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning"
- });
- const phone = userStore.userInfo?.phone || "";
- const currentUserPhoneId = phone.startsWith("store_") ? phone : `store_${phone}`;
- if (!user.phoneId) {
- ElMessage.error("用户信息不完整");
- return;
- }
- await cancelFollewed({
- followedId: user.phoneId,
- fansId: currentUserPhoneId
- });
- // 更新关注状态
- user.isFollowed = 0;
- user.isFollowThis = 0;
- ElMessage.success("已取消关注");
- } catch (error: any) {
- if (error !== "cancel") {
- console.error("取消关注失败:", error);
- ElMessage.error(error?.message || "取消关注失败,请重试");
- }
- }
- };
- // 初始化
- onMounted(() => {
- loadUserInfo();
- getDyanmicList();
- loadDraftCount();
- // 为统计数据添加点击事件监听
- // 这部分通过模板中的 @click 直接处理
- });
- </script>
- <style scoped lang="scss">
- // 详情 Drawer
- :deep(.detail-drawer) {
- .el-drawer__header {
- position: absolute;
- top: 0;
- right: 0;
- left: 0;
- z-index: 10;
- padding: 0;
- margin: 0;
- background: transparent;
- }
- .el-drawer__body {
- padding: 0;
- background: #000000;
- }
- .drawer-header {
- padding: 20px;
- .close-btn {
- padding: 8px;
- font-size: 24px;
- color: #ffffff;
- background: #ffffff;
- }
- }
- .detail-content {
- position: relative;
- display: flex;
- width: 100%;
- height: 100%;
- // 主内容区域
- .detail-main {
- display: flex;
- flex: 1;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 80px 120px 40px 40px;
- .media-container {
- position: relative;
- display: flex;
- flex: 1;
- align-items: center;
- justify-content: center;
- width: 100%;
- max-width: 800px;
- min-height: 400px;
- .media-carousel {
- width: 100%;
- height: 100%;
- min-height: 400px;
- max-height: 70vh;
- :deep(.el-carousel__container) {
- height: 100% !important;
- min-height: 400px;
- }
- :deep(.el-carousel__item) {
- display: flex;
- align-items: center;
- justify-content: center;
- background: transparent;
- }
- :deep(.el-carousel__arrow) {
- width: 48px;
- height: 48px;
- font-size: 24px;
- color: #ffffff;
- background-color: rgb(0 0 0 / 40%);
- border: none;
- &:hover {
- background-color: rgb(0 0 0 / 60%);
- }
- }
- :deep(.el-carousel__indicators) {
- bottom: -30px;
- .el-carousel__indicator {
- .el-carousel__button {
- width: 8px;
- height: 8px;
- background-color: rgb(255 255 255 / 40%);
- border-radius: 50%;
- }
- &.is-active .el-carousel__button {
- background-color: #409eff;
- }
- }
- }
- }
- .detail-media {
- max-width: 100%;
- max-height: 65vh;
- object-fit: contain;
- border-radius: 8px;
- }
- .detail-image {
- max-width: 100%;
- max-height: 65vh;
- object-fit: contain;
- border-radius: 8px;
- }
- .detail-video {
- width: 100%;
- max-height: 65vh;
- background: #000000;
- border-radius: 8px;
- }
- .media-placeholder {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 400px;
- height: 400px;
- background: rgb(255 255 255 / 5%);
- border-radius: 8px;
- }
- .media-counter {
- position: absolute;
- right: 16px;
- bottom: -24px;
- padding: 4px 12px;
- font-size: 14px;
- color: #ffffff;
- background: rgb(0 0 0 / 50%);
- border-radius: 12px;
- }
- }
- .detail-info {
- width: 100%;
- max-width: 800px;
- padding: 20px 0;
- .author-info {
- display: flex;
- gap: 12px;
- align-items: center;
- margin-bottom: 16px;
- .author-avatar {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 40px;
- height: 40px;
- overflow: hidden;
- background: rgb(255 255 255 / 10%);
- border-radius: 50%;
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- }
- .author-details {
- flex: 1;
- .author-name {
- margin-bottom: 4px;
- font-size: 16px;
- font-weight: 500;
- color: #ffffff;
- }
- .publish-time {
- font-size: 13px;
- color: rgb(255 255 255 / 60%);
- }
- }
- }
- .detail-description {
- position: relative;
- font-size: 15px;
- line-height: 1.6;
- color: #ffffff;
- p {
- margin: 0;
- word-break: break-word;
- &.text-ellipsis {
- display: -webkit-box;
- overflow: hidden;
- text-overflow: ellipsis;
- -webkit-line-clamp: 2;
- line-clamp: 2;
- -webkit-box-orient: vertical;
- }
- }
- .expand-btn {
- display: inline-block;
- margin-top: 8px;
- font-size: 14px;
- color: #409eff;
- cursor: pointer;
- user-select: none;
- &:hover {
- color: #66b1ff;
- }
- }
- }
- }
- }
- // 右侧操作栏
- .action-bar {
- position: fixed;
- right: 40px;
- bottom: 100px;
- z-index: 10;
- display: flex;
- flex-direction: column;
- gap: 24px;
- .action-item {
- display: flex;
- flex-direction: column;
- gap: 6px;
- align-items: center;
- cursor: pointer;
- transition: transform 0.3s;
- &:hover {
- transform: scale(1.1);
- }
- &.author-action {
- cursor: pointer;
- &:hover {
- transform: scale(1.1);
- }
- }
- .action-avatar {
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 48px;
- height: 48px;
- overflow: visible;
- cursor: pointer;
- background: rgb(255 255 255 / 20%);
- border: 2px solid #ffffff;
- border-radius: 50%;
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- border-radius: 50%;
- }
- }
- .action-icon {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 48px;
- height: 48px;
- background: rgb(0 0 0 / 50%);
- backdrop-filter: blur(10px);
- border-radius: 50%;
- &.follow-icon {
- background: #409eff;
- }
- }
- .action-count {
- font-size: 13px;
- color: #ffffff;
- text-align: center;
- text-shadow: 0 1px 3px rgb(0 0 0 / 50%);
- }
- }
- }
- }
- }
- // 更多操作 Popover
- :deep(.more-actions-popover) {
- min-width: 120px;
- padding: 8px 0;
- background: rgb(0 0 0 / 90%);
- backdrop-filter: blur(10px);
- border: 1px solid rgb(255 255 255 / 20%);
- .more-actions-menu {
- .menu-item {
- display: flex;
- gap: 8px;
- align-items: center;
- padding: 10px 16px;
- font-size: 14px;
- color: #ffffff;
- cursor: pointer;
- transition: background 0.3s;
- .el-icon {
- font-size: 18px;
- }
- }
- }
- }
- .my-dynamic-container {
- min-height: calc(100vh - 120px);
- padding: 20px;
- background: #ffffff;
- // 用户信息卡片
- .user-card {
- padding: 24px;
- margin-bottom: 20px;
- background: #ffffff;
- border: 1px solid #e4e7ed;
- border-radius: 8px;
- .user-header {
- margin-bottom: 16px;
- .user-avatar-section {
- display: flex;
- gap: 16px;
- align-items: center;
- .user-avatar-large {
- display: flex;
- flex-shrink: 0;
- align-items: center;
- justify-content: center;
- width: 80px;
- height: 80px;
- overflow: hidden;
- background: #f5f7fa;
- border-radius: 50%;
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- }
- .user-info-text {
- flex: 1;
- .user-name {
- margin-bottom: 8px;
- font-size: 20px;
- font-weight: 600;
- color: #303133;
- }
- .user-stats {
- display: flex;
- gap: 8px;
- align-items: center;
- font-size: 14px;
- color: #606266;
- .stat-item {
- cursor: pointer;
- transition: color 0.3s;
- &:hover {
- color: #409eff;
- }
- }
- .stat-divider {
- color: #dcdfe6;
- }
- }
- }
- .add-friend-btn {
- display: flex;
- flex-shrink: 0;
- align-items: center;
- margin-left: auto;
- .btn-icon {
- margin-right: 4px;
- }
- }
- }
- }
- .user-bio {
- font-size: 14px;
- line-height: 1.6;
- color: #606266;
- }
- }
- // 标签页区域
- .tabs-section {
- margin-bottom: 20px;
- border-bottom: 1px solid #e4e7ed;
- :deep(.el-tabs) {
- .el-tabs__header {
- margin-bottom: 0;
- }
- .el-tabs__nav-wrap::after {
- display: none;
- }
- }
- }
- // 内容区域
- .content-section {
- margin-top: 20px;
- }
- // 内容网格布局
- .content-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 16px;
- margin-bottom: 20px;
- .draft-card {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: 255px;
- padding: 20px;
- text-align: center;
- cursor: pointer;
- background: #f5f7fa;
- border: 1px dashed #dcdfe6;
- border-radius: 8px;
- transition: all 0.3s;
- &:hover {
- background: #ecf5ff;
- border-color: #409eff;
- .el-icon {
- color: #409eff !important;
- }
- }
- .draft-title {
- margin-top: 12px;
- font-size: 16px;
- font-weight: 500;
- color: #303133;
- }
- .draft-count {
- margin-top: 8px;
- font-size: 14px;
- color: #909399;
- }
- }
- @media (width <= 1400px) {
- grid-template-columns: repeat(3, 1fr);
- }
- @media (width <= 1024px) {
- grid-template-columns: repeat(2, 1fr);
- }
- @media (width <= 768px) {
- grid-template-columns: repeat(1, 1fr);
- }
- }
- // 内容卡片
- .content-card {
- cursor: pointer;
- border: 1px solid #e4e7ed;
- border-radius: 8px;
- transition: all 0.3s;
- &:hover {
- box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
- transform: translateY(-2px);
- .content-cover-wrapper {
- .play-overlay {
- opacity: 1;
- }
- }
- }
- // 封面区域
- .content-cover-wrapper {
- position: relative;
- width: 100%;
- aspect-ratio: 16 / 9;
- overflow: hidden;
- background: #f5f7fa;
- border-top-left-radius: 8px;
- border-top-right-radius: 8px;
- .content-cover {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .cover-placeholder {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 100%;
- height: 100%;
- background: #f5f7fa;
- }
- // 播放按钮覆盖层
- .play-overlay {
- position: absolute;
- inset: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgb(0 0 0 / 30%);
- opacity: 0;
- transition: opacity 0.3s;
- .el-icon {
- filter: drop-shadow(0 2px 4px rgb(0 0 0 / 20%));
- }
- }
- // 标题标签
- .content-label {
- position: absolute;
- top: 8px;
- left: 8px;
- padding: 4px 12px;
- font-size: 12px;
- color: #ffffff;
- background: rgb(0 0 0 / 60%);
- backdrop-filter: blur(4px);
- border-radius: 4px;
- }
- }
- // 底部信息
- .content-footer {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 4px;
- .footer-left {
- display: flex;
- gap: 4px;
- align-items: center;
- padding-top: 5px;
- }
- .footer-right {
- display: flex;
- gap: 4px;
- align-items: center;
- padding-top: 5px;
- }
- .view-count,
- .like-count {
- font-size: 13px;
- color: #666666;
- }
- }
- }
- // 空状态
- .empty-section {
- padding: 80px 0;
- text-align: center;
- }
- // 详情 Drawer
- :deep(.detail-drawer) {
- .el-drawer__header {
- position: absolute;
- top: 0;
- right: 0;
- left: 0;
- z-index: 10;
- padding: 0;
- margin: 0;
- background: transparent;
- }
- .el-drawer__body {
- padding: 0;
- background: #000000;
- }
- .drawer-header {
- padding: 20px;
- .close-btn {
- padding: 8px;
- font-size: 24px;
- color: #ffffff;
- }
- }
- .detail-content {
- position: relative;
- display: flex;
- width: 100%;
- height: 100%;
- // 主内容区域
- .detail-main {
- display: flex;
- flex: 1;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 80px 120px 40px 40px;
- .media-container {
- display: flex;
- flex: 1;
- align-items: center;
- justify-content: center;
- width: 100%;
- max-width: 800px;
- .detail-media {
- max-width: 100%;
- max-height: 100%;
- object-fit: contain;
- border-radius: 8px;
- }
- .media-placeholder {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 400px;
- height: 400px;
- background: rgb(255 255 255 / 5%);
- border-radius: 8px;
- }
- }
- .detail-info {
- width: 100%;
- max-width: 800px;
- padding: 20px 0;
- .author-info {
- display: flex;
- gap: 12px;
- align-items: center;
- margin-bottom: 16px;
- .author-avatar {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 40px;
- height: 40px;
- overflow: hidden;
- background: rgb(255 255 255 / 10%);
- border-radius: 50%;
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- }
- .author-details {
- flex: 1;
- .author-name {
- margin-bottom: 4px;
- font-size: 16px;
- font-weight: 500;
- color: #ffffff;
- }
- .publish-time {
- font-size: 13px;
- color: rgb(255 255 255 / 60%);
- }
- }
- }
- .detail-description {
- position: relative;
- font-size: 15px;
- line-height: 1.6;
- color: #ffffff;
- p {
- margin: 0;
- word-break: break-word;
- &.text-ellipsis {
- display: -webkit-box;
- overflow: hidden;
- text-overflow: ellipsis;
- -webkit-line-clamp: 2;
- line-clamp: 2;
- -webkit-box-orient: vertical;
- }
- }
- .expand-btn {
- display: inline-block;
- margin-top: 8px;
- font-size: 14px;
- color: #409eff;
- cursor: pointer;
- user-select: none;
- &:hover {
- color: #66b1ff;
- }
- }
- }
- }
- }
- // 右侧操作栏
- .action-bar {
- position: fixed;
- right: 40px;
- bottom: 100px;
- z-index: 10;
- display: flex;
- flex-direction: column;
- gap: 24px;
- .action-item {
- display: flex;
- flex-direction: column;
- gap: 6px;
- align-items: center;
- cursor: pointer;
- transition: transform 0.3s;
- &:hover {
- transform: scale(1.1);
- }
- &.author-action {
- cursor: default;
- &:hover {
- transform: none;
- }
- }
- .action-avatar {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 48px;
- height: 48px;
- overflow: hidden;
- background: rgb(255 255 255 / 20%);
- border: 2px solid #ffffff;
- border-radius: 50%;
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- }
- .action-icon {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 48px;
- height: 48px;
- background: rgb(0 0 0 / 50%);
- backdrop-filter: blur(10px);
- border-radius: 50%;
- }
- .action-count {
- font-size: 13px;
- color: #ffffff;
- text-align: center;
- text-shadow: 0 1px 3px rgb(0 0 0 / 50%);
- }
- }
- }
- }
- }
- // 更多操作 Popover
- :deep(.more-actions-popover) {
- min-width: 120px;
- padding: 8px 0;
- background: rgb(0 0 0 / 90%);
- backdrop-filter: blur(10px);
- border: 1px solid rgb(255 255 255 / 10%);
- .el-popper__arrow::before {
- background: rgb(0 0 0 / 90%);
- border: 1px solid rgb(255 255 255 / 10%);
- }
- .more-actions-menu {
- .menu-item {
- display: flex;
- gap: 12px;
- align-items: center;
- padding: 12px 16px;
- font-size: 14px;
- color: #ffffff;
- cursor: pointer;
- transition: all 0.3s;
- .el-icon {
- color: #ffffff;
- }
- span {
- flex: 1;
- }
- }
- }
- }
- // 对话框样式(关系对话框 + 举报对话框)
- :deep(.el-dialog) {
- // 关系对话框
- .relation-dialog-content {
- display: flex;
- flex-direction: column;
- max-height: 70vh;
- overflow: hidden;
- .el-tabs {
- flex-shrink: 0;
- margin-bottom: 16px;
- .el-tabs__header {
- margin-bottom: 16px;
- }
- }
- .search-box {
- flex-shrink: 0;
- margin-bottom: 16px;
- }
- .relation-list {
- flex: 1;
- min-height: 0;
- max-height: 320px;
- overflow: hidden auto;
- -webkit-overflow-scrolling: touch;
- .relation-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12px 0;
- border-bottom: 1px solid #f5f7fa;
- &:last-child {
- border-bottom: none;
- }
- .user-info-row {
- display: flex;
- flex: 1;
- gap: 12px;
- align-items: center;
- .user-avatar-small {
- display: flex;
- flex-shrink: 0;
- align-items: center;
- justify-content: center;
- width: 48px;
- height: 48px;
- overflow: hidden;
- background: #f5f7fa;
- border-radius: 50%;
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- }
- .user-details {
- flex: 1;
- min-width: 0;
- .user-name-text {
- margin-bottom: 4px;
- font-size: 15px;
- font-weight: 500;
- color: #303133;
- }
- .user-desc {
- overflow: hidden;
- font-size: 13px;
- color: #909399;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- }
- }
- .action-button {
- margin-left: 12px;
- .el-button {
- min-width: 80px;
- }
- }
- }
- .relation-load-more {
- padding: 12px 0;
- text-align: center;
- }
- }
- }
- // 举报对话框
- .report-dialog-content {
- .report-tip {
- margin-bottom: 20px;
- font-size: 14px;
- line-height: 1.6;
- color: #606266;
- }
- .report-reasons {
- margin-bottom: 20px;
- .el-radio-group {
- display: flex;
- flex-wrap: wrap;
- gap: 12px 16px;
- .el-radio {
- height: auto;
- margin-right: 0;
- white-space: nowrap;
- .el-radio__label {
- font-size: 14px;
- color: #303133;
- }
- }
- }
- }
- .report-description {
- margin-bottom: 20px;
- :deep(.el-textarea__inner) {
- font-size: 14px;
- }
- }
- .report-upload {
- margin-bottom: 20px;
- .upload-title {
- margin-bottom: 12px;
- font-size: 14px;
- font-weight: 500;
- color: #303133;
- }
- :deep(.el-upload-list) {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- }
- :deep(.el-upload--picture-card) {
- width: 100px;
- height: 100px;
- border-radius: 4px;
- }
- :deep(.el-upload-list--picture-card .el-upload-list__item) {
- width: 100px;
- height: 100px;
- margin: 0;
- border-radius: 4px;
- }
- }
- .report-agreement {
- .el-checkbox {
- .el-checkbox__label {
- font-size: 14px;
- color: #606266;
- }
- }
- }
- }
- .dialog-footer {
- display: flex;
- gap: 12px;
- justify-content: flex-end;
- }
- }
- // 评论侧边栏样式
- .comment-list-container {
- flex: 1;
- height: calc(100vh - 200px);
- padding: 0 20px;
- overflow-y: auto;
- .comment-list {
- .comment-item {
- display: flex;
- gap: 12px;
- padding: 16px 0;
- border-bottom: 1px solid #f0f0f0;
- &:last-child {
- border-bottom: none;
- }
- .comment-avatar {
- display: flex;
- flex-shrink: 0;
- align-items: center;
- justify-content: center;
- width: 40px;
- height: 40px;
- overflow: hidden;
- background: #f5f5f5;
- border-radius: 50%;
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- }
- .comment-content-wrapper {
- flex: 1;
- min-width: 0;
- .comment-header,
- .store-comment-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 8px;
- .comment-user-name {
- font-size: 14px;
- font-weight: 500;
- color: #303133;
- }
- .comment-time {
- font-size: 12px;
- color: #909399;
- }
- }
- .comment-text {
- margin-bottom: 8px;
- font-size: 14px;
- line-height: 1.6;
- color: #606266;
- word-break: break-all;
- }
- // 商家回复样式
- .store-comment-wrapper {
- padding: 12px;
- margin-top: 12px;
- background: #f5f7fa;
- border-radius: 8px;
- .store-comment-item {
- display: flex;
- gap: 10px;
- .store-comment-avatar {
- display: flex;
- flex-shrink: 0;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- overflow: hidden;
- background: #ffffff;
- border-radius: 50%;
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- }
- .store-comment-content {
- flex: 1;
- min-width: 0;
- .store-comment-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 6px;
- .store-comment-user-name {
- font-size: 13px;
- font-weight: 500;
- color: #409eff;
- }
- .store-comment-time {
- font-size: 11px;
- color: #909399;
- }
- }
- .store-comment-text {
- display: flex;
- align-items: center;
- justify-content: space-between;
- font-size: 13px;
- line-height: 1.5;
- color: #606266;
- word-break: break-all;
- }
- }
- }
- }
- .comment-actions {
- display: flex;
- gap: 20px;
- .comment-action-item {
- display: flex;
- gap: 4px;
- align-items: center;
- font-size: 13px;
- color: #909399;
- cursor: pointer;
- transition: color 0.3s;
- &:hover {
- color: #409eff;
- }
- span {
- font-size: 13px;
- }
- }
- }
- }
- }
- }
- }
- .comment-input-wrapper {
- position: absolute;
- right: 0;
- bottom: 0;
- left: 0;
- padding: 16px 20px;
- background: #ffffff;
- border-top: 1px solid #e4e7ed;
- box-shadow: 0 -2px 8px rgb(0 0 0 / 5%);
- .reply-hint {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 8px 12px;
- margin-bottom: 8px;
- background: #f5f7fa;
- border-radius: 4px;
- .reply-text {
- font-size: 13px;
- color: #409eff;
- }
- .cancel-reply {
- font-size: 16px;
- color: #909399;
- cursor: pointer;
- transition: color 0.3s;
- &:hover {
- color: #f56c6c;
- }
- }
- }
- :deep(.el-textarea) {
- margin-bottom: 12px;
- }
- .el-button {
- width: 100%;
- }
- }
- // 分享对话框
- .share-dialog-content {
- .search-box {
- margin-bottom: 16px;
- }
- .share-friend-list {
- max-height: 400px;
- margin-bottom: 16px;
- overflow-y: auto;
- .share-friend-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12px;
- cursor: pointer;
- border-radius: 8px;
- transition: background-color 0.3s;
- &:hover {
- background-color: #f5f7fa;
- }
- .friend-info {
- display: flex;
- gap: 12px;
- align-items: center;
- .friend-avatar {
- display: flex;
- flex-shrink: 0;
- align-items: center;
- justify-content: center;
- width: 40px;
- height: 40px;
- overflow: hidden;
- background: #f5f5f5;
- border-radius: 50%;
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- }
- .friend-name {
- font-size: 14px;
- font-weight: 500;
- color: #303133;
- }
- }
- }
- }
- .selected-friends {
- padding: 12px;
- background: #f5f7fa;
- border-radius: 8px;
- .selected-title {
- margin-bottom: 8px;
- font-size: 13px;
- color: #606266;
- }
- .selected-list {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- .el-tag {
- max-width: 120px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- }
- }
- }
- // 添加好友对话框样式
- .add-friend-dialog-content {
- .search-box {
- margin-bottom: 20px;
- }
- .search-results {
- max-height: 400px;
- overflow-y: auto;
- .search-loading {
- display: flex;
- gap: 8px;
- align-items: center;
- justify-content: center;
- padding: 40px 0;
- color: #909399;
- }
- .search-results-count {
- padding: 0 4px;
- margin-bottom: 12px;
- font-size: 14px;
- color: #909399;
- }
- .search-results-list {
- .search-result-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 16px;
- border-bottom: 1px solid #f0f0f0;
- transition: background-color 0.2s;
- &:hover {
- background-color: #f5f7fa;
- }
- &:last-child {
- border-bottom: none;
- }
- .user-info-row {
- display: flex;
- flex: 1;
- gap: 12px;
- align-items: center;
- .user-avatar-small {
- display: flex;
- flex-shrink: 0;
- align-items: center;
- justify-content: center;
- width: 48px;
- height: 48px;
- overflow: hidden;
- background-color: #f0f0f0;
- border-radius: 50%;
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- }
- .user-details {
- flex: 1;
- min-width: 0;
- .user-name-text {
- margin-bottom: 4px;
- overflow: hidden;
- font-size: 16px;
- font-weight: 500;
- color: #303133;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .user-desc {
- overflow: hidden;
- font-size: 14px;
- color: #909399;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- }
- }
- .action-button {
- flex-shrink: 0;
- margin-left: 12px;
- }
- }
- }
- .search-empty {
- padding: 40px 0;
- }
- }
- .search-placeholder {
- padding: 40px 0;
- }
- }
- }
- </style>
- <style scoped>
- @import "@/assets/dianzanFont/iconfont.css";
- </style>
|