diff --git a/frontend/assets/mod.css b/frontend/assets/mod.css new file mode 100644 index 0000000..4e8c596 --- /dev/null +++ b/frontend/assets/mod.css @@ -0,0 +1,944 @@ +/* From Uiverse.io by Galahhad */ +/* REMASTERED */ +/* RTX-ON */ +/* completely redone toggle and droid */ + +.bb8-toggle { + --toggle-size: 10px; + /* finally I removed the scale now everything depends on the font-size */ + /* --margin-top-for-head: 1.75em; */ + /* it's just in case 👆 */ + --toggle-width: 10.625em; + --toggle-height: 5.625em; + --toggle-offset: calc((var(--toggle-height) - var(--bb8-diameter)) / 2); + --toggle-bg: linear-gradient(#2c4770, #070e2b 35%, #628cac 50% 70%, #a6c5d4) + no-repeat; + --bb8-diameter: 4.375em; + --radius: 99em; + --transition: 0.4s; + --accent: #de7d2f; + --bb8-bg: #fff; +} + +.bb8-toggle, +.bb8-toggle *, +.bb8-toggle *::before, +.bb8-toggle *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.bb8-toggle { + cursor: pointer; + margin-top: var(--margin-top-for-head); + font-size: var(--toggle-size); +} + +.bb8-toggle__checkbox { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + display: none; +} + +.bb8-toggle__container { + width: var(--toggle-width); + height: var(--toggle-height); + background: var(--toggle-bg); + background-size: 100% 11.25em; + background-position-y: -5.625em; + border-radius: var(--radius); + position: relative; + -webkit-transition: var(--transition); + -o-transition: var(--transition); + transition: var(--transition); +} + +.bb8 { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + position: absolute; + top: calc(var(--toggle-offset) - 1.688em + 0.188em); + left: var(--toggle-offset); + -webkit-transition: var(--transition); + -o-transition: var(--transition); + transition: var(--transition); + z-index: 2; +} + +.bb8__head-container { + position: relative; + -webkit-transition: var(--transition); + -o-transition: var(--transition); + transition: var(--transition); + z-index: 2; + -webkit-transform-origin: 1.25em 3.75em; + -ms-transform-origin: 1.25em 3.75em; + transform-origin: 1.25em 3.75em; +} + +.bb8__head { + overflow: hidden; + margin-bottom: -0.188em; + width: 2.5em; + height: 1.688em; + background: -o-linear-gradient( + transparent 0.063em, + dimgray 0.063em 0.313em, + transparent 0.313em 0.375em, + var(--accent) 0.375em 0.5em, + transparent 0.5em 1.313em, + silver 1.313em 1.438em, + transparent 1.438em + ), + -o-linear-gradient(45deg, transparent 0.188em, var(--bb8-bg) 0.188em 1.25em, transparent + 1.25em), + -o-linear-gradient(135deg, transparent 0.188em, var(--bb8-bg) 0.188em 1.25em, transparent + 1.25em), + -o-linear-gradient(var(--bb8-bg) 1.25em, transparent 1.25em); + background: -o-linear-gradient( + transparent 0.063em, + dimgray 0.063em 0.313em, + transparent 0.313em 0.375em, + var(--accent) 0.375em 0.5em, + transparent 0.5em 1.313em, + silver 1.313em 1.438em, + transparent 1.438em + ), + -o-linear-gradient(45deg, transparent 0.188em, var(--bb8-bg) 0.188em 1.25em, transparent + 1.25em), + -o-linear-gradient(135deg, transparent 0.188em, var(--bb8-bg) 0.188em 1.25em, transparent + 1.25em), + -o-linear-gradient(var(--bb8-bg) 1.25em, transparent 1.25em); + background: -o-linear-gradient( + transparent 0.063em, + dimgray 0.063em 0.313em, + transparent 0.313em 0.375em, + var(--accent) 0.375em 0.5em, + transparent 0.5em 1.313em, + silver 1.313em 1.438em, + transparent 1.438em + ), + -o-linear-gradient(45deg, transparent 0.188em, var(--bb8-bg) 0.188em 1.25em, transparent + 1.25em), + -o-linear-gradient(135deg, transparent 0.188em, var(--bb8-bg) 0.188em 1.25em, transparent + 1.25em), + -o-linear-gradient(var(--bb8-bg) 1.25em, transparent 1.25em); + background: -o-linear-gradient( + transparent 0.063em, + dimgray 0.063em 0.313em, + transparent 0.313em 0.375em, + var(--accent) 0.375em 0.5em, + transparent 0.5em 1.313em, + silver 1.313em 1.438em, + transparent 1.438em + ), + -o-linear-gradient(45deg, transparent 0.188em, var(--bb8-bg) 0.188em 1.25em, transparent + 1.25em), + -o-linear-gradient(135deg, transparent 0.188em, var(--bb8-bg) 0.188em 1.25em, transparent + 1.25em), + -o-linear-gradient(var(--bb8-bg) 1.25em, transparent 1.25em); + background: linear-gradient( + transparent 0.063em, + dimgray 0.063em 0.313em, + transparent 0.313em 0.375em, + var(--accent) 0.375em 0.5em, + transparent 0.5em 1.313em, + silver 1.313em 1.438em, + transparent 1.438em + ), + linear-gradient( + 45deg, + transparent 0.188em, + var(--bb8-bg) 0.188em 1.25em, + transparent 1.25em + ), + linear-gradient( + -45deg, + transparent 0.188em, + var(--bb8-bg) 0.188em 1.25em, + transparent 1.25em + ), + linear-gradient(var(--bb8-bg) 1.25em, transparent 1.25em); + border-radius: var(--radius) var(--radius) 0 0; + position: relative; + z-index: 1; + -webkit-filter: drop-shadow(0 0.063em 0.125em gray); + filter: drop-shadow(0 0.063em 0.125em gray); +} + +.bb8__head::before { + content: ""; + position: absolute; + width: 0.563em; + height: 0.563em; + background: -o-radial-gradient( + 0.25em 0.375em, + 0.125em circle, + red, + transparent + ), + -o-radial-gradient(0.375em 0.188em, 0.063em circle, var(--bb8-bg) 50%, transparent + 100%), + -o-linear-gradient(45deg, #000 0.188em, dimgray 0.313em 0.375em, #000 0.5em); + background: -o-radial-gradient( + 0.25em 0.375em, + 0.125em circle, + red, + transparent + ), + -o-radial-gradient(0.375em 0.188em, 0.063em circle, var(--bb8-bg) 50%, transparent + 100%), + -o-linear-gradient(45deg, #000 0.188em, dimgray 0.313em 0.375em, #000 0.5em); + background: -o-radial-gradient( + 0.25em 0.375em, + 0.125em circle, + red, + transparent + ), + -o-radial-gradient(0.375em 0.188em, 0.063em circle, var(--bb8-bg) 50%, transparent + 100%), + -o-linear-gradient(45deg, #000 0.188em, dimgray 0.313em 0.375em, #000 0.5em); + background: -o-radial-gradient( + 0.25em 0.375em, + 0.125em circle, + red, + transparent + ), + -o-radial-gradient(0.375em 0.188em, 0.063em circle, var(--bb8-bg) 50%, transparent + 100%), + -o-linear-gradient(45deg, #000 0.188em, dimgray 0.313em 0.375em, #000 0.5em); + background: radial-gradient( + 0.125em circle at 0.25em 0.375em, + red, + transparent + ), + radial-gradient( + 0.063em circle at 0.375em 0.188em, + var(--bb8-bg) 50%, + transparent 100% + ), + linear-gradient(45deg, #000 0.188em, dimgray 0.313em 0.375em, #000 0.5em); + border-radius: var(--radius); + top: 0.413em; + left: 50%; + -webkit-transform: translate(-50%); + -ms-transform: translate(-50%); + transform: translate(-50%); + -webkit-box-shadow: 0 0 0 0.089em lightgray, 0.563em 0.281em 0 -0.148em, + 0.563em 0.281em 0 -0.1em var(--bb8-bg), 0.563em 0.281em 0 -0.063em; + box-shadow: 0 0 0 0.089em lightgray, 0.563em 0.281em 0 -0.148em, + 0.563em 0.281em 0 -0.1em var(--bb8-bg), 0.563em 0.281em 0 -0.063em; + z-index: 1; + -webkit-transition: var(--transition); + -o-transition: var(--transition); + transition: var(--transition); +} + +.bb8__head::after { + content: ""; + position: absolute; + bottom: 0.375em; + left: 0; + width: 100%; + height: 0.188em; + background: -o-linear-gradient( + left, + var(--accent) 0.125em, + transparent 0.125em 0.188em, + var(--accent) 0.188em 0.313em, + transparent 0.313em 0.375em, + var(--accent) 0.375em 0.938em, + transparent 0.938em 1em, + var(--accent) 1em 1.125em, + transparent 1.125em 1.875em, + var(--accent) 1.875em 2em, + transparent 2em 2.063em, + var(--accent) 2.063em 2.25em, + transparent 2.25em 2.313em, + var(--accent) 2.313em 2.375em, + transparent 2.375em 2.438em, + var(--accent) 2.438em + ); + background: -webkit-gradient( + linear, + left top, + right top, + color-stop(0.125em, var(--accent)), + color-stop(0.125em, transparent), + color-stop(0.188em, var(--accent)), + color-stop(0.313em, transparent), + color-stop(0.375em, var(--accent)), + color-stop(0.938em, transparent), + color-stop(1em, var(--accent)), + color-stop(1.125em, transparent), + color-stop(1.875em, var(--accent)), + color-stop(2em, transparent), + color-stop(2.063em, var(--accent)), + color-stop(2.25em, transparent), + color-stop(2.313em, var(--accent)), + color-stop(2.375em, transparent), + color-stop(2.438em, var(--accent)) + ); + background: linear-gradient( + to right, + var(--accent) 0.125em, + transparent 0.125em 0.188em, + var(--accent) 0.188em 0.313em, + transparent 0.313em 0.375em, + var(--accent) 0.375em 0.938em, + transparent 0.938em 1em, + var(--accent) 1em 1.125em, + transparent 1.125em 1.875em, + var(--accent) 1.875em 2em, + transparent 2em 2.063em, + var(--accent) 2.063em 2.25em, + transparent 2.25em 2.313em, + var(--accent) 2.313em 2.375em, + transparent 2.375em 2.438em, + var(--accent) 2.438em + ); + -webkit-transition: var(--transition); + -o-transition: var(--transition); + transition: var(--transition); +} + +.bb8__antenna { + position: absolute; + -webkit-transform: translateY(-90%); + -ms-transform: translateY(-90%); + transform: translateY(-90%); + width: 0.059em; + border-radius: var(--radius) var(--radius) 0 0; + -webkit-transition: var(--transition); + -o-transition: var(--transition); + transition: var(--transition); +} + +.bb8__antenna:nth-child(1) { + height: 0.938em; + right: 0.938em; + background: -o-linear-gradient(#000 0.188em, silver 0.188em); + background: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(0.188em, #000), + color-stop(0.188em, silver) + ); + background: linear-gradient(#000 0.188em, silver 0.188em); +} + +.bb8__antenna:nth-child(2) { + height: 0.375em; + left: 50%; + -webkit-transform: translate(-50%, -90%); + -ms-transform: translate(-50%, -90%); + transform: translate(-50%, -90%); + background: silver; +} + +.bb8__body { + width: 4.375em; + height: 4.375em; + background: var(--bb8-bg); + border-radius: var(--radius); + position: relative; + overflow: hidden; + -webkit-transition: var(--transition); + -o-transition: var(--transition); + transition: var(--transition); + z-index: 1; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + background: -webkit-gradient( + linear, + right top, + left top, + color-stop(4%, var(--bb8-bg)), + color-stop(4%, var(--accent)), + color-stop(10%, transparent), + color-stop(90%, var(--accent)), + color-stop(96%, var(--bb8-bg)) + ), + -webkit-gradient(linear, left top, left bottom, color-stop(4%, var(--bb8-bg)), color-stop(4%, var(--accent)), color-stop(10%, transparent), color-stop(90%, var(--accent)), color-stop(96%, var(--bb8-bg))), + -webkit-gradient(linear, left top, right top, color-stop(2.156em, transparent), color-stop(2.156em, silver), color-stop(2.188em, transparent)), + -webkit-gradient(linear, left top, left bottom, color-stop(2.156em, transparent), color-stop(2.156em, silver), color-stop(2.188em, transparent)); + background: -o-linear-gradient( + right, + var(--bb8-bg) 4%, + var(--accent) 4% 10%, + transparent 10% 90%, + var(--accent) 90% 96%, + var(--bb8-bg) 96% + ), + -o-linear-gradient(var(--bb8-bg) 4%, var(--accent) 4% 10%, transparent 10% + 90%, var(--accent) 90% 96%, var(--bb8-bg) 96%), + -o-linear-gradient(left, transparent 2.156em, silver 2.156em 2.219em, transparent + 2.188em), + -o-linear-gradient(transparent 2.156em, silver 2.156em 2.219em, transparent + 2.188em); + background: linear-gradient( + -90deg, + var(--bb8-bg) 4%, + var(--accent) 4% 10%, + transparent 10% 90%, + var(--accent) 90% 96%, + var(--bb8-bg) 96% + ), + linear-gradient( + var(--bb8-bg) 4%, + var(--accent) 4% 10%, + transparent 10% 90%, + var(--accent) 90% 96%, + var(--bb8-bg) 96% + ), + linear-gradient( + to right, + transparent 2.156em, + silver 2.156em 2.219em, + transparent 2.188em + ), + linear-gradient( + transparent 2.156em, + silver 2.156em 2.219em, + transparent 2.188em + ); + background-color: var(--bb8-bg); +} + +.bb8__body::after { + content: ""; + bottom: 1.5em; + left: 0.563em; + position: absolute; + width: 0.188em; + height: 0.188em; + background: rgb(236, 236, 236); + color: rgb(236, 236, 236); + border-radius: 50%; + -webkit-box-shadow: 0.875em 0.938em, 0 -1.25em, 0.875em -2.125em, + 2.125em -2.125em, 3.063em -1.25em, 3.063em 0, 2.125em 0.938em; + box-shadow: 0.875em 0.938em, 0 -1.25em, 0.875em -2.125em, 2.125em -2.125em, + 3.063em -1.25em, 3.063em 0, 2.125em 0.938em; +} + +.bb8__body::before { + content: ""; + width: 2.625em; + height: 2.625em; + position: absolute; + border-radius: 50%; + z-index: 0.1; + overflow: hidden; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + border: 0.313em solid var(--accent); + background: -o-radial-gradient( + center, + 1em circle, + rgb(236, 236, 236) 50%, + transparent 51% + ), + -o-radial-gradient(center, 1.25em circle, var(--bb8-bg) 50%, transparent 51%), + -o-linear-gradient(right, transparent 42%, var(--accent) 42% 58%, transparent + 58%), + -o-linear-gradient(var(--bb8-bg) 42%, var(--accent) 42% 58%, var(--bb8-bg) + 58%); + background: -o-radial-gradient( + center, + 1em circle, + rgb(236, 236, 236) 50%, + transparent 51% + ), + -o-radial-gradient(center, 1.25em circle, var(--bb8-bg) 50%, transparent 51%), + -o-linear-gradient(right, transparent 42%, var(--accent) 42% 58%, transparent + 58%), + -o-linear-gradient(var(--bb8-bg) 42%, var(--accent) 42% 58%, var(--bb8-bg) + 58%); + background: radial-gradient( + 1em circle at center, + rgb(236, 236, 236) 50%, + transparent 51% + ), + radial-gradient(1.25em circle at center, var(--bb8-bg) 50%, transparent 51%), + -webkit-gradient(linear, right top, left top, color-stop(42%, transparent), color-stop(42%, var(--accent)), color-stop(58%, transparent)), + -webkit-gradient(linear, left top, left bottom, color-stop(42%, var(--bb8-bg)), color-stop(42%, var(--accent)), color-stop(58%, var(--bb8-bg))); + background: radial-gradient( + 1em circle at center, + rgb(236, 236, 236) 50%, + transparent 51% + ), + radial-gradient(1.25em circle at center, var(--bb8-bg) 50%, transparent 51%), + linear-gradient( + -90deg, + transparent 42%, + var(--accent) 42% 58%, + transparent 58% + ), + linear-gradient(var(--bb8-bg) 42%, var(--accent) 42% 58%, var(--bb8-bg) 58%); +} + +.artificial__hidden { + position: absolute; + border-radius: inherit; + inset: 0; + pointer-events: none; + overflow: hidden; +} + +.bb8__shadow { + content: ""; + width: var(--bb8-diameter); + height: 20%; + border-radius: 50%; + background: #3a271c; + -webkit-box-shadow: 0.313em 0 3.125em #3a271c; + box-shadow: 0.313em 0 3.125em #3a271c; + opacity: 0.25; + position: absolute; + bottom: 0; + left: calc(var(--toggle-offset) - 0.938em); + -webkit-transition: var(--transition); + -o-transition: var(--transition); + transition: var(--transition); + -webkit-transform: skew(-70deg); + -ms-transform: skew(-70deg); + transform: skew(-70deg); + z-index: 1; +} + +.bb8-toggle__scenery { + width: 100%; + height: 100%; + pointer-events: none; + overflow: hidden; + position: relative; + border-radius: inherit; +} + +.bb8-toggle__scenery::before { + content: ""; + position: absolute; + width: 100%; + height: 30%; + bottom: 0; + background: #b18d71; + z-index: 1; +} + +.bb8-toggle__cloud { + z-index: 1; + position: absolute; + border-radius: 50%; +} + +.bb8-toggle__cloud:nth-last-child(1) { + width: 0.875em; + height: 0.625em; + -webkit-filter: blur(0.125em) drop-shadow(0.313em 0.313em #ffffffae) + drop-shadow(-0.625em 0 #fff) drop-shadow(-0.938em -0.125em #fff); + filter: blur(0.125em) drop-shadow(0.313em 0.313em #ffffffae) + drop-shadow(-0.625em 0 #fff) drop-shadow(-0.938em -0.125em #fff); + right: 1.875em; + top: 2.813em; + background: -o-linear-gradient(bottom left, #ffffffae, #ffffffae); + background: -webkit-gradient( + linear, + left bottom, + right top, + from(#ffffffae), + to(#ffffffae) + ); + background: linear-gradient(to top right, #ffffffae, #ffffffae); + -webkit-transition: var(--transition); + -o-transition: var(--transition); + transition: var(--transition); +} + +.bb8-toggle__cloud:nth-last-child(2) { + top: 0.625em; + right: 4.375em; + width: 0.875em; + height: 0.375em; + background: #dfdedeae; + -webkit-filter: blur(0.125em) drop-shadow(-0.313em -0.188em #e0dfdfae) + drop-shadow(-0.625em -0.188em #bbbbbbae) drop-shadow(-1em 0.063em #cfcfcfae); + filter: blur(0.125em) drop-shadow(-0.313em -0.188em #e0dfdfae) + drop-shadow(-0.625em -0.188em #bbbbbbae) drop-shadow(-1em 0.063em #cfcfcfae); + -webkit-transition: 0.6s; + -o-transition: 0.6s; + transition: 0.6s; +} + +.bb8-toggle__cloud:nth-last-child(3) { + top: 1.25em; + right: 0.938em; + width: 0.875em; + height: 0.375em; + background: #ffffffae; + -webkit-filter: blur(0.125em) drop-shadow(0.438em 0.188em #ffffffae) + drop-shadow(-0.625em 0.313em #ffffffae); + filter: blur(0.125em) drop-shadow(0.438em 0.188em #ffffffae) + drop-shadow(-0.625em 0.313em #ffffffae); + -webkit-transition: 0.8s; + -o-transition: 0.8s; + transition: 0.8s; +} + +.gomrassen, +.hermes, +.chenini { + position: absolute; + border-radius: var(--radius); + background: -o-linear-gradient(#fff, #6e8ea2); + background: -webkit-gradient( + linear, + left top, + left bottom, + from(#fff), + to(#6e8ea2) + ); + background: linear-gradient(#fff, #6e8ea2); + top: 100%; +} + +.gomrassen { + left: 0.938em; + width: 1.875em; + height: 1.875em; + -webkit-box-shadow: 0 0 0.188em #ffffff52, 0 0 0.188em #6e8ea24b; + box-shadow: 0 0 0.188em #ffffff52, 0 0 0.188em #6e8ea24b; + -webkit-transition: var(--transition); + -o-transition: var(--transition); + transition: var(--transition); +} + +.gomrassen::before, +.gomrassen::after { + content: ""; + position: absolute; + border-radius: inherit; + -webkit-box-shadow: inset 0 0 0.063em rgb(140, 162, 169); + box-shadow: inset 0 0 0.063em rgb(140, 162, 169); + background: rgb(184, 196, 200); +} + +.gomrassen::before { + left: 0.313em; + top: 0.313em; + width: 0.438em; + height: 0.438em; +} + +.gomrassen::after { + width: 0.25em; + height: 0.25em; + left: 1.25em; + top: 0.75em; +} + +.hermes { + left: 3.438em; + width: 0.625em; + height: 0.625em; + -webkit-box-shadow: 0 0 0.125em #ffffff52, 0 0 0.125em #6e8ea24b; + box-shadow: 0 0 0.125em #ffffff52, 0 0 0.125em #6e8ea24b; + -webkit-transition: 0.6s; + -o-transition: 0.6s; + transition: 0.6s; +} + +.chenini { + left: 4.375em; + width: 0.5em; + height: 0.5em; + -webkit-box-shadow: 0 0 0.125em #ffffff52, 0 0 0.125em #6e8ea24b; + box-shadow: 0 0 0.125em #ffffff52, 0 0 0.125em #6e8ea24b; + -webkit-transition: 0.8s; + -o-transition: 0.8s; + transition: 0.8s; +} + +.tatto-1, +.tatto-2 { + position: absolute; + width: 1.25em; + height: 1.25em; + border-radius: var(--radius); +} + +.tatto-1 { + background: #fefefe; + right: 3.125em; + top: 0.625em; + -webkit-box-shadow: 0 0 0.438em #fdf4e1; + box-shadow: 0 0 0.438em #fdf4e1; + -webkit-transition: var(--transition); + -o-transition: var(--transition); + transition: var(--transition); +} + +.tatto-2 { + background: -o-linear-gradient(#e6ac5c, #d75449); + background: -webkit-gradient( + linear, + left top, + left bottom, + from(#e6ac5c), + to(#d75449) + ); + background: linear-gradient(#e6ac5c, #d75449); + right: 1.25em; + top: 2.188em; + -webkit-box-shadow: 0 0 0.438em #e6ad5c3d, 0 0 0.438em #d755494f; + box-shadow: 0 0 0.438em #e6ad5c3d, 0 0 0.438em #d755494f; + -webkit-transition: 0.7s; + -o-transition: 0.7s; + transition: 0.7s; +} + +.bb8-toggle__star { + position: absolute; + width: 0.063em; + height: 0.063em; + background: #fff; + border-radius: var(--radius); + -webkit-filter: drop-shadow(0 0 0.063em #fff); + filter: drop-shadow(0 0 0.063em #fff); + color: #fff; + top: 100%; +} + +.bb8-toggle__star:nth-child(1) { + left: 3.75em; + -webkit-box-shadow: 1.25em 0.938em, -1.25em 2.5em, 0 1.25em, 1.875em 0.625em, + -3.125em 1.875em, 1.25em 2.813em; + box-shadow: 1.25em 0.938em, -1.25em 2.5em, 0 1.25em, 1.875em 0.625em, + -3.125em 1.875em, 1.25em 2.813em; + -webkit-transition: 0.2s; + -o-transition: 0.2s; + transition: 0.2s; +} + +.bb8-toggle__star:nth-child(2) { + left: 4.688em; + -webkit-box-shadow: 0.625em 0, 0 0.625em, -0.625em -0.625em, 0.625em 0.938em, + -3.125em 1.25em, 1.25em -1.563em; + box-shadow: 0.625em 0, 0 0.625em, -0.625em -0.625em, 0.625em 0.938em, + -3.125em 1.25em, 1.25em -1.563em; + -webkit-transition: 0.3s; + -o-transition: 0.3s; + transition: 0.3s; +} + +.bb8-toggle__star:nth-child(3) { + left: 5.313em; + -webkit-box-shadow: -0.625em -0.625em, -2.188em 1.25em, -2.188em 0, + -3.75em -0.625em, -3.125em -0.625em, -2.5em -0.313em, 0.75em -0.625em; + box-shadow: -0.625em -0.625em, -2.188em 1.25em, -2.188em 0, -3.75em -0.625em, + -3.125em -0.625em, -2.5em -0.313em, 0.75em -0.625em; + -webkit-transition: var(--transition); + -o-transition: var(--transition); + transition: var(--transition); +} + +.bb8-toggle__star:nth-child(4) { + left: 1.875em; + width: 0.125em; + height: 0.125em; + -webkit-transition: 0.5s; + -o-transition: 0.5s; + transition: 0.5s; +} + +.bb8-toggle__star:nth-child(5) { + left: 5em; + width: 0.125em; + height: 0.125em; + -webkit-transition: 0.6s; + -o-transition: 0.6s; + transition: 0.6s; +} + +.bb8-toggle__star:nth-child(6) { + left: 2.5em; + width: 0.125em; + height: 0.125em; + -webkit-transition: 0.7s; + -o-transition: 0.7s; + transition: 0.7s; +} + +.bb8-toggle__star:nth-child(7) { + left: 3.438em; + width: 0.125em; + height: 0.125em; + -webkit-transition: 0.8s; + -o-transition: 0.8s; + transition: 0.8s; +} + +/* actions */ + +.bb8-toggle__checkbox:checked + + .bb8-toggle__container + .bb8-toggle__star:nth-child(1) { + top: 0.625em; +} + +.bb8-toggle__checkbox:checked + + .bb8-toggle__container + .bb8-toggle__star:nth-child(2) { + top: 1.875em; +} + +.bb8-toggle__checkbox:checked + + .bb8-toggle__container + .bb8-toggle__star:nth-child(3) { + top: 1.25em; +} + +.bb8-toggle__checkbox:checked + + .bb8-toggle__container + .bb8-toggle__star:nth-child(4) { + top: 3.438em; +} + +.bb8-toggle__checkbox:checked + + .bb8-toggle__container + .bb8-toggle__star:nth-child(5) { + top: 3.438em; +} + +.bb8-toggle__checkbox:checked + + .bb8-toggle__container + .bb8-toggle__star:nth-child(6) { + top: 0.313em; +} + +.bb8-toggle__checkbox:checked + + .bb8-toggle__container + .bb8-toggle__star:nth-child(7) { + top: 1.875em; +} + +.bb8-toggle__checkbox:checked + .bb8-toggle__container .bb8-toggle__cloud { + right: -100%; +} + +.bb8-toggle__checkbox:checked + .bb8-toggle__container .gomrassen { + top: 0.938em; +} + +.bb8-toggle__checkbox:checked + .bb8-toggle__container .hermes { + top: 2.5em; +} + +.bb8-toggle__checkbox:checked + .bb8-toggle__container .chenini { + top: 2.75em; +} + +.bb8-toggle__checkbox:checked + .bb8-toggle__container { + background-position-y: 0; +} + +.bb8-toggle__checkbox:checked + .bb8-toggle__container .tatto-1 { + top: 100%; +} + +.bb8-toggle__checkbox:checked + .bb8-toggle__container .tatto-2 { + top: 100%; +} + +.bb8-toggle__checkbox:checked + .bb8-toggle__container .bb8 { + left: calc(100% - var(--bb8-diameter) - var(--toggle-offset)); +} + +.bb8-toggle__checkbox:checked + .bb8-toggle__container .bb8__shadow { + left: calc(100% - var(--bb8-diameter) - var(--toggle-offset) + 0.938em); + -webkit-transform: skew(70deg); + -ms-transform: skew(70deg); + transform: skew(70deg); +} + +.bb8-toggle__checkbox:checked + .bb8-toggle__container .bb8__body { + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(225deg); +} + +.bb8-toggle__checkbox:hover + .bb8-toggle__container .bb8__head::before { + left: 100%; +} + +.bb8-toggle__checkbox:not(:checked):hover + + .bb8-toggle__container + .bb8__antenna:nth-child(1) { + right: 1.5em; +} + +.bb8-toggle__checkbox:hover + + .bb8-toggle__container + .bb8__antenna:nth-child(2) { + left: 0.938em; +} + +.bb8-toggle__checkbox:hover + .bb8-toggle__container .bb8__head::after { + background-position: 1.375em 0; +} + +.bb8-toggle__checkbox:checked:hover + + .bb8-toggle__container + .bb8__head::before { + left: 0; +} + +.bb8-toggle__checkbox:checked:hover + + .bb8-toggle__container + .bb8__antenna:nth-child(2) { + left: calc(100% - 0.938em); +} + +.bb8-toggle__checkbox:checked:hover + .bb8-toggle__container .bb8__head::after { + background-position: -1.375em 0; +} + +.bb8-toggle__checkbox:active + .bb8-toggle__container .bb8__head-container { + -webkit-transform: rotate(25deg); + -ms-transform: rotate(25deg); + transform: rotate(25deg); +} + +.bb8-toggle__checkbox:checked:active + + .bb8-toggle__container + .bb8__head-container { + -webkit-transform: rotate(-25deg); + -ms-transform: rotate(-25deg); + transform: rotate(-25deg); +} + +.bb8:hover .bb8__head::before, +.bb8:hover .bb8__antenna:nth-child(2) { + left: 50% !important; +} + +.bb8:hover .bb8__antenna:nth-child(1) { + right: 0.938em !important; +} + +.bb8:hover .bb8__head::after { + background-position: 0 0 !important; +} \ No newline at end of file diff --git a/frontend/assets/mod.html b/frontend/assets/mod.html new file mode 100644 index 0000000..10c5c39 --- /dev/null +++ b/frontend/assets/mod.html @@ -0,0 +1,34 @@ + + \ No newline at end of file diff --git a/frontend/assets/pdd.svg b/frontend/assets/pdd.svg new file mode 100644 index 0000000..ab63c6b --- /dev/null +++ b/frontend/assets/pdd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/assets/yt.svg b/frontend/assets/yt.svg new file mode 100644 index 0000000..075977d --- /dev/null +++ b/frontend/assets/yt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/js/components/bom.js b/frontend/js/components/bom.js new file mode 100644 index 0000000..b02e0e0 --- /dev/null +++ b/frontend/js/components/bom.js @@ -0,0 +1,369 @@ +// BOM物料清单管理 +(() => { + let bomList = []; + let currentPage = 1; + const pageSize = 20; + let editingId = null; + + Router.register('/plan-mgmt/bom', async () => { + const html = ` + + +
+
+
+
+ + + +
+
+ +
+ + + + + + + + + + + + + + + + + + + +
产品编码产品名称物料编码物料名称单机用量单位最小包装供应商备注操作
加载中...
+
+ + +
+
+ + + + `; + + setTimeout(() => { + document.getElementById('add-bom-btn')?.addEventListener('click', () => openModal()); + document.getElementById('import-bom-btn')?.addEventListener('click', () => showImportDialog()); + document.getElementById('download-template-btn')?.addEventListener('click', () => downloadTemplate()); + document.getElementById('batch-delete-btn')?.addEventListener('click', () => batchDelete()); + document.getElementById('search-keyword')?.addEventListener('keypress', (e) => { + if (e.key === 'Enter') BOM.search(); + }); + loadList(); + }, 100); + + return html; + }); + + function showImportDialog() { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.xlsx,.xls,.csv'; + input.onchange = async (e) => { + const file = e.target.files[0]; + if (!file) return; + + const formData = new FormData(); + formData.append('file', file); + + try { + const overlay = document.getElementById('overlay'); + overlay.classList.remove('hidden'); + + const res = await fetch('/api/bom/import', { + method: 'POST', + body: formData, + credentials: 'include' + }); + + overlay.classList.add('hidden'); + + const data = await res.json(); + if (data.ok) { + alert(data.message || '导入成功'); + loadList(); + } else { + alert(data.error || '导入失败'); + } + } catch (err) { + document.getElementById('overlay').classList.add('hidden'); + alert('导入失败: ' + err.message); + } + }; + input.click(); + } + + function downloadTemplate() { + const headers = ['产品编码', '产品名称', '物料编码', '物料名称', '单机用量', '单位', '最小包装', '供应商', '备注']; + const example = ['AP05', 'AP05基站', 'PCB-001', '主控PCB板', '1', 'pcs', '10', '深圳电子', '']; + + const csvContent = [headers.join(','), example.join(',')].join('\n'); + const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' }); + const link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = 'BOM导入模板.csv'; + link.click(); + } + + async function loadList() { + try { + const res = await API.get('/api/bom'); + bomList = res.list || []; + renderList(); + } catch (e) { + console.error('加载BOM列表失败:', e); + document.getElementById('bom-list').innerHTML = '加载失败'; + } + } + + function renderList() { + const tbody = document.getElementById('bom-list'); + const keyword = (document.getElementById('search-keyword')?.value || '').toLowerCase(); + + let filtered = bomList; + if (keyword) { + filtered = bomList.filter(item => + (item.product_code || '').toLowerCase().includes(keyword) || + (item.product_name || '').toLowerCase().includes(keyword) || + (item.material_code || '').toLowerCase().includes(keyword) || + (item.material_name || '').toLowerCase().includes(keyword) + ); + } + + const totalPages = Math.ceil(filtered.length / pageSize); + const start = (currentPage - 1) * pageSize; + const pageData = filtered.slice(start, start + pageSize); + + if (pageData.length === 0) { + tbody.innerHTML = '暂无数据'; + } else { + tbody.innerHTML = pageData.map(item => ` + + + ${escapeHtml(item.product_code || '')} + ${escapeHtml(item.product_name || '')} + ${escapeHtml(item.material_code || '')} + ${escapeHtml(item.material_name || '')} + ${item.unit_qty || 1} + ${escapeHtml(item.unit || 'pcs')} + ${item.min_package || 1} + ${escapeHtml(item.supplier || '-')} + ${escapeHtml(item.remark || '-')} + + + + + + `).join(''); + } + + renderPagination(totalPages); + } + + function renderPagination(totalPages) { + const container = document.getElementById('pagination'); + if (totalPages <= 1) { + container.innerHTML = ''; + return; + } + + let html = ''; + html += ``; + html += `第 ${currentPage} / ${totalPages} 页`; + html += ``; + container.innerHTML = html; + } + + function openModal(item = null) { + editingId = item?.id || null; + document.getElementById('modal-title').textContent = item ? '编辑BOM' : '新增BOM'; + document.getElementById('product-code').value = item?.product_code || ''; + document.getElementById('product-name').value = item?.product_name || ''; + document.getElementById('material-code').value = item?.material_code || ''; + document.getElementById('material-name').value = item?.material_name || ''; + document.getElementById('unit-qty').value = item?.unit_qty || 1; + document.getElementById('unit').value = item?.unit || 'pcs'; + document.getElementById('min-package').value = item?.min_package || 1; + document.getElementById('supplier').value = item?.supplier || ''; + document.getElementById('remark').value = item?.remark || ''; + document.getElementById('bom-modal').style.display = 'flex'; + } + + function closeModal() { + document.getElementById('bom-modal').style.display = 'none'; + editingId = null; + } + + async function save() { + const data = { + product_code: document.getElementById('product-code').value.trim(), + product_name: document.getElementById('product-name').value.trim(), + material_code: document.getElementById('material-code').value.trim(), + material_name: document.getElementById('material-name').value.trim(), + unit_qty: parseFloat(document.getElementById('unit-qty').value) || 1, + unit: document.getElementById('unit').value.trim() || 'pcs', + min_package: parseInt(document.getElementById('min-package').value) || 1, + supplier: document.getElementById('supplier').value.trim(), + remark: document.getElementById('remark').value.trim() + }; + + if (!data.product_code || !data.product_name || !data.material_code || !data.material_name) { + alert('请填写所有必填项'); + return; + } + + try { + if (editingId) { + await API.put(`/api/bom/${editingId}`, data); + alert('更新成功'); + } else { + await API.post('/api/bom', data); + alert('创建成功'); + } + closeModal(); + loadList(); + } catch (e) { + alert(e.message || '操作失败'); + } + } + + async function edit(id) { + const item = bomList.find(x => x.id === id); + if (item) openModal(item); + } + + async function deleteBom(id) { + if (!confirm('确定要删除这条BOM吗?')) return; + try { + await API.delete(`/api/bom/${id}`); + alert('删除成功'); + loadList(); + } catch (e) { + alert(e.message || '删除失败'); + } + } + + async function batchDelete() { + const checked = document.querySelectorAll('.row-checkbox:checked'); + if (checked.length === 0) { + alert('请先选择要删除的项'); + return; + } + if (!confirm(`确定要删除选中的 ${checked.length} 条BOM吗?`)) return; + + const ids = Array.from(checked).map(cb => parseInt(cb.dataset.id)); + try { + await API.post('/api/bom/batch-delete', { ids }); + alert('批量删除成功'); + loadList(); + } catch (e) { + alert(e.message || '批量删除失败'); + } + } + + function toggleSelectAll(checkbox) { + document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = checkbox.checked); + } + + function search() { + currentPage = 1; + renderList(); + } + + function resetSearch() { + document.getElementById('search-keyword').value = ''; + currentPage = 1; + renderList(); + } + + function goPage(page) { + currentPage = page; + renderList(); + } + + function escapeHtml(str) { + if (!str) return ''; + return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + } + + window.BOM = { + search, + resetSearch, + edit, + delete: deleteBom, + closeModal, + save, + toggleSelectAll, + goPage + }; +})(); diff --git a/frontend/js/components/initial-stock.js b/frontend/js/components/initial-stock.js new file mode 100644 index 0000000..e1625f9 --- /dev/null +++ b/frontend/js/components/initial-stock.js @@ -0,0 +1,365 @@ +// 期初库存管理 +(() => { + let stockList = []; + let currentPage = 1; + const pageSize = 20; + let editingId = null; + + Router.register('/plan-mgmt/initial-stock', async () => { + const html = ` + + +
+
+
+
+ + + +
+
+ +
+ + + + + + + + + + + + + + + + + + +
物料编码物料名称库存数量单位最小包装供应商备注更新时间操作
加载中...
+
+ + +
+
+ + + + `; + + setTimeout(() => { + document.getElementById('add-stock-btn')?.addEventListener('click', () => openModal()); + document.getElementById('import-stock-btn')?.addEventListener('click', () => showImportDialog()); + document.getElementById('download-template-btn')?.addEventListener('click', () => downloadTemplate()); + document.getElementById('batch-delete-btn')?.addEventListener('click', () => batchDelete()); + document.getElementById('search-keyword')?.addEventListener('keypress', (e) => { + if (e.key === 'Enter') InitialStock.search(); + }); + loadList(); + }, 100); + + return html; + }); + + function showImportDialog() { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.xlsx,.xls,.csv'; + input.onchange = async (e) => { + const file = e.target.files[0]; + if (!file) return; + + const formData = new FormData(); + formData.append('file', file); + + try { + const overlay = document.getElementById('overlay'); + overlay.classList.remove('hidden'); + + const res = await fetch('/api/initial-stock/import', { + method: 'POST', + body: formData, + credentials: 'include' + }); + + overlay.classList.add('hidden'); + + const data = await res.json(); + if (data.ok) { + alert(data.message || '导入成功'); + loadList(); + } else { + alert(data.error || '导入失败'); + } + } catch (err) { + document.getElementById('overlay').classList.add('hidden'); + alert('导入失败: ' + err.message); + } + }; + input.click(); + } + + function downloadTemplate() { + const headers = ['物料编码', '物料名称', '库存数量', '单位', '最小包装', '供应商', '备注']; + const example = ['PCB-001', '主控PCB板', '50', 'pcs', '10', '深圳电子', '']; + + const csvContent = [headers.join(','), example.join(',')].join('\n'); + const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' }); + const link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = '期初库存导入模板.csv'; + link.click(); + } + + async function loadList() { + try { + const res = await API.get('/api/initial-stock'); + stockList = res.list || []; + renderList(); + } catch (e) { + console.error('加载库存列表失败:', e); + document.getElementById('stock-list').innerHTML = '加载失败'; + } + } + + function renderList() { + const tbody = document.getElementById('stock-list'); + const keyword = (document.getElementById('search-keyword')?.value || '').toLowerCase(); + + let filtered = stockList; + if (keyword) { + filtered = stockList.filter(item => + (item.material_code || '').toLowerCase().includes(keyword) || + (item.material_name || '').toLowerCase().includes(keyword) + ); + } + + const totalPages = Math.ceil(filtered.length / pageSize); + const start = (currentPage - 1) * pageSize; + const pageData = filtered.slice(start, start + pageSize); + + if (pageData.length === 0) { + tbody.innerHTML = '暂无数据'; + } else { + tbody.innerHTML = pageData.map(item => ` + + + ${escapeHtml(item.material_code || '')} + ${escapeHtml(item.material_name || '')} + ${item.stock_qty || 0} + ${escapeHtml(item.unit || 'pcs')} + ${item.min_package || 1} + ${escapeHtml(item.supplier || '-')} + ${escapeHtml(item.remark || '-')} + ${formatTime(item.updated_at)} + + + + + + `).join(''); + } + + renderPagination(totalPages); + } + + function renderPagination(totalPages) { + const container = document.getElementById('pagination'); + if (totalPages <= 1) { + container.innerHTML = ''; + return; + } + + let html = ''; + html += ``; + html += `第 ${currentPage} / ${totalPages} 页`; + html += ``; + container.innerHTML = html; + } + + function openModal(item = null) { + editingId = item?.id || null; + document.getElementById('modal-title').textContent = item ? '编辑期初库存' : '新增期初库存'; + document.getElementById('material-code').value = item?.material_code || ''; + document.getElementById('material-name').value = item?.material_name || ''; + document.getElementById('stock-qty').value = item?.stock_qty || 0; + document.getElementById('unit').value = item?.unit || 'pcs'; + document.getElementById('min-package').value = item?.min_package || 1; + document.getElementById('supplier').value = item?.supplier || ''; + document.getElementById('remark').value = item?.remark || ''; + document.getElementById('stock-modal').style.display = 'flex'; + } + + function closeModal() { + document.getElementById('stock-modal').style.display = 'none'; + editingId = null; + } + + async function save() { + const data = { + material_code: document.getElementById('material-code').value.trim(), + material_name: document.getElementById('material-name').value.trim(), + stock_qty: parseInt(document.getElementById('stock-qty').value) || 0, + unit: document.getElementById('unit').value.trim() || 'pcs', + min_package: parseInt(document.getElementById('min-package').value) || 1, + supplier: document.getElementById('supplier').value.trim(), + remark: document.getElementById('remark').value.trim() + }; + + if (!data.material_code || !data.material_name) { + alert('请填写物料编码和物料名称'); + return; + } + + try { + if (editingId) { + await API.put(`/api/initial-stock/${editingId}`, data); + alert('更新成功'); + } else { + await API.post('/api/initial-stock', data); + alert('创建成功'); + } + closeModal(); + loadList(); + } catch (e) { + alert(e.message || '操作失败'); + } + } + + async function edit(id) { + const item = stockList.find(x => x.id === id); + if (item) openModal(item); + } + + async function deleteStock(id) { + if (!confirm('确定要删除这条库存记录吗?')) return; + try { + await API.delete(`/api/initial-stock/${id}`); + alert('删除成功'); + loadList(); + } catch (e) { + alert(e.message || '删除失败'); + } + } + + async function batchDelete() { + const checked = document.querySelectorAll('.row-checkbox:checked'); + if (checked.length === 0) { + alert('请先选择要删除的项'); + return; + } + if (!confirm(`确定要删除选中的 ${checked.length} 条库存记录吗?`)) return; + + const ids = Array.from(checked).map(cb => parseInt(cb.dataset.id)); + try { + await API.post('/api/initial-stock/batch-delete', { ids }); + alert('批量删除成功'); + loadList(); + } catch (e) { + alert(e.message || '批量删除失败'); + } + } + + function toggleSelectAll(checkbox) { + document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = checkbox.checked); + } + + function search() { + currentPage = 1; + renderList(); + } + + function resetSearch() { + document.getElementById('search-keyword').value = ''; + currentPage = 1; + renderList(); + } + + function goPage(page) { + currentPage = page; + renderList(); + } + + function formatTime(ts) { + if (!ts) return '-'; + try { + const d = new Date(ts); + return d.toLocaleString('zh-CN', { hour12: false }); + } catch { + return ts; + } + } + + function escapeHtml(str) { + if (!str) return ''; + return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + } + + window.InitialStock = { + search, + resetSearch, + edit, + delete: deleteStock, + closeModal, + save, + toggleSelectAll, + goPage + }; +})(); diff --git a/frontend/js/components/purchase-demand.js b/frontend/js/components/purchase-demand.js new file mode 100644 index 0000000..9b18854 --- /dev/null +++ b/frontend/js/components/purchase-demand.js @@ -0,0 +1,419 @@ +// 采购需求清单管理 +(() => { + let demandList = []; + let productList = []; + let currentPage = 1; + const pageSize = 20; + + const statusMap = { + 'pending': { text: '待处理', color: 'var(--warning)' }, + 'ordered': { text: '已下单', color: 'var(--info)' }, + 'received': { text: '已收货', color: 'var(--primary)' }, + 'completed': { text: '已完成', color: 'var(--success)' }, + 'cancelled': { text: '已取消', color: 'var(--text-2)' } + }; + + Router.register('/plan-mgmt/purchase-demand', async () => { + const html = ` + + + +
+
+
+ 💡 +
+ 采购需求计算公式: + 客户订单数量 × BOM单机用量 - 期初库存 = 净需求 → 按最小包装向上取整 = 实际采购数量 +
+
+
+
+ +
+
+
+
+ + + + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
需求编号物料编码物料名称订单数量BOM用量总需求期初库存净需求最小包装实际采购单位供应商状态操作
加载中...
+
+ + +
+
+ + + + + + + `; + + setTimeout(() => { + document.getElementById('calc-demand-btn')?.addEventListener('click', () => openCalcModal()); + document.getElementById('calc-from-orders-btn')?.addEventListener('click', () => calcFromOrders()); + document.getElementById('batch-delete-btn')?.addEventListener('click', () => batchDelete()); + document.getElementById('search-keyword')?.addEventListener('keypress', (e) => { + if (e.key === 'Enter') PurchaseDemand.search(); + }); + loadList(); + loadProducts(); + }, 100); + + return html; + }); + + async function loadList() { + try { + const res = await API.get('/api/purchase-demand'); + demandList = res.list || []; + renderList(); + } catch (e) { + console.error('加载采购需求失败:', e); + document.getElementById('demand-list').innerHTML = '加载失败'; + } + } + + async function loadProducts() { + try { + const res = await API.get('/api/bom/products'); + productList = res.list || []; + const select = document.getElementById('product-select'); + if (select) { + select.innerHTML = '' + + productList.map(p => ``).join(''); + } + } catch (e) { + console.error('加载产品列表失败:', e); + } + } + + function renderList() { + const tbody = document.getElementById('demand-list'); + const keyword = (document.getElementById('search-keyword')?.value || '').toLowerCase(); + const filterStatus = document.getElementById('filter-status')?.value || ''; + + let filtered = demandList; + if (keyword) { + filtered = filtered.filter(item => + (item.demand_no || '').toLowerCase().includes(keyword) || + (item.material_code || '').toLowerCase().includes(keyword) || + (item.material_name || '').toLowerCase().includes(keyword) + ); + } + if (filterStatus) { + filtered = filtered.filter(item => item.status === filterStatus); + } + + const totalPages = Math.ceil(filtered.length / pageSize); + const start = (currentPage - 1) * pageSize; + const pageData = filtered.slice(start, start + pageSize); + + if (pageData.length === 0) { + tbody.innerHTML = '暂无数据'; + } else { + tbody.innerHTML = pageData.map(item => { + const status = statusMap[item.status] || { text: item.status, color: 'var(--text-2)' }; + return ` + + + ${escapeHtml(item.demand_no || '')} + ${escapeHtml(item.material_code || '')} + ${escapeHtml(item.material_name || '')} + ${item.order_qty || 0} + ${item.bom_unit_qty || 1} + ${item.total_demand || 0} + ${item.initial_stock || 0} + ${item.net_demand || 0} + ${item.min_package || 1} + ${item.actual_purchase_qty || 0} + ${escapeHtml(item.unit || 'pcs')} + ${escapeHtml(item.supplier || '-')} + ${status.text} + + + + + + `; + }).join(''); + } + + renderPagination(totalPages); + } + + function renderPagination(totalPages) { + const container = document.getElementById('pagination'); + if (totalPages <= 1) { + container.innerHTML = ''; + return; + } + + let html = ''; + html += ``; + html += `第 ${currentPage} / ${totalPages} 页`; + html += ``; + container.innerHTML = html; + } + + function openCalcModal() { + document.getElementById('product-select').value = ''; + document.getElementById('order-qty').value = 1; + document.getElementById('calc-modal').style.display = 'flex'; + } + + function closeCalcModal() { + document.getElementById('calc-modal').style.display = 'none'; + } + + async function doCalculate() { + const productCode = document.getElementById('product-select').value; + const orderQty = parseInt(document.getElementById('order-qty').value) || 0; + + if (!productCode) { + alert('请选择产品'); + return; + } + if (orderQty <= 0) { + alert('订单数量必须大于0'); + return; + } + + try { + const res = await API.post('/api/purchase-demand/calculate', { + product_code: productCode, + order_qty: orderQty + }); + + closeCalcModal(); + alert(res.message || '计算完成'); + loadList(); + + // 显示计算结果摘要 + if (res.list && res.list.length > 0) { + const totalPurchase = res.list.reduce((sum, item) => sum + (item.actual_purchase_qty || 0), 0); + console.log(`采购需求计算完成,需求编号: ${res.demand_no},共 ${res.list.length} 种物料,总采购数量: ${totalPurchase}`); + } + } catch (e) { + alert(e.message || '计算失败'); + } + } + + async function calcFromOrders() { + if (!confirm('将根据所有客户订单自动计算采购需求,确定继续吗?')) { + return; + } + + try { + const res = await API.post('/api/purchase-demand/calculate-from-orders', {}); + alert(res.message || '计算完成'); + loadList(); + } catch (e) { + alert(e.message || '计算失败'); + } + } + + let updatingId = null; + + function updateStatus(id) { + updatingId = id; + const item = demandList.find(x => x.id === id); + document.getElementById('update-status').value = item?.status || 'pending'; + document.getElementById('update-remark').value = item?.remark || ''; + document.getElementById('status-modal').style.display = 'flex'; + } + + function closeStatusModal() { + document.getElementById('status-modal').style.display = 'none'; + updatingId = null; + } + + async function doUpdateStatus() { + if (!updatingId) return; + + const status = document.getElementById('update-status').value; + const remark = document.getElementById('update-remark').value.trim(); + + try { + await API.put(`/api/purchase-demand/${updatingId}`, { status, remark }); + closeStatusModal(); + alert('更新成功'); + loadList(); + } catch (e) { + alert(e.message || '更新失败'); + } + } + + async function deleteDemand(id) { + if (!confirm('确定要删除这条采购需求吗?')) return; + try { + await API.delete(`/api/purchase-demand/${id}`); + alert('删除成功'); + loadList(); + } catch (e) { + alert(e.message || '删除失败'); + } + } + + async function batchDelete() { + const checked = document.querySelectorAll('.row-checkbox:checked'); + if (checked.length === 0) { + alert('请先选择要删除的项'); + return; + } + if (!confirm(`确定要删除选中的 ${checked.length} 条采购需求吗?`)) return; + + const ids = Array.from(checked).map(cb => parseInt(cb.dataset.id)); + try { + await API.post('/api/purchase-demand/batch-delete', { ids }); + alert('批量删除成功'); + loadList(); + } catch (e) { + alert(e.message || '批量删除失败'); + } + } + + function toggleSelectAll(checkbox) { + document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = checkbox.checked); + } + + function search() { + currentPage = 1; + renderList(); + } + + function resetSearch() { + document.getElementById('search-keyword').value = ''; + document.getElementById('filter-status').value = ''; + currentPage = 1; + renderList(); + } + + function goPage(page) { + currentPage = page; + renderList(); + } + + function escapeHtml(str) { + if (!str) return ''; + return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + } + + window.PurchaseDemand = { + search, + resetSearch, + delete: deleteDemand, + updateStatus, + closeCalcModal, + closeStatusModal, + doCalculate, + doUpdateStatus, + toggleSelectAll, + goPage + }; +})(); diff --git a/圆通@3x.svg b/圆通@3x.svg new file mode 100644 index 0000000..075977d --- /dev/null +++ b/圆通@3x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/拼多多.svg b/拼多多.svg new file mode 100644 index 0000000..ab63c6b --- /dev/null +++ b/拼多多.svg @@ -0,0 +1 @@ + \ No newline at end of file