From 16fb5030cbdffd82d7689fc6c6d6625933a362c8 Mon Sep 17 00:00:00 2001 From: Jay <193683414+sheephearttidy@users.noreply.github.com> Date: Wed, 26 Aug 2026 00:46:11 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E6=AC=A1=E5=AE=8C=E6=88=90=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E7=99=BB=E5=BD=95=EF=BC=8C=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E3=80=82=E9=A6=96=E6=AC=A1=E5=AE=8C=E6=88=90?= =?UTF-8?q?=EF=BC=8Cmigrate=5Fmanager.py=E8=BF=81=E7=A7=BB=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E6=95=B0=E6=8D=AE=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .flaskenv | 1 + .gitignore | 1 + .idea/.gitignore | 10 + .idea/LoveCards.iml | 11 + .idea/MarsCodeWorkspaceAppSettings.xml | 7 + .idea/dataSources.xml | 12 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + __pycache__/migrate_manager.cpython-312.pyc | Bin 0 -> 591 bytes app/__pycache__/app.cpython-312.pyc | Bin 0 -> 751 bytes app/__pycache__/config.cpython-312.pyc | Bin 0 -> 541 bytes .../migrate_manager.cpython-312.pyc | Bin 0 -> 316 bytes app/app.py | 18 ++ app/config.py | 18 ++ app/model/Card.py | 6 + app/model/User.py | 9 + app/model/__init__.py | 6 + app/model/__pycache__/Card.cpython-312.pyc | Bin 0 -> 548 bytes app/model/__pycache__/User.cpython-312.pyc | Bin 0 -> 874 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 294 bytes app/model/__pycache__/db.cpython-312.pyc | Bin 0 -> 750 bytes app/model/db.py | 13 + app/route/__init__.py | 5 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 302 bytes app/route/__pycache__/admin.cpython-312.pyc | Bin 0 -> 781 bytes app/route/__pycache__/auth.cpython-312.pyc | Bin 0 -> 1650 bytes app/route/__pycache__/public.cpython-312.pyc | Bin 0 -> 684 bytes app/route/admin.py | 11 + app/route/auth.py | 34 +++ app/route/public.py | 21 ++ app/static/css/admin.css | 260 ++++++++++++++++++ app/static/css/auth.css | 71 +++++ app/static/css/login.css | 63 +++++ app/static/css/register.css | 67 +++++ app/static/js/admin.js | 62 +++++ app/static/js/login.js | 23 ++ app/static/js/register.js | 43 +++ app/templates/admin/admin_index.html | 63 +++++ app/templates/public/about.html | 59 ++++ app/templates/public/index.html | 13 + app/templates/public/user/login.html | 49 ++++ app/templates/public/user/profile.html | 10 + app/templates/public/user/register.html | 64 +++++ migrate_manager.py | 10 + migrations/README | 1 + migrations/__pycache__/env.cpython-312.pyc | Bin 0 -> 4480 bytes migrations/alembic.ini | 50 ++++ migrations/env.py | 113 ++++++++ migrations/script.py.mako | 24 ++ migrations/versions/67c0c267b827_创建表.py | 37 +++ .../5655a068d090_创建表.cpython-312.pyc | Bin 0 -> 1822 bytes .../67c0c267b827_创建表.cpython-312.pyc | Bin 0 -> 1587 bytes requirements.txt | Bin 0 -> 778 bytes 54 files changed, 1285 insertions(+) create mode 100644 .flaskenv create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/LoveCards.iml create mode 100644 .idea/MarsCodeWorkspaceAppSettings.xml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 __pycache__/migrate_manager.cpython-312.pyc create mode 100644 app/__pycache__/app.cpython-312.pyc create mode 100644 app/__pycache__/config.cpython-312.pyc create mode 100644 app/__pycache__/migrate_manager.cpython-312.pyc create mode 100644 app/app.py create mode 100644 app/config.py create mode 100644 app/model/Card.py create mode 100644 app/model/User.py create mode 100644 app/model/__init__.py create mode 100644 app/model/__pycache__/Card.cpython-312.pyc create mode 100644 app/model/__pycache__/User.cpython-312.pyc create mode 100644 app/model/__pycache__/__init__.cpython-312.pyc create mode 100644 app/model/__pycache__/db.cpython-312.pyc create mode 100644 app/model/db.py create mode 100644 app/route/__init__.py create mode 100644 app/route/__pycache__/__init__.cpython-312.pyc create mode 100644 app/route/__pycache__/admin.cpython-312.pyc create mode 100644 app/route/__pycache__/auth.cpython-312.pyc create mode 100644 app/route/__pycache__/public.cpython-312.pyc create mode 100644 app/route/admin.py create mode 100644 app/route/auth.py create mode 100644 app/route/public.py create mode 100644 app/static/css/admin.css create mode 100644 app/static/css/auth.css create mode 100644 app/static/css/login.css create mode 100644 app/static/css/register.css create mode 100644 app/static/js/admin.js create mode 100644 app/static/js/login.js create mode 100644 app/static/js/register.js create mode 100644 app/templates/admin/admin_index.html create mode 100644 app/templates/public/about.html create mode 100644 app/templates/public/index.html create mode 100644 app/templates/public/user/login.html create mode 100644 app/templates/public/user/profile.html create mode 100644 app/templates/public/user/register.html create mode 100644 migrate_manager.py create mode 100644 migrations/README create mode 100644 migrations/__pycache__/env.cpython-312.pyc create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/67c0c267b827_创建表.py create mode 100644 migrations/versions/__pycache__/5655a068d090_创建表.cpython-312.pyc create mode 100644 migrations/versions/__pycache__/67c0c267b827_创建表.cpython-312.pyc create mode 100644 requirements.txt diff --git a/.flaskenv b/.flaskenv new file mode 100644 index 0000000..7252d7b --- /dev/null +++ b/.flaskenv @@ -0,0 +1 @@ +FLASK_APP=migrate_manager \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0cafc1c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.venv/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..f6906f2 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# 已忽略包含查询文件的默认文件夹 +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/LoveCards.iml b/.idea/LoveCards.iml new file mode 100644 index 0000000..bde4e67 --- /dev/null +++ b/.idea/LoveCards.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/MarsCodeWorkspaceAppSettings.xml b/.idea/MarsCodeWorkspaceAppSettings.xml new file mode 100644 index 0000000..b26fdc6 --- /dev/null +++ b/.idea/MarsCodeWorkspaceAppSettings.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..1fa01e0 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://localhost:3306/test + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..98ce2a2 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/__pycache__/migrate_manager.cpython-312.pyc b/__pycache__/migrate_manager.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5ad209899098ae19f649156e664a6682cec74ee GIT binary patch literal 591 zcmZ{gu}i~16vp2rX>&=9L5ho0#VLz}AfgmO#Z3hV5keuPZAtj3Blxn0??BW4y zkwFdD7;q{wX~xaa33q~8u7yAWf{8&gNw+&$M^5@nM(%LcuT~wz_3iMG`8DDori!|w zawRBoZ7(K_Ya2cb9Gk0gJ?2U!=33RS9r1Kn7QZ#Fl`3JG>p{rMJ|!G`-a!}<&*RoX zQ2bEcuQtVsO@Qh|TF literal 0 HcmV?d00001 diff --git a/app/__pycache__/app.cpython-312.pyc b/app/__pycache__/app.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62087795f3c4f8b144d30b67f7d8182e10264760 GIT binary patch literal 751 zcmah`O=}ZD7=GttKeB63q@-%CB3NiH{(vGPRE-DmR1lWHvYVa6O?P&e$!;N@5(JZr z(3}1RsXxJg;HeTZj1(15-U7XOawbi%Vxce0JJ0*R&-2VM^RZfWKt=rFb$15={M5xV z=mH#XsmU9NAVLXn2-JxbGl2{ zlh!NS;0uRD2P+IQ70%7on$`mgk#_g<+qBM6}*Sp5psBUt$c8;7v*!*s`wUuFX` z1u}Am&KIa1LT!I<0yn1M4)Dkwy5sE$T>1{=pV_i1CU9M87qfB#>oW)Fh4G6|(jE@K$2#up-aJ(#B~>gDW!^~5Mj%9B&DtugK%=!;x>+5%T2K- z(J4zITLYoswVOvz#e0{G;-)~iPASiPMnGO>H_gsJ^z`urhcBkrki*l{%{Dz8d zo9xbiBMsftw{+JG_8jXox|x6I&$L-+*6BgB+&!ViR-c5;R>j^pzufE%ne@2R zC2x=Dll~gC!%|S#0lDTd#&POyq{R2`U!wH2fbd!@-HP~lc|FpukQ}S4ksK?xBQe%i zqGYVB0x8;DAa^CGr;XFnS$Lj5&x~{9{joh+p5!N)X>MAdTC?5RyIK1(QTxFmyxw1d GiN64WUY+m& literal 0 HcmV?d00001 diff --git a/app/__pycache__/migrate_manager.cpython-312.pyc b/app/__pycache__/migrate_manager.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..debf5a953ce507c47835e533841b22ea3adc693a GIT binary patch literal 316 zcmX@j%ge<81W)$#W|aZy#~=<2us|7~oq&w#3@HpLj5!Rsj8Tk?AU0DDQ!Y~!6PV4M z!<@?!#gfY!#hS|&#m30M#E{CE%9P5onh9b?6niC$ChJQON0aduyKiQCQDR9dkjb1_ zP@u_tizy}P7E5SxYSAqg=ft8EKTVEXylFX!#o6(>5T!-TK&?d}7u{mb%}+_qDPjc) zuz?76kSIG;;YxSF}rVp$;Zftit!@h*enT?XO%3f&V5YpH>}`8_1>^W=R_rA9!nQgB^-(&vW}{pIwav!$U06e!{# zkdKgI%(1Y13jv00pjH#8%}~+pHxJIX6|?U|j+gi}!b1wvysjw`Rw|5=LYX&2sW4~) zuzRAD)zAV4KBC}bihK(Lvzb#&{(l}b#gA4S$LtS9w&y5&Ba~Fx1zq=b;%G%4i%>@7 zghe^eRW2c=P%uK&2qB_O8{BY`kZL33y_*yv^)Sy_ET#FGG()MOol>10GO6$}eSnV1 z{hi-j2@Y63*e#zj6I~1Rh=F!yJTN*_iDbe!m}0DIXoBQFxvTMfYjRpk gq<3vjmoEz;^bYBFSopRvTE1Mo$zElDKx4Q059vIAR{#J2 literal 0 HcmV?d00001 diff --git a/app/model/__pycache__/User.cpython-312.pyc b/app/model/__pycache__/User.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57a2c699e3dd8ff871cb9b8bb311bbec3d32ba07 GIT binary patch literal 874 zcmaKqy>HV%7{>4XkvM5W7%ET;2&n=IFBvM3Dufs)X;VZi#D`FpkP*xKo zo~V!tsKR2_HtKbZ^r~wGwo^TZ0GPV}U|(AR0}Ude2?%PU0I-DRS|(l|X(S+_D^4Ag zypla~?eu?YrG2(yJ{To;6H_X6B9)9$LX1kENGUNY?llvp)HIbId6-ZsVDX_U(3v_h zZG(jRa~#sF5%{j@R0+1R@|3?=-mXmZW*@h0m?FcD2W@QLYcQiXTNOR0=) z!?dVUH$3m9OVDR<$SCl4p>VzY7+1=7-4}S-Ajm7T!!0v&Y?b)}^YxGOQy-@i0Stu}8BbLZQo_Wj+`PHB&Hp7gHvFAs9XW^tIAYpd;r zU3Ev@Tkhn0vwdlhDKrc5dav`ece{UWkXw6QB=Zx`4K+cPaX)!fiI#fGYgmR=eTMBY z?-F~w2~=L^Jx9IHad`ZPb4*d?MzonX$wij(`{P|;;g<*@JOF18z?I*U4DY}0(awoQDVoeh%s|N^77)P- zB>XhlZ*k=2r=;fSr6hsnZgD}lAZ=iEFaeN$u%27&@$rc{Iq~r;89oDzVECoxY84Zn zniS)cUzQ3|RUDI8P!Iz)CMG^UGcU6wK3=b&@)m~;P^>g3)vkyasErYbi$#IN2WCb_ Y#=8vScNwIC=o1?Qw|pae5jRi<09qVI=l}o! literal 0 HcmV?d00001 diff --git a/app/model/__pycache__/db.cpython-312.pyc b/app/model/__pycache__/db.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2fb8e68374d0bb3de3d8615945f17e7f67b73bb GIT binary patch literal 750 zcma)4J8u&~5T4z8;v~)s3MzuEC|rsNi4LJifDw&}geZ_sC$08w*Km06;XB)k-9!;7 zC@7KA@CT6jBZxl`R&qr}M`Tx2?4E5AQDt;DJKx+lkDa;iYil)7@+tWE?i&L5sligL zg)wa@;~W?;Bp^XPLfXaxCx&lSwjs>K@+|}yo&z(_fLR=kjd{FpW8lVkk<537w&(oP z$U}0y$s<7v+KUJL2`#yD)n2`9Jr>bho(vUoU+^An(jJW#@zvojz;s_3=fJ^741BEX zFvKu3n8_?=f406lV|6PnzBzQg8`9OebfwX)_?Yb8i!#wqQgWXN8gk*4qej20+&vl! zPEwk1ue{2ddq$0@a}84a8l=tqf0AcO93M;E?@AnXByOuW@2F91s^W3_hD2F9;At<; z(j&!nN!2DHvPMXfvA*h?$W=nReJXUjgcN#_(oEDOzra)vwiq#g;}@-kX}}KAPX~u(++D-fY=W i>bHRqn!x@9_9k#=h8^U5IGKSuF1NtmcpseND#>5B)65wF literal 0 HcmV?d00001 diff --git a/app/model/db.py b/app/model/db.py new file mode 100644 index 0000000..5e79a2c --- /dev/null +++ b/app/model/db.py @@ -0,0 +1,13 @@ +from sqlalchemy.orm import DeclarativeBase +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import MetaData +class DataBase(DeclarativeBase): + meta = MetaData(naming_convention={ + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(column_0_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s", + "pk": "pk_%(table_name)s" + }) + +db = SQLAlchemy() \ No newline at end of file diff --git a/app/route/__init__.py b/app/route/__init__.py new file mode 100644 index 0000000..547f903 --- /dev/null +++ b/app/route/__init__.py @@ -0,0 +1,5 @@ +from route.public import public +from route.auth import auth +from route.admin import admin + +__all__ = ['public', 'auth', 'admin'] \ No newline at end of file diff --git a/app/route/__pycache__/__init__.cpython-312.pyc b/app/route/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..841e2a6c21448ed4a1b4ef8cf62394741622afcf GIT binary patch literal 302 zcmX@j%ge<81oJL;XSD$7#~=<2FhLog4Sy7Cl;j?$0QaM#DKNM#K&jmWtPOp>lIYq;;_lhPbtkwwJYKU jYGVZAVqqZhftit!@h*e>T?Xm946={7R2$igxPjsTlS536 literal 0 HcmV?d00001 diff --git a/app/route/__pycache__/admin.cpython-312.pyc b/app/route/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e75d32aecccaff279c1023e7e3b1312a7d8409b5 GIT binary patch literal 781 zcmZuu&ubGw6rP#g&2AG?sez)+!Gcg-3(dhsR6-9*@gh}dK?s3mbtWe1ZgzdUt8KxO zg3zKDJxFrwRnmk1hnLoP8Uzu&c&p^*$(h+igE%nneecaT@8^enK8wV8J9yP)sFPOmh!6xGg|p^kiVK5G(*kDvRo&rVZJWNWDr`<|_X%)>KjpIom8;DcPDO+an~riF-PU-R?7F0S6JJzx)O zcY~+wwhJ_>xnWp?AcijGcv+8Av);0IlaHTx7(ZjXkPeSnCn*F zR1UKzg~bEwA1gO^T)aBSEg$R4L-7->*%mkIo*Qj~oV+SpmB}r~k*zuo$UNLo)u_Dh z5ELYLM*6LaAJD{OD{xMja%Rzr5T_c(_zNnZpz>F1=JloC_4n2%>qf^o%^+g#7x#(> a>%H>fLa)>*4)B$qM91Z!hRC8qD*kU<-LBpM literal 0 HcmV?d00001 diff --git a/app/route/__pycache__/auth.cpython-312.pyc b/app/route/__pycache__/auth.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ba7b4c70679c8296f61abc928eec29dfee5bdae GIT binary patch literal 1650 zcmb_cO>7fa5Pom}ZO2K7+k`j=tW=;{l^nGtP@<@ZR-tK&_)(~kEKAnL?^_sW4 z1uTPtghV(X;1sKo^s$P47I~RvUqIwFdP;==mD7T(EZ`T_q5eN>=Jh@l-Q%2|?p+uy1gq`0(SVja9Y#|%xu`T5#Th7Z^*d$A_)w~+WvZdL2 zUdKp66cdFSt5TRFAEU9#$5G<$m#9P|`n>db*OSCKJk{QIMIo=FDkJos^@x7 zHuE2i%))U`3o znJ-tl{N2<=uADzNa}gYLn|cclaZBA25K=&a-l3gefGs0$_g|$`{jLE7f-irH5Xk7*@~~FK3g*OYkwX3q&3}|Sh=urZKcxgJJmK$ zZ6(s}kuTR1$J@&B9Y}`iu`x#H%dSV+zUhfu2h%$ZoqEsd-Tsi%dq*xMn6cEIX#+KI zLwc;PidzHSkUi0|fGWW@M_R`w26BKJa)b+v-Vxn*AQaSSKOy!|^!H6j>q3F6)Hcgj zR_>bEAcWS3_W~@MPl&NtfHBq&ZcH4*1ge<^jO#9dwCuognuKtzk7zbfY^Y@VMM2~5m1H6Wx(DabesJ{%RLW!nwQr4k1nZOsiD^4Kc=5fZKQqx zcpCBh@w@T+y?1*bT-z`*9sPwdyk-n<7$chkohQ~4C)>(N_7NO=Hk;(?oMpO~nHXzW zx_~A(Y==-QM`pv)F&o7mG}wfUCh?1&b6E_8SWFcWR!5Xy2k=vr+IB zFM~S|Y+J?{|Bc4i(D+L<^b(D{()%9_HV?1sql?P6h9vDy`c`^ry6Ly(n$BW+9p_$4 n8Xn(~kuBrWwm7}dA!s+wZQVf43oW2O8;3EG6F}zDX literal 0 HcmV?d00001 diff --git a/app/route/__pycache__/public.cpython-312.pyc b/app/route/__pycache__/public.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3754bef1ade5bb6d7d091df3334d4bf3f9e0963 GIT binary patch literal 684 zcmbtRze~eF6n=O4VPdUf9TasGp&*T;h&U*2(#1h3fsi&QR+BbdQ&DlKBIw}YrvHNU zPjL~t-6EokTP2&5@2;(i;N*k*?z_DAefRF=A)6frjMMz}zD)pLVZ=za#944DQ=P z>*C3HPw!OG_NcIzMBzQ*auWBDBErYq#ZkFzLI`(|a?3zHW%h|Kpt4fFD-YFrAsRneSD_PeXB= 0) { + errorBox.textContent = errors.join(';'); + errorBox.style.display = 'block'; + return; + } + + errorBox.style.display = 'none'; + submitBtn.disabled = true; + submitBtn.textContent = '登录中…'; + + setTimeout(function () { + form.style.display = 'none'; + document.getElementById('welcomeName').textContent = username; + successPanel.style.display = 'block'; + submitBtn.disabled = false; + submitBtn.textContent = '登 录'; + }, 900); +}); + +document.getElementById('togglePwd').addEventListener('click', function () { + var isHidden = passwordInput.type === 'password'; + passwordInput.type = isHidden ? 'text' : 'password'; + this.setAttribute('aria-label', isHidden ? '隐藏密码' : '显示密码'); + this.style.color = isHidden ? '#3b82f6' : ''; +}); + +function updateCapslockHint(e) { + var hint = document.getElementById('capslockHint'); + var capsOn = e.getModifierState && e.getModifierState('CapsLock'); + hint.classList.toggle('show', !!capsOn); +} +passwordInput.addEventListener('keydown', updateCapslockHint); +passwordInput.addEventListener('keyup', updateCapslockHint); + +document.getElementById('backBtn').addEventListener('click', function () { + successPanel.style.display = 'none'; + form.style.display = 'block'; + form.reset(); + document.getElementById('username').focus(); +}); \ No newline at end of file diff --git a/app/static/js/login.js b/app/static/js/login.js new file mode 100644 index 0000000..b50b425 --- /dev/null +++ b/app/static/js/login.js @@ -0,0 +1,23 @@ +document.getElementById('loginForm').addEventListener('submit', function (e) { + var errorBox = document.getElementById('errorBox'); + var username = document.getElementById('username').value.trim(); + var password = document.getElementById('password').value; + var errors = []; + + if (!username) { + errors.push('用户名不能为空'); + } + if (!password) { + errors.push('密码不能为空'); + } else if (password.length < 6) { + errors.push('密码长度至少为 6 位'); + } + + if (errors.length > 0) { + e.preventDefault(); + errorBox.textContent = errors.join(';'); + errorBox.style.display = 'block'; + } else { + errorBox.style.display = 'none'; + } +}); \ No newline at end of file diff --git a/app/static/js/register.js b/app/static/js/register.js new file mode 100644 index 0000000..b1b13de --- /dev/null +++ b/app/static/js/register.js @@ -0,0 +1,43 @@ +document.getElementById('registerForm').addEventListener('submit', function (e) { + var errorBox = document.getElementById('errorBox'); + var username = document.getElementById('username').value.trim(); + var email = document.getElementById('email').value.trim(); + var password = document.getElementById('password').value; + var confirmPassword = document.getElementById('confirmPassword').value; + var agree = document.getElementById('agree').checked; + var errors = []; + + if (!username) { + errors.push('用户名不能为空'); + } else if (username.length < 3 || username.length > 20) { + errors.push('用户名长度需在 3-20 个字符之间'); + } + + if (!email) { + errors.push('邮箱不能为空'); + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { + errors.push('邮箱格式不正确'); + } + + if (!password) { + errors.push('密码不能为空'); + } else if (password.length < 6) { + errors.push('密码长度至少为 6 位'); + } + + if (password !== confirmPassword) { + errors.push('两次输入的密码不一致'); + } + + if (!agree) { + errors.push('请先阅读并同意服务条款和隐私政策'); + } + + if (errors.length > 0) { + e.preventDefault(); + errorBox.textContent = errors.join(';'); + errorBox.style.display = 'block'; + } else { + errorBox.style.display = 'none'; + } +}); \ No newline at end of file diff --git a/app/templates/admin/admin_index.html b/app/templates/admin/admin_index.html new file mode 100644 index 0000000..e0c800a --- /dev/null +++ b/app/templates/admin/admin_index.html @@ -0,0 +1,63 @@ + + + + + + 后台管理系统 - 管理员登录 + + + + +
+
+ +

