Commit 15f5c94a by haojie

1

parent 3a12c6c1
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M31.9409 33.1981C31.8231 34.0374 31.6723 34.5303 31.4442 34.6946C31.3081 34.7803 31.0138 34.9017 30.5576 34.9874C30.1124 35.0732 29.5459 35.1946 28.8874 35.2732C28.2141 35.3946 27.4967 35.4803 26.6873 35.566C25.878 35.6625 25.0613 35.7482 24.2519 35.8161C23.4168 35.8839 22.6332 35.9304 21.8679 35.9732C21.2683 36.0089 18.0087 36.0089 17.4164 35.9732C16.6475 35.9304 15.8566 35.8875 15.0214 35.8161C14.2121 35.7482 13.3953 35.6589 12.5933 35.566C11.784 35.4803 11.0592 35.391 10.386 35.2732C9.72744 35.1946 9.17192 35.0732 8.71573 34.9874C8.27058 34.9017 7.97258 34.7803 7.84014 34.6946C7.61205 34.5303 7.45385 34.0374 7.34348 33.1981C7.22576 32.3587 7.26623 31.2944 7.4796 29.9872C7.57158 29.4836 7.72241 29.0764 7.97626 28.7657C8.21907 28.4442 8.53178 28.1763 8.896 27.9263C9.26021 27.7013 9.6649 27.512 10.0843 27.3549C10.5147 27.2263 10.9599 27.087 11.4418 26.9477C11.887 26.8084 12.3432 26.6691 12.7662 26.5155C13.1967 26.3512 13.6014 26.1762 13.9361 25.9441C14.3666 25.6762 14.6793 25.4262 14.9074 25.1726C15.1355 24.9226 15.2937 24.6797 15.404 24.4261C15.4887 24.1761 15.5659 23.9225 15.5659 23.6725C15.5659 23.4047 15.5659 23.1368 15.5402 22.7903C15.4887 22.3475 15.3305 21.9581 15.0582 21.6903C14.7897 21.4117 14.4769 21.1367 14.1385 20.8402C13.9766 20.7116 13.8258 20.5009 13.7081 20.2509C13.583 20.0009 13.4652 19.7473 13.3696 19.4794C13.285 19.1758 13.1672 18.8651 13.0826 18.5437C12.9465 18.5008 12.803 18.4329 12.6522 18.3365C12.5345 18.2508 12.3983 18.0865 12.2659 17.9043C12.1298 17.7222 11.9973 17.4722 11.9017 17.1079C11.8097 16.7686 11.7656 16.4507 11.7913 16.1828C11.8097 15.9042 11.8759 15.6542 11.9679 15.4292C12.0599 15.2042 12.1703 15.0149 12.3542 14.797C12.3542 14.0363 12.3947 13.2898 12.4646 12.5112C12.5565 11.879 12.6853 11.179 12.895 10.4325C13.09 9.68603 13.4101 9.02171 13.8147 8.43239C14.2194 7.86807 14.6425 7.41805 15.095 7.0716C15.5512 6.7323 16.0405 6.43942 16.5372 6.23227C17.0338 6.03226 17.5305 5.89296 18.0308 5.83225C18.5275 5.75367 18.991 5.71081 19.4472 5.71081C19.5134 5.71081 19.5833 5.71081 19.6422 5.71795C19.7084 5.71081 19.7673 5.71081 19.8372 5.71081C20.2823 5.71081 20.7569 5.75367 21.2536 5.83225C21.7502 5.89296 22.2395 6.03226 22.7472 6.23227C23.2365 6.43942 23.7332 6.73587 24.182 7.0716C24.6382 7.41805 25.0686 7.86807 25.4733 8.43239C25.8706 9.02171 26.1907 9.6896 26.393 10.4325C26.5954 11.1754 26.7315 11.879 26.8161 12.5112C26.8823 13.2898 26.9338 14.0363 26.9338 14.797C27.1104 15.0149 27.2282 15.2042 27.3201 15.4292C27.4047 15.6542 27.471 15.9042 27.4967 16.1828C27.5225 16.4507 27.471 16.7721 27.379 17.1079C27.2944 17.4722 27.1583 17.7222 27.0148 17.9043C26.8787 18.0865 26.7536 18.2508 26.6358 18.3365C26.474 18.4329 26.3415 18.5008 26.2054 18.5437C26.1134 18.8651 25.9957 19.1758 25.9111 19.4794C25.8191 19.7473 25.7014 19.9973 25.5726 20.2509C25.4623 20.5009 25.3041 20.7081 25.1422 20.8402C24.8037 21.1331 24.4947 21.4117 24.2225 21.6903C23.9539 21.9581 23.7994 22.3475 23.7516 22.7903C23.7258 23.1368 23.7258 23.4047 23.7258 23.6725C23.7258 23.9225 23.792 24.1761 23.8766 24.4261C23.9944 24.6762 24.1452 24.919 24.3733 25.1726C24.6087 25.4226 24.9141 25.6762 25.3519 25.9441C25.6904 26.1762 26.084 26.3512 26.5144 26.5155C26.9449 26.6727 27.39 26.8084 27.8462 26.9477C28.3171 27.087 28.7659 27.2263 29.1964 27.3549C29.6268 27.512 30.0315 27.7013 30.392 27.9263C30.7563 28.1763 31.0653 28.4442 31.3118 28.7657C31.5546 29.0764 31.7091 29.4836 31.8084 29.9872C32.0071 31.2908 32.0512 32.3552 31.9409 33.1981ZM12.6522 5.14292C12.6522 5.45722 12.5198 5.74295 12.3064 5.95011C12.093 6.15726 11.7987 6.28584 11.4749 6.28584H7.35452V10.2861C7.35452 10.9182 6.82843 11.429 6.17726 11.429C5.85351 11.429 5.5592 11.3004 5.34582 11.0933C5.13244 10.8861 5 10.6004 5 10.2861V5.14292C5 4.51074 5.52609 4 6.17726 4H11.4749C12.1261 4 12.6522 4.51074 12.6522 5.14292Z" fill="#2770FF"/>
<path d="M12.54 22.8571C12.54 23.1714 12.4095 23.4571 12.1993 23.6643C11.989 23.8714 11.699 24 11.38 24H6.16C5.51837 24 5 23.4893 5 22.8571V17.7143C5 17.0821 5.51837 16.5714 6.16 16.5714C6.479 16.5714 6.769 16.7 6.97925 16.9071C7.1895 17.1143 7.32 17.4 7.32 17.7143V21.7143H11.38C12.0216 21.7143 12.54 22.225 12.54 22.8571ZM34 17.7143V22.8571C34 23.4893 33.4816 24 32.84 24H27.62C26.9784 24 26.46 23.4893 26.46 22.8571C26.46 22.5429 26.5905 22.2571 26.8008 22.05C27.011 21.8429 27.301 21.7143 27.62 21.7143H31.68V17.7143C31.68 17.0821 32.1984 16.5714 32.84 16.5714C33.159 16.5714 33.449 16.7 33.6592 16.9071C33.8695 17.1143 34 17.4 34 17.7143ZM34 5.14286V10.2857C34 10.9179 33.4816 11.4286 32.84 11.4286C32.521 11.4286 32.231 11.3 32.0208 11.0929C31.8105 10.8857 31.68 10.6 31.68 10.2857V6.28571H27.62C26.9784 6.28571 26.46 5.775 26.46 5.14286C26.46 4.82857 26.5905 4.54286 26.8008 4.33571C27.011 4.12857 27.301 4 27.62 4H32.84C33.4816 4 34 4.51071 34 5.14286Z" fill="#2770FF"/>
</svg>
<svg width="22" height="25" viewBox="0 0 22 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.66854 3.93492H19.3315C20.2505 3.93492 21 4.67658 21 5.58518V8.50113C20.9999 8.55947 20.9764 8.61537 20.9347 8.65662C20.893 8.69787 20.8365 8.7211 20.7775 8.72123H1.22251C1.16353 8.7211 1.10702 8.69787 1.06532 8.65662C1.02362 8.61537 1.00013 8.55947 1 8.50113V5.58518C1 4.67658 1.7495 3.93492 2.66854 3.93492ZM2.36253 9.41173H19.6372C19.9435 9.41173 20.1935 9.65878 20.1935 9.96173V22.3495C20.1935 23.2586 19.444 24 18.5249 24H3.47506C2.55602 24 1.80652 23.2586 1.80652 22.3495V9.96173C1.80652 9.65878 2.05627 9.41173 2.36253 9.41173ZM8.64742 1H13.3523C13.9053 1 14.227 1.1657 14.4177 1.73242C14.5584 2.15066 14.6001 2.68569 14.6783 3.1783C14.6831 3.20969 14.6811 3.24175 14.6723 3.27229C14.6635 3.30283 14.6481 3.33113 14.6272 3.35525C14.6063 3.37937 14.5804 3.39874 14.5513 3.41205C14.5221 3.42535 14.4904 3.43227 14.4583 3.43233H7.54171C7.50961 3.43228 7.4779 3.42536 7.44876 3.41205C7.41962 3.39874 7.39372 3.37936 7.37286 3.35524C7.35199 3.33111 7.33664 3.30281 7.32786 3.27227C7.31908 3.24173 7.31707 3.20967 7.32198 3.1783C7.39968 2.68569 7.44155 2.15066 7.58232 1.73242C7.77304 1.1657 8.09469 1 8.64742 1ZM10.1532 11.766V21.7376C10.1532 22.8089 11.8468 22.8089 11.8468 21.7376V11.766C11.8468 10.4474 10.1532 10.4474 10.1532 11.766ZM14.9467 11.7667V21.7379C14.9467 22.8109 16.6806 22.8309 16.6806 21.7376V11.766C16.6806 10.4124 14.9467 10.4124 14.9467 11.7667ZM7.05305 11.7667C7.05305 10.4124 5.31943 10.4124 5.31918 11.766V21.7376C5.31918 22.8309 7.05305 22.8109 7.05305 21.7379V11.7667Z" fill="#B4B4B4"/>
</svg>
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M25.1653 5.55988C25.7444 5.55988 26.2137 6.02937 26.2137 6.60824V21.2869C26.2137 21.8661 25.7442 22.3353 25.1653 22.3353C24.5864 22.3353 24.1169 21.8658 24.1169 21.2869V6.60824C24.1169 6.02937 24.5862 5.55988 25.1653 5.55988ZM19.5735 12.5498C20.1526 12.5498 20.6219 13.0193 20.6219 13.5981V21.2869C20.6219 21.8661 20.1524 22.3353 19.5735 22.3353C18.9946 22.3353 18.5251 21.8658 18.5251 21.2869V13.5981C18.5251 13.019 18.9944 12.5498 19.5735 12.5498ZM13.9817 6.25879C14.5609 6.25879 15.0301 6.72828 15.0301 7.30715V21.2866C15.0301 21.8658 14.5606 22.335 13.9817 22.335C13.4026 22.335 12.9334 21.8655 12.9334 21.2866V7.30742C12.9334 6.72828 13.4026 6.25879 13.9817 6.25879ZM8.38991 3.46289C8.96905 3.46289 9.43827 3.93238 9.43827 4.51125V21.2866C9.43827 21.8658 8.96878 22.335 8.38991 22.335C7.81077 22.335 7.34155 21.8655 7.34155 21.2866V4.51125C7.34128 3.93238 7.81077 3.46289 8.38991 3.46289ZM2.79812 11.8506C3.37726 11.8506 3.84647 12.3201 3.84647 12.8989V21.2866C3.84647 21.8658 3.37698 22.335 2.79812 22.335C2.21925 22.335 1.74976 21.8655 1.74976 21.2866V12.8992C1.74948 12.3201 2.21897 11.8506 2.79812 11.8506Z" fill="white"/>
</svg>
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.61534 20.8332C5.24457 20.8332 4.87373 20.6917 4.59082 20.4088C4.02506 19.843 4.02506 18.9257 4.59082 18.3599L18.3599 4.59088C18.9256 4.02504 19.843 4.02504 20.4088 4.59088C20.9746 5.15664 20.9746 6.07403 20.4088 6.63985L6.6398 20.4088C6.35689 20.6918 5.98609 20.8332 5.61528 20.8332H5.61534Z" fill="white"/>
<path d="M19.3844 20.8331C19.0136 20.8331 18.6428 20.6916 18.3599 20.4088L4.59083 6.63981C4.02506 6.07405 4.02506 5.15663 4.59083 4.59085C5.15657 4.02509 6.07398 4.02502 6.63978 4.59085L20.4088 18.3598C20.9746 18.9256 20.9746 19.8429 20.4088 20.4088C20.126 20.6917 19.7552 20.8332 19.3844 20.8332L19.3844 20.8331Z" fill="white"/>
</svg>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.25 15.75H9V33.75H5.25V15.75ZM0 21.1406H3.75V29.8594H0V21.1406ZM10.5 12.75H14.25V36.75H10.5V12.75ZM16.5 21H20.25V28.5H16.5V21ZM22.5 18.75H26.25V30.75H22.5V18.75ZM28.5 15.75H32.25V33.75H28.5V15.75ZM33.75 21.75H37.5V27.75H33.75V21.75ZM39 18.75H42.75V30.75H39V18.75ZM44.25 21H48V28.5H44.25V21Z" fill="white"/>
</svg>
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.1765 19.4845C9.78115 19.4845 9.3874 19.3329 9.08584 19.0313L2.91553 12.836C2.31396 12.2313 2.31396 11.2501 2.91553 10.6454C3.51865 10.0407 4.49521 10.0407 5.09678 10.6454L10.1765 15.7454L19.8827 6.0001C20.4843 5.39385 21.4608 5.39385 22.064 6.0001C22.6655 6.60479 22.6655 7.58447 22.064 8.18916L11.2671 19.0313C10.9655 19.3329 10.5702 19.4845 10.1765 19.4845Z" fill="white"/>
</svg>
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34.815 6.22137V6.22255C34.5101 5.50216 33.8572 5 33.0975 5H11.9003C11.1406 5 10.4877 5.50216 10.185 6.22255C10.0651 6.50133 10.002 6.80405 10 7.11071V21.5232C10 21.7285 10.0352 21.9233 10.0859 22.1098C10.3171 22.9898 11.0382 23.6351 11.8992 23.6351H24.9976C25.1228 23.6609 25.2362 23.7312 25.3191 23.8345C25.3774 23.9518 25.4512 24.0574 25.5415 24.1478L27.6487 26.2949C27.8171 26.5127 28.0281 26.6887 28.2669 26.8105C28.5058 26.9323 28.7668 26.997 29.0316 27C29.123 27 29.2099 26.9812 29.2991 26.966C29.7703 26.8862 30.1942 26.614 30.4816 26.2057C30.6335 25.9945 30.7172 25.7352 30.7183 25.4677L30.7282 23.716C30.7853 23.6745 30.8504 23.6468 30.9187 23.6351H33.0964C33.9596 23.6351 34.6829 22.9874 34.9152 22.111C34.9692 21.9197 34.9978 21.7203 35 21.5197V7.11541C35 6.7951 34.9317 6.49357 34.8161 6.22137H34.815ZM17.4096 16.0006C17.1843 16 16.9612 15.952 16.7533 15.8595C16.5453 15.767 16.3565 15.6318 16.1976 15.4615C16.0387 15.2912 15.9129 15.0892 15.8273 14.867C15.7416 14.6449 15.698 14.4069 15.6987 14.1668C15.6987 13.1542 16.4628 12.3329 17.4074 12.3329H17.4118C18.3565 12.3329 19.1216 13.1542 19.1205 14.1668C19.1205 15.1793 18.3543 15.9994 17.4096 15.9994V16.0006ZM23.1105 16.0006C22.8851 16.0001 22.6621 15.9523 22.4541 15.86C22.246 15.7676 22.0571 15.6325 21.8981 15.4623C21.7392 15.2921 21.6132 15.0902 21.5275 14.8681C21.4417 14.646 21.3979 14.4081 21.3985 14.1679C21.3985 13.1554 22.1636 12.3341 23.1083 12.3341H23.1127C24.0573 12.3341 24.8225 13.1554 24.8214 14.1679C24.8214 15.1805 24.0551 16.0006 23.1105 16.0006ZM28.9094 16.0006C28.684 16.0001 28.461 15.9523 28.2529 15.86C28.0449 15.7676 27.856 15.6325 27.697 15.4623C27.538 15.2921 27.4121 15.0902 27.3263 14.8681C27.2406 14.646 27.1968 14.4081 27.1973 14.1679C27.1973 13.1554 27.9625 12.3341 28.9072 12.3341H28.9116C29.8562 12.3341 30.6214 13.1554 30.6203 14.1679C30.6203 15.1805 29.854 16.0006 28.9094 16.0006Z" fill="#00DFB0"/>
<path d="M23.968 29.6787L23.2704 28.9268H23.2583L20.4153 25.9165H12.9745C8.87831 25.8773 8.77269 22.1237 8.77269 22.1237L8.77159 13.1371C8.74421 12.8277 8.61529 12.5405 8.40958 12.3307C8.20388 12.1208 7.93588 12.003 7.65706 12H6.8979C5.85158 12 5 12.9911 5 14.2105V29.2715C5 30.4908 5.85488 31.4819 6.9034 31.4819H9.07856C9.14762 31.4944 9.21337 31.5237 9.2711 31.5678L9.2799 33.398C9.2799 33.5084 9.3019 33.6151 9.32831 33.723C9.36462 33.8837 9.42733 34.0358 9.51535 34.1695C9.87842 34.697 10.4043 35 10.9633 35H10.9655C11.4925 35 11.9887 34.7326 12.3484 34.264L12.8545 33.723L14.4521 32.018C14.5225 31.9444 14.6215 31.7849 14.6732 31.6941C14.756 31.5849 14.8697 31.5105 14.9956 31.4832H23.6104C24.155 31.4832 24.6457 31.2133 24.9934 30.7839L24.9813 30.7692H25L23.9691 29.6775L23.968 29.6787Z" fill="#00DFB0"/>
</svg>
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M76 70C76 73.1826 74.7357 76.2348 72.4853 78.4853C70.2348 80.7357 67.1826 82 64 82H16C12.8174 82 9.76516 80.7357 7.51472 78.4853C5.26428 76.2348 4 73.1826 4 70V30C4 26.8174 5.26428 23.7652 7.51472 21.5147C9.76516 19.2643 12.8174 18 16 18H64C67.1826 18 70.2348 19.2643 72.4853 21.5147C74.7357 23.7652 76 26.8174 76 30V38L90.212 30.894C90.822 30.5892 91.4998 30.4454 92.181 30.4762C92.8622 30.5071 93.5242 30.7116 94.1041 31.0703C94.6841 31.429 95.1627 31.93 95.4945 32.5257C95.8263 33.1214 96.0003 33.7921 96 34.474V65.528C96 66.2097 95.8257 66.8802 95.4937 67.4756C95.1618 68.0711 94.6831 68.5718 94.1033 68.9302C93.5234 69.2887 92.8615 69.493 92.1805 69.5238C91.4994 69.5546 90.8218 69.4107 90.212 69.106L76 62V70Z" fill="#888FA1"/>
<path d="M34.012 36.57L49.992 46.378C51.912 47.556 52.57 50.168 51.464 52.212C51.106 52.872 50.59 53.42 49.966 53.796L33.986 63.444C32.06 64.608 29.614 63.886 28.522 61.834C28.1781 61.1846 27.9989 60.4608 28 59.726V40.272C28 37.912 29.796 36 32.01 36C32.714 36 33.404 36.196 34.012 36.57Z" fill="#303030"/>
</svg>
<template>
<div
class="audio-player"
:style="{
background: bk,
}"
>
<template v-if="clickPlay">
<img :src="imgs.start" alt="" class="play-icon" @click="onPlay" v-show="!playStatus" />
<img :src="imgs.stop" alt="" class="play-icon" @click="onPause" v-show="playStatus" />
</template>
<template v-if="need_progress">
<span class="play-time"> {{ transTime(audioCurrent) }}/{{ transTime(audioDuration) }} </span>
<div class="play-progress">
<div class="play-current-progress" :style="{ width: `${playProgress}%` }"></div>
</div>
</template>
</div>
<audio ref="audioRef" :src="CurrentUrl()" @canplay="onCanplay"></audio>
</template>
<script setup lang="ts">
import { ref, onBeforeMount, onBeforeUnmount, computed, watch } from 'vue';
const props = withDefaults(
defineProps<{
url?: string | string[];
need_progress?: boolean;
bk?: string;
autoPlay?: boolean;
clickPlay?: boolean;
}>(),
{
need_progress: true,
url: '',
bk: 'transparent',
autoPlay: false,
clickPlay: false,
},
);
const imgs = {
start: new URL('../../assets/svg/admin/audioStart.svg', import.meta.url).href,
stop: new URL('../../assets/svg/admin/audioStop.svg', import.meta.url).href,
};
// const speedList = [
// {
// label: '2x',
// value: 2,
// },
// {
// label: '1.5x',
// value: 1.5,
// },
// {
// label: '1x',
// value: 1,
// },
// {
// label: '0.75x',
// value: 0.75,
// },
// ];
onBeforeMount(() => {
clearInterval(timeInterval.value);
});
onBeforeUnmount(() => {
clearInterval(timeInterval.value);
});
// const speedVisible = ref<boolean>(false); // 设置音频播放速度弹窗
const audioRef = ref(); // 音频标签对象
// const activeSpeed = ref(1); // 音频播放速度
const audioDuration = ref(0); // 音频总时长
const audioCurrent = ref(0); // 音频当前播放时间
// const audioVolume = ref(1); // 音频声音,范围 0-1
const playStatus = ref<boolean>(false); // 音频播放状态:true 播放,false 暂停
const playProgress = ref(0); // 音频播放进度
const timeInterval = ref(); // 获取音频播放进度定时器
// 当前播放的音频下标
const audio_index = ref(0);
// watch(props.url, (v) => {
// // 重新初始化音频
// audio_index.value = 0;
// audioCurrent.value = 0;
// onPause();
// });
// 当前播放的音频链接
const CurrentUrl = computed(() => {
return function () {
const { url } = props;
if (typeof url === 'string') {
return url;
} else {
return url[audio_index.value];
}
};
});
// 音频加载完毕的回调
const onCanplay = () => {
audioDuration.value = audioRef?.value.duration || 0;
};
// 更新音频
const UpdateAudio = async () => {
// 进入到下一个
const { url } = props;
if (url && url.length) {
if (audio_index.value === url.length - 1) {
// 最后一个,暂停播放
await onPause();
} else if (audio_index.value < url.length - 1) {
audio_index.value += 1;
await onPause();
onPlay();
}
}
};
const onPlay = async () => {
// 音频播放完后,重新播放
// if (playProgress.value === 100) audioRef.value.currentTime = 0;
await audioRef.value.play();
playStatus.value = true;
audioDuration.value = audioRef.value.duration;
timeInterval.value = setInterval(() => {
audioCurrent.value = audioRef.value.currentTime;
playProgress.value = (audioCurrent.value / audioDuration.value) * 100;
if (playProgress.value === 100) {
if (typeof props.url === 'string') {
//
} else {
UpdateAudio();
}
}
}, 100);
};
// 暂停播放
const onPause = async () => {
await audioRef.value.pause();
playStatus.value = false;
clearInterval(timeInterval.value);
};
// const onChangeSpeed = (value: number) => {
// activeSpeed.value = value;
// // 设置倍速
// audioRef.value.playbackRate = value;
// speedVisible.value = false;
// };
// const onHandleSpeed = () => {
// speedVisible.value = !speedVisible.value;
// };
// 设置声音
// const onSetVolume = (value: number) => {
// audioRef.value.volume = value;
// audioVolume.value = value;
// };
// 音频播放时间换算
const transTime = (value: number) => {
let time = '';
let h = parseInt(String(value / 3600));
value %= 3600;
let m = parseInt(String(value / 60));
let s = parseInt(String(value % 60));
if (h > 0) {
time = formatTime(h + ':' + m + ':' + s);
} else {
time = formatTime(m + ':' + s);
}
return time;
};
// 格式化时间显示,补零对齐
const formatTime = (value: string) => {
let time = '';
let s = value.split(':');
let i = 0;
for (; i < s.length - 1; i++) {
time += s[i].length == 1 ? '0' + s[i] : s[i];
time += ':';
}
time += s[i].length == 1 ? '0' + s[i] : s[i];
return time;
};
</script>
<style lang="less" scoped>
@import '@/style/variables.less';
.audio-player {
border-radius: 42px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
.play-icon {
width: 34px;
height: 34px;
cursor: pointer;
}
.play-time {
width: 72px;
display: inline-block;
font-weight: 400;
font-size: @size-14;
}
.play-progress {
flex: 1;
height: 12px;
background: #fff;
box-shadow: inset 0px 1px 0px 0px #fff;
border-radius: 10px;
position: relative;
.play-current-progress {
height: 12px;
background: #0dd;
border-radius: 10px;
position: absolute;
top: 0;
left: 0;
}
}
.play-voice {
width: 20px;
height: 20px;
margin-right: 14px;
cursor: pointer;
}
.play-speed {
cursor: pointer;
color: #00e5ff;
}
}
</style>
......@@ -76,4 +76,15 @@ const emit = defineEmits(['update:modelValue']);
background: #fff;
}
}
.t-button.c-button-opacity {
background: transparent;
border-color: #e0e0e0;
--ripple-color: transparent !important;
color: #e0e0e0;
&:hover {
border-color: #e0e0e0;
background: transparent;
}
}
</style>
<template>
<t-dialog
v-model:visible="visible"
attach="body"
:class="className"
:destroyOnClose="destroyOnClose"
class="c-dialog-confirm-default"
:placement="placement"
>
<template #body>
<div class="custom-confirm-dialog-body">
<div class="title">
{{ title }}
</div>
</div>
</template>
<template #footer>
<div class="footer-default" v-if="footer === null">
<Button @click="visible = false" class="footer-cancel footer-public-btn">取消</Button>
<Button @click="onConfirm" class="footer-confrim footer-public-btn">确定</Button>
</div>
</template>
</t-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import Button from '@/components/Button.vue';
const props = withDefaults(
defineProps<{
modelValue: boolean;
header?: any;
footer?: boolean;
placement?: string;
destroyOnClose?: boolean;
className?: string;
title?: string;
}>(),
{
footer: null,
placement: 'center',
destroyOnClose: false,
className: '',
title: '',
},
);
const emit = defineEmits(['update:modelValue', 'confirm']);
const visible = ref(props.modelValue);
watch(
() => props.modelValue,
(v) => {
visible.value = v;
},
);
watch(
() => visible.value,
(v) => {
emit('update:modelValue', v);
},
);
const onConfirm = () => {
emit('update:modelValue', false);
emit('confirm');
};
</script>
<style lang="less">
@import '@/style/variables.less';
.c-dialog-confirm-default {
.t-dialog {
background: #303030;
border: none;
border-radius: 4px;
width: 250px;
}
.header-default {
font-size: @size-20;
color: #fff;
}
.footer-default {
text-align: center;
}
.footer-cancel {
border: 1px solid #e0e0e0;
background: transparent;
--ripple-color: none !important;
&:hover {
border: 1px solid #e0e0e0;
background: transparent;
}
}
.footer-confrim {
background: #04ae8a;
border-color: transparent;
--ripple-color: none !important;
&:hover {
background: #04ae8a;
border-color: transparent;
}
}
.footer-public-btn {
width: 76px;
height: 28px;
border-radius: 6px;
font-size: @size-14;
font-weight: 700;
}
.t-dialog__body {
margin: 0;
padding: 0;
.custom-confirm-dialog-body {
color: #fff;
font-size: @size-14;
margin: 20px;
text-align: center;
}
}
.t-dialog__footer {
padding: 0;
}
}
</style>
......@@ -8,22 +8,32 @@
left: 0;
bottom: 0;
height: 2px;
background: var(--theme-color-1);
background: #04ae8a;
transition: all 0.3s;
}
.c-tabs__nav-item {
.c-nav-item-default {
height: 48px;
line-height: 48px;
padding: 0 16px;
cursor: pointer;
transition: all 0.2s;
color: #191919;
text-shadow: 0px 0px 0.25px 0px #191919;
.c-tabs__nav-item-wrapper {
font-size: @size-20;
color: var(--td-text-color-secondary);
}
}
.c-nav-item-light {
color: #191919;
.c-tabs__nav-item-wrapper {
font-size: @size-20;
}
}
.c-nav-item-dark {
.c-tabs__nav-item-wrapper {
color: #777;
font-size: @size-15;
}
}
.c-nav-item_active {
background: white;
transition: all 0.2s;
......@@ -31,5 +41,12 @@
color: var(--theme-color-1);
}
}
.c-nav-item_active_dark {
background: transparent;
transition: all 0.2s;
.c-tabs__nav-item-wrapper {
color: #04ae8a;
}
}
}
}
......@@ -4,6 +4,14 @@ import './index.less';
export default defineComponent({
props: {
modelValue: String,
theme: {
type: String,
default: 'light',
},
defaultLineWidth: {
type: Number,
default: 0,
},
},
emits: ['update:modelValue'],
setup(props, context) {
......@@ -59,7 +67,11 @@ export default defineComponent({
const getTabBarWidth = () => {
nextTick(() => {
const dom: HTMLDivElement = navItem.value[navIndex.value];
tab_bar.width = dom.clientWidth;
if (!dom.clientWidth) {
tab_bar.width = props.defaultLineWidth;
} else {
tab_bar.width = dom.clientWidth;
}
});
};
......@@ -87,30 +99,55 @@ export default defineComponent({
getTabBarLocation();
};
// 根据主题修改class
const getActiveClass = (item: any) => {
if (props.modelValue === item.name) {
if (props.theme === 'light') {
return 'c-nav-item_active';
} else {
return 'c-nav-item_active_dark';
}
}
return '';
};
const getDefaultClass = (item: any) => {
if (props.theme === 'light') {
return 'c-nav-item-light';
} else {
return 'c-nav-item-dark';
}
};
onMounted(() => {
getTabBarWidth();
});
return () => (
<div class="custom-c-tabs">
<div class="c-tabs-header">
{slots.rightComponent?.()}
{titles.value.map((item: any, index) => (
<div
key={item.name}
class={['c-tabs__nav-item', props.modelValue === item.name ? 'c-nav-item_active' : '']}
class={['c-nav-item-default', getDefaultClass(item), getActiveClass(item)]}
ref={setRef}
onClick={onChange.bind(this, item.name, index)}
>
<div class="c-tabs__nav-item-wrapper">{item.label}</div>
</div>
))}
{/* <div
class="c-tabs__bar"
style={{
width: tab_bar.width + 'px',
left: tab_bar.left + 'px',
right: tab_bar.right + 'px',
}}
></div> */}
{props.theme === 'light' ? (
''
) : (
<div
class="c-tabs__bar"
style={{
width: tab_bar.width + 'px',
left: tab_bar.left + 'px',
right: tab_bar.right + 'px',
}}
></div>
)}
</div>
<div class="c-tabs-content">{slots.default?.()}</div>
</div>
......
@import '@/style/variables.less';
.custom-izable-page {
.header {
margin: 20px 0;
.da();
font-size: @size-24;
font-weight: 700;
color: #fff;
& > :last-child {
margin-left: 8px;
}
}
.izable-page-upload-box {
background: rgb(48, 48, 48);
.upload-box-footer {
padding: 0 12px;
.dja(flex-end);
border-top: 1px solid #9f9f9f;
height: 60px;
& > :last-child {
margin-left: 20px;
}
}
.custom-real-upload {
background: rgb(48, 48, 48);
}
}
}
import './index.less';
import { defineComponent } from 'vue';
import Upload from '../upload';
import Button from '../Button.vue';
export default defineComponent({
props: {
icon: Object as any,
label: String,
uploadInfo: Object as any,
},
setup(props) {
return () => (
<div class="custom-izable-page">
<div class="header">
{props.icon}
<span>{props.label}</span>
</div>
<div class="izable-page-upload-box">
<Upload uploadInfo={props.uploadInfo}></Upload>
<div class="upload-box-footer">
<Button theme="opacity">重置</Button>
<Button theme="green">生成</Button>
</div>
</div>
</div>
);
},
});
<template>
<t-dialog v-model:visible="visible" class="c-dialog-default" :placement="placement">
<t-dialog
v-model:visible="visible"
attach="body"
:class="className"
:destroyOnClose="destroyOnClose"
class="c-dialog-default"
:placement="placement"
>
<slot></slot>
<div class="header-default">
<slot name="header"></slot>
</div>
<slot name="body"></slot>
<template v-if="footer">
<slot name="footer"></slot>
<slot name="footer"> </slot>
</template>
<template #footer>
<div class="footer-default" v-if="!footer">
<div class="footer-default" v-if="footer === null">
<Button @click="visible = false" class="footer-cancel footer-public-btn">取消</Button>
<Button @click="onConfirm" class="footer-confrim footer-public-btn">确定</Button>
</div>
......@@ -27,10 +34,14 @@ const props = withDefaults(
header?: any;
footer?: boolean;
placement?: string;
destroyOnClose?: boolean;
className?: string;
}>(),
{
footer: false,
footer: null,
placement: 'center',
destroyOnClose: false,
className: '',
},
);
const emit = defineEmits(['update:modelValue', 'confirm']);
......@@ -96,5 +107,8 @@ const onConfirm = () => {
margin: 0;
padding: 0;
}
.t-dialog__footer {
padding: 0;
}
}
</style>
<template>
<TPopup
v-model="visible"
:placement="placement"
:trigger="trigger"
:overlayClassName="['custom-globle-popup', className]"
>
<slot></slot>
<template #content>
<slot name="content"></slot>
</template>
</TPopup>
</template>
<script lang="ts" setup>
import { Popup as TPopup } from 'tdesign-vue-next';
import { ref, watch } from 'vue';
const props = withDefaults(
defineProps<{
modelValue: boolean;
className?: string;
trigger?: string;
placement?: string;
}>(),
{
className: '',
trigger: 'click',
placement: 'bottom-right',
},
);
const emit = defineEmits(['update:modelValue']);
const visible = ref(false);
watch(
() => props.modelValue,
(v) => {
visible.value = v;
},
);
watch(
() => visible.value,
(v) => {
emit('update:modelValue', v);
},
);
</script>
<style lang="less">
.custom-globle-popup {
.t-popup__content {
background: #303030;
box-shadow: none;
border-radius: 6px;
}
}
</style>
......@@ -4,6 +4,7 @@
v-model="SelectValue"
:autoWidth="autoWidth"
:placeholder="placeholder"
:multiple="multiple"
@change="SelectChange"
:popupProps="{
overlayClassName: [className, 'custom-select-popup'],
......@@ -20,16 +21,18 @@ import { ref, watch } from 'vue';
const props = withDefaults(
defineProps<{
options: any[];
modelValue: number | string;
modelValue: number | string | any[];
width?: string;
placeholder?: string;
className?: string;
autoWidth?: boolean;
multiple?: boolean;
}>(),
{
width: '50%',
placeholder: '请选择',
autoWidth: true,
multiple: false,
},
);
const emit = defineEmits(['update:modelValue', 'change']);
......@@ -47,7 +50,7 @@ watch(
SelectValue.value = v;
},
);
const SelectChange = (value: string) => {
const SelectChange = (value: string | any) => {
emit('change', value);
};
</script>
......@@ -66,6 +69,18 @@ const SelectChange = (value: string) => {
.t-select-option {
color: white;
--ripple-color: #00dddd;
.t-checkbox__label {
color: #f0f0f0;
}
}
.t-select-option.t-is-selected {
.t-checkbox__input {
background: #00dddd;
border-color: #00dddd;
}
.t-checkbox__label {
color: #00dddd;
}
}
.t-select-option__hover,
.t-is-selected {
......@@ -77,10 +92,26 @@ const SelectChange = (value: string) => {
.custom-select-box {
.t-select__wrap {
height: 38px;
.t-input__prefix {
display: flex;
.t-tag {
background: #00dddd;
color: white;
svg {
color: black;
}
}
}
.t-input__wrap {
border-radius: 8px;
background: transparent;
}
.t-select-input--multiple {
.t-input--focused {
box-shadow: 0px 0px 0px 1px #00dddd;
}
}
.t-input.t-is-default {
height: 100%;
background-color: #181818;
......
<template>
<div
class="custom-input-global"
:class="['custom-input-global', className]"
:style="{
width: width,
}"
......@@ -15,6 +15,7 @@
'custom-input-error': ruleError.status,
'dark-input-box': theme === 'dark',
'light-input-box': theme === 'light',
'opacity-input-box': theme === 'opacity',
}"
>
<slot name="leftIcon">
......@@ -63,6 +64,7 @@ import { reactive, ref, watch, onMounted } from 'vue';
const input_value = ref<string>(props.modelValue);
const props = withDefaults(
defineProps<{
className?: string;
type?: string;
placeholder?: string;
num?: number;
......@@ -77,8 +79,11 @@ const props = withDefaults(
theme?: string;
borderRadius?: string;
width?: string;
id?: number | string;
blurNum?: number;
}>(),
{
className: '',
// 输入框类型
type: 'text',
align: 'center',
......@@ -92,6 +97,8 @@ const props = withDefaults(
theme: 'dark',
borderRadius: '4px',
width: '200px',
id: '',
blurNum: 1,
},
);
const emit = defineEmits(['update:modelValue', 'submitType', 'submitAccount', 'inputChange', 'inputBlur']);
......@@ -127,7 +134,7 @@ const onInputBlur = () => {
if (props.needSelect) {
input_focus.value = false;
}
emit('inputBlur');
emit('inputBlur', input_value.value, props.id);
};
// 错误状态
const errorinput = (rule: any) => {
......@@ -235,6 +242,15 @@ onMounted(() => {
customInput.value.focus();
}
});
watch(
() => props.blurNum,
(v) => {
if (customInput.value) {
customInput.value.focus();
}
},
);
</script>
<style lang="less">
......@@ -284,7 +300,7 @@ onMounted(() => {
font-weight: bold;
cursor: pointer;
.account {
color: var(--theme-color-35);
color: var(--theme-color-4);
}
.password {
color: var(--theme-color-4);
......@@ -318,6 +334,10 @@ onMounted(() => {
}
}
}
.opacity-input-box {
background: transparent;
border-color: transparent;
}
.custom-input-error {
border-color: #f05451 !important;
transition: all 0.3s;
......
<template>
<t-switch class="c-default-switch" v-model="checked" :size="size" :label="['开', '关']"></t-switch>
</template>
<script lang="ts" setup>
import { Switch as TSwitch } from 'tdesign-vue-next';
import { ref, watch } from 'vue';
const props = withDefaults(
defineProps<{
modelValue: boolean;
size?: string;
}>(),
{
size: 'large',
},
);
const emit = defineEmits(['update:modelValue']);
const checked = ref(props.modelValue);
watch(
() => props.modelValue,
(v) => {
checked.value = v;
},
);
watch(
() => checked.value,
(v) => {
emit('update:modelValue', v);
},
);
</script>
<style lang="less">
.c-default-switch {
}
.c-default-switch.t-is-checked {
background: #00cca2;
}
</style>
<template>
<TTable class="admin-real-table" row-key="index" :data="list" :columns="columns" :loading="loading">
<template #check_all>
<template v-if="checkbox">
<TCheckbox class="check-all-box" v-model="is_check_all" @change="CheckAll"></TCheckbox>
</template>
</template>
<template #checkbox="{ row }">
<template v-if="checkbox">
<TCheckbox v-model="row.is_check" class="check-all-box" @change="CheckOne(row)"></TCheckbox>
</template>
</template>
</TTable>
<div class="custom-pagination" v-if="pagination">
<t-pagination
v-model="pageNum"
v-model:page-size="pageSize"
:total="total"
:pageSizeOptions="[]"
@current-change="pageChange"
/>
</div>
</template>
<script lang="ts" setup>
import { Table as TTable, Checkbox as TCheckbox } from 'tdesign-vue-next';
import { onBeforeMount, reactive, ref, watch } from 'vue';
import { Pagination as TPagination } from 'tdesign-vue-next';
import { getLocalData, setLocalData } from '@/utils/tool';
import { EditButtonEvent, language_type } from '@/constants/admin_form';
import { show_message } from '@/utils/tool';
const props = withDefaults(
defineProps<{
columns: any[];
checkbox?: boolean;
filter_num?: number;
filter_api?: any;
AlreadyChoose?: any;
pageNum: number;
pageSize: number;
loading: boolean;
total: number;
list: any[];
pagination?: boolean;
}>(),
{
checkbox: true,
filter_num: 1,
filter_api: false,
AlreadyChoose: {},
pagination: true,
},
);
const emit = defineEmits(['ChangeList', 'ChoseList', 'PageNumChange']);
// const pageNum = ref<number>(props.pageNum);
// const pageSize = ref<number>(props.pageSize);
// const total = ref<number>(props.total);
// 是否选择全部
const is_check_all = ref(false);
// 修改当前页
const ChangeCurrentPage = (value: number) => {
pageNum.value = value;
emit('PageNumChange', value);
};
const pageChange = (value: number) => {
ChangeCurrentPage(value);
};
// 提交选中项
const SubmitChoseRow = (list: any[] = []) => {
emit('ChoseList', list);
};
// 选择全部
const CheckAll = () => {
props.list.forEach((item: any) => {
item.is_check = is_check_all.value;
});
if (is_check_all.value) {
// 通知父组件
SubmitChoseRow(props.list);
} else {
SubmitChoseRow();
}
};
// 选择行
const CheckOne = (row: any) => {
let check_all_num = 0;
let not_check_num = 0;
// 判断是否全选或全不选
for (let i = 0; i < props.list.length; i++) {
let item = props.list[i];
if (item.is_check) {
check_all_num += 1;
} else {
not_check_num += 1;
}
}
if (check_all_num == props.list.length) {
is_check_all.value = true;
SubmitChoseRow(props.list);
} else if (not_check_num == props.list.length) {
is_check_all.value = false;
SubmitChoseRow();
} else {
// 找到所有选择项
const list = props.list.filter((item: any) => item.is_check == true);
SubmitChoseRow(list);
}
};
// 添加表格多选框和状态
const setTableCheckBox = (res: any) => {
if (props.checkbox) {
// 增加一个选中状态
res.list.forEach((item: any, index: number) => {
item.is_check = false;
item.index = index;
});
}
return res;
};
</script>
<style lang="less">
@import '@/style/variables.less';
.t-table.admin-real-table {
background-color: transparent;
min-height: 500px;
.t-table__content {
background-color: transparent;
}
.t-loading--center {
border-radius: 8px;
.t-loading__gradient {
color: #00dddd;
}
}
.check-all-box {
.t-checkbox__input {
border: 1px solid #ffffff;
border-radius: 2px;
background: transparent;
}
}
.t-is-checked {
.t-checkbox__input {
border: 1px solid white;
background: transparent;
&::after {
border-color: black;
}
}
}
.t-table__header {
tr {
background-color: transparent;
th {
border: none;
background-color: #1e1e1e !important;
color: white;
font-size: @size-15;
font-weight: 400;
padding-top: 8px;
padding-bottom: 8px;
}
// & > :first-child {
// border-top-left-radius: 8px;
// border-bottom-left-radius: 8px;
// }
// & > :last-child {
// border-top-right-radius: 8px;
// border-bottom-right-radius: 8px;
// }
}
}
tbody {
.t-table__empty-row {
.t-table__empty {
color: white;
background-color: rgb(48, 48, 48);
}
}
tr {
td {
color: #ffffff;
border-bottom: 1px solid #464646;
font-weight: 400;
font-size: @size-14;
background: rgb(48, 48, 48);
.edit-text {
cursor: pointer;
transition: color 0.2s;
&:hover {
color: white;
transition: color 0.2s;
}
}
}
&:hover {
td {
background: rgb(48, 48, 48) !important;
}
}
}
}
.t-loading--center {
background: rgba(255, 255, 255, 0.1);
}
}
.custom-pagination {
padding: 12px 0;
.t-pagination__total {
color: white;
}
.t-is-current {
background: #00f9f9;
border-radius: 2px;
border: none;
color: #000000;
}
.t-pagination__btn {
& > :nth-child(1) {
color: #c9cdd4;
}
}
}
</style>
......@@ -2,9 +2,10 @@
<div class="custom-textarea-box">
<TTextarea
class="custom-t-textarea"
:disabled="disabled"
v-model="textarea_value"
:placeholder="placeholder"
:maxcharacter="maxlength"
:maxcharacter="maxlength ? maxlength : null"
:autosize="{
maxRows: maxRows,
minRows: minRows,
......@@ -29,6 +30,7 @@ const props = withDefaults(
minRows?: number;
maxlength?: number | null;
showLimit?: boolean;
disabled?: boolean;
}>(),
{
placeholder: '请输入',
......@@ -36,6 +38,7 @@ const props = withDefaults(
minRows: 5,
showLimit: false,
maxlength: 0,
disabled: false,
},
);
const emit = defineEmits(['update:modelValue', 'change']);
......
......@@ -44,15 +44,15 @@
color: #888fa1;
}
.custom-chose-file {
background: #00dddd !important;
font-size: @size-18;
background: #04ae8a;
border-radius: 8px;
border: none;
width: 200px;
height: 46px;
--ripple-color: none !important;
font-weight: 600;
font-size: @size-18;
color: #000000;
color: #fff;
}
}
.t-upload__dragger {
......@@ -87,14 +87,19 @@
flex-direction: column;
width: 100%;
height: 100%;
.UploadSuccess-img {
width: 100%;
height: 100%;
.icon {
img {
width: 100px;
height: 100px;
}
}
.file-name {
font-size: @size-16;
color: #888fa1;
}
.title1 {
font-weight: 600;
font-size: @size-18;
color: #000000;
.reset-submit {
width: 200px;
height: 46px;
}
}
}
......
......@@ -13,11 +13,17 @@ import { getUserCookie } from '@/utils/api/userApi';
import request from '@/utils/otherRequest';
import { show_message } from '@/utils/tool';
import { v4 } from 'uuid';
import Button from '../Button.vue';
export default defineComponent({
props: {
modelValue: String,
config: Object as any,
rules: Array,
uploadInfo: {
type: Object as any,
default: {},
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
......@@ -168,6 +174,15 @@ export default defineComponent({
}, 1000);
});
};
const reStart = () => {
// 重置
files.value = [];
Curfile.url = '';
Curfile.status = 0;
Curfile.uploadStatus = false;
emit('update:modelValue', '');
};
const requestSuccessMethod = async (file: UploadFile | UploadFile[]) => {
return ExtranetUpload(file);
};
......@@ -191,12 +206,12 @@ export default defineComponent({
onFail={handleFail}
>
<div class="custom-upload-click-box">
<div class="title">选择图片</div>
<div class="title2">或拖拽图片到此处</div>
<div class="title">{props.uploadInfo.label1 ?? '选择图片'}</div>
<div class="title2">{props.uploadInfo.label2 ?? '或拖拽图片到此处'}</div>
<div>
<UploadTip></UploadTip>
</div>
<TButton class="custom-chose-file">选择文件</TButton>
<TButton class="custom-chose-file">{props.uploadInfo.buttonLabel ?? '选择文件'}</TButton>
</div>
</TUpload>
);
......@@ -219,7 +234,13 @@ export default defineComponent({
const UploadSuccess = () => {
return (
<div class="custom-UploadSuccess-stauts">
<img class="UploadSuccess-img" src={props.modelValue} alt="" />
<div class="icon">
{props.uploadInfo.successIcon ? <img src={props.uploadInfo.successIcon} alt="" /> : ''}
</div>
<div class="file-name"></div>
<Button class="reset-submit" theme="green" onClick={reStart}>
{props.uploadInfo.successButtonLabel ?? '重新上传'}
</Button>
</div>
);
};
......
......@@ -55,6 +55,30 @@ export const getRoutes = () => {
component: () => import('@/pages/createLive/index.vue'),
meta: { title: 'snowhome', liveName: true },
},
{
path: routerConfig.createInteract.path,
name: routerConfig.createInteract.name,
component: () => import('@/pages/createInteract/index.vue'),
meta: { title: 'snowhome' },
},
{
path: routerConfig.startLive.path,
name: routerConfig.startLive.name,
component: () => import('@/pages/startLive/index.vue'),
meta: { title: 'snowhome' },
},
{
path: routerConfig.login.path,
name: routerConfig.login.name,
component: () => import('@/pages/login/index.vue'),
meta: { title: 'snowhome' },
},
{
path: routerConfig.ImageCustomization.path,
name: routerConfig.ImageCustomization.name,
component: () => import('@/pages/ImageCustomization/index.vue'),
meta: { title: 'snowhome' },
},
];
}
};
......
......@@ -27,8 +27,7 @@ const routeQuery = route.query;
const liveName = computed(() => store.getters['live/getName']);
onMounted(() => {
console.log(routeQuery.title);
store.commit('live/setName', routeQuery.title);
store.commit('live/setName', routeQuery.title ? routeQuery.title : '');
});
const liveNameEvent = async (value: string) => {
......
<template>
<div class="">
<Customizable :icon="getIcon()" :uploadInfo="uploadInfo" :label="'形象定制'"></Customizable>
</div>
</template>
<script lang="tsx" setup>
import Customizable from '@/components/Customizable';
import PersonSvg from '@/assets/svg/custom/person.svg';
import UploadSuccessIcon from '@/assets/svg/upload/video.svg';
const getIcon = () => {
return <PersonSvg></PersonSvg>;
};
const uploadInfo = {
label1: '选择视频',
label2: '或拖视频到此处上传',
buttonLabel: '选择视频',
successIcon: <UploadSuccessIcon></UploadSuccessIcon>,
successButtonLabel: '替换视频',
};
</script>
<style lang="less"></style>
<template>
<div class="right-content">
<div class="right-content-header">
<span class="label">互动内容</span>
<Button class="add-interact-button" @click="openDialog()">+ 新增互动内容</Button>
</div>
<div class="interact-table">
<Table
:pageNum="pageNum"
:pageSize="pageSize"
:total="total"
:list="tableList.list"
:columns="columns"
:loading="loading"
:pagination="false"
@PageNumChange="PageNumChange"
></Table>
</div>
<AddInteractDialog v-model="dialogVisible"></AddInteractDialog>
</div>
</template>
<script lang="tsx" setup>
import Button from '@/components/Button.vue';
import Table from '@/components/table.vue';
import CustomSwitch from '@/components/switch.vue';
import { reactive, ref } from 'vue';
import EditSvg from '@/assets/svg/home/edit.svg';
import DeleteSvg from '@/assets/svg/home/delete.svg';
import AddInteractDialog from './AddInteractDialog.vue';
const loading = ref(false);
const pageNum = ref<number>(1);
const pageSize = ref<number>(10);
const total = ref<number>(0);
const dialogVisible = ref(false);
const tableList = reactive({
list: [
{
get1: '这件衣服多少钱',
status: false,
},
],
});
const columns = [
{
title: '问题',
colKey: 'get1',
width: '60%',
},
{
title: '状态',
colKey: 'status',
cell: (h, { col, row }) => (
<div>
{row[col.colKey]}
<CustomSwitch v-model={row.status}></CustomSwitch>
</div>
),
},
{
title: '操作',
colKey: 'get3',
cell: (h, { col, row }) => (
<div class="edit-box">
<span class="edit-icon">
<EditSvg></EditSvg>
</span>
<span class="delete-icon">
<DeleteSvg></DeleteSvg>
</span>
</div>
),
},
];
const PageNumChange = (value: number) => {
pageNum.value = value;
};
const openDialog = () => {
dialogVisible.value = true;
};
</script>
<style lang="less">
@import '@/style/variables';
.right-content {
.right-content-header {
display: flex;
justify-content: space-between;
padding: 12px 12px 20px 12px;
.label {
font-size: @size-20;
font-weight: 500;
color: #fff;
}
}
.interact-table {
.edit-box {
display: flex;
.edit-icon,
.delete-icon {
cursor: pointer;
display: flex;
align-items: center;
}
.delete-icon {
margin-left: 12px;
}
}
}
}
</style>
<template>
<div class="custom-create-interact-page">
<div class="header">
<MessageSvg></MessageSvg>
<span>互动回答</span>
</div>
<div class="content">
<div class="left-content">
<div class="left-content-header">
<span class="label">互动回答库</span>
<Button class="add-interact-button" @click="addInteract">+ 新增互动回答库</Button>
</div>
<div
class="interact-list narrow-scrollbar"
:style="{
maxHeight: maxHeight + 'px',
}"
ref="interactRef"
>
<div class="interact-items" v-for="item in interactList.list" :key="item.id">
<div class="name">
<template v-if="!item.edit"> 默认知识库 </template>
<template v-else>
<Input
className="reset-edit-name-input"
align="left"
theme="opacity"
v-model="item.c_name"
:id="item.id"
:blurNum="item.blurNum"
@inputBlur="inputBlur"
></Input>
</template>
</div>
<div class="total">共{{ item.total }}条</div>
<div class="tool" v-show="item.status">
<Popup v-model="item.popup">
<div class="tool-dot">
<div></div>
<div></div>
<div></div>
</div>
<template #content>
<div class="custom-popup-tool-content">
<div class="custom-popup-tool-item" @click="resetName(item)">
<EditSvg></EditSvg>
<span>重命名</span>
</div>
<div class="custom-popup-tool-item" @click="onDelete(item)">
<DeleteSvg></DeleteSvg>
<span>删除</span>
</div>
</div>
</template>
</Popup>
</div>
<div class="confirm-box" v-show="!item.status">
<span @click="confirm(item)">
<YesSvg></YesSvg>
</span>
<span @click="cancel">
<NoSvg></NoSvg>
</span>
</div>
</div>
</div>
</div>
<div class="right-content-box">
<InteractTable></InteractTable>
</div>
</div>
<ConfirmDialog v-model="confirmDialog" title="确定删除吗?" @confirm="onConfirm"></ConfirmDialog>
</div>
</template>
<script lang="ts" setup>
import ConfirmDialog from '@/components/ConfirmDialog.vue';
import EditSvg from '@/assets/svg/home/edit.svg';
import DeleteSvg from '@/assets/svg/home/delete.svg';
import Popup from '@/components/Popup.vue';
import MessageSvg from '@/assets/svg/interact/message.svg';
import Button from '@/components/Button.vue';
import { createTestData, getElBounding, getWindowClient, show_message } from '@/utils/tool';
import { onMounted, reactive, ref } from 'vue';
import InteractTable from './components/InteractTable.vue';
import Input from '@/components/input/index.vue';
import YesSvg from '@/assets/svg/home/yes.svg';
import NoSvg from '@/assets/svg/home/no.svg';
const interactRef = ref<HTMLDivElement>();
const maxHeight = ref();
// 确认弹窗
const confirmDialog = ref(false);
// 本次item的参数
const confirmParams = ref({});
const interactList = reactive({
list: [],
});
const resetName = (item: any) => {
// 修改当前name
item.edit = true;
item.blurNum += 1;
item.popup = false;
};
const getInteractList = () => {
try {
// let res:any = await ddd();
interactList.list = createTestData(
{
name: '你好',
c_name: '你好',
total: 10,
status: true,
popup: false,
edit: false,
blurNum: 1,
loading: false,
},
5,
);
} catch (e) {
console.log(e);
return [];
}
};
// 输入框失去焦点,更新name
const inputBlur = (value: string, id: number) => {
let index = interactList.list.findIndex((item: any) => item.id === id);
if (index !== -1) {
let item = interactList.list[index];
if (!item.status) {
return;
}
// 提交
console.log(value);
console.log(id);
}
// 关闭所有输入框
interactList.list.forEach((item: any) => {
item.edit = false;
});
};
// 新增互动
const addInteract = () => {
interactList.list.push({
name: '',
c_name: '',
total: 0,
status: false,
popup: false,
edit: true,
blurNum: 1,
loading: false,
});
};
// 删除互动库
const onDelete = (item: any) => {
item.popup = false;
//打开弹窗
confirmDialog.value = true;
confirmParams.value = item;
};
// 确认删除
const onConfirm = () => {};
const confirm = (item: any) => {
if (!item.c_name) {
show_message('请输入标题');
return;
}
// 提交创建
console.log('可以提交');
// 提交成功后修改状态
item.status = true;
};
const cancel = () => {
interactList.list.pop();
};
onMounted(() => {
// 计算最大高度
let obj = getElBounding(interactRef.value);
let client = getWindowClient();
maxHeight.value = client.height - obj.top;
getInteractList();
});
</script>
<style lang="less">
@import '@/style/variables';
.custom-create-interact-page {
margin: 0 auto;
display: flex;
flex-direction: column;
.header {
font-size: @size-24;
font-weight: 700;
color: #fff;
display: flex;
align-items: center;
margin: 20px 0;
span {
margin-left: 6px;
}
}
.content {
flex: 1;
display: flex;
.add-interact-button {
border-radius: 6px;
border: 1px solid #e0e0e0;
background: transparent;
height: 38px;
font-size: @size-14;
--ripple-color: none !important;
color: #e0e0e0;
&:hover {
border: 1px solid #e0e0e0;
background: transparent;
}
}
& > * {
border-radius: 4px;
background: #303030;
height: 100%;
}
.left-content {
width: 30%;
display: flex;
flex-direction: column;
.left-content-header {
display: flex;
justify-content: space-between;
padding: 12px 12px 20px 12px;
.label {
font-size: @size-20;
font-weight: 500;
color: #fff;
}
}
.interact-list {
padding: 0 12px;
flex: 1;
overflow-y: auto;
.interact-items {
border-radius: 4px;
background: #049c78;
height: 115px;
width: 100%;
margin-top: 12px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
padding: 0 20px;
position: relative;
.reset-edit-name-input {
.cust-input {
font-size: @size-18;
font-weight: 700;
color: #fff;
&::placeholder {
color: #ddd;
}
}
}
.name {
font-size: @size-18;
font-weight: 700;
color: #fff;
}
.total {
font-size: @size-16;
font-weight: 700;
color: #fff;
}
.tool {
position: absolute;
right: 30px;
top: 20px;
.tool-dot {
display: flex;
cursor: pointer;
& > * {
margin-left: 2px;
width: 7px;
height: 7px;
background: white;
border-radius: 50%;
}
}
}
.confirm-box {
position: absolute;
right: 20px;
top: 50%;
transform: translate(0, -50%);
& > * {
cursor: pointer;
}
& > :last-child {
margin-left: 30px;
}
}
}
}
}
.right-content-box {
padding: 0 12px;
margin-left: 4px;
width: 70%;
}
}
}
.custom-popup-tool-content {
font-size: @size-14;
color: #b4b4b4;
padding: 4px 6px 6px 6px;
& > * {
margin-top: 6px;
}
.custom-popup-tool-item {
display: flex;
align-items: center;
cursor: pointer;
& > * {
svg {
width: 18px;
height: 18px;
}
}
& > :last-child {
margin-left: 12px;
}
}
}
</style>
......@@ -119,9 +119,11 @@ watch(
() => props.toolHeight,
(v) => {
if (v) {
let obj = getElBounding(personList.value);
let client = getWindowClient();
maxHeight.value = client.height - obj.top - v;
setTimeout(() => {
let obj = getElBounding(personList.value);
let client = getWindowClient();
maxHeight.value = client.height - obj.top - v;
}, 500);
}
},
);
......
<template>
<div class="chose-interact-box">
<div class="all-select">
<Button theme="dark" class="reset-button" @click="toAdd">+ 添加互动回答</Button>
</div>
<div class="chose-interact-content">
<div class="label">评论方式</div>
<div class="value">
<Select
:autoWidth="false"
className="interact-reset-select"
:options="commentList"
v-model="currentComment"
@change="onCommentChange"
></Select>
</div>
<div class="label">互动问答库选择</div>
<div class="value">
<Select
:autoWidth="false"
className="interact-reset-select"
:options="interactiveLibraryList"
:multiple="true"
v-model="interactiveLibrary"
@change="interactiveLibraryChange"
></Select>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import Select from '@/components/Select.vue';
import Button from '@/components/Button.vue';
import CardOne from '@/components/cardOne.vue';
import { ref, onMounted, reactive, watch } from 'vue';
import { getElBounding, getWindowClient } from '@/utils/tool';
import { useStore } from 'vuex';
import { createLiveKeys } from '@/service/CreateLive';
import { useLiveInfoSubmit } from '@/hooks/useStoreCommit';
import routerConfig from '@/router/tool';
import { useRouter } from 'vue-router';
const [commitInfo] = useLiveInfoSubmit();
const store = useStore();
const router = useRouter();
const currentComment = ref('');
const commentList = ref([]);
// 互动库
const interactiveLibrary = ref([]);
const interactiveLibraryList = ref([
{
label: '1',
value: '1',
},
{
label: '2',
value: '2',
},
]);
const onCommentChange = (value: string) => {
// 提交
};
const interactiveLibraryChange = () => {
// 提交
};
const toAdd = () => {
router.push({
path: routerConfig.createInteract.path,
name: routerConfig.createInteract.name,
});
};
</script>
<style lang="less">
@import '@/style/variables';
.chose-interact-box {
width: 550px;
flex: 1;
display: flex;
flex-direction: column;
padding: 0 4px;
overflow: hidden;
.all-select {
padding: 12px 4px;
text-align: right;
.reset-button {
font-size: @size-14;
color: #b4b4b4;
}
}
.chose-interact-content {
flex: 1;
border-radius: 0px 3px 3px 3px;
border: 1px solid #464646;
background: #1e1e1e;
padding: 20px 12px;
.label {
font-size: @size-15;
color: #fafafa;
margin-top: 12px;
}
.value {
margin-top: 6px;
.interact-reset-select {
}
.custom-select-box {
.t-input__wrap {
.t-input {
border-radius: 3px;
border: 1px solid #464646;
background: black;
}
.t-input__inner {
text-align: left;
}
}
}
}
}
}
</style>
......@@ -213,6 +213,11 @@ watch(
onMounted(async () => {
// 获取上传配置
ossConfig.value = await getUploadConfig();
// 提交默认脚本类型
commitInfo({
[createLiveKeys.scriptType]: currentOption.value,
});
});
</script>
......
......@@ -15,10 +15,20 @@
<ScriptVue></ScriptVue>
</div>
</template>
<template v-if="setpsList[2].load">
<div class="steps-item" v-show="currentSetp == '3'">
<InteractVue></InteractVue>
</div>
</template>
<div class="public-tool" ref="publicTool">
<Button height="28px" theme="light" class="tool-button">保存草稿</Button>
<Button height="28px" theme="light" class="tool-button" @click="onBack">上一步</Button>
<Button height="28px" theme="green" class="tool-button" @click="onNext">下一步</Button>
<template v-if="currentSetp != '3'">
<Button height="28px" theme="green" class="tool-button" @click="onNext">下一步</Button>
</template>
<template v-else>
<Button height="28px" theme="green" class="tool-button" @click="onSave">合成</Button>
</template>
</div>
</div>
<div class="create-live-right-drag">
......@@ -36,6 +46,7 @@
<script lang="tsx" setup>
import StepList from './components/SetpsList';
import ScriptVue from './components/scripts.vue';
import InteractVue from './components/interact.vue';
import Drag from '@/components/drag.vue';
import Button from '@/components/Button.vue';
import ChoseDigitalPerson from './components/ChoseDigitalPerson.vue';
......@@ -45,7 +56,7 @@ import ScriptsSvg from '@/assets/svg/createLive/scripts.svg';
import { computed, onMounted, ref } from 'vue';
import { getElBounding, show_message } from '@/utils/tool';
import { useStore } from 'vuex';
import { createLiveKeys } from '@/service/CreateLive';
import { createLiveKeys, scriptTypeText, scriptTypePhonetics } from '@/service/CreateLive';
const store = useStore();
const liveImage = computed(() => store.getters['live/getLiveimage']);
......@@ -78,7 +89,20 @@ const setpsList = [
value: 2,
load: false,
icon: <InteractSvg></InteractSvg>,
field: [],
roles: [
{
key: createLiveKeys.scriptType,
value: scriptTypeText,
require: [createLiveKeys.textTones, createLiveKeys.textScriptValue],
message: '音调或文本必填',
},
{
key: createLiveKeys.scriptType,
value: scriptTypePhonetics,
require: [createLiveKeys.phoneticsSoundColor, createLiveKeys.phoneticsFile],
message: '音色或音频文件必填',
},
],
},
{
label: '互动设置',
......@@ -102,15 +126,32 @@ const currentModuleField = () => {
let status = true;
let message = '';
roles.forEach((item: any) => {
// 必填字段是否都存在
Object.keys(createLiveInfo.value).forEach((key: string) => {
if (item.key === key) {
if (!createLiveInfo.value[key]) {
status = false;
message = item.message;
}
if (item.value) {
if (createLiveInfo.value[item.key] === item.value) {
let field: string[] = item.require;
field.forEach((it: any) => {
// 分类判断
Object.keys(createLiveInfo.value).forEach((key: string) => {
if (it === key) {
if (!createLiveInfo.value[key]) {
status = false;
message = item.message;
}
}
});
});
}
});
} else {
// 必填字段是否都存在
Object.keys(createLiveInfo.value).forEach((key: string) => {
if (item.key === key) {
if (!createLiveInfo.value[key]) {
status = false;
message = item.message;
}
}
});
}
});
if (!status) {
show_message(message);
......@@ -126,10 +167,7 @@ const onBack = () => {
if (currentSetp.value == 1) {
return;
}
let status = currentModuleField();
if (status) {
currentSetp.value -= 1;
}
currentSetp.value -= 1;
};
const onNext = () => {
......@@ -142,6 +180,8 @@ const onNext = () => {
currentSetp.value += 1;
}
};
const onSave = () => {};
</script>
<style lang="less">
......
......@@ -6,7 +6,9 @@
<template #hover>
<div class="my-digtal-people-hover">
<template v-if="true">
<Button class="digtal-people-start-end" theme="danger" height="40px">开启直播</Button>
<Button class="digtal-people-start-end" theme="danger" height="40px" @click="startLive(item)"
>开启直播</Button
>
</template>
<template v-else>
<Button class="digtal-people-start-end" theme="danger" height="40px">关闭直播</Button>
......@@ -30,6 +32,10 @@ import { reactive } from 'vue';
import CardTwo from '@/components/cardTwo.vue';
import CustomLoading from '@/components/loading.vue';
import Button from '@/components/Button.vue';
import { useRouter } from 'vue-router';
import routerConfig from '@/router/tool';
const router = useRouter();
const createList = () => {
let list = [];
......@@ -48,6 +54,17 @@ const myDigtalList = reactive({
list: createList(),
loading: false,
});
const startLive = (item: any) => {
// 传一个id跳转到页面
router.push({
path: routerConfig.startLive.path,
name: routerConfig.startLive.name,
query: {
id: item.id,
},
});
};
</script>
<style lang="less">
......
......@@ -7,7 +7,7 @@
</div>
<div class="tool-list">
<template v-for="item in toolList" :key="item.icon">
<div class="tool-item">
<div class="tool-item" @click="openPage(item)">
<img :src="item.icon" alt="" />
<span>{{ item.label }}</span>
</div>
......@@ -84,6 +84,7 @@ const toolList = [
{
label: '形象定制',
icon: imgs.profile,
path: routerConfig.ImageCustomization,
},
{
label: '音色定制',
......@@ -95,6 +96,15 @@ const toolList = [
},
];
const openPage = (item: any) => {
if (item.path) {
router.push({
path: routerConfig.ImageCustomization.path,
name: routerConfig.ImageCustomization.name,
});
}
};
const createList = () => {
let list = [];
for (let i = 0; i < 20; i++) {
......
<template>
<div class="login-page">
<div class="logo">
<img :src="imgs.logo" alt="" />
</div>
<div class="login-content">
<div class="label">登录账户</div>
<t-form
ref="form"
class="custom-login-form"
:data="formData"
:rules="FORM_RULES"
:colon="true"
:label-width="0"
@submit="onSubmit"
>
<t-form-item name="account">
<div class="form-item-box">
<div class="label required">账号</div>
<CustomInput
placeholder=""
:needSelect="true"
:selectList="rememberList.list"
v-model="formData.account"
@submitAccount="submitAccount"
align="left"
className="reset-login-input"
></CustomInput>
</div>
</t-form-item>
<t-form-item name="password">
<div class="form-item-box">
<div class="label required">请输入密码</div>
<CustomInput
placeholder=""
className="reset-login-input"
type="password"
v-model="formData.password"
align="left"
></CustomInput>
</div>
</t-form-item>
<t-form-item>
<t-checkbox class="remember-password-box" v-model="remember">记住密码</t-checkbox>
</t-form-item>
<t-form-item>
<div class="submit-box">
<Button type="submit" theme="green" class="reset-login-submit-btn">登录</Button>
</div>
</t-form-item>
</t-form>
</div>
</div>
</template>
<script lang="ts" setup>
import CustomInput from '@/components/input/index.vue';
import Button from '@/components/Button.vue';
import { Form as TForm } from 'tdesign-vue-next';
import LogoSvg from '@/assets/svg/logo.svg';
import { computed, onMounted, reactive, ref } from 'vue';
import { show_message } from '@/utils/tool';
import { useStore } from 'vuex';
import { setRememberList, getRememberList } from '@/utils/remember';
const store = useStore();
const loading = ref(false);
const imgs = {
logo: new URL('../../assets/svg/logo.svg', import.meta.url).href,
};
// 是否记住密码
const remember = ref(false);
const rememberList = reactive({
list: [],
});
const formData = reactive({
account: '',
password: '',
});
const FORM_RULES = computed(() => {
return {
account: [{ required: true, message: '账号不能为空', type: 'error' }] as FormRule[],
password: [{ required: true, message: '密码不能为空', type: 'error' }] as FormRule[],
};
});
const submitAccount = (item: any) => {
formData.account = item.account;
formData.password = item.password;
};
const onSubmit = async ({ validateResult, firstError }: any) => {
if (validateResult === true) {
try {
loading.value = true;
let res: any = await UserLogin({
email: formData.account,
password: formData.password,
});
if (res.code == 0) {
show_message('登录成功', 'success');
store.commit('user/setToken', {
token: res.data.access_token,
time: res.data.expires_in,
});
// 获取用户信息
store.dispatch('user/UserInfo');
router.replace({
path: '/',
});
// 记住密码
if (remember.value) {
// 保存账号密码
setRememberList(
{
account: formData.account,
password: formData.password,
},
'email',
);
}
}
loading.value = false;
} catch (e) {
console.log(e);
loading.value = false;
}
} else {
show_message(firstError, 'warning');
}
};
onMounted(() => {
// 获取本地邮箱账号
rememberList.list = getRememberList('email');
});
</script>
<style lang="less">
@import '@/style/variables.less';
.login-page {
.dja();
flex-direction: column;
.logo {
img {
width: 120px;
height: 120px;
}
}
.login-content {
border-radius: 6px;
background: #303030;
box-shadow: 0px 0px 32px 0px rgba(0, 0, 0, 0.1);
width: 540px;
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 30px;
.label {
font-size: @size-28;
color: #fff;
font-weight: 600;
}
.custom-login-form {
margin-top: 40px;
.t-form__item {
.submit-box {
width: 100%;
.dja();
.reset-login-submit-btn {
height: 50px !important;
width: 216px;
border-radius: 25px;
}
}
.form-item-box {
.label {
font-weight: 400;
font-size: @size-16;
color: #ffffff;
padding-bottom: 12px;
}
.required {
&::before {
content: '*';
color: red;
margin-right: 2px;
}
}
.custom-input-global.reset-login-input {
width: 423px !important;
height: 50px !important;
.custom-input-box {
height: 100% !important;
}
.cust-input {
color: white;
}
}
}
.remember-password-box {
.t-checkbox__input {
background: transparent;
border: 1px solid #848e9c;
border-radius: 0;
border-radius: 2px;
}
.t-checkbox__label {
color: white;
font-weight: 300;
font-size: @size-12;
}
}
.t-is-checked {
.t-checkbox__input {
background: #00f9f9;
border-color: transparent;
&::after {
border-color: black;
}
}
}
}
}
}
}
</style>
<template>
<div class="start-live-audio-box">
<div class="start-live-audio-content">
<div class="label">音频脚本</div>
<div class="play-audio-box">
<div class="line">
<PlayAudioSvg></PlayAudioSvg>
<Button theme="opacity" style="color: #fff" v-if="true" @click="showTextarea">查看文字脚本</Button>
</div>
<div class="">
<CustomPlayAudio :url="url"></CustomPlayAudio>
</div>
</div>
<div v-if="textareaStatus">
<CustomTextarea :disabled="true" class="reset-live-audio-textarea" v-model="textareaValue"></CustomTextarea>
</div>
</div>
<div class="start-live-audio-footer">
<div class="live-status">
<div class="live-icon">
<LiveSvg></LiveSvg>
</div>
<span>直播中</span>
</div>
<div class="stop">
<span class="start-time">00:52:20</span>
<Button theme="opacity">关闭直播</Button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import CustomTextarea from '@/components/textarea.vue';
import CustomPlayAudio from '@/components/Audio.vue';
import Button from '@/components/Button.vue';
import LiveSvg from '@/assets/svg/home/live.svg';
import PlayAudioSvg from '@/assets/svg/home/playAudio.svg';
import { ref, watch } from 'vue';
const props = withDefaults(
defineProps<{
url: string;
value?: string;
}>(),
{
value: '',
},
);
const textareaStatus = ref(false);
const textareaValue = ref(props.value);
const showTextarea = () => {
textareaStatus.value = true;
};
watch(
() => props.value,
(v) => {
if (v) {
textareaValue.value = v;
}
},
);
</script>
<style lang="less">
@import '@/style/variables.less';
.start-live-audio-box {
display: flex;
flex-direction: column;
padding: 0 4px;
.start-live-audio-content {
flex: 1;
margin-top: 40px;
border-radius: 0px 3px 3px 3px;
border: 1px solid #464646;
background: #1e1e1e;
padding: 12px;
.label {
font-size: @size-15;
color: #fff;
margin-bottom: 12px;
}
.play-audio-box {
border-radius: 8px;
background: linear-gradient(355deg, #00b58f 0%, rgba(255, 255, 255, 0.8) 100%, rgba(255, 255, 255, 0.9) 100%);
height: 120px;
padding: 12px;
.dj(space-around);
flex-direction: column;
.line {
.dja(space-between);
}
}
.reset-live-audio-textarea {
margin-top: 12px;
.t-textarea__inner {
border-radius: 8px;
background: #454545;
font-size: @size-14;
}
}
}
.start-live-audio-footer {
font-size: @size-15;
color: #00cca2;
padding: 20px 0;
.dja(space-between);
.stop {
.dja();
.start-time {
font-size: @size-14;
color: #bababa;
font-weight: 600;
margin-right: 12px;
}
}
.live-status {
.da();
.live-icon {
background: #04ae8a;
border-radius: 50%;
width: 40px;
height: 40px;
.dja();
}
& > :last-child {
margin-left: 8px;
}
}
}
}
</style>
<template>
<div class="start-live-human-box">
<div class="content">
<div class="header">人工回复</div>
<div class="chose-sound-color">
<div class="label">选择音色</div>
<div class="value">
<div class="right-chose-tones">
<div class="default-label">
<SelectionPopup
v-model="textTonesVisible"
title="选择一种音调"
:event="getTonesList"
v-model:value="textTonesValue"
@itemChange="textTonesChange"
>
<div>
<template v-if="!textTonesValue"> 音调</template>
<template v-else>
<div class="chose-tones-item">
<img :src="textTonesInfo.img" alt="" />
<div>
<div class="name">{{ textTonesInfo.c_name }}</div>
<div class="categorie">{{ textTonesInfo.c_categorie }}</div>
</div>
</div>
</template>
</div>
</SelectionPopup>
</div>
<div class="default-add">+</div>
<div @click="openSoundColor" class="default-label">
<SelectionPopup
title="选择一种音色"
v-model="soundColorVisible"
:disabled="disabled"
v-model:value="soundColorValue"
:event="getSoundColorList"
@itemChange="soundColorItemChange"
>
<div>
<template v-if="!soundColorValue"> 音色</template>
<template v-else>
<div class="chose-tones-item">
<img :src="soundColorInfo.img" alt="" />
<div>
<div class="name">{{ soundColorInfo.c_name }}</div>
</div>
</div>
</template>
</div></SelectionPopup
>
</div>
</div>
</div>
</div>
<div class="input-box">
<Textarea v-model="textareaValue" placeholder="输入内容点击下方发送,数字人将口播内容"></Textarea>
</div>
</div>
<div class="footer">
<Button theme="green" class="reset-send-btn">发送</Button>
</div>
</div>
</template>
<script lang="ts" setup>
import Button from '@/components/Button.vue';
import Textarea from '@/components/textarea.vue';
import SelectionPopup from '@/components/SelectionPopup.vue';
import { createTestData } from '@/utils/tool';
import { ref, watch } from 'vue';
const textTonesVisible = ref(false);
const textTonesValue = ref('');
const textTonesInfo = ref({});
const soundColorVisible = ref(false);
const soundColorValue = ref('');
const soundColorInfo = ref({});
const disabled = ref(true);
const textareaValue = ref('');
const textTonesChange = (item: any) => {
textTonesInfo.value = item;
};
const soundColorItemChange = (item: any) => {
soundColorInfo.value = item;
};
// 获取 音调列表
const getTonesList = () => {
try {
// let res:any = await ddd();
return createTestData({
img: 'https://img1.baidu.com/it/u=1546227440,2897989905&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500',
c_name: '云依',
c_categorie: '悦耳男声2',
});
} catch (e) {
console.log(e);
return [];
}
};
// 获取音频 音色列表
const getSoundColorList = () => {
try {
// let res:any = await ddd();
return createTestData({
img: 'https://img1.baidu.com/it/u=1546227440,2897989905&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500',
c_name: '云依',
c_categorie: '悦耳男声2',
});
} catch (e) {
console.log(e);
return [];
}
};
watch(
() => textTonesValue.value,
(v) => {
if (v) {
disabled.value = false;
} else {
disabled.value = true;
}
},
);
</script>
<style lang="less">
@import '@/style/variables.less';
.start-live-human-box {
display: flex;
flex-direction: column;
padding: 0 4px;
.content {
flex: 1;
border-radius: 0px 3px 3px 3px;
border: 1px solid #464646;
background: #1e1e1e;
margin-top: 40px;
padding: 12px;
display: flex;
flex-direction: column;
.header {
font-size: @size-15;
color: #fff;
margin-bottom: 12px;
}
.chose-sound-color {
.da();
.label {
font-size: @size-15;
color: #b4b4b4;
}
.value {
.right-chose-tones {
margin-left: 12px;
width: 245px;
height: 38px;
border-radius: 6px;
border: 1px solid #b5b5b5;
background: #1a1b1b;
display: flex;
justify-content: space-around;
align-items: center;
.default-label {
color: #b5b5b5;
font-weight: 600;
font-size: @size-14;
cursor: pointer;
}
.default-add {
color: rgb(180, 180, 180);
font-size: @size-20;
font-weight: bold;
}
.chose-tones-item {
width: 110px;
height: 34px;
border-radius: 6px;
border: 1px solid #363636;
background: #1a1b1b;
transition: all 0.2s;
display: flex;
justify-content: space-around;
align-items: center;
cursor: pointer;
padding: 0 6px;
& > :last-child {
margin-left: 4px;
}
img {
width: 25px;
height: 25px;
border-radius: 50%;
}
.name {
font-size: @size-12;
color: #e2e2e2;
line-height: 14px;
}
.categorie {
font-size: @size-10;
color: #d0d0d0;
}
}
}
}
}
.input-box {
flex: 1;
margin-top: 12px;
.custom-textarea-box {
height: 100%;
.t-textarea {
height: 100%;
.t-textarea__inner {
height: 100% !important;
border-radius: 6px;
border: 1px solid #d0d0d0;
&::placeholder {
font-size: @size-14;
}
}
}
}
}
}
.footer {
padding: 20px 0;
text-align: right;
.reset-send-btn {
width: 125px;
border-radius: 4px;
}
}
}
</style>
<template>
<div class="start-live-video">
<template v-if="video">
<video :src="video"></video>
</template>
<template v-else>
<div class="default-video"></div>
</template>
</div>
</template>
<script lang="ts" setup>
const props = withDefaults(
defineProps<{
video: string;
}>(),
{},
);
const emit = defineEmits(['update:modelValue']);
</script>
<style lang="less">
.start-live-video {
padding: 20px 40px;
.default-video {
background: rgb(61, 222, 99);
height: 100%;
}
}
</style>
<template>
<div class="custom-start-live-page">
<Video :video="realVideo"></Video>
<Audio :url="realAudio"></Audio>
<Human></Human>
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue';
import request from '@/utils/otherRequest';
import Video from './components/video.vue';
import Audio from './components/audio.vue';
import Human from './components/human.vue';
const liveInfo = reactive({
// 原始链接
video: [
'http://yunyi-tiktok.oss-cn-shenzhen.aliyuncs.com/files/user/admin/cea8fe95-bf49-480b-ac79-c95fcd3be26f.mp4',
'http://yunyi-tiktok.oss-cn-shenzhen.aliyuncs.com/files/user/admin/cea8fe95-bf49-480b-ac79-c95fcd3be26f.mp4',
],
// 音频从视频中提取出来
audio: '',
scriptText: '1111',
status: 1,
startTime: '00:50:00',
});
// 处理后的视频
const realVideo = ref('');
// 处理后的音频
const realAudio = ref('');
const getVideo = (url: string) => {
return request.get(url);
};
const installVideo = async () => {
try {
let list = [];
liveInfo.video.forEach((item: string) => {
list.push(getVideo(item));
});
let res: any = await request.all(list);
if (res && res.length) {
// 通知python合并
console.log(res);
}
} catch (e) {
console.log(e);
}
};
onMounted(() => {
// 下载视频
installVideo();
});
</script>
<style lang="less">
.custom-start-live-page {
display: flex;
& > * {
width: 32%;
background: #303030;
height: 100%;
}
& > :not(:first-child) {
margin-left: 4px;
}
}
</style>
......@@ -12,4 +12,22 @@ export default {
path: '/createLive',
name: 'createLive',
},
// 添加互动页面
createInteract: {
path: '/createInteract',
name: 'createInteract',
},
// 直播页面
startLive: {
path: '/startLive',
name: 'startLive',
},
login: {
path: '/login',
name: 'login',
},
ImageCustomization: {
path: '/ImageCustomization',
name: 'ImageCustomization',
},
};
......@@ -8,6 +8,7 @@ export const createLiveKeys = {
textSoundColor: 'textSoundColor', // 文本音色
textScriptValue: 'textScriptValue', // 文字脚本的文本
phoneticsSoundColor: 'phoneticsSoundColor', // 音频 音色
phoneticsFile: 'phoneticsFile', // 音频文件
};
// 脚本类型
......
......@@ -5,6 +5,12 @@ const state = {
createLive: {
[createLiveKeys.id]: '',
[createLiveKeys.id_type]: '',
[createLiveKeys.scriptType]: '',
[createLiveKeys.textTones]: '',
[createLiveKeys.textSoundColor]: '',
[createLiveKeys.textScriptValue]: '',
[createLiveKeys.phoneticsSoundColor]: '',
[createLiveKeys.phoneticsFile]: '',
},
liveImage: '',
};
......
......@@ -52,7 +52,7 @@
height: calc(100vh - 50px);
overflow-y: auto;
overflow-x: hidden;
background: #f5f5f5;
background: rgb(30, 30, 30);
& > :first-child {
width: @pageWidth;
height: 100%;
......
......@@ -6,6 +6,7 @@
@pageWidth: 1200px;
// 各大小对应的rem --基于font-size:14px;
@size-10: 0.71rem;
@size-12: 0.85rem;
@size-13: 0.92rem;
@size-14: 1rem;
......@@ -14,6 +15,22 @@
@size-18: 1.28rem;
@size-20: 1.4rem;
@size-24: 1.7rem;
@size-28: 2rem;
//默认center
.dja(@flex:center,@align:center) {
display: flex;
justify-content: @flex;
align-items: @align;
}
.dj(@flex:center) {
display: flex;
justify-content: @flex;
}
.da(@align:center) {
display: flex;
align-items: @align;
}
/** 公共前缀 */
@prefix: tdesign-starter;
......
import axios from 'axios';
import { MessagePlugin } from 'tdesign-vue-next';
const instance = axios.create({
const instance: any = axios.create({
timeout: 6000000,
withCredentials: false,
});
instance.all = axios.all;
// 请求头
instance.interceptors.request.use((config: any) => {
return config;
......@@ -13,7 +14,7 @@ instance.interceptors.response.use(
(response) => {
const { data, status } = response;
if (status == 201 || status == 200) {
return status;
return data;
}
if (data.code === 0) {
return data;
......@@ -33,7 +34,7 @@ instance.interceptors.response.use(
}
return err.response;
}
}
},
);
export default instance;
// 记住密码存入本地的key
export const remember_password_phone = 'remember_phone';
export const remember_password_email = 'remember_email';
export const getRememberList = (type: string) => {
// localStorage.setItem(remember_password_phone, JSON.stringify([]));
// localStorage.setItem(remember_password_email, JSON.stringify([]));
let new_type = '';
if (type == 'phone') {
new_type = remember_password_phone;
} else {
new_type = remember_password_email;
}
const list = localStorage.getItem(new_type);
if (list) {
return JSON.parse(list);
} else {
return [];
}
};
export const setRememberList = (obj: any, type: string) => {
let list = [obj];
let new_type = '';
if (type == 'phone') {
new_type = remember_password_phone;
} else if (type == 'email') {
new_type = remember_password_email;
}
// 本地已经存储的地址
const locaList: any = getRememberList(new_type);
if (locaList.length) {
// 判断本地是否存在相同账号
const index = locaList.findIndex((item: any) => item.account == obj.account);
if (index !== -1) {
// 找到相同的,删除
locaList.splice(index, 1);
}
list = list.concat(locaList);
}
// 存储
localStorage.setItem(new_type, JSON.stringify(list));
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment