ResumeEvaluation.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. <template>
  2. <view class="max-w-4xl mx-auto p-6 relative">
  3. <!-- 页面标题 -->
  4. <view class="bg-[#1d1e3a] text-white p-4 rounded-t-lg">
  5. <view class="flex justify-between items-center">
  6. <view class="text-sm">No.{{ candidateInfo.idNumber || '未知' }}</view>
  7. <view class="text-sm">{{ formatDate(applicationData?.application?.create_datetime) }}</view>
  8. </view>
  9. </view>
  10. <view class="bg-white rounded-b-lg shadow-lg p-6">
  11. <!-- 头部信息 -->
  12. <view class="flex justify-between items-center mb-8 pb-4 border-b">
  13. <view class="flex items-center space-x-4">
  14. <view class="w-20 h-20 bg-gray-200 rounded-lg overflow-hidden">
  15. <image :src="candidateInfo.additionalInfo.faceVerification.images[0]" mode="aspectFill" class="w-full h-full"></image>
  16. </view>
  17. <view class="flex flex-col custom-column" style="margin-left: 10px;">
  18. <text class="text-2xl font-bold">{{ candidateInfo.name }}</text>
  19. <text class="text-gray-600 block mt-1">{{ candidateInfo.phoneNumber }}</text>
  20. </view>
  21. </view>
  22. <view class="text-right">
  23. <view class="text-4xl font-bold text-yellow-500">{{ candidateInfo.score }}<text class="text-base ml-1">分</text></view>
  24. <view class="text-sm text-yellow-500">推荐 👍</view>
  25. </view>
  26. </view>
  27. <!-- 基本信息 -->
  28. <view class="grid grid-cols-1 gap-4 mb-8">
  29. <view>
  30. <text class="text-gray-600 block">身份证号</text>
  31. <text>{{ candidateInfo.idNumber }}</text>
  32. </view>
  33. <view>
  34. <text class="text-gray-600 block">入职之后,您缺岗的班次时间段是?</text>
  35. <text>{{ candidateInfo.availabilityPeriod }}</text>
  36. </view>
  37. <view>
  38. <text class="text-gray-600 block">请问您通过面试之后多久可以入职?</text>
  39. <text>{{ candidateInfo.onboardingTime }}</text>
  40. </view>
  41. </view>
  42. <!-- AI维度分析 -->
  43. <view class="mb-8">
  44. <text class="text-xl font-bold mb-6 block">1. AI维度分析</text>
  45. <view class="space-y-6">
  46. <view v-for="(value, key) in candidateInfo.dimensions" :key="key" class="border-b pb-4">
  47. <view class="flex items-center mb-2">
  48. <text class="text-gray-600 w-32">{{ dimensionLabels[key] }}</text>
  49. <text class="ml-4" :class="{
  50. 'text-red-500': value === '欠佳',
  51. 'text-green-500': value === '优秀',
  52. 'text-yellow-500': value === '中等'
  53. }">{{ value }}</text>
  54. </view>
  55. <text class="text-gray-600 text-sm block">{{ candidateInfo.dimensionDetails[key] }}</text>
  56. </view>
  57. </view>
  58. </view>
  59. <!-- DUV分析评估 -->
  60. <view class="mb-8">
  61. <text class="text-xl font-bold mb-6 block">2. DUV分析评估</text>
  62. <view class="space-y-4">
  63. <view v-for="(item, index) in candidateInfo.duvAnalysis" :key="index" class="border-b pb-4">
  64. <view class="flex items-center justify-between mb-2">
  65. <text class="text-gray-600">{{ item.title }}</text>
  66. <text :class="{
  67. 'text-green-500': item.type === 'positive',
  68. 'text-red-500': item.type === 'negative',
  69. 'text-yellow-500': item.type === 'neutral'
  70. }">{{ item.score }}</text>
  71. </view>
  72. <text class="text-gray-600 text-sm block">{{ item.content }}</text>
  73. </view>
  74. </view>
  75. </view>
  76. <!-- 面试记录 -->
  77. <view class="mb-8">
  78. <text class="text-xl font-bold mb-6 block">3. 面试记录</text>
  79. <view class="space-y-6">
  80. <view v-for="(record, index) in candidateInfo.interviewRecord" :key="index" class="border-b pb-4">
  81. <view class="mb-2">
  82. <text class="font-semibold block">问题:{{ record.question }}</text>
  83. <text class="text-gray-600 mt-2 block">答案:{{ record.answer }}</text>
  84. <text class="text-gray-500 mt-2 text-sm block">分析:{{ record.analysis }}</text>
  85. <!-- 视频回放 -->
  86. <view v-if="record.videoUrl" class="mt-4">
  87. <view class="video-container">
  88. <video
  89. class="w-full h-full"
  90. :src="record.videoUrl"
  91. :poster="record.thumbnail"
  92. controls
  93. ></video>
  94. </view>
  95. </view>
  96. <view class="mt-2 flex justify-end">
  97. <text class="text-blue-500">得分:{{ record.score }}</text>
  98. </view>
  99. </view>
  100. </view>
  101. </view>
  102. </view>
  103. <!-- 视频记录 -->
  104. <view class="mb-8">
  105. <text class="text-xl font-bold mb-6 block">4. 视频记录</text>
  106. <view class="space-y-8">
  107. <view v-for="(category, index) in candidateInfo.videoRecords" :key="index">
  108. <text class="text-lg font-semibold mb-4 block">{{ category.category }}</text>
  109. <view class="grid grid-cols-1 gap-4">
  110. <view v-for="(video, vIndex) in category.videos" :key="vIndex" class="bg-gray-50 rounded-lg p-4">
  111. <view class="video-container">
  112. <video
  113. class="w-full h-full"
  114. :src="video.url"
  115. :poster="video.thumbnail"
  116. controls
  117. ></video>
  118. </view>
  119. <text class="text-sm text-gray-600 mt-2 block">{{ video.description }}</text>
  120. </view>
  121. </view>
  122. </view>
  123. </view>
  124. </view>
  125. <!-- 其他信息 -->
  126. <view class="mb-8">
  127. <text class="text-xl font-bold mb-6 block">5. 其他信息</text>
  128. <view class="space-y-6">
  129. <!-- 验证状态 -->
  130. <view class="grid grid-cols-1 gap-4 mb-4">
  131. <view class="flex items-center space-x-2">
  132. <text class="text-gray-600">身份证验证:</text>
  133. <text class="text-green-500">{{ candidateInfo.additionalInfo.idVerification }}</text>
  134. </view>
  135. <view class="flex items-center space-x-2">
  136. <text class="text-gray-600">手机号验证:</text>
  137. <text class="text-green-500">{{ candidateInfo.additionalInfo.phoneVerification }}</text>
  138. </view>
  139. </view>
  140. <!-- 手势验证 -->
  141. <view class="mb-6">
  142. <text class="font-semibold mb-4 block">手势验证</text>
  143. <view class="grid grid-cols-2 gap-8">
  144. <!-- 左手手势 -->
  145. <view>
  146. <text class="text-sm font-medium mb-2 block">左手手势验证</text>
  147. <view class="grid grid-cols-3 gap-2">
  148. <view v-for="(image, index) in candidateInfo.additionalInfo.leftHandGestures.images" :key="index"
  149. class="aspect-square rounded-lg overflow-hidden bg-gray-100">
  150. <image :src="image" mode="aspectFill" class="w-full h-full"></image>
  151. </view>
  152. </view>
  153. </view>
  154. <!-- 右手手势 -->
  155. <view>
  156. <text class="text-sm font-medium mb-2 block">右手手势验证</text>
  157. <view class="grid grid-cols-3 gap-2">
  158. <view v-for="(image, index) in candidateInfo.additionalInfo.rightHandGestures.images" :key="index"
  159. class="aspect-square rounded-lg overflow-hidden bg-gray-100">
  160. <image :src="image" mode="aspectFill" class="w-full h-full"></image>
  161. </view>
  162. </view>
  163. </view>
  164. </view>
  165. </view>
  166. <!-- 人脸验证 -->
  167. <view>
  168. <text class="font-semibold mb-4 block">人脸验证</text>
  169. <view class="grid grid-cols-3 gap-2">
  170. <view v-for="(image, index) in candidateInfo.additionalInfo.faceVerification.images" :key="index"
  171. class="aspect-square rounded-lg overflow-hidden bg-gray-100">
  172. <image :src="image" mode="aspectFill" class="w-full h-full"></image>
  173. </view>
  174. </view>
  175. </view>
  176. </view>
  177. </view>
  178. <!-- 评估表单 --><!-- 评分 --><!-- 评价 --><!-- 提交按钮 -->
  179. <!-- <view>
  180. <view class="mb-8">
  181. <text class="text-xl font-bold mb-4 block">6. 面试评分</text>
  182. <view class="form-item">
  183. <text class="block mb-2">请为候选人打分</text>
  184. <uni-rate v-model="evaluationScore" :max="5" allow-half />
  185. </view>
  186. </view>
  187. <view class="mb-8">
  188. <text class="text-xl font-bold mb-4 block">评价意见</text>
  189. <view class="form-item">
  190. <text class="block mb-2">请输入评价意见</text>
  191. <textarea
  192. v-model="evaluationComments"
  193. class="w-full border p-2 rounded"
  194. placeholder="请输入您的评价意见..."
  195. :rows="4"
  196. ></textarea>
  197. </view>
  198. </view>
  199. <view class="flex justify-end">
  200. <button
  201. class="bg-blue-500 text-white px-4 py-2 rounded"
  202. @click="handleSubmit"
  203. >提交评估</button>
  204. </view>
  205. </view> -->
  206. </view>
  207. <!-- 悬浮按钮 -->
  208. <!-- <view class="fixed-buttons">
  209. <button class="action-button share-button" @click="handleShare">
  210. <text class="iconfont">&#xe600;</text>
  211. </button>
  212. <button class="action-button download-button" @click="handleDownload">
  213. <text class="iconfont">&#xe601;</text>
  214. </button>
  215. <button class="action-button top-button" @click="scrollToTop">
  216. <text class="iconfont">&#xe602;</text>
  217. </button>
  218. </view> -->
  219. </view>
  220. </template>
  221. <script>
  222. import { apiBaseUrl } from '@/common/config';
  223. export default {
  224. data() {
  225. return {
  226. applicationData: null, // 存储API返回的原始数据
  227. candidateInfo: {
  228. name: '',
  229. idNumber: '',
  230. phoneNumber: '',
  231. score: 0,
  232. availabilityPeriod: '',
  233. onboardingTime: '',
  234. dimensions: {
  235. teamwork: '',
  236. learningAbility: '',
  237. attention: '',
  238. workAdaptability: '',
  239. serviceAwareness: ''
  240. },
  241. dimensionDetails: {
  242. teamwork: '',
  243. learningAbility: '',
  244. attention: '',
  245. workAdaptability: '',
  246. serviceAwareness: ''
  247. },
  248. duvAnalysis: [],
  249. interviewRecord: [],
  250. videoRecords: [],
  251. additionalInfo: {
  252. idVerification: '',
  253. phoneVerification: '',
  254. leftHandGestures: {
  255. passed: true,
  256. images: []
  257. },
  258. rightHandGestures: {
  259. passed: true,
  260. images: []
  261. },
  262. faceVerification: {
  263. passed: true,
  264. images: []
  265. }
  266. }
  267. },
  268. evaluationScore: 0,
  269. evaluationComments: '',
  270. dimensionLabels: {
  271. teamwork: '团队合作能力',
  272. learningAbility: '学习能力',
  273. attention: '细致严谨',
  274. workAdaptability: '工作适应性',
  275. serviceAwareness: '服务意识'
  276. }
  277. }
  278. },
  279. onLoad(options) {
  280. // 获取URL参数
  281. const id = options.id || 1;
  282. const tenant_id = options.tenant_id || 1;
  283. const application_id = options.application_id || 1;
  284. // 调用API获取数据
  285. this.fetchApplicationDetail(id, tenant_id, application_id);
  286. },
  287. methods: {
  288. // 获取申请详情数据
  289. fetchApplicationDetail(id, tenant_id, application_id) {
  290. uni.showLoading({
  291. title: '加载中...'
  292. });
  293. uni.request({
  294. url: `${apiBaseUrl}/api/job/application_detail?id=${id}&tenant_id=${tenant_id}&application_id=${application_id}`,
  295. method: 'GET',
  296. success: (res) => {
  297. if (res.data && res.data.code === 2000) {
  298. this.applicationData = res.data.data;
  299. this.processApiData();
  300. } else {
  301. uni.showToast({
  302. title: '获取数据失败',
  303. icon: 'none'
  304. });
  305. }
  306. },
  307. fail: () => {
  308. uni.showToast({
  309. title: '网络请求失败',
  310. icon: 'none'
  311. });
  312. },
  313. complete: () => {
  314. uni.hideLoading();
  315. }
  316. });
  317. },
  318. // 处理API返回的数据,映射到现有数据结构
  319. processApiData() {
  320. if (!this.applicationData) return;
  321. const { application, applicant, position, interview_progress, posture_photos } = this.applicationData;
  322. // 基本信息
  323. this.candidateInfo.name = applicant.name || '';
  324. this.candidateInfo.idNumber = applicant.id || '';
  325. this.candidateInfo.phoneNumber = applicant.phone || '';
  326. // 计算综合得分 (使用AI分析的平均分或综合分)
  327. if (application.comprehensive_score) {
  328. this.candidateInfo.score = application.comprehensive_score;
  329. } else if (application.ai_capability_scores) {
  330. const scores = Object.values(application.ai_capability_scores);
  331. this.candidateInfo.score = scores.length ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;
  332. }
  333. // 处理AI维度分析
  334. if (application.ai_capability_scores) {
  335. // 将API返回的能力分数映射到现有维度
  336. const scoreMapping = {
  337. '专业性': 'workAdaptability',
  338. '沟通能力': 'teamwork',
  339. '技术匹配度': 'learningAbility',
  340. '解决问题能力': 'attention'
  341. };
  342. // 根据分数设置评价等级
  343. Object.entries(application.ai_capability_scores).forEach(([key, value]) => {
  344. const mappedKey = scoreMapping[key] || 'serviceAwareness';
  345. let rating = '中等';
  346. if (value >= 80) rating = '优秀';
  347. else if (value < 65) rating = '欠佳';
  348. this.candidateInfo.dimensions[mappedKey] = rating;
  349. });
  350. }
  351. // 处理维度详情
  352. if (application.ai_analysis_details && application.ai_analysis_details.requirements_feedback) {
  353. // 将反馈分配到各个维度
  354. const feedback = application.ai_analysis_details.requirements_feedback;
  355. Object.keys(this.candidateInfo.dimensionDetails).forEach(key => {
  356. this.candidateInfo.dimensionDetails[key] = feedback;
  357. });
  358. }
  359. // 处理DUV分析
  360. if (application.ai_overall_feedback) {
  361. this.candidateInfo.duvAnalysis = [
  362. {
  363. title: '综合评估',
  364. content: application.ai_overall_feedback,
  365. score: this.candidateInfo.score >= 70 ? '良好' : '需改进',
  366. type: this.candidateInfo.score >= 70 ? 'positive' : 'negative'
  367. }
  368. ];
  369. // 添加职位匹配度
  370. if (position) {
  371. this.candidateInfo.duvAnalysis.push({
  372. title: '职位匹配度',
  373. content: `应聘职位: ${position.title}, ${position.department}, ${position.location}`,
  374. score: '匹配',
  375. type: 'positive'
  376. });
  377. }
  378. }
  379. // 处理面试记录
  380. if (interview_progress && interview_progress.length) {
  381. this.candidateInfo.interviewRecord = interview_progress
  382. .filter(item => item.question_text)
  383. .map(item => {
  384. const answer = this.extractAnswerFromNotes(application.note, item.question_text);
  385. return {
  386. question: item.question_text,
  387. answer: answer || '未回答',
  388. analysis: '候选人回答简短,需进一步评估',
  389. score: '待评估',
  390. videoUrl: item.video_answer ? item.video_answer.video_url : null,
  391. thumbnail: '/images/video-placeholder.jpg'
  392. };
  393. });
  394. }
  395. // 处理视频记录
  396. const videoAnswers = interview_progress
  397. .filter(item => item.video_answer && item.video_answer.video_url);
  398. if (videoAnswers.length) {
  399. this.candidateInfo.videoRecords = [
  400. {
  401. category: '面试视频',
  402. videos: videoAnswers.map(item => ({
  403. url: item.video_answer.video_url,
  404. thumbnail: '/images/thumbnail1.jpg',
  405. description: item.question_text
  406. }))
  407. }
  408. ];
  409. }
  410. // 处理手势验证和人脸验证
  411. if (posture_photos && posture_photos.length) {
  412. // 分类照片
  413. const leftHandPhotos = posture_photos.filter(p =>
  414. p.description.includes('left') && !p.description.includes('face'));
  415. const rightHandPhotos = posture_photos.filter(p =>
  416. p.description.includes('right') && !p.description.includes('face'));
  417. const facePhotos = posture_photos.filter(p =>
  418. !p.description.includes('left') && !p.description.includes('right'));
  419. // 更新照片数据
  420. this.candidateInfo.additionalInfo.leftHandGestures.images =
  421. leftHandPhotos.map(p => p.photo_url);
  422. this.candidateInfo.additionalInfo.rightHandGestures.images =
  423. rightHandPhotos.map(p => p.photo_url);
  424. this.candidateInfo.additionalInfo.faceVerification.images =
  425. facePhotos.map(p => p.photo_url);
  426. }
  427. // 设置验证状态
  428. this.candidateInfo.additionalInfo.idVerification = '通过';
  429. this.candidateInfo.additionalInfo.phoneVerification = '通过';
  430. },
  431. // 从面试记录中提取答案
  432. extractAnswerFromNotes(notes, question) {
  433. if (!notes) return '';
  434. const pattern = new RegExp(`问题 ${question}:\\s*([^\\n]+)`, 'i');
  435. const match = notes.match(pattern);
  436. return match ? match[1].trim() : '';
  437. },
  438. // 格式化日期
  439. formatDate(dateString) {
  440. if (!dateString) return '';
  441. try {
  442. const date = new Date(dateString);
  443. return date.toLocaleString('zh-CN', {
  444. year: 'numeric',
  445. month: '2-digit',
  446. day: '2-digit',
  447. hour: '2-digit',
  448. minute: '2-digit',
  449. second: '2-digit'
  450. });
  451. } catch (e) {
  452. return dateString;
  453. }
  454. },
  455. handleSubmit() {
  456. if (this.evaluationScore === 0 || !this.evaluationComments.trim()) {
  457. uni.showToast({
  458. title: '请完成所有必填项',
  459. icon: 'none'
  460. });
  461. return;
  462. }
  463. uni.showToast({
  464. title: '评估已提交',
  465. icon: 'success'
  466. });
  467. },
  468. scrollToTop() {
  469. uni.pageScrollTo({
  470. scrollTop: 0,
  471. duration: 300
  472. });
  473. },
  474. handleShare() {
  475. uni.showToast({
  476. title: '分享链接已复制',
  477. icon: 'success'
  478. });
  479. },
  480. handleDownload() {
  481. uni.showToast({
  482. title: '报告下载中...',
  483. icon: 'loading'
  484. });
  485. }
  486. }
  487. }
  488. </script>
  489. <style>
  490. .max-w-4xl {
  491. max-width: 56rem;
  492. margin: 0 auto;
  493. }
  494. .p-6 {
  495. padding: 1.5rem;
  496. }
  497. .relative {
  498. position: relative;
  499. }
  500. /* .bg-\[\#1d1e3a\] {
  501. background-color: #1d1e3a;
  502. } */
  503. .text-white {
  504. color: white;
  505. }
  506. .p-4 {
  507. padding: 1rem;
  508. }
  509. .rounded-t-lg {
  510. border-top-left-radius: 0.5rem;
  511. border-top-right-radius: 0.5rem;
  512. }
  513. .flex {
  514. display: flex;
  515. }
  516. .justify-between {
  517. justify-content: space-between;
  518. }
  519. .items-center {
  520. align-items: center;
  521. }
  522. .text-sm {
  523. font-size: 0.875rem;
  524. }
  525. .bg-white {
  526. background-color: white;
  527. }
  528. .rounded-b-lg {
  529. border-bottom-left-radius: 0.5rem;
  530. border-bottom-right-radius: 0.5rem;
  531. }
  532. .shadow-lg {
  533. box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
  534. }
  535. .mb-8 {
  536. margin-bottom: 2rem;
  537. }
  538. .pb-4 {
  539. padding-bottom: 1rem;
  540. }
  541. .border-b {
  542. border-bottom-width: 1px;
  543. border-bottom-style: solid;
  544. border-bottom-color: #e5e7eb;
  545. }
  546. /* .space-x-4 > * + * {
  547. margin-left: 1rem;
  548. } */
  549. .w-20 {
  550. width: 5rem;
  551. }
  552. .h-20 {
  553. height: 5rem;
  554. }
  555. .bg-gray-200 {
  556. background-color: #e5e7eb;
  557. }
  558. .rounded-lg {
  559. border-radius: 0.5rem;
  560. }
  561. .overflow-hidden {
  562. overflow: hidden;
  563. }
  564. .w-full {
  565. width: 100%;
  566. }
  567. .h-full {
  568. height: 100%;
  569. }
  570. .object-cover {
  571. object-fit: cover;
  572. }
  573. .text-2xl {
  574. font-size: 1.5rem;
  575. }
  576. .font-bold {
  577. font-weight: bold;
  578. }
  579. .text-gray-600 {
  580. color: #4b5563;
  581. }
  582. .text-right {
  583. text-align: right;
  584. }
  585. .text-4xl {
  586. font-size: 2.25rem;
  587. }
  588. .text-yellow-500 {
  589. color: #f59e0b;
  590. }
  591. .ml-1 {
  592. margin-left: 0.25rem;
  593. }
  594. .text-base {
  595. font-size: 1rem;
  596. }
  597. .grid {
  598. display: grid;
  599. }
  600. .grid-cols-1 {
  601. grid-template-columns: repeat(1, minmax(0, 1fr));
  602. }
  603. .grid-cols-2 {
  604. grid-template-columns: repeat(2, minmax(0, 1fr));
  605. }
  606. .grid-cols-3 {
  607. grid-template-columns: repeat(3, minmax(0, 1fr));
  608. }
  609. .gap-4 {
  610. gap: 1rem;
  611. }
  612. .gap-2 {
  613. gap: 0.5rem;
  614. }
  615. .gap-8 {
  616. gap: 2rem;
  617. }
  618. .text-xl {
  619. font-size: 1.25rem;
  620. }
  621. .mb-6 {
  622. margin-bottom: 1.5rem;
  623. }
  624. .mb-4 {
  625. margin-bottom: 1rem;
  626. }
  627. .mb-2 {
  628. margin-bottom: 0.5rem;
  629. }
  630. /* .space-y-6 > * + * {
  631. margin-top: 1.5rem;
  632. }
  633. .space-y-4 > * + * {
  634. margin-top: 1rem;
  635. }
  636. .space-y-8 > * + * {
  637. margin-top: 2rem;
  638. } */
  639. .w-32 {
  640. width: 8rem;
  641. }
  642. .text-red-500 {
  643. color: #ef4444;
  644. }
  645. .text-green-500 {
  646. color: #10b981;
  647. }
  648. .text-sm {
  649. font-size: 0.875rem;
  650. }
  651. .font-semibold {
  652. font-weight: 600;
  653. }
  654. .mt-2 {
  655. margin-top: 0.5rem;
  656. }
  657. .text-gray-500 {
  658. color: #6b7280;
  659. }
  660. .mt-4 {
  661. margin-top: 1rem;
  662. }
  663. .video-container {
  664. position: relative;
  665. width: 100%;
  666. max-width: 240px;
  667. aspect-ratio: 3/4;
  668. margin: 0 auto;
  669. background: #000;
  670. border-radius: 12px;
  671. overflow: hidden;
  672. }
  673. .justify-end {
  674. justify-content: flex-end;
  675. }
  676. .text-blue-500 {
  677. color: #3b82f6;
  678. }
  679. .text-lg {
  680. font-size: 1.125rem;
  681. }
  682. .bg-gray-50 {
  683. background-color: #f9fafb;
  684. }
  685. .aspect-square {
  686. aspect-ratio: 1 / 1;
  687. }
  688. .bg-gray-100 {
  689. background-color: #f3f4f6;
  690. }
  691. .form-item {
  692. margin-bottom: 1.5rem;
  693. }
  694. .border {
  695. border-width: 1px;
  696. border-style: solid;
  697. border-color: #e5e7eb;
  698. }
  699. .rounded {
  700. border-radius: 0.25rem;
  701. }
  702. .bg-blue-500 {
  703. background-color: #3b82f6;
  704. }
  705. .px-4 {
  706. padding-left: 1rem;
  707. padding-right: 1rem;
  708. }
  709. .py-2 {
  710. padding-top: 0.5rem;
  711. padding-bottom: 0.5rem;
  712. }
  713. .fixed-buttons {
  714. position: fixed;
  715. right: 2rem;
  716. bottom: 6rem;
  717. display: flex;
  718. flex-direction: column;
  719. gap: 1rem;
  720. z-index: 100;
  721. }
  722. .action-button {
  723. width: 40px;
  724. height: 40px;
  725. border-radius: 50%;
  726. background-color: #3b82f6;
  727. color: white;
  728. display: flex;
  729. align-items: center;
  730. justify-content: center;
  731. box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
  732. }
  733. /* 为了模拟图标,你可能需要引入字体图标或使用其他方式 */
  734. @font-face {
  735. font-family: "iconfont";
  736. src: url('data:font/woff2;charset=utf-8;base64,...') format('woff2');
  737. }
  738. .iconfont {
  739. font-family: "iconfont" !important;
  740. font-style: normal;
  741. -webkit-font-smoothing: antialiased;
  742. -moz-osx-font-smoothing: grayscale;
  743. }
  744. /* 媒体查询适配移动端 */
  745. @media (max-width: 768px) {
  746. .p-6 {
  747. padding: 1rem;
  748. }
  749. .fixed-buttons {
  750. right: 1rem;
  751. }
  752. }
  753. .custom-column {
  754. flex-direction: column;
  755. }
  756. </style>