后台管理系统

+

ADMIN CONSOLE

+
+ + + +
+
+ +
+ +
+
+ +
+ +
+ + +
+

提示:大写锁定(Caps Lock)已开启

+
+ + +
+ +
+
+ +
+

登录成功

+

欢迎,管理员
本页面为前端登录演示,未连接真实账号系统。

+ +
+ +

演示提示:输入任意用户名和 6 位以上密码即可体验完整登录流程。
接入真实后端时,将表单 action 替换为实际登录接口地址,并移除脚本中的演示拦截逻辑即可。

+
+ + + + \ No newline at end of file diff --git a/app/templates/public/about.html b/app/templates/public/about.html new file mode 100644 index 0000000..7ee13c8 --- /dev/null +++ b/app/templates/public/about.html @@ -0,0 +1,59 @@ + + + + + + 关于我 | About Me + + + + + + + + + + + +
+ +
+ + +
+ +

关于作者

+ + + + + GitHub Profile + + + + + 发送邮件 + + + +
+

创作经历

+

+ 我是一名热爱开源的开发者,长期活跃于 GitHub 社区。 + 在这里,我分享我的代码片段、开源项目以及技术学习笔记。 + 我相信代码可以改变世界,也希望通过我的项目能为社区带来一些微小的价值。 +

