Commit 3a12c6c1 by haojie

1

parent 5f08ff4a
......@@ -22,6 +22,7 @@
"js-cookie": "^3.0.1",
"tdesign-icons-vue-next": "^0.0.6",
"tdesign-vue-next": "^0.22.1",
"uuid": "^9.0.0",
"vue": "^3.2.31",
"vue-i18n": "^9.2.0-beta.34",
"vue-router": "^4.0.11",
......@@ -33,6 +34,7 @@
"@commitlint/config-conventional": "^15.0.0",
"@types/echarts": "^4.9.10",
"@types/js-cookie": "^3.0.3",
"@types/uuid": "^9.0.2",
"@types/ws": "^8.2.2",
"@typescript-eslint/eslint-plugin": "^4.29.3",
"@typescript-eslint/parser": "^4.29.3",
......
<svg width="30" height="30" viewBox="0 0 30 30" fill="" xmlns="http://www.w3.org/2000/svg">
<path d="M15.6839 14.9624C18.4341 14.6587 20.6609 12.4317 20.9646 9.68113C21.3637 6.06002 18.5411 3 15.0014 3C11.6873 3 9.00072 5.68402 9.00072 8.99855V9.00145C9.00072 12.5416 12.0633 15.3615 15.6839 14.9624ZM15.0014 16.7151C10.9961 16.7151 3 19.0116 3 23.5727V26.1439C3 26.6182 3.38462 27 3.85601 27H26.144C26.6183 27 27 26.6153 27 26.1439V23.5727C27.0029 19.0145 19.0067 16.7151 15.0014 16.7151Z" fill=""/>
</svg>
<svg width="30" height="30" viewBox="0 0 30 30" fill="" xmlns="http://www.w3.org/2000/svg">
<path d="M26.87 3.81441V3.81537C26.6299 3.22443 26.1158 2.8125 25.5176 2.8125H8.82619C8.228 2.8125 7.71389 3.22443 7.47548 3.81537C7.38107 4.04406 7.33142 4.29239 7.32983 4.54395V16.3667C7.32983 16.5351 7.35758 16.6949 7.39746 16.8479C7.57952 17.5697 8.14737 18.0991 8.82532 18.0991H19.1394C19.2381 18.1202 19.3273 18.1779 19.3926 18.2627C19.4385 18.3589 19.4966 18.4456 19.5677 18.5197L21.2271 20.2809C21.3596 20.4596 21.5258 20.604 21.7139 20.7039C21.9019 20.8038 22.1074 20.8569 22.316 20.8594C22.3879 20.8594 22.4564 20.844 22.5266 20.8315C22.8977 20.766 23.2315 20.5427 23.4577 20.2078C23.5774 20.0346 23.6433 19.8219 23.6441 19.6024L23.6519 18.1655C23.6969 18.1314 23.7481 18.1087 23.8019 18.0991H25.5167C26.1964 18.0991 26.766 17.5678 26.9489 16.8489C26.9914 16.692 27.014 16.5284 27.0157 16.3638V4.5478C27.0157 4.28505 26.9619 4.0377 26.8709 3.81441H26.87ZM13.1644 11.8364C12.987 11.8359 12.8113 11.7966 12.6476 11.7207C12.4838 11.6448 12.3351 11.5339 12.21 11.3942C12.0849 11.2545 11.9858 11.0888 11.9184 10.9065C11.851 10.7243 11.8166 10.5291 11.8172 10.3321C11.8172 9.50152 12.4188 8.8278 13.1627 8.8278H13.1662C13.91 8.8278 14.5125 9.50152 14.5117 10.3321C14.5117 11.1627 13.9083 11.8355 13.1644 11.8355V11.8364ZM17.6535 11.8364C17.476 11.836 17.3004 11.7968 17.1366 11.7211C16.9728 11.6453 16.824 11.5345 16.6988 11.3948C16.5737 11.2552 16.4745 11.0896 16.407 10.9074C16.3394 10.7252 16.3049 10.5301 16.3054 10.3331C16.3054 9.50248 16.9079 8.82877 17.6518 8.82877H17.6552C18.3991 8.82877 19.0016 9.50248 19.0007 10.3331C19.0007 11.1637 18.3973 11.8364 17.6535 11.8364ZM22.2197 11.8364C22.0423 11.836 21.8666 11.7968 21.7028 11.7211C21.539 11.6453 21.3903 11.5345 21.2651 11.3948C21.1399 11.2552 21.0407 11.0896 20.9732 10.9074C20.9057 10.7252 20.8712 10.5301 20.8716 10.3331C20.8716 9.50248 21.4741 8.82877 22.218 8.82877H22.2215C22.9653 8.82877 23.5678 9.50248 23.567 10.3331C23.567 11.1637 22.9636 11.8364 22.2197 11.8364Z" fill=""/>
<path d="M17.8282 22.7778L17.2804 22.1878H17.2709L15.0382 19.8258H9.19456C5.97768 19.795 5.89473 16.8498 5.89473 16.8498L5.89387 9.79849C5.87236 9.55573 5.77112 9.33039 5.60957 9.16571C5.44802 9.00103 5.23755 8.90863 5.01858 8.90625H4.42238C3.60066 8.90625 2.93188 9.68395 2.93188 10.6407V22.4583C2.93188 23.415 3.60326 24.1927 4.4267 24.1927H6.13494C6.18918 24.2025 6.24081 24.2255 6.28615 24.2601L6.29306 25.6961C6.29306 25.7827 6.31034 25.8665 6.33108 25.9512C6.35959 26.0772 6.40885 26.1966 6.47797 26.3015C6.76311 26.7154 7.17613 26.9531 7.61507 26.9531H7.6168C8.03068 26.9531 8.42037 26.7433 8.70291 26.3756L9.10038 25.9512L10.355 24.6133C10.4103 24.5555 10.4881 24.4304 10.5287 24.3592C10.5937 24.2735 10.683 24.2151 10.7818 24.1936H17.5474C17.9751 24.1936 18.3605 23.9819 18.6335 23.645L18.624 23.6335H18.6387L17.8291 22.7768L17.8282 22.7778Z" fill=""/>
</svg>
<svg width="30" height="30" viewBox="0 0 30 30" fill="" xmlns="http://www.w3.org/2000/svg">
<path d="M24.3512 3.75H5.61951C5.12271 3.75 4.64626 3.94754 4.29498 4.29917C3.94369 4.65081 3.74634 5.12772 3.74634 5.625V24.375C3.74634 24.8723 3.94369 25.3492 4.29498 25.7008C4.64626 26.0525 5.12271 26.25 5.61951 26.25H24.3512C24.848 26.25 25.3245 26.0525 25.6757 25.7008C26.027 25.3492 26.2244 24.8723 26.2244 24.375V5.625C26.2244 5.12772 26.027 4.65081 25.6757 4.29917C25.3245 3.94754 24.848 3.75 24.3512 3.75ZM11.58 18.3618L10.2553 19.6875L5.61951 15.0472L6.9439 13.7215L10.2553 10.4068L11.5797 11.7325L8.26858 15.0472L11.58 18.3618ZM14.0488 22.5H12.1756L15.9219 7.5H17.7951L14.0488 22.5ZM19.7154 19.6875L18.391 18.3618L21.7021 15.0472L18.3907 11.7325L19.7151 10.4068L23.0268 13.7212L24.3512 15.0472L19.7154 19.6875Z" fill=""/>
</svg>
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.4945 2.87072C16.5423 2.97866 16.5811 3.10459 16.611 3.24851C16.6409 3.39243 16.6558 3.54235 16.6558 3.69826C16.6558 3.85418 16.6259 4.01009 16.5662 4.16601C16.5065 4.32192 16.4109 4.47184 16.2795 4.61576C16.1361 4.75968 16.0077 4.88561 15.8942 4.99355C15.7807 5.10149 15.6822 5.19744 15.5985 5.28139C15.5029 5.37734 15.4133 5.46129 15.3297 5.53325L11.9787 2.1691C12.122 2.03718 12.2923 1.87826 12.4894 1.69237C12.6865 1.50647 12.8508 1.35955 12.9822 1.25161C13.1494 1.11968 13.3227 1.02673 13.5019 0.972762C13.6811 0.918792 13.8573 0.894805 14.0305 0.900802C14.2037 0.906799 14.371 0.933784 14.5323 0.981757C14.6936 1.02973 14.8339 1.0837 14.9534 1.14367C15.2043 1.2756 15.482 1.50647 15.7867 1.83629C16.0913 2.16611 16.3273 2.51092 16.4945 2.87072ZM2.53477 11.6499C2.60645 11.5779 2.7737 11.407 3.03653 11.1372C3.29936 10.8673 3.62789 10.5345 4.02214 10.1387L5.3303 8.82544L6.78183 7.36824L10.6347 3.50037L13.9857 6.88251L10.1329 10.7504L8.69928 12.2076C8.22141 12.6753 7.79133 13.1041 7.40904 13.4939C7.02674 13.8837 6.71015 14.2045 6.45927 14.4563C6.20839 14.7082 6.05905 14.8521 6.01127 14.8881C5.8918 14.996 5.75441 15.11 5.59911 15.2299C5.4438 15.3499 5.28252 15.4458 5.11526 15.5178C4.94801 15.6017 4.7031 15.7037 4.38054 15.8236C4.05798 15.9435 3.72048 16.0605 3.36805 16.1744C3.01562 16.2883 2.68111 16.3873 2.36453 16.4712C2.04794 16.5552 1.81199 16.6092 1.65668 16.6331C1.33412 16.6691 1.11908 16.6212 1.01156 16.4892C0.904038 16.3573 0.874171 16.1354 0.921958 15.8236C0.945851 15.6557 1.0026 15.4128 1.0922 15.095C1.1818 14.7772 1.27737 14.4474 1.37892 14.1055C1.48047 13.7637 1.57903 13.4489 1.6746 13.1611C1.77018 12.8732 1.84783 12.6753 1.90756 12.5674C1.97924 12.3995 2.05988 12.2466 2.14948 12.1086C2.23909 11.9707 2.36751 11.8178 2.53477 11.6499Z" fill="#B4B4B4"/>
</svg>
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.0892 17.8016C22.1207 16.8939 22.9629 15.9589 23.5523 15.2433C24.0051 14.6935 24.2316 14.4186 24.2316 14C24.2316 13.5814 24.0051 13.3065 23.5523 12.7567C21.8955 10.7452 18.2413 7 13.9996 7C12.927 7 11.892 7.23946 10.9174 7.62981L13.7935 10.506C13.8617 10.502 13.9304 10.5 13.9996 10.5C15.9326 10.5 17.4996 12.067 17.4996 14C17.4996 14.0692 17.4976 14.1379 17.4936 14.2061L21.0892 17.8016ZM11.0936 12.0487C10.7185 12.6062 10.4996 13.2775 10.4996 14C10.4996 15.933 12.0666 17.5 13.9996 17.5C14.722 17.5 15.3934 17.2811 15.9509 16.906L18.6424 19.5975C17.2425 20.4217 15.6664 21 13.9996 21C9.75788 21 6.10363 17.2548 4.44686 15.2433C3.99401 14.6935 3.76758 14.4186 3.76758 14C3.76758 13.5814 3.99401 13.3065 4.44686 12.7567C5.26698 11.761 6.57655 10.3404 8.20455 9.15965L11.0936 12.0487Z" fill="#B9BDCA"/>
<path d="M5.83301 2.3335L24.4997 21.0002" stroke="#B9BDCA" stroke-width="2"/>
</svg>
\ No newline at end of file
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.2731 14C24.2731 13.6466 24.0798 13.4072 23.6931 12.9284C22.1093 10.9673 18.366 7 14.0003 7C9.63468 7 5.89132 10.9673 4.30753 12.9284C3.92087 13.4072 3.72754 13.6466 3.72754 14C3.72754 14.3534 3.92087 14.5928 4.30753 15.0716C5.89132 17.0327 9.63468 21 14.0003 21C18.366 21 22.1093 17.0327 23.6931 15.0716C24.0798 14.5928 24.2731 14.3534 24.2731 14ZM14.0003 17.5C15.9333 17.5 17.5003 15.933 17.5003 14C17.5003 12.067 15.9333 10.5 14.0003 10.5C12.0673 10.5 10.5003 12.067 10.5003 14C10.5003 15.933 12.0673 17.5 14.0003 17.5Z" fill="#CCD2E3"/>
</svg>
\ No newline at end of file
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.1567 24.8875C16.3149 25.9593 13.9912 25.8461 11.4194 24.5064C9.13124 23.4693 7.5726 21.5405 7.50715 21.4593L7.25215 21.1395L10.3108 19.3914C10.3676 19.3669 10.422 19.337 10.4731 19.302C10.3925 19.2431 10.305 19.1944 10.2126 19.1568C6.48442 17.0343 2.00283 14.4966 1.54124 14.2777C1.50606 14.2585 1.46731 14.2466 1.42737 14.243C1.40351 14.2695 1.36464 14.4216 1.36328 14.5382V24.5636C1.36328 24.6584 1.37964 24.6905 1.37964 24.6918C1.36942 24.657 1.41919 24.6414 1.44305 24.6305L4.51396 22.8809L4.69328 23.1605C4.70419 23.1761 5.80192 24.8541 8.93214 26.8184C10.786 27.9836 13.1049 28.6239 15.4612 28.6239C17.3799 28.6239 19.2371 28.1834 20.8306 27.3495C24.1149 25.6314 26.4167 22.6436 27.0787 21.7177L24.6071 20.0425C23.1685 22.3375 21.151 24.1348 19.1567 24.8875ZM28.619 5.305C28.6251 5.33909 28.5678 5.36023 28.5596 5.36432L25.4853 7.11591L25.3053 6.83568C25.2951 6.81932 24.2001 5.14477 21.0658 3.17705C19.2119 2.01523 16.8951 1.375 14.5415 1.375C12.6228 1.375 10.7656 1.81545 9.17215 2.64932C5.88715 4.3675 3.58601 7.35523 2.92396 8.28114L5.39078 9.94886C6.83078 7.65386 8.8476 5.85659 10.8426 5.10523C13.6837 4.03341 16.0081 4.14591 18.5792 5.485C20.8674 6.52205 22.4274 8.45091 22.4915 8.53273L22.7465 8.85182L19.6878 10.5993C19.6304 10.6249 19.5756 10.656 19.5242 10.692C19.5678 10.7268 19.6469 10.778 19.7867 10.8393C22.7942 12.5507 27.9631 15.4825 28.4574 15.717C28.4928 15.7365 28.5317 15.7485 28.5719 15.7525L28.5726 16.0948L28.5753 15.7525C28.5992 15.7239 28.6367 15.5609 28.6367 15.4593V5.4325C28.636 5.33773 28.6196 5.305 28.619 5.305Z" fill="#888FA1"/>
</svg>
<svg width="40" height="30" viewBox="0 0 40 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.125 20.8333V25.4167H30.4583C33.375 25 35.6667 22.2917 35.6667 19.1667C35.6667 15.625 32.9583 12.9167 29.4167 12.9167C28.5833 12.9167 27.9583 13.125 27.3333 13.3333V12.9167C27.3333 8.33334 23.5833 4.58334 19 4.58334C14.4167 4.58334 10.6667 8.33334 10.6667 12.9167C10.6667 13.75 10.875 14.375 10.875 15.2083C10.4583 15 10.0417 15 9.625 15C6.70833 15 4.41667 17.2917 4.41667 20.2083C4.41667 23.125 6.70833 25.4167 9.625 25.4167H17.9583V20.8333L15.6667 23.125L12.75 20.2083L20.0417 12.9167L27.3333 20.2083L24.4167 23.125L22.125 20.8333ZM22.125 25.4167V29.5833H17.9583V25.4167H15.875V29.5833H9.625C4.41667 29.5833 0.25 25.4167 0.25 20.2083C0.25 16.0417 2.95833 12.5 6.5 11.25C7.33333 5.20834 12.5417 0.416672 19 0.416672C24.4167 0.416672 29.2083 3.95834 30.875 8.75C35.875 9.375 39.8333 13.75 39.8333 19.1667C39.8333 24.5833 35.6667 28.9583 30.4583 29.5833H24.2083V25.4167H22.125Z" fill="#999999"/>
<path d="M22.125 20.8333V25.4167H30.4583C33.375 25 35.6667 22.2917 35.6667 19.1667C35.6667 15.625 32.9583 12.9167 29.4167 12.9167C28.5833 12.9167 27.9583 13.125 27.3333 13.3333V12.9167C27.3333 8.33334 23.5833 4.58334 19 4.58334C14.4167 4.58334 10.6667 8.33334 10.6667 12.9167C10.6667 13.75 10.875 14.375 10.875 15.2083C10.4583 15 10.0417 15 9.625 15C6.70833 15 4.41667 17.2917 4.41667 20.2083C4.41667 23.125 6.70833 25.4167 9.625 25.4167H17.9583V20.8333L15.6667 23.125L12.75 20.2083L20.0417 12.9167L27.3333 20.2083L24.4167 23.125L22.125 20.8333ZM22.125 25.4167V29.5833H17.9583V25.4167H15.875V29.5833H9.625C4.41667 29.5833 0.25 25.4167 0.25 20.2083C0.25 16.0417 2.95833 12.5 6.5 11.25C7.33333 5.20834 12.5417 0.416672 19 0.416672C24.4167 0.416672 29.2083 3.95834 30.875 8.75C35.875 9.375 39.8333 13.75 39.8333 19.1667C39.8333 24.5833 35.6667 28.9583 30.4583 29.5833H24.2083V25.4167H22.125Z" fill="#999999"/>
</svg>
\ No newline at end of file
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M48.75 27.1875C48.75 26.875 48.75 26.5625 48.4375 25.9375L42.5 15.625C41.875 14.6875 40.9375 14.375 40 15C39.0625 15.625 38.75 16.5625 39.375 17.5L44.0625 25.625H36.25C35.3125 25.625 34.6875 26.25 34.375 27.1875C34.0625 29.375 32.8125 31.5625 31.25 32.8125C29.6875 34.0625 27.5 35 25 35C22.8125 35 20.625 34.0625 18.75 32.8125C17.1875 31.25 15.9375 29.375 15.625 27.1875C15.625 26.25 14.6875 25.625 13.75 25.625H5.9375L10.625 17.5C11.25 16.5625 10.9375 15.625 10 15C9.0625 14.375 8.125 14.6875 7.5 15.625L1.5625 25.9375C1.25 26.25 1.25 26.875 1.25 27.1875V44.0625C1.25 45 2.1875 45.9375 3.125 45.9375H46.875C47.8125 45.9375 48.75 45 48.75 44.0625V27.1875ZM45 42.1875H5V29.0625H12.5C13.125 31.5625 14.6875 33.75 16.5625 35.3125C18.75 37.5 21.875 38.75 25 38.75C28.125 38.75 31.25 37.5 33.4375 35.625C35.3125 34.0625 36.875 31.875 37.5 29.375H45V42.1875ZM14.6875 18.125H20V29.375C20 30 20.625 30.3125 20.9375 30.3125H28.4375C29.0625 30.3125 29.6875 29.6875 29.6875 29.375V18.125H35C35.625 18.125 36.25 17.5 36.25 16.875C36.25 16.5625 36.25 16.25 35.9375 16.25L25.625 6.25C25.3125 5.625 24.6875 5.625 24.0625 6.25L14.0625 16.25C13.75 16.5625 13.75 17.5 14.0625 17.8125C14.0625 18.125 14.375 18.125 14.6875 18.125ZM25 8.4375L32.8125 15.9375H28.75C28.125 15.9375 27.8125 16.5625 27.8125 17.1875V28.125H22.1875V17.1875C22.1875 16.5625 21.5625 16.25 20.9375 16.25H17.5L25 8.4375Z" fill="#888FA1"/>
</svg>
\ No newline at end of file
<template>
<t-button
class="default-t-button"
:style="{
height: height,
}"
:class="[`c-button-${size}`, `c-button-${theme}`]"
><slot></slot
></t-button>
</template>
<script lang="ts" setup>
const props = withDefaults(
defineProps<{
theme?: string;
size?: string;
width?: string;
height?: string;
}>(),
{
theme: 'default',
size: '16',
height: '32px',
width: '',
},
);
const emit = defineEmits(['update:modelValue']);
</script>
<style lang="less">
@import '@/style/variables';
.default-t-button {
border-radius: 6px;
}
.c-button-16 {
font-size: @size-16;
}
.c-button-13 {
font-size: @size-13;
}
.t-button.c-button-danger {
background: #fc384b;
border-color: #fc384b;
--ripple-color: #fc384b !important;
&:hover {
border-color: #fc384b;
background: #fc384b;
}
}
.t-button.c-button-dark {
background: #1e1e1e;
border-color: #1e1e1e;
--ripple-color: #1e1e1e !important;
&:hover {
border-color: #1e1e1e;
background: #1e1e1e;
}
}
.t-button.c-button-green {
background: #00cca2;
border-color: #00cca2;
--ripple-color: #00cca2 !important;
color: #fff;
&:hover {
border-color: #00cca2;
background: #00cca2;
}
}
.t-button.c-button-light {
background: #fff;
border-color: #e0e0e0;
--ripple-color: #fff !important;
color: #4d4d4d;
&:hover {
border-color: #e0e0e0;
background: #fff;
}
}
</style>
@import '@/style/variables.less';
.custom-c-tabs {
.c-tabs-header {
display: flex;
......@@ -15,13 +16,17 @@
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: 15px;
font-weight: 700;
font-size: @size-20;
color: var(--td-text-color-secondary);
}
}
.c-nav-item_active {
background: white;
transition: all 0.2s;
.c-tabs__nav-item-wrapper {
color: var(--theme-color-1);
}
......
import { computed, defineComponent, nextTick, onMounted, reactive, ref, watch, provide, useSlots } from 'vue';
import { defineComponent, nextTick, onMounted, reactive, ref, provide, useSlots } from 'vue';
import './index.less';
import { useStore } from 'vuex';
export default defineComponent({
props: {
modelValue: String,
language: {
type: Boolean,
default: true,
},
},
emits: ['update:modelValue'],
setup(props, context) {
const { slots, emit } = context;
const store = useStore();
const use_slots = useSlots();
// 当前tab
const currentTab = ref(props.modelValue);
......@@ -35,7 +29,6 @@ export default defineComponent({
const titles = ref(getTitles());
const language = computed(() => store.getters['language/getLang']);
const tab_bar = reactive({
width: 0,
left: 0,
......@@ -77,18 +70,9 @@ export default defineComponent({
for (let i = 0; i < navIndex.value; i++) {
total_width += navItem.value[i].clientWidth;
}
if (props.language && language.value) {
// 根据语言来判断
if (language.value == 'ar') {
// 定位到右边
tab_bar.left = null;
tab_bar.right = total_width;
} else {
// 定位到左边
tab_bar.right = null;
tab_bar.left = total_width;
}
}
// 定位到左边
tab_bar.right = null;
tab_bar.left = total_width;
});
};
......@@ -103,18 +87,6 @@ export default defineComponent({
getTabBarLocation();
};
// 监听语言切换
watch(
() => language.value,
(v) => {
if (v) {
titles.value = getTitles();
getTabBarWidth();
getTabBarLocation();
}
},
);
onMounted(() => {
getTabBarWidth();
});
......@@ -131,14 +103,14 @@ export default defineComponent({
<div class="c-tabs__nav-item-wrapper">{item.label}</div>
</div>
))}
<div
{/* <div
class="c-tabs__bar"
style={{
width: tab_bar.width + 'px',
left: tab_bar.left + 'px',
right: tab_bar.right + 'px',
}}
></div>
></div> */}
</div>
<div class="c-tabs-content">{slots.default?.()}</div>
</div>
......
<template>
<t-dialog v-model:visible="visible" 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>
</template>
<template #footer>
<div class="footer-default" v-if="!footer">
<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;
}>(),
{
footer: false,
placement: 'center',
},
);
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('confirm');
};
</script>
<style lang="less">
@import '@/style/variables.less';
.c-dialog-default {
.t-dialog {
background: #303030;
border: none;
border-radius: 4px;
}
.header-default {
font-size: @size-20;
color: #fff;
}
.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;
}
}
</style>
<template>
<div class="custom-select-box">
<TSelect
v-model="SelectValue"
:autoWidth="autoWidth"
:placeholder="placeholder"
@change="SelectChange"
:popupProps="{
overlayClassName: [className, 'custom-select-popup'],
}"
>
<t-option v-for="item in options" :key="item.value" :value="item.value" :label="item.label"></t-option>
</TSelect>
</div>
</template>
<script lang="ts" setup>
import { Select as TSelect, Option as TOption } from 'tdesign-vue-next';
import { ref, watch } from 'vue';
const props = withDefaults(
defineProps<{
options: any[];
modelValue: number | string;
width?: string;
placeholder?: string;
className?: string;
autoWidth?: boolean;
}>(),
{
width: '50%',
placeholder: '请选择',
autoWidth: true,
},
);
const emit = defineEmits(['update:modelValue', 'change']);
const SelectValue = ref(props.modelValue);
watch(
() => SelectValue.value,
(v) => {
emit('update:modelValue', v);
},
);
watch(
() => props.modelValue,
(v) => {
SelectValue.value = v;
},
);
const SelectChange = (value: string) => {
emit('change', value);
};
</script>
<style lang="less">
.custom-select-popup {
border: none;
box-shadow: none;
.t-popup__content {
background: #181818;
}
.t-select__list {
background: #181818;
box-shadow: none;
border: none;
.t-select-option {
color: white;
--ripple-color: #00dddd;
}
.t-select-option__hover,
.t-is-selected {
color: #00dddd;
background: rgba(0, 0, 0, 0.3) !important;
}
}
}
.custom-select-box {
.t-select__wrap {
height: 38px;
.t-input__wrap {
border-radius: 8px;
background: transparent;
}
.t-input.t-is-default {
height: 100%;
background-color: #181818;
border: none;
border-radius: 8px;
.t-input__inner {
text-align: center;
color: #ffffff;
&::placeholder {
color: #888fa1;
}
}
}
.t-is-focused {
box-shadow: 0px 0px 0px 1px #00dddd;
}
.t-fake-arrow {
color: #00dddd;
}
.t-select-input {
height: 100%;
.t-input__wrap {
height: 100%;
}
}
}
}
</style>
<template>
<t-popup
:overlayClassName="['custom-chose-tones']"
:disabled="disabled"
:placement="placement"
trigger="click"
v-model:visible="visible"
>
<template #content>
<div class="custom-chose-tones-content">
<div class="title">{{ title }}:</div>
<div class="list narrow-scrollbar">
<template v-if="list">
<div class="chose-tones-item" v-for="item in list" :key="item.id"></div>
</template>
<template v-else>
<div
class="chose-tones-item"
:class="{
'chose-tones-item_active': item.id === value,
}"
@click="onSelect(item)"
v-for="item in popupList.list"
:key="item.id"
>
<img :src="item.img" alt="" />
<div>
<div class="name">{{ item.c_name }}</div>
<template v-if="needCategorie">
<div class="categorie">{{ item.c_categorie }}</div>
</template>
</div>
</div>
</template>
</div>
</div>
</template>
<slot></slot>
</t-popup>
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue';
const props = withDefaults(
defineProps<{
modelValue: boolean;
value: string | number;
disabled?: boolean;
title?: string;
list?: any[] | null;
event?: Function;
placement?: string;
needCategorie?: boolean;
}>(),
{
disabled: false,
title: '',
list: null,
event: () => '',
placement: 'bottom',
needCategorie: true,
},
);
const emit = defineEmits(['update:modelValue', 'update:value', 'itemChange']);
const visible = ref(props.modelValue);
const popupList = reactive({
list: [],
});
const isFirst = ref(true);
const getList = async () => {
let list = await props.event();
popupList.list = list;
};
watch(
() => props.modelValue,
(v) => {
visible.value = v;
},
);
watch(
() => visible.value,
(v) => {
if (v && isFirst.value && !props.list) {
getList();
}
emit('update:modelValue', v);
},
);
const onSelect = (item: any) => {
visible.value = false;
emit('update:value', item.id);
emit('itemChange', item);
};
</script>
<style lang="less">
@import '@/style/variables';
.custom-chose-tones {
.t-popup__content {
padding: 0;
margin-top: 20px !important;
background: #303030;
border-radius: 6px;
}
}
.custom-chose-tones-content {
width: 490px;
height: 219px;
border-radius: 6px;
border: 1px solid #00f9f9;
background: #303030;
padding: 12px;
.title {
color: #b4b4b4;
font-size: @size-14;
}
.list {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
row-gap: 6px;
height: 180px;
overflow-y: auto;
.chose-tones-item {
width: 110px;
height: 45px;
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: 35px;
height: 35px;
border-radius: 50%;
}
.name {
font-size: @size-14;
font-weight: 600;
color: #e2e2e2;
line-height: 14px;
}
.categorie {
font-size: @size-12;
color: #d0d0d0;
}
}
.chose-tones-item_active {
border: 1px solid #0dd;
transition: all 0.2s;
}
}
}
</style>
<template>
<div
class="custom-card-box"
@click="onChange"
:class="{
'cursor-pointer': clicked,
}"
>
<div class="custom-card-box" @click="onChange" :class="[clicked ? 'cursor-pointer' : '', className]">
<img :src="img" alt="" />
<div>
{{ name }}
......@@ -20,15 +14,20 @@ const props = withDefaults(
img: string;
name: string;
clicked?: boolean;
className?: string;
row?: any;
}>(),
{
clicked: true,
customLast: true,
className: '',
row: {},
},
);
const emit = defineEmits(['change']);
const onChange = () => {
props.clicked && emit('change', props.id);
props.clicked && emit('change', props.id, props.row);
};
</script>
......@@ -39,11 +38,11 @@ const onChange = () => {
height: 224px;
background: #fff;
border-radius: 8px;
transition: all 0.2s;
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
padding: 12px;
text-align: center;
font-size: @size-16;
box-sizing: border-box;
img {
width: 100%;
height: 174px;
......
<template>
<div
class="custom-card-two-box"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
@click="onChange"
:class="{
'cursor-pointer': clicked,
}"
>
<div class="custom-card-two-image">
<img :src="img" alt="" />
<div v-show="showHover">
<slot name="hover"></slot>
</div>
</div>
<div class="custom-card-footer">
<ChangeName :value="value"></ChangeName>
<div class="create-time">
{{ created_at }}
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import ChangeName from '@/components/changeName.vue';
const props = withDefaults(
defineProps<{
id: string | number;
img: string;
value: string;
created_at: string;
clicked?: boolean;
}>(),
{
clicked: true,
customLast: true,
},
);
const emit = defineEmits(['change']);
// hover内容是否显示
const showHover = ref(false);
const onChange = () => {
props.clicked && emit('change', props.id);
};
const handleMouseEnter = () => {
showHover.value = true;
};
const handleMouseLeave = () => {
showHover.value = false;
};
</script>
<style lang="less">
@import '@/style/variables.less';
.custom-card-two-box {
width: 200px;
height: 310px;
background: #fff;
border-radius: 8px;
transition: all 0.2s;
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.3);
text-align: center;
font-size: @size-16;
display: flex;
flex-direction: column;
.custom-card-two-image {
position: relative;
height: 236px;
img {
width: 100%;
height: 100%;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
}
.custom-card-footer {
padding: 6px 12px;
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-evenly;
.create-time {
font-size: @size-13;
color: #b3b3b3;
text-align: left;
}
}
}
.cursor-pointer {
cursor: pointer;
}
</style>
<template>
<div class="custom-change-name-box">
<template v-if="isEdit">
<CustomInput
v-model="inputValue"
align="left"
theme="light"
width="auto"
height="23px"
:autoFocus="true"
@inputBlur="inputBlur"
></CustomInput>
</template>
<template v-else-if="!value">
<span class="empty-tips">未命名草稿</span>
</template>
<template v-else-if="!isEdit"> {{ value }}</template>
<span @click="onEdit" class="edit-icon">
<EditSvg></EditSvg>
</span>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import CustomInput from './input/index.vue';
import EditSvg from '@/assets/svg/home/edit.svg';
const props = withDefaults(
defineProps<{
isEdit?: boolean;
event?: Function;
value: string;
}>(),
{
event: () => true,
isEdit: false,
},
);
const emit = defineEmits(['change']);
// 是否处于编辑状态
const isEdit = ref(false);
// 输入框的内容
const inputValue = ref(props.value);
// 进入编辑模式
const onEdit = () => {
isEdit.value = true;
};
// 退出编辑模式
const inputBlur = async () => {
isEdit.value = false;
let status = await props.event(inputValue.value);
if (!status) {
// 恢复默认值
inputValue.value = props.value;
}
emit('change', inputValue.value);
};
watch(
() => props.value,
(v) => {
inputValue.value = v;
},
);
</script>
<style lang="less">
@import '@/style/variables.less';
.custom-change-name-box {
color: @main-color;
font-size: @size-16;
display: flex;
justify-content: space-between;
.edit-icon {
display: flex;
align-items: center;
margin-left: 12px;
cursor: pointer;
}
}
</style>
<template>
<div ref="DragBox" :class="[DefaultClassName, 'drag']">
<img class="show-img" @dragstart.prevent :src="img" />
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
const props = defineProps<{
id: string;
img: string;
}>();
const emit = defineEmits(['delete']);
// 拖拽元素
const DragBox = ref();
// 默认类名
const DefaultClassName = ref('custom-drag-img' + props.id);
class Drag {
constructor(target, options = { limit: true, drag: true, zoom: true, minWidth: 0, minHeight: 0 }) {
this.target = target;
this.options = options;
this.init();
}
// 初始化
init() {
this.target.style.position = 'relative';
this.getBoundary();
if (this.options.drag) {
this.drag();
}
if (this.options.zoom) {
this.addHorn();
this.addBorder();
this.leftZoom();
this.rightZoom();
this.topZoom();
this.bottomZoom();
this.leftTopZoom();
this.leftBottomZoom();
this.rightTopZoom();
this.rightBottomZoom();
}
}
// 获取父元素的宽高
getBoundary() {
this.maxWidth = this.target.parentNode.clientWidth;
this.maxHeight = this.target.parentNode.clientHeight;
}
// 获取自身起始信息
getInfo(e) {
this.width = this.target.clientWidth;
this.height = this.target.clientHeight;
let translateStr = this.target.style.transform;
if (translateStr) {
const reg = /\d+/g;
let translateArr = translateStr.match(reg);
this.tx = Number(translateArr[0]);
this.ty = Number(translateArr[1]);
} else {
this.tx = 0;
this.ty = 0;
}
this.startX = e.clientX;
this.startY = e.clientY;
}
// 拖动实现
drag() {
this.target.addEventListener('mousedown', (e) => {
this.getInfo(e);
document.onmousemove = (e) => {
if (this.options.limit) {
this.distanceX = Math.max(0, Math.min(this.tx + e.clientX - this.startX, this.maxWidth - this.width));
this.distanceY = Math.max(0, Math.min(this.ty + e.clientY - this.startY, this.maxHeight - this.height));
} else {
this.distanceX = this.tx + e.clientX - this.startX;
this.distanceY = this.ty + e.clientY - this.startY;
}
this.target.style.transform = `translate(${this.distanceX}px, ${this.distanceY}px)`;
};
document.onmouseup = () => {
document.onmousemove = null;
};
});
}
// 添加四个角
addHorn() {
this.leftTop = document.createElement('div');
this.rightTop = document.createElement('div');
this.leftBottom = document.createElement('div');
this.rightBottom = document.createElement('div');
this.leftTop.className = 'horn leftTop';
this.rightTop.className = 'horn rightTop';
this.leftBottom.className = 'horn leftBottom';
this.rightBottom.className = 'horn rightBottom';
this.target.append(this.leftTop);
this.target.append(this.rightTop);
this.target.append(this.leftBottom);
this.target.append(this.rightBottom);
}
// 添加四条边
addBorder() {
this.left = document.createElement('div');
this.right = document.createElement('div');
this._top = document.createElement('div');
this.bottom = document.createElement('div');
this.left.className = 'vertical left';
this.right.className = 'vertical right';
this._top.className = 'horizontal top';
this.bottom.className = 'horizontal bottom';
this.target.append(this.left);
this.target.append(this.right);
this.target.append(this._top);
this.target.append(this.bottom);
}
// 缩放实现
zoom(el: any, direction: any) {
el.addEventListener('mousedown', (e) => {
e.stopPropagation();
this.getInfo(e);
document.onmousemove = (e) => {
switch (direction) {
case 'left':
this.leftInfo(e);
break;
case 'right':
this.rightInfo(e);
break;
case 'top':
this.topInfo(e);
break;
case 'bottom':
this.bottomInfo(e);
break;
case 'leftTop':
this.leftTopInfo(e);
break;
case 'leftBottom':
this.leftBottomInfo(e);
break;
case 'rightTop':
this.rightTopInfo(e);
break;
case 'rightBottom':
this.rightBottomInfo(e);
break;
}
// 这里不能直接使用对this.newWidth隐式类型转换来判断,因为this.newWidth===0时,会使用this.width
let width = this.newWidth !== undefined ? this.newWidth : this.width;
let height = this.newHeight !== undefined ? this.newHeight : this.height;
let translateX = this.distanceX !== undefined ? this.distanceX : this.tx;
let translateY = this.distanceY !== undefined ? this.distanceY : this.ty;
this.target.style.width = `${width}px`;
this.target.style.height = `${height}px`;
this.target.style.transform = `translate(${translateX}px, ${translateY}px)`;
};
document.onmouseup = () => {
document.onmousemove = null;
};
});
}
// 获取缩放时宽高、translate等参数的值
leftInfo(e: any) {
this.newWidth = this.width - (e.clientX - this.startX);
this.distanceX = this.tx + (e.clientX - this.startX);
if (this.options.limit) {
this.newWidth = Math.max(this.options.minWidth, Math.min(this.newWidth, this.width + this.tx));
this.distanceX = Math.max(0, Math.min(this.distanceX, this.width + this.tx - this.options.minWidth));
}
}
rightInfo(e: any) {
this.newWidth = this.width + (e.clientX - this.startX);
if (this.options.limit) {
this.newWidth = Math.max(this.options.minWidth, Math.min(this.newWidth, this.maxWidth - this.tx));
}
}
topInfo(e: any) {
this.newHeight = this.height - (e.clientY - this.startY);
this.distanceY = this.ty + (e.clientY - this.startY);
if (this.options.limit) {
this.newHeight = Math.max(this.options.minHeight, Math.min(this.newHeight, this.height + this.ty));
this.distanceY = Math.max(0, Math.min(this.distanceY, this.height + this.ty - this.options.minHeight));
}
}
bottomInfo(e: any) {
this.newHeight = this.height + (e.clientY - this.startY);
if (this.options.limit) {
this.newHeight = Math.max(this.options.minHeight, Math.min(this.newHeight, this.maxHeight - this.ty));
}
}
leftTopInfo(e) {
this.leftInfo(e);
this.topInfo(e);
}
leftBottomInfo(e) {
this.leftInfo(e);
this.bottomInfo(e);
}
rightTopInfo(e) {
this.rightInfo(e);
this.topInfo(e);
}
rightBottomInfo(e) {
this.rightInfo(e);
this.bottomInfo(e);
}
leftZoom() {
this.zoom(this.left, 'left');
}
rightZoom() {
this.zoom(this.right, 'right');
}
topZoom() {
this.zoom(this._top, 'top');
}
bottomZoom() {
this.zoom(this.bottom, 'bottom');
}
leftTopZoom() {
this.zoom(this.leftTop, 'leftTop');
}
leftBottomZoom() {
this.zoom(this.leftBottom, 'leftBottom');
}
rightTopZoom() {
this.zoom(this.rightTop, 'rightTop');
}
rightBottomZoom() {
this.zoom(this.rightBottom, 'rightBottom');
}
}
onMounted(() => {
let dragEl = document.querySelector(`.${DefaultClassName.value}`);
if (dragEl) {
new Drag(dragEl);
}
});
</script>
<style lang="less">
.drag {
height: 100px;
width: 100px;
background-repeat: no-repeat;
background-size: 100% 100%;
-moz-background-size: 100% 100%;
border: 1px dashed transparent;
cursor: all-scroll;
.show-img {
width: 100%;
height: 100%;
user-select: none;
}
&:hover {
border-color: #ddd;
}
.horn {
width: 14px;
height: 14px;
position: absolute;
}
.vertical {
width: 10px;
height: calc(100% - 14px);
margin: 7px 0px;
position: absolute;
cursor: col-resize;
}
.horizontal {
width: calc(100% - 14px);
height: 10px;
margin: 0px 7px;
position: absolute;
cursor: row-resize;
}
.top {
top: -5px;
left: 0;
}
.left {
top: 0;
left: -5px;
}
.bottom {
left: 0;
bottom: -5px;
}
.right {
top: 0;
right: -5px;
}
.leftTop {
cursor: nw-resize;
left: -7px;
top: -7px;
}
.rightTop {
cursor: ne-resize;
right: -7px;
top: -7px;
}
.leftBottom {
cursor: sw-resize;
bottom: -7px;
left: -7px;
}
.rightBottom {
cursor: se-resize;
right: -7px;
bottom: -7px;
}
}
</style>
export interface RulesType {
required?: boolean;
type?: string;
message?: string;
min?: number;
max?: number;
validator?: Function;
}
<template>
<div
class="custom-input-global"
:style="{
width: width,
}"
>
<div
class="custom-input-box"
:style="{
height: height,
borderRadius: borderRadius,
}"
:class="{
'custom-input-error': ruleError.status,
'dark-input-box': theme === 'dark',
'light-input-box': theme === 'light',
}"
>
<slot name="leftIcon">
<span class="left-input-icon"></span>
</slot>
<input
ref="customInput"
:type="input_type"
v-model="input_value"
class="cust-input"
:disabled="disabled"
:placeholder="placeholder"
@focus="onInputFocus"
@blur="onInputBlur"
@input="numberInput(input_value)"
:style="{ 'text-align': align, borderRadius: borderRadius }"
/>
<slot name="rightIcon">
<span v-if="type === 'password'" class="custom-pwd-hide-button">
<PrivatePwdSvg v-if="Cur_pwd_type === 'private'" @click="changePwd('text')"></PrivatePwdSvg>
<PublicPwdSvg v-else @click="changePwd('password')"></PublicPwdSvg>
</span>
</slot>
<!-- remember -->
<transition name="remember-fade">
<div class="remember-select-box" v-if="needSelect && input_focus && selectList.length">
<div v-for="item in selectList" :key="item.account" class="line" @click="QuickInputAccount(item)">
<div class="account">{{ item.account }}</div>
<div class="password">********</div>
</div>
</div>
</transition>
</div>
<div class="custom-input-rule" v-if="rules && rules.length">
{{ ruleError.message }}
</div>
</div>
</template>
<script lang="ts" setup>
import PrivatePwdSvg from '@/assets/svg/login/privatePwd.svg?component';
import PublicPwdSvg from '@/assets/svg/login/publicPwd.svg?component';
import { RulesType } from './Interfase';
import { emailReg } from '@/constants/token';
import { reactive, ref, watch, onMounted } from 'vue';
const input_value = ref<string>(props.modelValue);
const props = withDefaults(
defineProps<{
type?: string;
placeholder?: string;
num?: number;
rules?: Array<RulesType>;
modelValue: string;
disabled?: boolean;
needSelect?: boolean;
selectList?: any;
align?: string;
height?: string;
autoFocus?: boolean;
theme?: string;
borderRadius?: string;
width?: string;
}>(),
{
// 输入框类型
type: 'text',
align: 'center',
placeholder: '请输入',
needSelect: false,
selectList: [],
// rules: [],
disabled: false,
height: '32px',
autoFocus: false,
theme: 'dark',
borderRadius: '4px',
width: '200px',
},
);
const emit = defineEmits(['update:modelValue', 'submitType', 'submitAccount', 'inputChange', 'inputBlur']);
// element
const customInput = ref<HTMLInputElement>();
// 是否聚焦
const input_focus = ref(false);
// 校验显示的错误
const ruleError = reactive({
status: false,
message: '',
});
const input_type = ref<string>('text');
// 当输入框类型为password时,展示私有密码svg
const Cur_pwd_type = ref<string>('private');
// 输入框校验
const numberInput = (e: string) => {
const { type } = props;
if (type == 'number') {
input_value.value = e.replace(/[^\d]/g, '');
}
// 提交输入事件
emit('inputChange', input_value.value);
};
// 聚焦事件
const onInputFocus = () => {
if (props.needSelect) {
input_focus.value = true;
}
};
// 失去焦点
const onInputBlur = () => {
if (props.needSelect) {
input_focus.value = false;
}
emit('inputBlur');
};
// 错误状态
const errorinput = (rule: any) => {
ruleError.status = true;
ruleError.message = rule.message;
// 提交失败信息
emit('submitType', false);
};
// 提交用户的账号密码
const QuickInputAccount = (item: any) => {
emit('submitAccount', item);
};
// 重置输入框状态
const ResetInput = () => {
ruleError.status = false;
ruleError.message = '';
// 提交通过信息
emit('submitType', true);
};
if (props.type == 'password') {
input_type.value = props.type;
}
//
const changePwd = (value: string) => {
if (value === 'text') {
Cur_pwd_type.value = 'public';
} else {
Cur_pwd_type.value = 'private';
}
input_type.value = value;
};
// 表单校验
const FormValidation = () => {
const { rules } = props;
let v = input_value.value;
if (rules) {
for (let i in rules) {
let rule: any = rules[i];
if (rule.required && !v) {
// 校验空input
errorinput(rule);
return;
} else {
ResetInput();
}
// 自定义校验逻辑
if (rule.validator) {
let res = rule.validator(v);
if (!res.status) {
errorinput(res);
return;
} else {
ResetInput();
}
}
// 邮箱验证模块
if (rule.type == 'email') {
if (!emailReg.test(v)) {
// 邮箱校验不通过
errorinput(rule);
return;
} else {
ResetInput();
}
}
// 输入框最小长度校验
if (rule.min && v.length < rule.min) {
errorinput(rule);
return;
} else {
ResetInput();
}
}
}
};
watch(
() => input_value.value,
(v) => {
emit('update:modelValue', v);
// 判断输入的内容是否符合校验规则
// 校验
FormValidation();
},
);
watch(
() => props.modelValue,
(v) => {
input_value.value = v;
},
);
//
watch(
() => props.num,
(v) => {
// 校验
FormValidation();
},
);
// 自动聚焦
onMounted(() => {
if (props.autoFocus) {
customInput.value.focus();
}
});
</script>
<style lang="less">
@import '@/style/flex.less';
@import '@/style/variables.less';
.custom-input-global {
height: 100%;
.custom-input-box {
width: 100%;
transition: all 0.3s;
position: relative;
.da();
.cust-input {
height: 100%;
width: 100%;
flex: 1;
outline: none;
border: none;
padding-left: 12px;
background: transparent;
&::placeholder {
font-weight: 500;
font-size: @size-14;
}
}
.left-input-icon {
.da();
}
.custom-pwd-hide-button {
.da();
padding: 0 8px;
cursor: pointer;
}
.remember-select-box {
z-index: 200;
position: absolute;
top: 50px;
width: 100%;
box-shadow: 0 3px 14px 2px rgba(0, 0, 0, 0.05), 0 8px 10px 1px rgba(0, 0, 0, 6%),
0 5px 5px -3px rgba(0, 0, 0, 10%);
background: #ddd;
border-radius: 4px;
max-height: 200px;
overflow-y: auto;
.line {
padding: 6px 12px;
font-weight: bold;
cursor: pointer;
.account {
color: var(--theme-color-35);
}
.password {
color: var(--theme-color-4);
}
}
}
}
.dark-input-box {
background: #181818;
border: 1px solid #393939;
&:focus-within {
border-color: #00f9f9;
}
.cust-input {
color: white;
&::placeholder {
color: #888fa1;
}
}
}
.light-input-box {
background: white;
border: 1px solid #ddd;
&:focus-within {
border-color: #00f9f9;
}
.cust-input {
color: var(--theme-color-4);
&::placeholder {
color: #ddd;
}
}
}
.custom-input-error {
border-color: #f05451 !important;
transition: all 0.3s;
}
.custom-input-rule {
color: #f05451;
font-weight: 400;
font-size: @size-12;
line-height: 26px;
padding-left: 4px;
min-height: 26px;
}
}
//内容打开动画
.remember-fade-enter-active {
transition: all 0.15s ease-out;
}
.remember-fade-leave-active {
transition: all 0.15s cubic-bezier(1, 0.5, 0.8, 1);
}
.remember-fade-enter-from,
.remember-fade-leave-to {
transform: translateY(-6px);
opacity: 0;
}
</style>
<template>
<div
class="custom-loading-two"
:style="{
position: position,
}"
>
<div class="loading">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</template>
<script lang="ts" setup>
const props = withDefaults(
defineProps<{
position?: string;
}>(),
{
position: 'absolute',
},
);
</script>
<style lang="less">
.custom-loading-two {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.loading,
.loading > div {
position: relative;
box-sizing: border-box;
}
.loading {
display: block;
font-size: 0;
color: #888fa1;
}
.loading.la-dark {
color: #333;
}
.loading > div {
display: inline-block;
float: none;
background-color: currentColor;
border: 0 solid currentColor;
}
.loading {
width: 58px;
height: 58px;
}
.loading > div {
position: absolute;
width: 4px;
height: 14px;
margin: 2px;
margin-top: -5px;
margin-left: -1px;
border-radius: 4px;
animation: line-spin-clockwise-fade 1s infinite ease-in-out;
}
.loading > div:nth-child(1) {
top: 15%;
left: 50%;
transform: rotate(0deg);
animation-delay: -0.875s;
}
.loading > div:nth-child(2) {
top: 25.2512626585%;
left: 74.7487373415%;
transform: rotate(45deg);
animation-delay: -0.75s;
}
.loading > div:nth-child(3) {
top: 50%;
left: 85%;
transform: rotate(90deg);
animation-delay: -0.625s;
}
.loading > div:nth-child(4) {
top: 74.7487373415%;
left: 74.7487373415%;
transform: rotate(135deg);
animation-delay: -0.5s;
}
.loading > div:nth-child(5) {
top: 84.9999999974%;
left: 50.0000000004%;
transform: rotate(180deg);
animation-delay: -0.375s;
}
.loading > div:nth-child(6) {
top: 74.7487369862%;
left: 25.2512627193%;
transform: rotate(225deg);
animation-delay: -0.25s;
}
.loading > div:nth-child(7) {
top: 49.9999806189%;
left: 15.0000039834%;
transform: rotate(270deg);
animation-delay: -0.125s;
}
.loading > div:nth-child(8) {
top: 25.2506949798%;
left: 25.2513989292%;
transform: rotate(315deg);
animation-delay: 0s;
}
.loading.la-sm {
width: 16px;
height: 16px;
}
.loading.la-sm > div {
width: 1px;
height: 4px;
margin-top: -2px;
margin-left: 0;
}
.loading.la-2x {
width: 64px;
height: 64px;
}
.loading.la-2x > div {
width: 4px;
height: 20px;
margin-top: -10px;
margin-left: -2px;
}
.loading.la-3x {
width: 96px;
height: 96px;
}
.loading.la-3x > div {
width: 6px;
height: 30px;
margin-top: -15px;
margin-left: -3px;
}
@keyframes line-spin-clockwise-fade {
50% {
opacity: 0.2;
}
100% {
opacity: 1;
}
}
</style>
<template>
<div class="custom-textarea-box">
<TTextarea
class="custom-t-textarea"
v-model="textarea_value"
:placeholder="placeholder"
:maxcharacter="maxlength"
:autosize="{
maxRows: maxRows,
minRows: minRows,
}"
></TTextarea>
<div class="position-text" v-if="showLimit">
<span> {{ textareaLength }}/{{ maxlength }}</span>
<span class="reset-btn" @click="reset">清空</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { Textarea as TTextarea } from 'tdesign-vue-next';
import { computed, ref, watch } from 'vue';
const props = withDefaults(
defineProps<{
modelValue: string;
placeholder?: string;
maxRows?: number;
minRows?: number;
maxlength?: number | null;
showLimit?: boolean;
}>(),
{
placeholder: '请输入',
maxRows: 5,
minRows: 5,
showLimit: false,
maxlength: 0,
},
);
const emit = defineEmits(['update:modelValue', 'change']);
const textarea_value = ref('');
const reset = () => {
textarea_value.value = '';
};
const textareaLength = computed(() => {
// 文本总长度
const total_len = textarea_value.value.length;
if (total_len) {
// 获取中文文本的长度
let re = /[\u4E00-\u9FA5]/g;
let list = textarea_value.value.match(re);
if (list && list.length) {
// 加上其他字符的长度
const other_len = total_len - list.length;
return list.length * 2 + other_len;
}
}
return total_len;
});
watch(
() => textarea_value.value,
(v) => {
emit('update:modelValue', v);
emit('change');
},
);
watch(
() => props.modelValue,
(v) => {
textarea_value.value = v;
},
);
</script>
<style lang="less">
@import '@/style/variables.less';
.custom-textarea-box {
width: 100%;
background: #181818;
// border: @main-border;
color: white;
resize: none;
border-radius: 8px;
transition: border 0.2s;
&:focus-within {
border-color: #00f9f9;
transition: all 0.2s;
}
.custom-t-textarea {
.t-textarea__inner {
background: #181818;
border: none;
color: white;
padding: 6px 12px;
resize: none;
border-radius: 8px;
transition: border 0.2s;
&::placeholder {
color: #888fa2;
}
}
.t-textarea__limit {
display: none;
}
.t-is-focused {
box-shadow: none;
}
}
.position-text {
font-size: @size-12;
text-align: right;
padding-right: 12px;
padding-bottom: 6px;
.reset-btn {
cursor: pointer;
user-select: none;
color: #00f9f9;
}
}
}
</style>
<template>
<div class="custom-upload-sell">
<t-upload
v-model="file1"
theme="image"
:percent="1"
:requestMethod="requestSuccessMethod"
:showUploadProgress="false"
accept="image/*"
:headers="{
authorization: `Bearer ${getUserCookie()}`,
lang: getLanguage(),
}"
:disabled="false"
:auto-upload="true"
:formatResponse="formatResponseOne"
:upload-all-files-in-one-request="false"
@fail="handleFail"
></t-upload>
<Uploading v-model="custom_masking1" :progress="cur_progress1"></Uploading>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { getUserCookie, getLanguage } from '@/utils/api/userApi';
import Uploading from './uploadTips.vue';
import { zipImg } from '@/utils/file';
import request from '@/utils/request';
import { MessagePlugin } from 'tdesign-vue-next';
import { useI18n } from 'vue-i18n';
const props = defineProps<{
modelValue: string;
}>();
const emit = defineEmits(['update:modelValue']);
const { t } = useI18n();
const file1 = ref([]);
let custom_progress: any = null;
// 图片回调地址
const image_url = ref('');
// 上传中的遮罩层是否显示
const custom_masking1 = ref(false);
// 上传进度
const cur_progress1 = ref(0);
const handleFail = ({ file }) => {
MessagePlugin.error(t('message.Erroralfile'));
};
watch(
() => props.modelValue,
(v) => {
if (!v) {
file1.value = [];
}
image_url.value = v;
},
);
watch(
() => image_url.value,
(v) => {
emit('update:modelValue', v);
},
);
// 可共用的模块
const publicMethod = async (file: any) => {
custom_progress = window.setInterval(() => {
if (cur_progress1.value >= 99) {
return;
}
cur_progress1.value += 1;
}, 50);
let newFile: any = await zipImg(file.raw);
let data = new FormData();
data.append('file', newFile);
return data;
};
const requestSuccessMethod = async (file: any) => {
custom_masking1.value = true;
cur_progress1.value = file.percent;
let data = await publicMethod(file);
return new Promise((resolve) => {
file.percent = 0;
const timer = setTimeout(() => {
request
.post('/api/users/common/upload', data, {
headers: {
authorization: `Bearer ${getUserCookie()}`,
lang: getLanguage(),
},
})
.then((res: any) => {
// 清除
window.clearInterval(custom_progress);
// 关闭遮罩层
custom_masking1.value = false;
cur_progress1.value = 100;
// resolve 参数为关键代码
if (res.code == 0) {
resolve({
status: 'success',
response: { url: res.data.path },
});
}
});
clearTimeout(timer);
}, 1000);
});
};
// 第一个上传链接
const formatResponseOne = (response: any, context: any) => {
emit('update:modelValue', response.url);
return { name: 'FileName', url: response.url };
};
</script>
<style lang="less">
.custom-upload-sell {
position: relative;
width: 112px;
.t-upload__card {
width: 112px;
.t-size-s {
display: none;
}
.t-icon-add {
margin-bottom: 0;
}
}
}
</style>
@import '@/style/variables.less';
.custom-real-upload {
background: #181818;
// border: @main-border;
border-radius: 8px;
width: 100%;
height: 330px;
padding: 20px 46px;
position: relative;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.real-upload-close-icon {
position: absolute;
right: 12px;
top: 12px;
cursor: pointer;
}
.real-upload-content {
display: flex;
justify-content: space-between;
.custom-real-upload-component {
width: 360px;
height: 200px;
.t-upload {
width: 100%;
height: 100%;
.custom-upload-click-box {
border-radius: 8px;
width: 360px;
height: 200px;
display: flex;
justify-content: space-evenly;
align-items: center;
flex-direction: column;
.title {
font-weight: 600;
font-size: @size-20;
color: #b3bfde;
}
.title2 {
font-size: @size-16;
color: #888fa1;
}
.custom-chose-file {
background: #00dddd !important;
border-radius: 8px;
border: none;
width: 200px;
height: 46px;
--ripple-color: none !important;
font-weight: 600;
font-size: @size-18;
color: #000000;
}
}
.t-upload__dragger {
padding: 0;
border: none;
width: 360px;
height: 200px;
}
}
.custom-uploading-stauts {
display: flex;
justify-content: space-evenly;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
.uploading-title {
font-weight: 400;
font-size: @size-15;
color: #8b8b8b;
}
.custom-t-progress {
.t-progress__info {
color: #00f9f9;
}
}
}
.custom-UploadSuccess-stauts {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
.UploadSuccess-img {
width: 100%;
height: 100%;
}
.title1 {
font-weight: 600;
font-size: @size-18;
color: #000000;
}
}
}
}
}
import { defineComponent, reactive, ref, watch } from 'vue';
import './index.less';
import UploadTip from '@/assets/svg/upload/uploadTip2.svg';
import {
MessagePlugin,
Button as TButton,
Upload as TUpload,
Progress as TProgress,
UploadFile,
RequestMethodResponse,
} from 'tdesign-vue-next';
import { getUserCookie } from '@/utils/api/userApi';
import request from '@/utils/otherRequest';
import { show_message } from '@/utils/tool';
import { v4 } from 'uuid';
export default defineComponent({
props: {
modelValue: String,
config: Object as any,
rules: Array,
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const files = ref([]);
// 文件地址
const Curfile = reactive({
url: '',
status: 0,
// 当前上传模块提交的状态
uploadStatus: false,
});
const actionUrl = ref('');
// 上传进度条
const percentage = ref(0);
// 定时器
let percentageInterval: any = null;
// 上传进度定时器
const openpercentage = () => {
// 开启一个定时器,模拟上传进度
percentage.value = 0;
percentageInterval = setInterval(() => {
if (percentage.value == 99) {
return;
}
percentage.value += 1;
}, 100);
};
// 获取文件尺寸
const getFileSize = async (file: any) => {
return new Promise((resolve, reject) => {
// 获取文件尺寸
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = async (e: any) => {
let data = e.target.result;
let image = new Image();
image.src = data;
image.onload = () => {
resolve(`${image.width},${image.height}`);
};
image.onerror = () => {
reject('');
};
};
});
};
const beforeUpload = async (file: any) => {
try {
const { config, rules } = props;
const config_len = Object.keys(config).length;
if (!config_len) {
show_message('无法上传,请尝试刷新页面');
return false;
} else if (rules && rules.length) {
// 判断是否存在 image_size 规则
const rule_data: any = rules.find((item: any) => item.type == 'image_size');
if (rule_data && rule_data !== -1) {
// 格式 1024,1024
const image_size = rule_data.value ?? '';
if (!image_size) {
show_message('缺少图片尺寸校验');
return false;
}
const size = await getFileSize(file.raw);
if (size !== image_size) {
show_message(rule_data.message);
return false;
}
}
}
return true;
} catch (e) {
console.log(e);
return false;
}
};
const handleFail = ({ file }: any) => {
MessagePlugin.error(`文件 ${file.name} 上传失败`);
};
// 上传成功回调
const UploadSuccessCallback = (uuid: any, url: any) => {
// 关闭定时器
window.clearInterval(percentageInterval);
MessagePlugin.success('上传成功');
// 将将完整url传给父组件
Curfile.url = url;
// 成功2
Curfile.status = 2;
emit('update:modelValue', Curfile.url);
};
// 上传失败回调
const UploadErrorCallback = () => {
// 关闭定时器
window.clearInterval(percentageInterval);
Curfile.url = '';
// 失败0
Curfile.status = 0;
emit('update:modelValue', Curfile.url);
MessagePlugin.warning('上传失败');
};
// 外网上传-func
const ExtranetUpload = (file: any) => {
openpercentage();
return new Promise<RequestMethodResponse>((resolve) => {
const uuid = v4();
// 上传中状态
Curfile.status = 1;
let url = '';
const { config } = props;
url = 'https://' + config.host;
setTimeout(() => {
const formData = new FormData();
formData.append('key', config.dir + uuid + '.png');
formData.append('policy', config.policy);
formData.append('OSSAccessKeyId', config.accessid);
formData.append('success_action_status', '200');
formData.append('callback', config.callback);
formData.append('signature', config.signature);
// formData.append('name', uuid + '.png');
formData.append('file', file[0].raw);
request
.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data;charset=utf-8',
// Accept: '*/*',
},
})
.then((res: any) => {
// resolve 参数为关键代码
if (res == 200) {
// 外网url
const url = config.domain + config.dir + uuid + '.png';
UploadSuccessCallback(uuid, url);
//
Curfile.uploadStatus = true;
resolve({
status: 'success',
response: { url: Curfile.url },
});
} else {
UploadErrorCallback();
Curfile.uploadStatus = false;
}
})
.catch((e) => {
console.log(e);
});
}, 1000);
});
};
const requestSuccessMethod = async (file: UploadFile | UploadFile[]) => {
return ExtranetUpload(file);
};
// 未上传
const notUploadHtml = () => {
return (
<TUpload
v-model={files.value}
method="PUT"
requestMethod={requestSuccessMethod}
action={actionUrl.value}
headers={{
authorization: `Bearer ${getUserCookie()}`,
}}
accept={'video'}
theme="custom"
before-upload={beforeUpload}
multiple
max={1}
draggable={true}
onFail={handleFail}
>
<div class="custom-upload-click-box">
<div class="title">选择图片</div>
<div class="title2">或拖拽图片到此处</div>
<div>
<UploadTip></UploadTip>
</div>
<TButton class="custom-chose-file">选择文件</TButton>
</div>
</TUpload>
);
};
// 上传中
const UploadingHtml = () => {
return (
<div class="custom-uploading-stauts">
<TProgress
class="custom-t-progress"
theme="circle"
percentage={percentage.value}
size={'small'}
color={'#00f9f9'}
/>
<div class="uploading-title">正在上传</div>
</div>
);
};
const UploadSuccess = () => {
return (
<div class="custom-UploadSuccess-stauts">
<img class="UploadSuccess-img" src={props.modelValue} alt="" />
</div>
);
};
// 获取当前上传状态
const currentUploadStatus = () => {
if (Curfile.status == 0) {
return notUploadHtml();
} else if (Curfile.status == 1) {
// 上传中
return UploadingHtml();
} else {
// 上传完成
return UploadSuccess();
}
};
watch(
() => props.modelValue,
(v) => {
if (!v) {
Curfile.status = 0;
}
},
);
return () => (
<div class="custom-real-upload">
<div class="real-upload-content">
<div class="custom-real-upload-component">{currentUploadStatus()}</div>
</div>
</div>
);
},
});
import routerConfig from '@/router/tool';
import routerConfig from '../router/tool';
// 网站可选项
export const webNameList = {
SILKR: 'SILKR',
......@@ -49,6 +49,12 @@ export const getRoutes = () => {
component: () => import('@/pages/liveManagement/index.vue'),
meta: { title: 'snowhome' },
},
{
path: routerConfig.createLive.path,
name: routerConfig.createLive.name,
component: () => import('@/pages/createLive/index.vue'),
meta: { title: 'snowhome', liveName: true },
},
];
}
};
......@@ -73,3 +79,6 @@ export const SiteConfig = {
],
},
};
// 邮箱正则
export const emailReg = /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,8}){1,2}$/;
import { ref } from 'vue';
export const useDomMinHeight = () => {
const domheight = ref('');
const getDomHeight = (dom: any) => {
let client = dom.getBoundingClientRect();
let documentheight = document.documentElement.clientHeight;
if (documentheight <= client.bottom) {
domheight.value = 600 + 'px';
} else {
domheight.value = client.bottom + 'px';
}
};
return {
domheight,
getDomHeight,
};
};
import { useStore } from 'vuex';
export const useLiveInfoSubmit = () => {
const store = useStore();
const commitInfo = (info: any) => {
store.commit('live/setLiveInfo', info);
};
return [commitInfo];
};
// 监听是否在当前窗口
export default function () {
// 监听是否切除页面
const listenerWindow = (fn: any) => {
document.addEventListener('visibilitychange', fn);
};
// 取消监听
const closeListener = (fn: any) => {
document.removeEventListener('visibilitychange', fn);
};
return {
listenerWindow,
closeListener,
};
}
<template>
<div class="s-header">
<div class="logo">猫眼数字人</div>
<div class="s-header-live-name" v-if="route.meta.liveName">
<ChangeName :value="liveName" :event="liveNameEvent"></ChangeName>
</div>
<div class="right">
<div class="user-info">
<UserSvg></UserSvg>
......@@ -11,8 +14,39 @@
<script setup lang="ts">
import UserSvg from '@/assets/svg/header/user.svg';
import { useRoute, useRouter } from 'vue-router';
import ChangeName from '@/components/changeName.vue';
import { useStore } from 'vuex';
import { computed, onMounted } from 'vue';
import routerConfig from '@/router/tool';
const route = useRoute();
const router = useRouter();
const store = useStore();
const routeQuery = route.query;
// 获取标题
const liveName = computed(() => store.getters['live/getName']);
onMounted(() => {
console.log(routeQuery.title);
store.commit('live/setName', routeQuery.title);
});
const liveNameEvent = async (value: string) => {
router.replace({
path: routerConfig.createLive.path,
name: routerConfig.createLive.name,
query: {
id: routeQuery.id,
title: value,
},
});
// 更新store
store.commit('live/setName', value);
return true;
};
</script>
<style lang="less" scoped>
<style lang="less">
@import '@/style/variables';
.s-header {
width: 100%;
height: 50px;
......@@ -28,6 +62,16 @@ import UserSvg from '@/assets/svg/header/user.svg';
font-weight: 700;
font-size: 24px;
}
.s-header-live-name {
.custom-change-name-box {
color: #b4b4b4;
font-size: @size-12;
}
.empty-tips {
color: #b4b4b4;
font-size: @size-12;
}
}
.right {
.user-info {
cursor: pointer;
......
<template>
<div class="chose-digital-person-box">
<div class="all-select">
<Select :options="options" v-model="currentOption" @change="onSelectChange"></Select>
</div>
<div
class="create-digital-person-list-box narrow-scrollbar"
ref="personList"
:style="{
maxHeight: maxHeight + 'px',
}"
>
<template v-for="item in digitalList.list" :key="item.id">
<CardOne
:row="item"
:id="item.id"
:img="item.img"
:name="item.name"
:className="item.id === currentCard ? 'card-active' : ''"
@change="onCardChange"
></CardOne>
</template>
</div>
</div>
</template>
<script lang="ts" setup>
import Select from '@/components/Select.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';
const [commitInfo] = useLiveInfoSubmit();
const props = withDefaults(
defineProps<{
toolHeight: number;
}>(),
{},
);
const store = useStore();
// el元素
const personList = ref();
const maxHeight = ref();
// 当前选中项
const currentOption = ref('1');
// 当前选择的数字人
const currentCard = ref('');
const options = [
{
label: '数字人库',
value: '1',
},
{
label: '我的数字人',
value: '2',
},
];
const onSelectChange = (value: string) => {
commitInfo({
[createLiveKeys.id_type]: value,
});
};
const onCardChange = (id: string | number, row: any) => {
currentCard.value = id;
commitInfo({
[createLiveKeys.id]: id,
});
// 将对应的图片传给右侧工具
store.commit('live/setLiveImage', row.img);
};
const createTest = () => {
let list = [];
for (let i = 0; i < 20; i++) {
list.push({
id: i + 1,
img: 'https://img1.baidu.com/it/u=1546227440,2897989905&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500',
name: '夏波',
});
}
return list;
};
// 数字人列表
const digitalList = reactive({
// 当前展示的列表
list: createTest(),
// 后台列表
admin: [],
// 我的数字人
my: [],
});
const getList = async () => {
try {
// let res:any = await ddd();
} catch (e) {
console.log(e);
}
};
onMounted(() => {
// 获取数字人列表
getList();
// 提交默认参数
commitInfo({
[createLiveKeys.id_type]: currentOption.value,
});
});
watch(
() => props.toolHeight,
(v) => {
if (v) {
let obj = getElBounding(personList.value);
let client = getWindowClient();
maxHeight.value = client.height - obj.top - v;
}
},
);
</script>
<style lang="less">
.chose-digital-person-box {
width: 550px;
flex: 1;
display: flex;
flex-direction: column;
padding: 0 4px;
overflow: hidden;
.all-select {
padding: 12px 0;
}
.create-digital-person-list-box {
flex-grow: 1;
border-radius: 3px;
border: 1px solid #464646;
background: #1e1e1e;
padding: 12px;
overflow-y: auto;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
row-gap: 12px;
.custom-card-box {
width: 150px;
height: 200px;
img {
height: 150px;
}
}
.card-active {
border: 2px solid #00dfb0;
}
}
}
</style>
@import '@/style/variables.less';
.create-live-step {
width: 80px;
height: 80px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-weight: 700;
font-size: @size-12;
color: #b4b4b4;
.icon {
svg {
fill: #b4b4b4;
}
}
}
.create-live-step_active {
border-radius: 6px;
background: #000;
transition: all 0.2s;
color: #00dfb0;
.icon {
svg {
fill: #00dfb0;
}
}
}
import './index.less';
import { defineComponent, ref, watch } from 'vue';
export default defineComponent({
props: {
modelValue: [String, Number],
list: Object as any,
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const current = ref(props.modelValue);
watch(
() => props.modelValue,
(v) => {
current.value = v;
},
);
return () => (
<>
{props.list.map((item: any) => (
<div class={['create-live-step', current.value === item.value ? 'create-live-step_active' : '']}>
<div class="icon">{item.icon}</div>
<div>{item.label}</div>
</div>
))}
</>
);
},
});
<template>
<div class="create-live-script-setting">
<div class="all-select">
<Select :options="scriptTypeList" v-model="currentOption" @change="scriptTypeChange"></Select>
<div class="right-chose-tones" v-show="currentOption === scriptTypeText">
<div @click="tonesVisible = true" class="default-label">
<SelectionPopup
v-model="tonesVisible"
title="选择一种音调"
:event="getTonesList"
v-model:value="tonesValue"
@itemChange="tonesItemChange"
>
<div>
<template v-if="!tonesValue"> 音调</template>
<template v-else>
<div class="chose-tones-item">
<img :src="tonesInfo.img" alt="" />
<div>
<div class="name">{{ tonesInfo.c_name }}</div>
<div class="categorie">{{ tonesInfo.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 class="categorie">{{ soundColorInfo.c_categorie }}</div>
</div>
</div>
</template>
</div></SelectionPopup
>
</div>
</div>
<!-- -->
<div class="phonetics-chose" v-show="currentOption === scriptTypePhonetics">
<div class="default-label">
<SelectionPopup
title="选择一种音色"
:needCategorie="false"
v-model="phoneticsVisible"
v-model:value="phoneticsValue"
:event="getSoundColorList"
@itemChange="phoneticsItemChange"
>
<div>
<template v-if="!phoneticsValue">我的音色</template>
<template v-else>
<div class="phonetics-chose-item">
<img :src="phoneticsInfo.img" alt="" />
<div>
<div class="name">{{ phoneticsInfo.c_name }}</div>
</div>
</div>
</template>
</div></SelectionPopup
>
</div>
</div>
</div>
<div class="script-setting-text flex1" v-show="currentOption === scriptTypeText">
<Textarea v-model="textareaValue" @change="textareaChange"></Textarea>
</div>
<div class="script-setting-upload flex1" v-show="currentOption === scriptTypePhonetics">
<CustomUpload></CustomUpload>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
import CustomUpload from '@/components/upload';
import Select from '@/components/Select.vue';
import SelectionPopup from '@/components/SelectionPopup.vue';
import { createTestData, show_message } from '@/utils/tool';
import { createLiveKeys, scriptTypeList, scriptTypeText, scriptTypePhonetics } from '@/service/CreateLive';
import { useLiveInfoSubmit } from '@/hooks/useStoreCommit';
import Textarea from '@/components/textarea.vue';
import { getUploadConfig } from '@/service/Common';
const [commitInfo] = useLiveInfoSubmit();
// 音调弹窗
const tonesVisible = ref(false);
// 选择的音调id
const tonesValue = ref('');
// 选择的音调对象
const tonesInfo = ref({});
// 文本 我的音色弹窗
const soundColorVisible = ref(false);
const soundColorValue = ref('');
const soundColorInfo = ref({});
const disabled = ref(true);
// 阿里云上传配置
const ossConfig = ref({});
// 音频 我的音色
const phoneticsVisible = ref(false);
const phoneticsValue = ref('');
const phoneticsInfo = ref({});
// 文本内容
const textareaValue = ref('');
const currentOption = ref(scriptTypeText);
// 获取音调列表
const getTonesList = async () => {
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 = async () => {
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: '云依',
});
} catch (e) {
console.log(e);
return [];
}
};
const openSoundColor = () => {
if (tonesValue.value) {
// 允许打开
soundColorVisible.value = true;
} else {
show_message('请先选择音调');
}
};
// 音调提交
const tonesItemChange = (item: any) => {
tonesInfo.value = item;
commitInfo({
[createLiveKeys.textTones]: item.id,
});
};
// 文本 音色提交
const soundColorItemChange = (item: any) => {
soundColorInfo.value = item;
commitInfo({
[createLiveKeys.textSoundColor]: item.id,
});
};
// 音频 音色提交
const phoneticsItemChange = (item: any) => {
phoneticsInfo.value = item;
commitInfo({
[createLiveKeys.phoneticsSoundColor]: item.id,
});
};
// 文本提交
const textareaChange = () => {
commitInfo({
[createLiveKeys.textScriptValue]: textareaValue.value,
});
};
// 脚本类型提交
const scriptTypeChange = (id: string | number) => {
commitInfo({
[createLiveKeys.scriptType]: id,
});
};
watch(
() => tonesValue.value,
(v) => {
if (v) {
disabled.value = false;
} else {
disabled.value = true;
}
},
);
onMounted(async () => {
// 获取上传配置
ossConfig.value = await getUploadConfig();
});
</script>
<style lang="less">
@import '@/style/variables';
.create-live-script-setting {
width: 550px;
flex: 1;
display: flex;
flex-direction: column;
padding: 0 4px;
overflow: hidden;
.all-select {
height: 74px;
padding: 12px 0;
display: flex;
justify-content: space-between;
align-items: center;
.right-chose-tones {
width: 245px;
height: 50px;
border-radius: 6px;
border: 1px solid #0dd;
background: #1a1b1b;
display: flex;
justify-content: space-around;
align-items: center;
.default-label {
color: #e2e2e2;
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: 45px;
border-radius: 6px;
border: 1px solid #363636;
background: #1a1b1b;
transition: all 0.2s;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
padding: 0 6px;
& > :last-child {
margin-left: 4px;
}
img {
width: 35px;
height: 35px;
border-radius: 50%;
}
.name {
font-size: @size-14;
font-weight: 600;
color: #e2e2e2;
line-height: 14px;
}
.categorie {
font-size: @size-12;
color: #d0d0d0;
}
}
}
.phonetics-chose {
border-radius: 6px;
border: 1px solid #464646;
background: #1a1b1b;
height: 45px;
color: #e2e2e2;
font-size: @size-14;
display: flex;
align-items: center;
justify-content: center;
padding: 0 20px;
font-weight: 600;
cursor: pointer;
.phonetics-chose-item {
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
padding: 0 6px;
& > :last-child {
margin-left: 4px;
}
img {
width: 35px;
height: 35px;
border-radius: 50%;
}
.name {
font-size: @size-14;
font-weight: 600;
color: #e2e2e2;
line-height: 14px;
}
.categorie {
font-size: @size-12;
color: #d0d0d0;
}
}
}
}
.flex1 {
flex: 1;
}
.script-setting-text {
.custom-textarea-box {
height: 100%;
.custom-t-textarea {
height: 97%;
.t-textarea__inner {
height: 100% !important;
}
}
}
}
.script-setting-upload {
.custom-real-upload {
height: 100%;
}
}
}
</style>
<template>
<div class="custom-create-live-page">
<div class="setps-list">
<StepList v-model="currentSetp" :list="setpsList"></StepList>
</div>
<div class="setps-right-box">
<div class="setps-body">
<template v-if="setpsList[0].load">
<div class="steps-item" v-show="currentSetp == '1'">
<ChoseDigitalPerson :toolHeight="toolHeight"></ChoseDigitalPerson>
</div>
</template>
<template v-if="setpsList[1].load">
<div class="steps-item" v-show="currentSetp == '2'">
<ScriptVue></ScriptVue>
</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>
</div>
</div>
<div class="create-live-right-drag">
<div class="label">画面预览:点中主播,即可缩放、移动位置</div>
<div class="drag-box">
<template v-if="liveImage">
<Drag :img="liveImage" id="1"></Drag>
</template>
</div>
</div>
</div>
</div>
</template>
<script lang="tsx" setup>
import StepList from './components/SetpsList';
import ScriptVue from './components/scripts.vue';
import Drag from '@/components/drag.vue';
import Button from '@/components/Button.vue';
import ChoseDigitalPerson from './components/ChoseDigitalPerson.vue';
import HomeSvg from '@/assets/svg/createLive/home.svg';
import InteractSvg from '@/assets/svg/createLive/interact.svg';
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';
const store = useStore();
const liveImage = computed(() => store.getters['live/getLiveimage']);
// 创建直播的已有的字段
const createLiveInfo = computed(() => store.getters['live/getLiveInfo']);
const publicTool = ref<HTMLElement>();
const toolHeight = ref(0);
const currentSetp = ref(1);
const setpsList = [
{
label: '人物选择',
value: 1,
icon: <HomeSvg></HomeSvg>,
load: true,
roles: [
{
key: createLiveKeys.id_type,
message: '数字人库必选',
},
{
key: createLiveKeys.id,
message: '数字人必选',
},
],
},
{
label: '脚本设置',
value: 2,
load: false,
icon: <InteractSvg></InteractSvg>,
field: [],
},
{
label: '互动设置',
value: 3,
load: false,
icon: <InteractSvg></InteractSvg>,
field: [],
},
];
onMounted(() => {
let obj = getElBounding(publicTool.value);
toolHeight.value = obj.height;
});
//
const currentModuleField = () => {
let index = setpsList.findIndex((item: any) => item.value === currentSetp.value);
if (index !== -1) {
let roles: string[] = setpsList[index].roles;
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 (!status) {
show_message(message);
return;
}
return true;
} else {
return;
}
};
const onBack = () => {
if (currentSetp.value == 1) {
return;
}
let status = currentModuleField();
if (status) {
currentSetp.value -= 1;
}
};
const onNext = () => {
if (currentSetp.value == 3) {
return;
}
let status = currentModuleField();
if (status) {
setpsList[currentSetp.value].load = true;
currentSetp.value += 1;
}
};
</script>
<style lang="less">
@import '@/style/variables';
.custom-create-live-page {
background: rgb(25, 25, 25);
padding-top: 4px;
display: flex;
& > * {
background: rgb(48, 48, 48);
height: 100%;
}
& > :not(:first-child) {
margin-left: 4px;
}
.setps-list {
width: 100px;
padding: 10px;
}
.setps-right-box {
flex: 1;
display: flex;
background: rgb(25, 25, 25);
& > * {
width: 50%;
background: rgb(48, 48, 48);
}
& > :not(:first-child) {
margin-left: 4px;
}
.setps-body {
display: flex;
flex-direction: column;
.steps-item {
display: flex;
flex-direction: column;
flex: 1;
}
.public-tool {
padding: 20px;
text-align: right;
.tool-button {
font-size: @size-14;
font-weight: 700;
}
}
}
.create-live-right-drag {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 30px 0 40px 0;
.label {
border-radius: 5px;
background: rgba(0, 0, 0, 0.4);
font-size: @size-16;
color: white;
width: 400px;
display: flex;
justify-content: center;
align-items: center;
padding: 6px 0;
}
.drag-box {
width: 400px;
height: 400px;
background: rgb(61, 222, 99);
border-radius: 2px;
margin-top: 20px;
flex: 1;
.drag {
width: 100%;
height: 100%;
}
}
}
}
}
</style>
......@@ -15,7 +15,7 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import Card from '@/components/card.vue';
import Card from '@/components/cardOne.vue';
const current_card = ref('');
......
<template>
<div class="digital-people-draft">
<div class="card-item-list">
<template v-for="item in myDigtalList.list" :key="item.id">
<CardTwo :id="item.id" :img="item.img" :created_at="item.created_at" :value="item.value">
<template #hover>
<div class="my-digtal-people-hover">
<template v-if="true">
<Button class="digtal-people-start-end" theme="danger" height="40px">编辑</Button>
</template>
<template v-else>
<Button class="digtal-people-start-end" theme="danger" height="40px">关闭直播</Button>
</template>
<div class="digtal-people-hover-tool">
<Button size="13" theme="dark">删除</Button>
</div>
</div>
</template>
</CardTwo>
</template>
</div>
<CustomLoading v-show="myDigtalList.loading"></CustomLoading>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import CardTwo from '@/components/cardTwo.vue';
import CustomLoading from '@/components/loading.vue';
import Button from '@/components/Button.vue';
const createList = () => {
let list = [];
for (let i = 0; i < 20; i++) {
list.push({
id: 1,
img: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTwRTUox_S_qSFas_3lp-8Z00Las63sh2W6ORvmWgHw&s',
value: '123456',
created_at: '2023-7-1 16:05',
});
}
return list;
};
const myDigtalList = reactive({
list: createList(),
loading: false,
});
</script>
<style lang="less">
.digital-people-draft {
padding: 20px 30px;
background: white;
position: relative;
min-height: 300px;
.card-item-list {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
row-gap: 30px;
margin-left: -20px;
& > * {
margin-left: 20px;
}
.my-digtal-people-hover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 0 12px;
display: flex;
flex-direction: column;
.digtal-people-start-end {
margin-top: 60px;
}
.digtal-people-hover-tool {
flex: 1;
display: flex;
align-items: flex-end;
margin-bottom: 20px;
}
}
}
}
</style>
<template>
<div class="my-digtal-people">
<div class="card-item-list">
<template v-for="item in myDigtalList.list" :key="item.id">
<CardTwo :id="item.id" :img="item.img" :created_at="item.created_at" :value="item.value">
<template #hover>
<div class="my-digtal-people-hover">
<template v-if="true">
<Button class="digtal-people-start-end" theme="danger" height="40px">开启直播</Button>
</template>
<template v-else>
<Button class="digtal-people-start-end" theme="danger" height="40px">关闭直播</Button>
</template>
<div class="digtal-people-hover-tool">
<Button size="13" theme="dark">编辑</Button>
<Button size="13" theme="dark">下载</Button>
<Button size="13" theme="dark">删除</Button>
</div>
</div>
</template>
</CardTwo>
</template>
</div>
<CustomLoading v-show="myDigtalList.loading"></CustomLoading>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';
import CardTwo from '@/components/cardTwo.vue';
import CustomLoading from '@/components/loading.vue';
import Button from '@/components/Button.vue';
const createList = () => {
let list = [];
for (let i = 0; i < 20; i++) {
list.push({
id: 1,
img: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTwRTUox_S_qSFas_3lp-8Z00Las63sh2W6ORvmWgHw&s',
value: '123456',
created_at: '2023-7-1 16:05',
});
}
return list;
};
const myDigtalList = reactive({
list: createList(),
loading: false,
});
</script>
<style lang="less">
.my-digtal-people {
padding: 20px 30px;
background: white;
position: relative;
min-height: 300px;
.card-item-list {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
row-gap: 30px;
margin-left: -20px;
& > * {
margin-left: 20px;
}
.my-digtal-people-hover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 0 12px;
display: flex;
flex-direction: column;
.digtal-people-start-end {
margin-top: 60px;
}
.digtal-people-hover-tool {
flex: 1;
display: flex;
align-items: flex-end;
margin-bottom: 20px;
}
}
}
}
</style>
......@@ -24,18 +24,56 @@
<div class="label">选择数字人</div>
</div>
<div class="digital-people-list">
<template v-for="item in digitalPeopleList.list" :key="item.id">
<CardOne :id="item.id" :img="item.img" :name="item.name"></CardOne>
<template v-for="(item, index) in digitalPeopleList.list" :key="item.id">
<CardOne :id="item.id" :img="item.img" :name="item.name" @change="cardClick"></CardOne>
<template v-if="index === digitalPeopleList.list.length - 1">
<div class="more-choices">更多选择 ></div>
</template>
</template>
</div>
</div>
</div>
<div class="home-tool-bar">
<CustomTabs v-model="currentTab">
<CustomTabPanel label="数字人作品" name="1"> <MyDigtalPeople></MyDigtalPeople> </CustomTabPanel>
<CustomTabPanel label="数字人草稿" name="2"> <DigitalPeopleDraft></DigitalPeopleDraft> </CustomTabPanel>
</CustomTabs>
</div>
<CustomDialog v-model="dialogVisible" @confirm="dialogConfirm">
<template #header> 直播创建 </template>
<template #body>
<div class="create-live-dialog-body">
<span class="label"> 直播名称: </span>
<CustomInput v-model="liveName" placeholder="请输入直播标题" align="left"></CustomInput>
</div>
</template>
</CustomDialog>
</div>
</template>
<script lang="tsx" setup>
import { reactive } from 'vue';
import { reactive, ref } from 'vue';
import CardOne from '@/components/cardOne.vue';
import CustomTabs from '@/components/CustomTabs';
import CustomTabPanel from '@/components/CustomTabPanel';
import MyDigtalPeople from './components/myDigtalPeople.vue';
import DigitalPeopleDraft from './components/digitalPeopleDraft.vue';
import CustomDialog from '@/components/Dialog.vue';
import CustomInput from '@/components/input/index.vue';
import routerConfig from '@/router/tool';
import { show_message } from '@/utils/tool';
import { useRouter } from 'vue-router';
const router = useRouter();
// 当前tab栏
const currentTab = ref('1');
// 弹窗状态
const dialogVisible = ref(false);
// 当前选择的数字人id
const currentCard = ref<number | string>();
// 直播名称
const liveName = ref('');
const imgs = {
profile: new URL('../../assets/svg/home/profile.svg', import.meta.url).href,
......@@ -57,16 +95,46 @@ const toolList = [
},
];
// 数字人列表
const digitalPeopleList = reactive({
list: [
{
const createList = () => {
let list = [];
for (let i = 0; i < 20; i++) {
list.push({
id: 1,
img: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTwRTUox_S_qSFas_3lp-8Z00Las63sh2W6ORvmWgHw&s',
name: '夏波丽丽',
},
],
});
}
return choseList(list);
};
// 取五条数据
const choseList = (list: any[]) => {
return list.splice(0, 5);
};
// 数字人列表
const digitalPeopleList = reactive({
list: createList(),
});
// 数字人点击事件
const cardClick = (id: string | number) => {
currentCard.value = id;
dialogVisible.value = true;
};
// 弹窗确认
const dialogConfirm = () => {
dialogVisible.value = false;
router.push({
path: routerConfig.createLive.path,
name: routerConfig.createLive.name,
query: {
id: currentCard.value,
title: liveName.value,
},
});
};
</script>
<style lang="less">
......@@ -113,7 +181,7 @@ const digitalPeopleList = reactive({
width: 100%;
height: 348px;
background: white;
padding: 33px 100px 0px 99px;
padding: 33px 30px 0px 30px;
border-radius: 6px;
.tool-line {
display: flex;
......@@ -126,8 +194,43 @@ const digitalPeopleList = reactive({
}
.digital-people-list {
margin-top: 20px;
display: flex;
margin-left: -24px;
& > * {
margin-left: 24px;
}
.more-choices {
width: 170px;
height: 224px;
background: #fff;
border-radius: 8px;
transition: all 0.2s;
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
text-align: center;
font-size: @size-16;
display: flex;
justify-content: center;
align-items: center;
color: #808080;
cursor: pointer;
}
}
}
}
}
//
.create-live-dialog-body {
display: flex;
align-items: center;
margin: 50px 0;
.label {
font-size: @size-15;
color: #fff;
margin-right: 12px;
}
.custom-input-global {
flex: 1;
}
}
</style>
......@@ -8,4 +8,8 @@ export default {
path: '/live',
name: 'Live',
},
createLive: {
path: '/createLive',
name: 'createLive',
},
};
export const getUploadConfig = async () => {
return {};
};
// 创建直播时提交的变量名
export const createLiveKeys = {
id_type: 'id_type', // 数字人库类型(数字人库和我的数字人)
id: 'id', // 数字人id
// 脚本设置
scriptType: 'scriptType', // 脚本类型
textTones: 'textTones', // 文本--音调
textSoundColor: 'textSoundColor', // 文本音色
textScriptValue: 'textScriptValue', // 文字脚本的文本
phoneticsSoundColor: 'phoneticsSoundColor', // 音频 音色
};
// 脚本类型
export const scriptTypeText = '1'; // 文本
export const scriptTypePhonetics = '2'; // 音频
export const scriptTypeList = [
{
label: '文本脚本',
value: scriptTypeText,
},
{
label: '音频脚本',
value: scriptTypePhonetics,
},
];
......@@ -3,6 +3,7 @@ import user from './modules/user';
import setting from './modules/setting';
import theme from './modules/theme';
import language from './modules/language';
import live from './modules/live';
export const store = createStore({
modules: {
......@@ -10,6 +11,7 @@ export const store = createStore({
setting,
theme,
language,
live,
},
});
......
import { createLiveKeys } from '@/service/CreateLive';
const state = {
name: '',
createLive: {
[createLiveKeys.id]: '',
[createLiveKeys.id_type]: '',
},
liveImage: '',
};
type StateType = typeof state;
const mutations = {
setName(state: StateType, info: string) {
state.name = info;
},
// 创建直播时的参数
setLiveInfo(state: StateType, info: any) {
Object.keys(info).forEach((item: any) => {
state.createLive[item] = info[item];
});
},
setLiveImage(state: StateType, info: string) {
state.liveImage = info;
},
};
const getters = {
getName: (state: StateType) => {
return state.name;
},
getLiveInfo: (state: StateType) => {
return state.createLive;
},
// 当前选择的机器人
getLiveimage: (state: StateType) => {
return state.liveImage;
},
};
const actions = {};
export default {
namespaced: true,
state,
mutations,
actions,
getters,
};
......@@ -51,7 +51,7 @@
.s-layout-content {
height: calc(100vh - 50px);
overflow-y: auto;
overflow-x: auto;
overflow-x: hidden;
background: #f5f5f5;
& > :first-child {
width: @pageWidth;
......
......@@ -60,3 +60,13 @@ input::-webkit-inner-spin-button {
html {
overflow: hidden;
}
.narrow-scrollbar::-webkit-scrollbar {
width: 5px;
height: 5px;
}
.narrow-scrollbar::-webkit-scrollbar-thumb {
background: #b4b4b4;
border-radius: 8px;
}
@main-color: #191919;
/* 各个页面的padding */
@homepadding:0 100px;
// 部分页面需要固定宽度
@pageWidth: 1300px;
@pageWidth: 1200px;
// 各大小对应的rem --基于font-size:14px;
@size-12: 0.85rem;
@size-13: 0.92rem;
@size-14: 1rem;
@size-15: 1.07rem;
@size-16: 1.14rem;
@size-18: 1.28rem;
@size-20: 1.4rem;
......
......@@ -8,6 +8,7 @@ export const getUserCookie = () => {
export const getLanguage = () => {
return store.getters['language/getLang'];
};
// 获取邮箱注册验证码
export const getVerificationCode = (data: any) => {
return request.post('/api/users/register-code', {
......
import axios from 'axios';
import { MessagePlugin } from 'tdesign-vue-next';
const instance = axios.create({
timeout: 6000000,
withCredentials: false,
});
// 请求头
instance.interceptors.request.use((config: any) => {
return config;
});
instance.interceptors.response.use(
(response) => {
const { data, status } = response;
if (status == 201 || status == 200) {
return status;
}
if (data.code === 0) {
return data;
} else {
MessagePlugin.error(data.msg || '请求错误');
return Promise.reject(data.msg);
}
},
(err) => {
console.log(err);
if ('response' in err) {
const { message: msg } = err.response.data;
if (err.response.data.indexOf('<Code>UserDisable</Code>') !== -1) {
MessagePlugin.error('阿里云可能欠费');
} else {
MessagePlugin.error(msg || '请求错误');
}
return err.response;
}
}
);
export default instance;
......@@ -243,129 +243,6 @@ export const scientificToNumber = function (num: number) {
return num.toFixed(Math.max(0, (m[1] || '').length - m[2]));
};
// 获取k线图价格精度
export const PriceAccuracy = (number: number) => {
let num = number + '';
// 有的数据是科学计数法展示的
let StrValue = num.toString().replace('$', '');
let numLength = 0;
// 判断数据是否有{}
if (StrValue.indexOf('{') !== -1) {
let par = /{(.+?)}/g;
let zeroNum: any = StrValue.match(par);
let ThisZero = zeroNum[0].replace('{', '');
ThisZero = ThisZero.replace('}', '');
if (parseInt(ThisZero) <= 10) {
let newValue = StrValue.replace(zeroNum[0], '');
let newArr: any = newValue.split('.')[1];
// 在小数点后面添0
for (let i = 0; i <= parseInt(ThisZero); i++) {
// 拆分后的
newArr = '0' + newArr;
}
newArr = newArr.match('[0]{0,20}')[0];
numLength = parseInt('1' + newArr) * 100;
}
if (numLength === 0) {
numLength = 10000000000;
}
return numLength;
} else {
// 没有{}的
if (StrValue.indexOf('.') !== -1 && StrValue.indexOf('e') === -1) {
StrValue = StrValue.toString().split('.')[1];
let pat = '[0]{0,20}';
let zeroNum: any = StrValue.match(pat);
StrValue = '1' + zeroNum[0];
} else if (StrValue.indexOf('e') !== -1) {
// 科学计数法
let NewNum = parseFloat(StrValue);
StrValue = scientificToNumber(NewNum);
StrValue = StrValue.toString().split('.')[1];
let pat = '[0]{0,20}';
let zeroNum: any = StrValue.match(pat);
if (zeroNum[0].length <= 10) {
StrValue = '1' + zeroNum[0];
} else if (zeroNum[0].length == 15) {
StrValue = '1' + zeroNum[0] + '0';
return parseInt(StrValue);
} else {
// 最多支持16位小数
return 10000000000000000;
}
} else {
StrValue = '1';
}
return parseInt(StrValue) * 10000;
}
};
// k线图计算当前时间
export const computedTime = (value: string) => {
// 需要返回的时间戳
let FinishTime = null;
// 当前时间
let current = new Date().getTime();
// 当前时间的整分时间
let mm = dayjs(current).format('YYYY-MM-DD HH:mm');
// 当前时间的整时时间
let hh = dayjs(current).format('YYYY-MM-DD HH');
if (value == '1') {
// 上一分钟--ok
FinishTime = dayjs(mm).subtract(1, 'minute').valueOf();
} else if (value === '5') {
// 上五分钟
let current = new Date().getTime();
let Time = parseInt(dayjs(current).format('mm'));
if (Time % 5 == 0) {
FinishTime = dayjs(mm).subtract(5, 'minute').valueOf();
} else {
FinishTime = dayjs(mm)
.subtract(Time % 5, 'minute')
.valueOf();
}
} else if (value == '15') {
// 15分钟的
let current = new Date().getTime();
let Time = parseInt(dayjs(current).format('mm'));
if (Time % 15 == 0) {
FinishTime = dayjs(mm).subtract(15, 'minute').valueOf();
} else {
FinishTime = dayjs(mm)
.subtract(Time % 15, 'minute')
.valueOf();
}
} else if (value == '60') {
// 一小时的
FinishTime = dayjs(hh).valueOf();
} else if (value == '240') {
// 当前的整点时间
let curH = dayjs(current).format('HH');
// 应该减去的时间
let num = parseInt(curH) % 4;
FinishTime = dayjs(hh).subtract(num, 'hour').valueOf(); //ok
} else if (value == '1D') {
// 一天的
let DD = dayjs(current).format('YYYY-MM-DD');
FinishTime = dayjs(DD).valueOf(); // ok
}
return FinishTime;
};
// 右侧详情图标排序,有链接的放前面
export const sortImg = (list: any) => {
let temp = [];
for (let i = 0; i < list.length; i++) {
// 如果没有链接,放最后
if (!list[i]['url']) {
temp.push(list[i]);
} else {
temp.unshift(list[i]);
}
}
return temp;
};
// 过滤两者之间的值--比较number大小
export const filtercriteria = (filter, list) => {
// 解构
......@@ -541,6 +418,29 @@ export const show_message = (
} else if (type == 'error') {
MessagePlugin.error(value);
}
// 类型报错-所以用上面的方法
// MessagePlugin[type](value);
};
export const getWindowClient = () => {
let w = window.innerWidth || document.documentElement.clientWidth;
let h = window.innerHeight || document.documentElement.clientHeight;
return {
width: w,
height: h,
};
};
// 获取dom的bound
export const getElBounding = (el: HTMLElement) => {
return el.getBoundingClientRect();
};
// 生成测试数据
export const createTestData = (obj: any, num: number = 20) => {
let list = [];
for (let i = 0; i < num; i++) {
let newObj = JSON.parse(JSON.stringify(obj));
newObj.id = i + 1;
list.push(newObj);
}
return 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