ImageToImage.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. <template>
  2. <!-- 图生图 -->
  3. <div class="number-people">
  4. <div class="content-box">
  5. <!-- AI对话框 -->
  6. <div class="chat-dialog">
  7. <!-- 对话消息列表 -->
  8. <div class="message-list">
  9. <div v-if="imageAllList.length > 0">
  10. <div v-for="(item, index) in imageAllList" :key="index">
  11. <!-- 用户消息 -->
  12. <div class="user-message" v-if="item.type === 'user'">
  13. {{ item.content }}
  14. <div class="user-image-list" v-if="item.imageUrl">
  15. <el-image
  16. style="width: fit-content; height: 180px; margin: 10px;"
  17. :src="item.imageUrl"
  18. :preview-src-list="[item.imageUrl]"
  19. fit="cover"
  20. show-progress
  21. >
  22. <template
  23. #toolbar="{ actions, prev, next, reset, activeIndex, setActiveItem }"
  24. >
  25. <el-icon @click="prev"><Back /></el-icon>
  26. <el-icon @click="next"><Right /></el-icon>
  27. <el-icon @click="setActiveItem(item.imageList.length - 1)">
  28. <DArrowRight />
  29. </el-icon>
  30. <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
  31. <el-icon @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })"><ZoomIn /></el-icon>
  32. <el-icon @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })"><RefreshRight /></el-icon>
  33. <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
  34. <el-icon @click="reset"><Refresh /></el-icon>
  35. <el-icon @click="download(activeIndex)"><Download /></el-icon>
  36. </template>
  37. </el-image>
  38. </div>
  39. </div>
  40. <!-- AI生成图片对话框 -->
  41. <div class="ai-message" v-if="item.type !== 'user'">
  42. {{ item.content }}
  43. <span v-if="item.loading" class="loading-dots">
  44. <span class="dot"></span>
  45. <span class="dot"></span>
  46. <span class="dot"></span>
  47. </span>
  48. <div class="image-list" v-if="item.imageList">
  49. <el-image
  50. v-for="(image, index) in item.imageList"
  51. :key="index"
  52. style=" width: fit-content; height: 220px; margin: 10px;"
  53. :src="image"
  54. :preview-src-list="item.imageList"
  55. fit="cover"
  56. show-progress
  57. >
  58. <template
  59. #toolbar="{ actions, reset, activeIndex}"
  60. >
  61. <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
  62. <el-icon
  63. @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })">
  64. <ZoomIn />
  65. </el-icon>
  66. <el-icon
  67. @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })">
  68. <RefreshRight />
  69. </el-icon>
  70. <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
  71. <el-icon @click="reset"><Refresh /></el-icon>
  72. <el-icon @click="download(activeIndex)"><Download /></el-icon>
  73. </template>
  74. </el-image>
  75. </div>
  76. </div>
  77. </div>
  78. </div>
  79. </div>
  80. <!-- 输入框和发送按钮 -->
  81. <div class="input-section">
  82. <input
  83. ref="inputRef"
  84. type="text"
  85. v-model="inputMessage"
  86. placeholder="描述任何画面..."
  87. @keyup.enter="sendMessage"
  88. style="flex: 1; margin-right: 8px;"
  89. />
  90. <!-- 风格选择下拉框 -->
  91. <el-select
  92. v-model="selectedStyle"
  93. placeholder="选择风格"
  94. @change="handleStyleSelectChange"
  95. size="small"
  96. class="custom-style-select"
  97. >
  98. <el-option
  99. v-for="style in styleList"
  100. :key="style"
  101. :label="style"
  102. :value="style"
  103. />
  104. </el-select>
  105. <!-- 参考图 -->
  106. <ImageUpload v-model="uploadedImage" ref="imageUploadRef"/>
  107. <!-- 语音输入按钮 -->
  108. <button
  109. @click="toggleSpeechInput"
  110. class="speech-btn"
  111. :class="{ 'recording': isRecording }"
  112. >
  113. <el-icon v-if="!isRecording"><Microphone /></el-icon>
  114. <el-icon v-else><Mute /></el-icon>
  115. <!-- 显示倒计时(仅录音时显示) -->
  116. <span v-if="isRecording" class="countdown-text">{{ countdown }}s</span>
  117. </button>
  118. <!-- 终止按钮 -->
  119. <div
  120. v-if="conversationInProgress"
  121. @click="stopStream"
  122. class="stop-btn"
  123. title="终止问答"
  124. >
  125. <img :src="stopicon" alt="停止" />
  126. </div>
  127. <button v-if="!conversationInProgress"
  128. @click="sendMessage">发送</button>
  129. </div>
  130. </div>
  131. </div>
  132. </div>
  133. </template>
  134. <script setup>
  135. import { ref, onMounted,onUnmounted} from 'vue'
  136. import {AiImageStatusEnum, CreatePainting, PaintingGetMys} from '@/api/questions.js'
  137. import { useRouter, useRoute } from 'vue-router'
  138. import {
  139. Document,
  140. Menu as IconMenu,
  141. Location,
  142. Setting,
  143. ArrowLeftBold,
  144. Fold,
  145. Expand,
  146. ChatLineRound,
  147. Picture,
  148. MagicStick,
  149. Tickets,
  150. User,
  151. Brush,
  152. Check,
  153. EditPen,
  154. ArrowDown,
  155. ArrowUp
  156. } from '@element-plus/icons-vue'
  157. import { saveRecord } from '@/api/personalized/index.js'
  158. // 导入全局状态
  159. import { globalState } from '@/utils/globalState.js'
  160. // 语音图标
  161. import { Microphone, Mute } from "@element-plus/icons-vue";
  162. // 终止按钮
  163. import stopicon from "@/assets/icon/stopicon.png";
  164. // 消息组件
  165. import {Message} from "@/utils/message/Message.js";
  166. // 上传参考图
  167. import ImageUpload from '@/components/ImageUpload/index.vue';
  168. // 导入接口
  169. import { getModelIdByType } from '@/api/teachers.js'
  170. import { ModelTypeEnum } from '@/api/teachers.js'
  171. // 存储上传的图片
  172. const uploadedImage = ref('');
  173. const imageUploadRef = ref(null);
  174. // 当前选中的风格
  175. const selectedStyle = ref('');
  176. // 已移除dropdownVisible,使用el-select的v-model
  177. // 语音输入响应式变量
  178. const isRecording = ref(false); // 录音状态
  179. const recognition = ref(null); // 语音识别实例
  180. const countdown = ref(0); // 倒计时剩余秒数
  181. const countdownTimer = ref(null); // 倒计时定时器
  182. // 风格列表
  183. const styleList = ['电影写真', '中国风', '卡通', '动漫', '素描', '像素风格'];
  184. // 对话状态变量
  185. const conversationInProgress = ref(false); // 对话是否正在进行中
  186. const conversationInAbortController = ref(); // 对话进行中 abort 控制器
  187. // tts 语音
  188. import { useAudioPlayer } from '@/api/tts/useAudioPlayer';
  189. // 添加抽屉显示状态
  190. const drawerVisible = ref(true)
  191. // 年级ID相关
  192. const gradeId = ref('')
  193. // 添加消息计数器变量
  194. const messageCount = ref(0)
  195. // modelId响应式变量
  196. const modelId = ref(0)
  197. // 保存记录
  198. onMounted(async () => {
  199. // 从全局状态初始化年级ID
  200. gradeId.value = globalState.initGradeId()
  201. try{
  202. const res = await saveRecord({
  203. brpNjId: gradeId.value,
  204. brpType: "aiCount",
  205. brpProgress: 1
  206. });
  207. // 获取modelId
  208. const modelRes = await getModelIdByType({ type: ModelTypeEnum.IMAGE_TO_IMAGE, platform: "DouBao" })
  209. modelId.value = modelRes.data
  210. }catch(error){
  211. console.error('保存记录失败:', error);
  212. }
  213. });
  214. // 消息列表和输入内容的响应式变量
  215. const inputMessage = ref('')
  216. // 输入框引用
  217. const inputRef = ref(null)
  218. // 初始化语音识别
  219. const initSpeechRecognition = () => {
  220. const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
  221. if (!SpeechRecognition) {
  222. alert("当前浏览器不支持语音输入功能");
  223. return null;
  224. }
  225. const instance = new SpeechRecognition();
  226. instance.lang = 'zh-CN';
  227. instance.interimResults = false;
  228. instance.onresult = (event) => {
  229. if (event.results?.[0]?.[0]) {
  230. inputMessage.value += event.results[0][0].transcript;
  231. }
  232. };
  233. // 识别器结束时清除定时器
  234. instance.onend = () => {
  235. clearInterval(countdownTimer.value);
  236. isRecording.value = false;
  237. countdown.value = 0;
  238. };
  239. instance.onerror = (event) => {
  240. console.error('语音识别错误:', event.error);
  241. clearInterval(countdownTimer.value); // 出错时清除定时器
  242. isRecording.value = false;
  243. Message().error('语音输入失败,请重试!', true)
  244. countdown.value = 0;
  245. };
  246. return instance;
  247. };
  248. // 切换录音状态
  249. const toggleSpeechInput = () => {
  250. // 清除可能存在的旧定时器
  251. clearInterval(countdownTimer.value);
  252. countdownTimer.value = null;
  253. if (isRecording.value) {
  254. // 手动停止时重置状态
  255. countdown.value = 0;
  256. recognition.value?.stop();
  257. isRecording.value = false;
  258. } else {
  259. // 初始化倒计时前再次清除定时器(防止快速点击)
  260. clearInterval(countdownTimer.value);
  261. countdown.value = 10; // 重置为10秒
  262. recognition.value = initSpeechRecognition();
  263. if (!recognition.value) return;
  264. navigator.mediaDevices.getUserMedia({ audio: true })
  265. .then(() => {
  266. recognition.value.start();
  267. isRecording.value = true;
  268. // 启动新的倒计时定时器
  269. countdownTimer.value = setInterval(() => {
  270. countdown.value--;
  271. if (countdown.value <= 0) {
  272. clearInterval(countdownTimer.value); // 倒计时结束清除
  273. recognition.value.stop();
  274. isRecording.value = false;
  275. countdown.value = 0;
  276. }
  277. }, 1000);
  278. })
  279. .catch((err) => {
  280. console.error("麦克风权限获取失败:", err);
  281. alert("请允许麦克风权限以使用语音输入");
  282. // 出错时重置状态
  283. isRecording.value = false;
  284. countdown.value = 0;
  285. });
  286. }
  287. };
  288. // 停止操作函数
  289. const stopStream = async () => {
  290. // tip:如果 stream 进行中的 message,就需要调用 controller 结束
  291. if (conversationInAbortController.value) {
  292. conversationInAbortController.value.abort();
  293. }
  294. // 设置为 false
  295. conversationInProgress.value = false;
  296. };
  297. // 发送消息函数,图片数据
  298. const sendMessage = async() => {
  299. if (uploadedImage.value) {
  300. // 创建 AbortController 实例,以便中止请求
  301. conversationInAbortController.value = new AbortController();
  302. // 标记对话进行中
  303. conversationInProgress.value = true;
  304. // 先保存内容 再置空输入框
  305. let content = inputMessage.value;
  306. inputMessage.value = '';
  307. // 创建用户消息对象,包含可能的图片
  308. const userMessage = {
  309. type: 'user',
  310. content: content,
  311. };
  312. // 如果有上传的图片,添加到用户消息中
  313. if (uploadedImage.value) {
  314. userMessage.imageUrl = uploadedImage.value;
  315. // 清空上传的图片
  316. // uploadedImage.value = '';
  317. }
  318. imageAllList.value.push(userMessage);
  319. imageAllList.value.push({
  320. type: 'ai',
  321. content: "正在为您生成图片,请稍等",
  322. loading: true
  323. })
  324. // 递增消息计数器
  325. messageCount.value++
  326. // 发送saveRecord请求 保存消息次数
  327. try{
  328. await saveRecord({
  329. brpNjId: gradeId.value,
  330. brpType: "aiCount",
  331. brpProgress: messageCount.value
  332. });
  333. console.log('保存记录成功,消息次数:', messageCount.value);
  334. }catch(error){
  335. console.error('保存记录失败:', error);
  336. conversationInProgress.value = false;
  337. }
  338. try {
  339. CreatePainting({
  340. "modelId": modelId.value,
  341. "prompt":content,
  342. "width":1024,
  343. "height":1024,
  344. "promptImage":uploadedImage.value
  345. }).then(res=>{
  346. console.log("生成图片",res)
  347. //目前写死调用已生成的图片,全部通了后再改
  348. inProgressImageMap.value[res.data] = {id:res.data,status:AiImageStatusEnum.IN_PROGRESS}
  349. // inProgressImageMap.value[260] = {id:260,status:AiImageStatusEnum.IN_PROGRESS}
  350. }).finally(() => {
  351. // 图片生成请求完成后更新状态
  352. conversationInProgress.value = false;
  353. });
  354. } catch (error) {
  355. console.error('生成图片失败:', error);
  356. conversationInProgress.value = false;
  357. }
  358. } else {
  359. // 如果没有上传图片,显示提示信息
  360. Message().error('请先上传参考图!', true);
  361. }
  362. // 调用子组件的方法清除预览图
  363. imageUploadRef.value?.clearPreview();
  364. };
  365. // 生成图片
  366. import { ElIcon } from 'element-plus'
  367. import {
  368. Back,
  369. DArrowRight,
  370. Download,
  371. Refresh,
  372. RefreshLeft,
  373. RefreshRight,
  374. Right,
  375. ZoomIn,
  376. ZoomOut,
  377. } from '@element-plus/icons-vue'
  378. const imageAllList = ref([]) // 对话的消息列表
  379. // 图片轮询相关的参数(正在生成中的)
  380. const inProgressImageMap = ref({}) // 监听的 image 映射,一般是生成中(需要轮询),key 为 image 编号,value 为 image
  381. const inProgressTimer = ref() // 生成中的 image 定时器,轮询生成进展
  382. /** 轮询生成中的 image 列表 */
  383. const refreshWatchImages = async () => {
  384. const imageIds = Object.keys(inProgressImageMap.value).map(Number)
  385. if (imageIds.length === 0) {
  386. return
  387. }
  388. const list = await PaintingGetMys(imageIds)
  389. const newWatchImages = {}
  390. list.data.forEach((image) => {
  391. if (image.status === AiImageStatusEnum.IN_PROGRESS) {
  392. newWatchImages[image.id] = image
  393. } else {
  394. imageAllList.value.pop();
  395. console.log('AI生成的图片地址:', image.picUrl);
  396. imageAllList.value.push({
  397. type: 'ai',
  398. content: "已为您生成图片:",
  399. imageList: [image.picUrl],
  400. })
  401. }
  402. })
  403. inProgressImageMap.value = newWatchImages
  404. if (newWatchImages.size === 0) {
  405. inProgressTimerFun()
  406. }
  407. }
  408. /** 组件挂在的时候 */
  409. onMounted(async () => {
  410. refreshWatchImagesFun()
  411. })
  412. /** 组件取消挂在的时候 */
  413. onUnmounted(async () => {
  414. inProgressTimerFun()
  415. })
  416. // 自动刷新 image 列表
  417. const refreshWatchImagesFun = () => {
  418. inProgressTimer.value = setInterval(async () => {
  419. await refreshWatchImages()
  420. }, 1000 * 3)
  421. }
  422. // 停止刷新image列表
  423. const inProgressTimerFun = () => {
  424. if (inProgressTimer.value) {
  425. clearInterval(inProgressTimer.value)
  426. }
  427. }
  428. // 处理风格选择变化事件
  429. const handleStyleSelectChange = (style) => {
  430. // 检查输入框中是否已经包含风格描述,如果有则替换,没有则添加
  431. const styleRegex = /生成图片风格为[^,]+/g;
  432. if (styleRegex.test(inputMessage.value)) {
  433. // 替换现有的风格描述
  434. inputMessage.value = inputMessage.value.replace(styleRegex, `生成图片风格为${style}`);
  435. } else {
  436. // 添加新的风格描述
  437. inputMessage.value += `生成图片风格为${style}`;
  438. }
  439. // 选中风格后,将光标自动对焦到输入
  440. inputRef.value?.focus();
  441. }
  442. </script>
  443. <style scoped lang="scss">
  444. @use 'sass:math';
  445. // 定义rpx转换函数
  446. @function rpx($px) {
  447. @return math.div($px, 750) * 100vw;
  448. }
  449. // 用户图片列表样式
  450. .user-image-list {
  451. display: flex;
  452. flex-wrap: wrap;
  453. margin-top: rpx(5);
  454. }
  455. .number-people {
  456. flex: 1;
  457. height: 100%;
  458. display: flex;
  459. background-color: #ece9fd;
  460. }
  461. .content-box {
  462. flex: 1;
  463. // margin-top: rpx(10);
  464. // margin-bottom: rpx(10);
  465. margin: rpx(7);
  466. border-radius: rpx(15);
  467. background: rgba($color: #ffffff, $alpha: 0.5);
  468. overflow-y: auto;
  469. }
  470. // 对话框
  471. .chat-dialog {
  472. display: flex;
  473. flex-direction: column;
  474. height: 100%;
  475. }
  476. .message-list {
  477. flex: 1;
  478. overflow-y: auto;
  479. padding: rpx(15);
  480. }
  481. /* 自定义滚动条样式 */
  482. .message-list::-webkit-scrollbar {
  483. width: rpx(2); /* 滚动条宽度 */
  484. }
  485. .message-list::-webkit-scrollbar-track {
  486. background: #f1effd; /* 滚动条轨道背景色 */
  487. border-radius: rpx(4);
  488. }
  489. .message-list::-webkit-scrollbar-thumb {
  490. background: #e2ddfc; /* 滚动条滑块颜色 */
  491. border-radius: rpx(4);
  492. }
  493. .message-list::-webkit-scrollbar-thumb:hover {
  494. background: #e2ddfc; /* 滚动条滑块 hover 状态颜色 */
  495. }
  496. .message-list .user-message {
  497. background-color: #ffffff;
  498. margin-left: auto; // 消息靠右显示
  499. margin-right: 0; // 重置右边距
  500. max-width: rpx(400);
  501. font-size: rpx(8);
  502. width: fit-content; // 宽度随文字内容变化
  503. border-radius: rpx(5);
  504. padding: rpx(5);
  505. text-align: left; // 文字左对齐
  506. }
  507. .message-list .ai-message {
  508. background-color: #ffdd55;
  509. margin-left: 0; // 消息靠左显示
  510. margin-right: auto; // 重置右边距
  511. margin-bottom: rpx(10);
  512. width: fit-content;
  513. max-width: rpx(400);
  514. padding: rpx(5);
  515. font-size: rpx(8);
  516. border-radius: rpx(5);
  517. text-align: left; // 文字左对齐
  518. }
  519. // 加载动画效果
  520. .loading-dots {
  521. display: inline-block;
  522. margin-left: rpx(5);
  523. }
  524. .loading-dots .dot {
  525. display: inline-block;
  526. width: rpx(3);
  527. height: rpx(3);
  528. border-radius: 50%;
  529. background-color: #333;
  530. margin: 0 rpx(1);
  531. animation: loading-dot 1.4s infinite ease-in-out both;
  532. }
  533. .loading-dots .dot:nth-child(1) {
  534. animation-delay: -0.32s;
  535. }
  536. .loading-dots .dot:nth-child(2) {
  537. animation-delay: -0.16s;
  538. }
  539. @keyframes loading-dot {
  540. 0%, 80%, 100% {
  541. transform: scale(0);
  542. }
  543. 40% {
  544. transform: scale(1);
  545. }
  546. }
  547. .image-list {
  548. display: flex;
  549. flex-wrap: wrap;
  550. }
  551. .content-demo {
  552. background-color: #f4f2fa;
  553. border-radius: 15px;
  554. padding: 30px 10px;
  555. }
  556. // 风格下拉框
  557. .style-dropdown{
  558. // min-width: auto;
  559. width: fit-content;
  560. padding: rpx(6);
  561. }
  562. .style-dropdown ::v-deep(.el-dropdown-menu__item) {
  563. font-size: rpx(8);
  564. color: black;
  565. border-radius: rpx(5);
  566. min-width: auto;
  567. width: rpx(55);
  568. height: rpx(15);
  569. display: flex;
  570. justify-content: space-between;
  571. align-items: center;
  572. padding-right: rpx(10);
  573. }
  574. .selected-icon {
  575. color: black;
  576. font-size: rpx(10);
  577. }
  578. .style-dropdown ::v-deep(.el-dropdown-menu__item:hover),
  579. .style-dropdown ::v-deep(.el-dropdown-menu__item:focus),
  580. .style-dropdown ::v-deep(.el-dropdown-menu__item:active) {
  581. background: linear-gradient(
  582. to bottom,
  583. #fee78a,
  584. #ffce1b
  585. );
  586. }
  587. .input-section {
  588. display: flex;
  589. padding: rpx(10);
  590. gap: rpx(5);
  591. .speech-btn {
  592. padding: rpx(5) rpx(10);
  593. background: #fff;
  594. border: 1px solid #ffce1b;
  595. border-radius: rpx(5);
  596. cursor: pointer;
  597. display: flex;
  598. align-items: center;
  599. gap: rpx(4);
  600. &.recording {
  601. background: #ffeeba;
  602. border-color: #ffc107;
  603. .el-icon {
  604. color: #dc3545;
  605. }
  606. }
  607. .el-icon {
  608. font-size: rpx(8);
  609. color: #666;
  610. }
  611. .dropdown-icon {
  612. font-size: rpx(6);
  613. transition: transform 0.3s ease;
  614. }
  615. }
  616. // 终止按钮样式
  617. .stop-btn {
  618. cursor: pointer;
  619. display: flex;
  620. align-items: center;
  621. img {
  622. width: rpx(20);
  623. height: rpx(20);
  624. }
  625. }
  626. }
  627. .input-section input {
  628. flex: 1;
  629. padding: rpx(5);
  630. font-size: rpx(7);
  631. border: 1px solid #ccc;
  632. border-radius: rpx(5);
  633. }
  634. .input-section button {
  635. padding: rpx(5) rpx(15);
  636. background: linear-gradient(
  637. to bottom,
  638. #fee78a,
  639. #ffce1b
  640. ); /* 设置悬停、聚焦、点击状态下的背景色 */
  641. color: black;
  642. border: none;
  643. font-size: rpx(7);
  644. border-radius: rpx(5);
  645. cursor: pointer;
  646. box-shadow: 0 0px 2px rgba(0, 0, 0, 0.3);
  647. }
  648. .image-upload-section {
  649. padding: rpx(10);
  650. display: flex;
  651. justify-content: center;
  652. align-items: center;
  653. }
  654. // 风格下拉框
  655. .custom-style-select {
  656. width: rpx(55);
  657. }
  658. .custom-style-select ::v-deep(.el-select__wrapper) {
  659. background-color: #ffff;
  660. width: rpx(55);
  661. height: rpx(20);
  662. font-size: rpx(6);
  663. color: black;
  664. border: 1px solid #ffce1b;
  665. border-radius: rpx(5);
  666. }
  667. </style>