+

+ 主要技术栈: Python, Flask, HTML/CSS, JavaScript +

+
+
+ + + \ No newline at end of file diff --git a/app/templates/public/index.html b/app/templates/public/index.html new file mode 100644 index 0000000..33852ef --- /dev/null +++ b/app/templates/public/index.html @@ -0,0 +1,13 @@ + + + + + Title + + + + + +Index + + \ No newline at end of file diff --git a/app/templates/public/user/login.html b/app/templates/public/user/login.html new file mode 100644 index 0000000..3a23457 --- /dev/null +++ b/app/templates/public/user/login.html @@ -0,0 +1,49 @@ + + + + + + 用户登录 + + + + + +
+

欢迎登录

+

请输入您的账号信息

+ + + +
+
+ + +
+ +
+ + +
+ +
+ + 忘记密码? +
+ + +
+ + +
+ + + + \ No newline at end of file diff --git a/app/templates/public/user/profile.html b/app/templates/public/user/profile.html new file mode 100644 index 0000000..8619f40 --- /dev/null +++ b/app/templates/public/user/profile.html @@ -0,0 +1,10 @@ + + + + + 个人主页 + + + + + \ No newline at end of file diff --git a/app/templates/public/user/register.html b/app/templates/public/user/register.html new file mode 100644 index 0000000..166e757 --- /dev/null +++ b/app/templates/public/user/register.html @@ -0,0 +1,64 @@ + + + + + + 用户注册 + + + + + +
+

创建账号

+

填写以下信息完成注册

+ + + +
+
+ + +
+ +
+ + +
+ +
+ + +

建议使用字母、数字和符号的组合,提高安全性

+
+ +
+ + +
+ + + + +
+ + +
+ + + + \ No newline at end of file diff --git a/migrate_manager.py b/migrate_manager.py new file mode 100644 index 0000000..0185dc7 --- /dev/null +++ b/migrate_manager.py @@ -0,0 +1,10 @@ +import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app')) + +from flask_migrate import Migrate +from app import app +from model import db, User, Card + +migrate = Migrate(app, db) \ No newline at end of file diff --git a/migrations/README b/migrations/README new file mode 100644 index 0000000..0e04844 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Single-database configuration for Flask. diff --git a/migrations/__pycache__/env.cpython-312.pyc b/migrations/__pycache__/env.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ece34adeb98e149e3a18a6387ca1d0478e2d8482 GIT binary patch literal 4480 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 N?_A3>uFZwRe*hV7z@`8I literal 0 HcmV?d00001 diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 0000000..ec9d45c --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000..4c97092 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,113 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except (TypeError, AttributeError): + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/67c0c267b827_创建表.py b/migrations/versions/67c0c267b827_创建表.py new file mode 100644 index 0000000..df088dd --- /dev/null +++ b/migrations/versions/67c0c267b827_创建表.py @@ -0,0 +1,37 @@ +"""创建表 + +Revision ID: 67c0c267b827 +Revises: +Create Date: 2026-08-26 00:13:19.101390 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '67c0c267b827' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('username', sa.String(length=80), nullable=False), + sa.Column('email', sa.String(length=120), nullable=True), + sa.Column('password', sa.String(length=200), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email'), + sa.UniqueConstraint('password'), + sa.UniqueConstraint('username') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('user') + # ### end Alembic commands ### diff --git a/migrations/versions/__pycache__/5655a068d090_创建表.cpython-312.pyc b/migrations/versions/__pycache__/5655a068d090_创建表.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5640cb74baaed326949b8e9c259d1deb599f6ea1 GIT binary patch literal 1822 zcmb^yOK1~8bU(A7tc@S62J0m%6oORKNMa4*!B(Mus%S+Cfn}Q++AaHuvztor(t@BD zp{0c?3gWF&51tjgdhjHTD5EzO^roi0JG04{R0}EMz9?B=Pxop--=LTTV}|jK#h4O_ zgd!JA@+A2I-JuvNoSjrT)!L!dCf8f%oLX)YP<{%QnOC~nZbTovamq22mYWfAeX&!F zN}I+1ALB+A)i#e^Jo47wA?uASYMZpEZ}=TIvKZT>#eaC2Hp^RES#t_%`~^Gc#Wty@ za=qnP!-ptTg_JQuK*_?8NH#o8%U<^?j$@5Eh=59fFwq(IEEf?4xt8sW61{8%!3`h7 zl#W>~lCDzJTV@ztlLCvVgT7O7J)#YJAsVMvx-VkewQ%(;st)>I5Ms;rLZV&{vF(kM z?N>bedIhy4PlP%PaqR>}r}D+CXsme7pF)E)&YVl$mxD#2Le7OHn>VE3M!DmVh{8QLaNV(-(gbp=nDr6Sar1vb;>?%XV#l!Ml_pGD-^dAw z0Z|!wFfe^Dst(M;_%mGnxa-4;gHGW6RLW**kfG?iEQ;a}Nfp7`RzXaB5E9>Yp{r+F af4{wZI^IC>9n;27b(^Txdu6dF%HkLBY+JGb literal 0 HcmV?d00001 diff --git a/migrations/versions/__pycache__/67c0c267b827_创建表.cpython-312.pyc b/migrations/versions/__pycache__/67c0c267b827_创建表.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7fadbf08055204e93e327f8f8bb424eaca7af76b GIT binary patch literal 1587 zcmb7EUx*V&7@yrtHk-XfJZedeRMQiMK)oh8dUpn~#k&e;^^{ssLSWfshTI-zxmDg&3xZH zOQ#J4R7Y{uimC`c=EN$P;7XjtDjLiLOr6B9 zp-PfyK#g`RpJ-iz?Qd?}fAi$Gzkgjbj34Nd9oW8?o0~1?Ca3C!dTDa1HeH(PS!hts z88eJpAcS#N z8W>0qb;ls7RTL`Huqcz13RsB}#-+HZNp7$bb;zy{^paceGbHv9uZvS*4DI+BVtmLR zJxFhtq(pm|SntRDKT!2;P?JNb#*Skh*#BL+HB(cF7TahkheF z^ovWs^aEF-xHsyG;%@eIAxD(2Gayy9bA;!#h=H1$=}Pbl{(q+$DelaH`36!!&ck3+iS5-=F3N62OpCLxnrV} z{p#_d4*nJlf8u@5@xHsW8~BSz$~?!JyNL~)8%V|mKKP-uHJXfT>@$e=gPs>xE@pQe z$o4%i>mYi6b)Jbf&8xkKGYIpWy=P+?&_{={y}?@v>`vj6Q)W0x@q!+G?;KSwPHmc#Zrv;;nm;lw$=DZQy%}Bot>HX z<2|vJ&24RWwy@OBxjJ^jedeFXb_B8l+t?Cb(J+L!O?c{f3a;rP=cnA=3d=!r<`Q-! z&S+PRfiuS7iS4TC9+>v`eqb#d**yyKp)FnCP&@e2XxSy(vWBN!gFp2qa5DE*l1rL$ zt=*C8yXMrc(H-G5wMTF3;1jjmj=PoGoNEJbZe_#hquF=Hg3}#25;z&00