Compare commits
	
		
			524 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					ebe73e2f19 | ||
| 
						 | 
					44805ce580 | ||
| 
						 | 
					2a6d620830 | ||
| 
						 | 
					01d3e1ad28 | ||
| 
						 | 
					f4162c38db | ||
| 
						 | 
					1a4626c24d | ||
| 
						 | 
					d6eb9683d1 | ||
| 
						 | 
					e2b524dadb | ||
| 
						 | 
					8998a21626 | ||
| 
						 | 
					abc015aec0 | ||
| 
						 | 
					4ef8d27b1e | ||
| 
						 | 
					40b6e603fc | ||
| 
						 | 
					21498584b1 | ||
| 
						 | 
					408bac09a1 | ||
| 
						 | 
					582d879a77 | ||
| 
						 | 
					38ff5152e0 | ||
| 
						 | 
					d1d372e1bf | ||
| 
						 | 
					5e4793433b | ||
| 
						 | 
					54ad19f97e | ||
| 
						 | 
					fc166650b3 | ||
| 
						 | 
					2acc295259 | ||
| 
						 | 
					4b3ed1310d | ||
| 
						 | 
					b2cfd1517c | ||
| 
						 | 
					b13d27ccd6 | ||
| 
						 | 
					68e0088016 | ||
| 
						 | 
					bd1e83989d | ||
| 
						 | 
					263dfa6be7 | ||
| 
						 | 
					eb55f93864 | ||
| 
						 | 
					8589105e44 | ||
| 
						 | 
					986b187f0a | ||
| 
						 | 
					008d34c453 | ||
| 
						 | 
					49d3f988c9 | ||
| 
						 | 
					76475e807e | ||
| 
						 | 
					f93231da61 | ||
| 
						 | 
					bf75483a3c | ||
| 
						 | 
					b56b0187cf | ||
| 
						 | 
					7e7f02b502 | ||
| 
						 | 
					878985f7c5 | ||
| 
						 | 
					2133d9b737 | ||
| 
						 | 
					d711a36749 | ||
| 
						 | 
					9dbf104ef1 | ||
| 
						 | 
					20eb06fb28 | ||
| 
						 | 
					9c20bdef39 | ||
| 
						 | 
					3fdd98a390 | ||
| 
						 | 
					d4f456c0cf | ||
| 
						 | 
					f2b6e15cf4 | ||
| 
						 | 
					6be0ea6aed | ||
| 
						 | 
					eee08be2cc | ||
| 
						 | 
					252fc553f2 | ||
| 
						 | 
					ac2ceed3f9 | ||
| 
						 | 
					3f828cc5b0 | ||
| 
						 | 
					fc1b9ef35d | ||
| 
						 | 
					d0b71a1c40 | ||
| 
						 | 
					a743a6a05a | ||
| 
						 | 
					0e6b9713ce | ||
| 
						 | 
					b9afbc764d | ||
| 
						 | 
					923e183a67 | ||
| 
						 | 
					7e9a381641 | ||
| 
						 | 
					bed95254d0 | ||
| 
						 | 
					e4d13f3377 | ||
| 
						 | 
					d530365ef9 | ||
| 
						 | 
					070d4ea104 | ||
| 
						 | 
					3fc86f0fae | ||
| 
						 | 
					3b77ab2727 | ||
| 
						 | 
					76cb991282 | ||
| 
						 | 
					9efd20f1b9 | ||
| 
						 | 
					de5b9e46d3 | ||
| 
						 | 
					f27d3d200f | ||
| 
						 | 
					f4a64b96a9 | ||
| 
						 | 
					9a59749763 | ||
| 
						 | 
					b017b902f8 | ||
| 
						 | 
					7c53353c60 | ||
| 
						 | 
					63f0615445 | ||
| 
						 | 
					94da6df33e | ||
| 
						 | 
					cc3981d99c | ||
| 
						 | 
					8332d9b354 | ||
| 
						 | 
					493925064c | ||
| 
						 | 
					c0232c4c75 | ||
| 
						 | 
					b873855b44 | ||
| 
						 | 
					9c524292f0 | ||
| 
						 | 
					bfd346e65a | ||
| 
						 | 
					bc811cbd49 | ||
| 
						 | 
					bbec3eca0d | ||
| 
						 | 
					3857d674ba | ||
| 
						 | 
					25b0ae4d2f | ||
| 
						 | 
					b7aa281611 | ||
| 
						 | 
					3c89a285f5 | ||
| 
						 | 
					d3d26c85c3 | ||
| 
						 | 
					a764c4f974 | ||
| 
						 | 
					af454f7d5d | ||
| 
						 | 
					eea759e10e | ||
| 
						 | 
					e158422091 | ||
| 
						 | 
					5ada63d4a1 | ||
| 
						 | 
					5ef35001cc | ||
| 
						 | 
					0be50f0995 | ||
| 
						 | 
					61e1b0ca70 | ||
| 
						 | 
					85910bf440 | ||
| 
						 | 
					7a7a7020b4 | ||
| 
						 | 
					ae3d2659aa | ||
| 
						 | 
					76fd6675b5 | ||
| 
						 | 
					664118a709 | ||
| 
						 | 
					e344722794 | ||
| 
						 | 
					1a7d425f60 | ||
| 
						 | 
					a0582192bf | ||
| 
						 | 
					4ac9f02d6a | ||
| 
						 | 
					7e0febef8f | ||
| 
						 | 
					94ed4b77d6 | ||
| 
						 | 
					2b419bca11 | ||
| 
						 | 
					54a0f0b3c7 | ||
| 
						 | 
					86bccc3b3d | ||
| 
						 | 
					0e601b5033 | ||
| 
						 | 
					85d745fcee | ||
| 
						 | 
					550631c03b | ||
| 
						 | 
					f29a1560aa | ||
| 
						 | 
					8c4c41cf0b | ||
| 
						 | 
					f5c90277b1 | ||
| 
						 | 
					2ae0cd7ab4 | ||
| 
						 | 
					1f6c14ee2f | ||
| 
						 | 
					574d27f6da | ||
| 
						 | 
					7d62841783 | ||
| 
						 | 
					970d74bd70 | ||
| 
						 | 
					0c797f8da9 | ||
| 
						 | 
					68f8603c75 | ||
| 
						 | 
					06bce33c48 | ||
| 
						 | 
					f8837f28c3 | ||
| 
						 | 
					61aed08dde | ||
| 
						 | 
					a5a813f95f | ||
| 
						 | 
					18cf2e54c4 | ||
| 
						 | 
					5c72b1de57 | ||
| 
						 | 
					6e44e90d67 | ||
| 
						 | 
					0e699ba20e | ||
| 
						 | 
					cf24c2671f | ||
| 
						 | 
					d3b99ec88d | ||
| 
						 | 
					0b5ab090a4 | ||
| 
						 | 
					454698286c | ||
| 
						 | 
					14e0aadbba | ||
| 
						 | 
					73986a834c | ||
| 
						 | 
					8faf1831d9 | ||
| 
						 | 
					d86ef0a9ab | ||
| 
						 | 
					c2bb0c589e | ||
| 
						 | 
					9994f20a2c | ||
| 
						 | 
					b5014c307f | ||
| 
						 | 
					75bd4ca3df | ||
| 
						 | 
					d00bd2ed72 | ||
| 
						 | 
					e444500835 | ||
| 
						 | 
					6709135a0b | ||
| 
						 | 
					59a7ff9ac7 | ||
| 
						 | 
					172c729535 | ||
| 
						 | 
					ac5198db1c | ||
| 
						 | 
					5c5c2c2037 | ||
| 
						 | 
					1db990b554 | ||
| 
						 | 
					70c887a16a | ||
| 
						 | 
					2430c4f6aa | ||
| 
						 | 
					84fd14c129 | ||
| 
						 | 
					a376a82240 | ||
| 
						 | 
					e1e03dc09a | ||
| 
						 | 
					790d644c34 | ||
| 
						 | 
					9de8dae954 | ||
| 
						 | 
					57361d8241 | ||
| 
						 | 
					b347bd7ef5 | ||
| 
						 | 
					070c8ac0da | ||
| 
						 | 
					e221c2f42e | ||
| 
						 | 
					c7bab3a71b | ||
| 
						 | 
					82c17a51a2 | ||
| 
						 | 
					e4447e6bc2 | ||
| 
						 | 
					b9570d9a5f | ||
| 
						 | 
					01e8a2c14d | ||
| 
						 | 
					64bd51c3b0 | ||
| 
						 | 
					54ab34df3f | ||
| 
						 | 
					206490ba3e | ||
| 
						 | 
					16612d2c9c | ||
| 
						 | 
					6b65605360 | ||
| 
						 | 
					bb37ed3b95 | ||
| 
						 | 
					d102cc8c08 | ||
| 
						 | 
					a6df74d63d | ||
| 
						 | 
					f79760943e | ||
| 
						 | 
					a40ec21a05 | ||
| 
						 | 
					43230267b6 | ||
| 
						 | 
					0ae99cdaf9 | ||
| 
						 | 
					f234c72514 | ||
| 
						 | 
					76527d95bd | ||
| 
						 | 
					27c53385f2 | ||
| 
						 | 
					a1b25e9766 | ||
| 
						 | 
					abad0ed481 | ||
| 
						 | 
					eddda41291 | ||
| 
						 | 
					d9adf0fd25 | ||
| 
						 | 
					0ce82b41ba | ||
| 
						 | 
					37026f3269 | ||
| 
						 | 
					3155380f16 | ||
| 
						 | 
					f2b0f294d8 | ||
| 
						 | 
					12f63ef3dd | ||
| 
						 | 
					a1303b52eb | ||
| 
						 | 
					10f6b03fb5 | ||
| 
						 | 
					45d2449221 | ||
| 
						 | 
					9e5f146e05 | ||
| 
						 | 
					2b91bbe185 | ||
| 
						 | 
					747ea6404d | ||
| 
						 | 
					ccfc6bd1df | ||
| 
						 | 
					361eafedae | ||
| 
						 | 
					a64b894b08 | ||
| 
						 | 
					0ad805c170 | ||
| 
						 | 
					ba82b5b516 | ||
| 
						 | 
					f04b82c933 | ||
| 
						 | 
					23b137ab9b | ||
| 
						 | 
					a4d3a4627a | ||
| 
						 | 
					77ae6e3bab | ||
| 
						 | 
					e0f1f40ba0 | ||
| 
						 | 
					d300f604f1 | ||
| 
						 | 
					2c2c0ff40b | ||
| 
						 | 
					b4ddbbd38f | ||
| 
						 | 
					7544288451 | ||
| 
						 | 
					41443dccc0 | ||
| 
						 | 
					22e218fc5f | ||
| 
						 | 
					4da0d1abaa | ||
| 
						 | 
					6563b53436 | ||
| 
						 | 
					fac71a4794 | ||
| 
						 | 
					92dff6fdc3 | ||
| 
						 | 
					a1eca3d691 | ||
| 
						 | 
					6681dc1057 | ||
| 
						 | 
					829a68feaa | ||
| 
						 | 
					72677e270d | ||
| 
						 | 
					dd4ac390de | ||
| 
						 | 
					0bd7d38c23 | ||
| 
						 | 
					ead3b0d0d8 | ||
| 
						 | 
					4b973b22a4 | ||
| 
						 | 
					e4e68d02bc | ||
| 
						 | 
					ef8822d671 | ||
| 
						 | 
					8e75e1f6ef | ||
| 
						 | 
					08c381fa60 | ||
| 
						 | 
					d7a10d4032 | ||
| 
						 | 
					c324a030f9 | ||
| 
						 | 
					b618b8f93b | ||
| 
						 | 
					4d2e110e1e | ||
| 
						 | 
					ecd79a2e15 | ||
| 
						 | 
					f4f297d3f7 | ||
| 
						 | 
					b5549c0fae | ||
| 
						 | 
					929bfb3200 | ||
| 
						 | 
					7d3593a944 | ||
| 
						 | 
					9e0db2bc99 | ||
| 
						 | 
					25b0d276b3 | ||
| 
						 | 
					0cb7a7cf83 | ||
| 
						 | 
					52f72400ba | ||
| 
						 | 
					0eaff33168 | ||
| 
						 | 
					086dbf278b | ||
| 
						 | 
					57a5e237ae | ||
| 
						 | 
					eee6cf7b14 | ||
| 
						 | 
					b9c6ac8d6d | ||
| 
						 | 
					618d782af3 | ||
| 
						 | 
					d0ac7de4cb | ||
| 
						 | 
					baf8053613 | ||
| 
						 | 
					b973d63331 | ||
| 
						 | 
					85b64d7e8d | ||
| 
						 | 
					86ad183c41 | ||
| 
						 | 
					f7b685cfad | ||
| 
						 | 
					649116a0b8 | ||
| 
						 | 
					899a3a8243 | ||
| 
						 | 
					d51cd4b289 | ||
| 
						 | 
					537b179e78 | ||
| 
						 | 
					1e5b1868ab | ||
| 
						 | 
					245406673c | ||
| 
						 | 
					51fa197af6 | ||
| 
						 | 
					649b2bb165 | ||
| 
						 | 
					3634c902d0 | ||
| 
						 | 
					756e580469 | ||
| 
						 | 
					4e1350d1cc | ||
| 
						 | 
					2e969d46fb | ||
| 
						 | 
					a5bcbe151d | ||
| 
						 | 
					c4abba361a | ||
| 
						 | 
					24b46b1133 | ||
| 
						 | 
					3ae7e0de75 | ||
| 
						 | 
					c2ee4f9955 | ||
| 
						 | 
					2479412334 | ||
| 
						 | 
					6da8d7fd67 | ||
| 
						 | 
					0f596a712d | ||
| 
						 | 
					8f37b71d7f | ||
| 
						 | 
					5083b2bdfe | ||
| 
						 | 
					155ae65b4a | ||
| 
						 | 
					ffacfc3ae8 | ||
| 
						 | 
					b1ab66ecf9 | ||
| 
						 | 
					f5bb0cad3e | ||
| 
						 | 
					a0de5afcb0 | ||
| 
						 | 
					358d33d60e | ||
| 
						 | 
					062d28b6e6 | ||
| 
						 | 
					513f8ea012 | ||
| 
						 | 
					179b58e557 | ||
| 
						 | 
					b7450f8869 | ||
| 
						 | 
					7f9e972828 | ||
| 
						 | 
					7b51705f4e | ||
| 
						 | 
					6bd9e5333d | ||
| 
						 | 
					112d735ac0 | ||
| 
						 | 
					52553ed53f | ||
| 
						 | 
					70d84e32d1 | ||
| 
						 | 
					3269dfa5d6 | ||
| 
						 | 
					183a6e4905 | ||
| 
						 | 
					5463ae9d7e | ||
| 
						 | 
					f25bdb07ce | ||
| 
						 | 
					aa5c08d564 | ||
| 
						 | 
					dc9a2985f3 | ||
| 
						 | 
					f4ac6d8360 | ||
| 
						 | 
					3266039aaf | ||
| 
						 | 
					e3f4c298b0 | ||
| 
						 | 
					fa58f6d2de | ||
| 
						 | 
					ae5a1fd7de | ||
| 
						 | 
					c240079df4 | ||
| 
						 | 
					aca4e6751e | ||
| 
						 | 
					ce32fc7f2c | ||
| 
						 | 
					d423572e01 | ||
| 
						 | 
					d9807b1bf0 | ||
| 
						 | 
					1bc53b4c80 | ||
| 
						 | 
					6bc2603a4d | ||
| 
						 | 
					e2c929aae1 | ||
| 
						 | 
					0d155d592b | ||
| 
						 | 
					ae510ff1ff | ||
| 
						 | 
					5b0654ad2c | ||
| 
						 | 
					466f97ecbe | ||
| 
						 | 
					27a14c22d7 | ||
| 
						 | 
					4709edcd1c | ||
| 
						 | 
					414de9f2eb | ||
| 
						 | 
					a53e7e7dab | ||
| 
						 | 
					7fa6628dc5 | ||
| 
						 | 
					62c25afea8 | ||
| 
						 | 
					481b622e3b | ||
| 
						 | 
					64f8f9a200 | ||
| 
						 | 
					0eca951465 | ||
| 
						 | 
					ef4e34c584 | ||
| 
						 | 
					d91acbc7ee | ||
| 
						 | 
					b397d1022e | ||
| 
						 | 
					b42a98aff5 | ||
| 
						 | 
					adc65439e4 | ||
| 
						 | 
					445cf3716b | ||
| 
						 | 
					f4b59b8503 | ||
| 
						 | 
					4f08975df2 | ||
| 
						 | 
					570db453d7 | ||
| 
						 | 
					b93984bf6f | ||
| 
						 | 
					e483db1b97 | ||
| 
						 | 
					17d96acceb | ||
| 
						 | 
					9900b236ef | ||
| 
						 | 
					4fa52412c1 | ||
| 
						 | 
					0076869deb | ||
| 
						 | 
					af55193591 | ||
| 
						 | 
					1d858118d5 | ||
| 
						 | 
					8e64ba67fa | ||
| 
						 | 
					58fb11b78f | ||
| 
						 | 
					f6e9076a40 | ||
| 
						 | 
					2fefa43aea | ||
| 
						 | 
					f43b0467ba | ||
| 
						 | 
					14da4d77e0 | ||
| 
						 | 
					cefad86b85 | ||
| 
						 | 
					738ff86344 | ||
| 
						 | 
					110abc4ac7 | ||
| 
						 | 
					5f1aaa40d8 | ||
| 
						 | 
					0695ad9a85 | ||
| 
						 | 
					7c086bbec8 | ||
| 
						 | 
					c75fe7135a | ||
| 
						 | 
					edf29976dd | ||
| 
						 | 
					7b5f963ec4 | ||
| 
						 | 
					f22badb861 | ||
| 
						 | 
					8c501c90cd | ||
| 
						 | 
					11eebdfcf0 | ||
| 
						 | 
					46e331783f | ||
| 
						 | 
					7e33e64659 | ||
| 
						 | 
					7e4152ad6d | ||
| 
						 | 
					d189ee7c22 | ||
| 
						 | 
					641c2abb24 | ||
| 
						 | 
					3ab4ac891b | ||
| 
						 | 
					70b586e45a | ||
| 
						 | 
					77aa724003 | ||
| 
						 | 
					db7b50d96a | ||
| 
						 | 
					00fee24a85 | ||
| 
						 | 
					fa0cb73ec9 | ||
| 
						 | 
					6ee815b71d | ||
| 
						 | 
					311a048af5 | ||
| 
						 | 
					3da9ecfaa3 | ||
| 
						 | 
					4c2c6f613e | ||
| 
						 | 
					ba63736871 | ||
| 
						 | 
					ed3dbafc35 | ||
| 
						 | 
					fdeffbd495 | ||
| 
						 | 
					9870812e6b | ||
| 
						 | 
					46f35e14c4 | ||
| 
						 | 
					e89cf96ff4 | ||
| 
						 | 
					5cd19bf38d | ||
| 
						 | 
					a6f69f2b62 | ||
| 
						 | 
					e34b9adada | ||
| 
						 | 
					9f43f731b5 | ||
| 
						 | 
					594ca43505 | ||
| 
						 | 
					2a91cdb67a | ||
| 
						 | 
					11148e720b | ||
| 
						 | 
					c5835d6d8c | ||
| 
						 | 
					4a26bb3ba5 | ||
| 
						 | 
					4fec38724d | ||
| 
						 | 
					85349df8a1 | ||
| 
						 | 
					ffe250f8a9 | ||
| 
						 | 
					eeb310a1d2 | ||
| 
						 | 
					a42c606d20 | ||
| 
						 | 
					4e64d46cd2 | ||
| 
						 | 
					9886ee6828 | ||
| 
						 | 
					0e1bde09c3 | ||
| 
						 | 
					dda600709b | ||
| 
						 | 
					15f38491b2 | ||
| 
						 | 
					4b140732f7 | ||
| 
						 | 
					4cb9ff3f14 | ||
| 
						 | 
					e4f3e2c4c1 | ||
| 
						 | 
					a80adb7dd8 | ||
| 
						 | 
					195127a9d4 | ||
| 
						 | 
					f2119b2c52 | ||
| 
						 | 
					f15c45793b | ||
| 
						 | 
					24f543e667 | ||
| 
						 | 
					772995705f | ||
| 
						 | 
					3475c39fe6 | ||
| 
						 | 
					e6e393379f | ||
| 
						 | 
					03cc91c3e5 | ||
| 
						 | 
					f82f7bec6a | ||
| 
						 | 
					4afd5bbd5e | ||
| 
						 | 
					86aac2bf08 | ||
| 
						 | 
					70c8b25a67 | ||
| 
						 | 
					231af72444 | ||
| 
						 | 
					480e930385 | ||
| 
						 | 
					debc34f0fb | ||
| 
						 | 
					99ce3bd099 | ||
| 
						 | 
					99431cf9a2 | ||
| 
						 | 
					83711c69f9 | ||
| 
						 | 
					9e67032280 | ||
| 
						 | 
					fa37937410 | ||
| 
						 | 
					1be2cad78e | ||
| 
						 | 
					2b1e687ed4 | ||
| 
						 | 
					881009321b | ||
| 
						 | 
					aed99b63b8 | ||
| 
						 | 
					dfa34ba371 | ||
| 
						 | 
					20beb30dd8 | ||
| 
						 | 
					4475972af3 | ||
| 
						 | 
					a843c65783 | ||
| 
						 | 
					c2de1d3fa2 | ||
| 
						 | 
					c8d091da06 | ||
| 
						 | 
					553208ba57 | ||
| 
						 | 
					072028699a | ||
| 
						 | 
					9cdcf145a5 | ||
| 
						 | 
					4df1c19e81 | ||
| 
						 | 
					ac26a214bc | ||
| 
						 | 
					ad616496d1 | ||
| 
						 | 
					9870582779 | ||
| 
						 | 
					20cc696b33 | ||
| 
						 | 
					d7263f2b3c | ||
| 
						 | 
					74e5ee41fb | ||
| 
						 | 
					f936331dff | ||
| 
						 | 
					ba311c3504 | ||
| 
						 | 
					03291594b1 | ||
| 
						 | 
					a6d9a4b5ae | ||
| 
						 | 
					875de022c1 | ||
| 
						 | 
					2c863a2774 | ||
| 
						 | 
					f2f086a82c | ||
| 
						 | 
					936ca61f94 | ||
| 
						 | 
					87ae2f81fa | ||
| 
						 | 
					ecf67db2b1 | ||
| 
						 | 
					2e5589e112 | ||
| 
						 | 
					2598a60898 | ||
| 
						 | 
					0de226bbf3 | ||
| 
						 | 
					422f0d8491 | ||
| 
						 | 
					b028708b94 | ||
| 
						 | 
					812c0d0f6a | ||
| 
						 | 
					46df5293dd | ||
| 
						 | 
					ab42b3e90b | ||
| 
						 | 
					1378259cc7 | ||
| 
						 | 
					c8f0b0a83f | ||
| 
						 | 
					acec760ec1 | ||
| 
						 | 
					2fe70d49f6 | ||
| 
						 | 
					9013fff804 | ||
| 
						 | 
					e925a808c4 | ||
| 
						 | 
					6c197edddd | ||
| 
						 | 
					c35e91b7b6 | ||
| 
						 | 
					575947795a | ||
| 
						 | 
					51f116c7d2 | ||
| 
						 | 
					c28254855c | ||
| 
						 | 
					e8f3671ffb | ||
| 
						 | 
					ac62767a18 | ||
| 
						 | 
					2db4c20dd3 | ||
| 
						 | 
					cfb7fd5b29 | ||
| 
						 | 
					22c401f9d8 | ||
| 
						 | 
					be00b90c1d | ||
| 
						 | 
					fb3f89c594 | ||
| 
						 | 
					e7a66378ea | ||
| 
						 | 
					2f88b48973 | ||
| 
						 | 
					7761fe0288 | ||
| 
						 | 
					09e6bdcf7e | ||
| 
						 | 
					61a4d87f59 | ||
| 
						 | 
					c219ec33b0 | ||
| 
						 | 
					fd86f36218 | ||
| 
						 | 
					efac41f392 | ||
| 
						 | 
					52df61ae0d | ||
| 
						 | 
					cf2bc6785c | ||
| 
						 | 
					98a4c92576 | ||
| 
						 | 
					b1ee9b65ff | ||
| 
						 | 
					99cc4c5e5e | ||
| 
						 | 
					226bb8f089 | ||
| 
						 | 
					37ed5134e8 | ||
| 
						 | 
					0f54d4a472 | ||
| 
						 | 
					64805360d6 | ||
| 
						 | 
					7f69fe2ad9 | ||
| 
						 | 
					f913510d3c | ||
| 
						 | 
					f2d9e7786d | ||
| 
						 | 
					e1afb1ed54 | ||
| 
						 | 
					12f8cf0111 | ||
| 
						 | 
					daa2ef5203 | ||
| 
						 | 
					1e3e183930 | ||
| 
						 | 
					366563a0fe | ||
| 
						 | 
					577802e5ad | ||
| 
						 | 
					76d6fc3ba5 | ||
| 
						 | 
					f0540559bb | ||
| 
						 | 
					802e379f60 | ||
| 
						 | 
					8c9253da80 | ||
| 
						 | 
					5271bd21e8 | ||
| 
						 | 
					db554ebdc9 | ||
| 
						 | 
					1c18a01bf6 | ||
| 
						 | 
					729a3d7028 | ||
| 
						 | 
					b88923a128 | ||
| 
						 | 
					fe8cd93c78 | ||
| 
						 | 
					64b49dae2e | ||
| 
						 | 
					edbbbca5f9 | ||
| 
						 | 
					5ad0d90038 | ||
| 
						 | 
					9b9173dea7 | ||
| 
						 | 
					f58331c1c1 | ||
| 
						 | 
					b2dc9dff0b | ||
| 
						 | 
					51d06ab206 | ||
| 
						 | 
					799e0ac9fc | ||
| 
						 | 
					56e7a8843b | 
							
								
								
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -15,4 +15,12 @@
 | 
			
		||||
*.sum
 | 
			
		||||
 | 
			
		||||
*/node_modules/
 | 
			
		||||
**/vendor/
 | 
			
		||||
**/vendor/
 | 
			
		||||
.idea
 | 
			
		||||
.vscode
 | 
			
		||||
out
 | 
			
		||||
 | 
			
		||||
server/docs/docker-compose
 | 
			
		||||
server/config.yml
 | 
			
		||||
server/ip2region.xdb
 | 
			
		||||
mayfly-go.log
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,18 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
    // 使用 IntelliSense 了解相关属性。 
 | 
			
		||||
    // 悬停以查看现有属性的描述。
 | 
			
		||||
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
 | 
			
		||||
    "version": "0.2.0",
 | 
			
		||||
    "configurations": [
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            "name": "mayfly-go",
 | 
			
		||||
            "type": "go",
 | 
			
		||||
            "request": "launch",
 | 
			
		||||
            "mode": "auto",
 | 
			
		||||
            "program": "${fileDirname}/main.go",
 | 
			
		||||
            "env": {},
 | 
			
		||||
            "args": []
 | 
			
		||||
        },
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,43 @@
 | 
			
		||||
# 构建前端资源
 | 
			
		||||
FROM node:18-bookworm-slim as fe-builder
 | 
			
		||||
 | 
			
		||||
WORKDIR /mayfly
 | 
			
		||||
 | 
			
		||||
COPY mayfly_go_web .
 | 
			
		||||
 | 
			
		||||
RUN yarn config set registry 'https://registry.npmmirror.com' && \
 | 
			
		||||
    yarn install && \
 | 
			
		||||
    yarn build
 | 
			
		||||
 | 
			
		||||
# 构建后端资源
 | 
			
		||||
FROM golang:1.22 as be-builder
 | 
			
		||||
 | 
			
		||||
ENV GOPROXY https://goproxy.cn
 | 
			
		||||
WORKDIR /mayfly
 | 
			
		||||
 | 
			
		||||
# Copy the go source for building server
 | 
			
		||||
COPY server .
 | 
			
		||||
 | 
			
		||||
RUN go mod tidy && go mod download
 | 
			
		||||
 | 
			
		||||
COPY --from=fe-builder /mayfly/dist /mayfly/static/static
 | 
			
		||||
 | 
			
		||||
# Build
 | 
			
		||||
RUN GO111MODULE=on CGO_ENABLED=0 GOOS=linux \
 | 
			
		||||
    go build -a -ldflags=-w \
 | 
			
		||||
    -o mayfly-go main.go
 | 
			
		||||
 | 
			
		||||
FROM debian:bookworm-slim
 | 
			
		||||
 | 
			
		||||
RUN apt-get update && \
 | 
			
		||||
    apt-get install -y ca-certificates expat libncurses5 && \
 | 
			
		||||
    apt-get clean
 | 
			
		||||
 | 
			
		||||
ENV TZ=Asia/Shanghai
 | 
			
		||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
 | 
			
		||||
 | 
			
		||||
WORKDIR /mayfly
 | 
			
		||||
 | 
			
		||||
COPY --from=be-builder /mayfly/mayfly-go /usr/local/bin/mayfly-go
 | 
			
		||||
 | 
			
		||||
CMD ["mayfly-go"]
 | 
			
		||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						@@ -1,6 +1,6 @@
 | 
			
		||||
                                 Apache License
 | 
			
		||||
                           Version 2.0, January 2004
 | 
			
		||||
                        http://www.apache.org/licenses/
 | 
			
		||||
                        http://www.apache.org/licenses/ 
 | 
			
		||||
 | 
			
		||||
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								README.en.md
									
									
									
									
									
								
							
							
						
						@@ -1,36 +0,0 @@
 | 
			
		||||
# mayfly-go
 | 
			
		||||
 | 
			
		||||
#### Description
 | 
			
		||||
golang实现linux运维等
 | 
			
		||||
 | 
			
		||||
#### Software Architecture
 | 
			
		||||
Software architecture description
 | 
			
		||||
 | 
			
		||||
#### Installation
 | 
			
		||||
 | 
			
		||||
1.  xxxx
 | 
			
		||||
2.  xxxx
 | 
			
		||||
3.  xxxx
 | 
			
		||||
 | 
			
		||||
#### Instructions
 | 
			
		||||
 | 
			
		||||
1.  xxxx
 | 
			
		||||
2.  xxxx
 | 
			
		||||
3.  xxxx
 | 
			
		||||
 | 
			
		||||
#### Contribution
 | 
			
		||||
 | 
			
		||||
1.  Fork the repository
 | 
			
		||||
2.  Create Feat_xxx branch
 | 
			
		||||
3.  Commit your code
 | 
			
		||||
4.  Create Pull Request
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### Gitee Feature
 | 
			
		||||
 | 
			
		||||
1.  You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
 | 
			
		||||
2.  Gitee blog [blog.gitee.com](https://blog.gitee.com)
 | 
			
		||||
3.  Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
 | 
			
		||||
4.  The most valuable open source project [GVP](https://gitee.com/gvp)
 | 
			
		||||
5.  The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
 | 
			
		||||
6.  The most popular members  [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
 | 
			
		||||
							
								
								
									
										98
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -1,33 +1,103 @@
 | 
			
		||||
# mayfly-go
 | 
			
		||||
# 🌈mayfly-go
 | 
			
		||||
 | 
			
		||||
#### 介绍
 | 
			
		||||
简单基于DDD(领域驱动设计)分层架构实现web版mysql,redis,linux统一操作管理平台
 | 
			
		||||
<p align="center">
 | 
			
		||||
  <a href="https://gitee.com/dromara/mayfly-go" target="_blank">
 | 
			
		||||
    <img src="https://gitee.com/dromara/mayfly-go/badge/star.svg?theme=white" alt="star"/>
 | 
			
		||||
    <img src="https://gitee.com/dromara/mayfly-go/badge/fork.svg" alt="fork"/>
 | 
			
		||||
  </a>
 | 
			
		||||
  <a href="https://github.com/dromara/mayfly-go" target="_blank">
 | 
			
		||||
    <img src="https://img.shields.io/github/stars/dromara/mayfly-go.svg?style=social" alt="github star"/>
 | 
			
		||||
    <img src="https://img.shields.io/github/forks/dromara/mayfly-go.svg?style=social" alt="github fork"/>
 | 
			
		||||
  </a>
 | 
			
		||||
  <a href="https://hub.docker.com/r/mayflygo/mayfly-go/tags" target="_blank">
 | 
			
		||||
    <img src="https://img.shields.io/docker/pulls/mayflygo/mayfly-go.svg?label=docker%20pulls&color=fac858" alt="docker pulls"/>
 | 
			
		||||
  </a>
 | 
			
		||||
  <a href="https://github.com/golang/go" target="_blank">
 | 
			
		||||
    <img src="https://img.shields.io/badge/Golang-1.22%2B-yellow.svg" alt="golang"/>
 | 
			
		||||
  </a>
 | 
			
		||||
  <a href="https://cn.vuejs.org" target="_blank">
 | 
			
		||||
    <img src="https://img.shields.io/badge/Vue-3.x-green.svg" alt="vue">
 | 
			
		||||
  </a>
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
### 介绍
 | 
			
		||||
 | 
			
		||||
web 版 **linux(终端[终端回放] 文件 脚本 进程 计划任务)、数据库(mysql postgres oracle sqlserver 达梦 高斯 sqlite)、redis(单机 哨兵 集群)、mongo 等集工单流程审批于一体的统一管理操作平台**
 | 
			
		||||
 | 
			
		||||
### 开发语言与主要框架
 | 
			
		||||
 | 
			
		||||
- 前端:typescript、vue3、element-plus
 | 
			
		||||
- 后端:golang、gin、gorm
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 交流及问题反馈加 QQ 群
 | 
			
		||||
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?jump_from=webapi">119699946</a>
 | 
			
		||||
 | 
			
		||||
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=IdJSHW0jTMhmWFHBUS9a83wxtrxDDhFj&jump_from=webapi">119699946</a>
 | 
			
		||||
 | 
			
		||||
### 系统相关资料
 | 
			
		||||
- 项目文档: https://objs.gitee.io/mayfly-go-docs
 | 
			
		||||
 | 
			
		||||
- 项目文档: https://www.yuque.com/may-fly/mayfly-go
 | 
			
		||||
- 系统操作视频: https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854
 | 
			
		||||
 | 
			
		||||
### 系统功能
 | 
			
		||||
### 演示环境
 | 
			
		||||
 | 
			
		||||
记录操作记录
 | 
			
		||||

 | 
			
		||||
http://go.mayfly.run
 | 
			
		||||
账号/密码:test/test123.
 | 
			
		||||
 | 
			
		||||
### 系统核心功能截图
 | 
			
		||||
 | 
			
		||||
##### 记录操作记录
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
#### 机器操作
 | 
			
		||||
 | 
			
		||||
##### 状态查看
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
##### ssh 终端
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
##### 文件操作
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
#### 数据库操作
 | 
			
		||||
 | 
			
		||||
##### sql 编辑器
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
##### 在线增删改查数据
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
#### Redis 操作
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
#### Mongo 操作
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
##### 系统管理
 | 
			
		||||
 | 
			
		||||
##### 账号管理
 | 
			
		||||
 | 
			
		||||
#### 系统管理
 | 
			
		||||
账号管理
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
角色管理
 | 
			
		||||
##### 角色管理
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
资源管理
 | 
			
		||||
##### 资源管理
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
**其他更多功能&操作指南可查看在线文档**:  https://objs.gitee.io/mayfly-go-docs
 | 
			
		||||
**其他更多功能&操作指南可查看在线文档**: https://www.yuque.com/may-fly/mayfly-go
 | 
			
		||||
 | 
			
		||||
#### 💌 支持作者
 | 
			
		||||
 | 
			
		||||
如果觉得项目不错,或者已经在使用了,希望你可以去 <a target="_blank" href="https://github.com/dromara/mayfly-go">Github</a> 或者 <a target="_blank" href="https://gitee.com/dromara/mayfly-go">Gitee</a> 帮我点个 ⭐ Star,这将是对我极大的鼓励与支持。
 | 
			
		||||
 
 | 
			
		||||
@@ -1,74 +0,0 @@
 | 
			
		||||
package biz
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mayfly-go/base/global"
 | 
			
		||||
	"mayfly-go/base/utils"
 | 
			
		||||
 | 
			
		||||
	"reflect"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ErrIsNil(err error, msg string, params ...interface{}) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		global.Log.Error(msg + ": " + err.Error())
 | 
			
		||||
		panic(NewBizErr(fmt.Sprintf(msg, params...)))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ErrIsNilAppendErr(err error, msg string) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(NewBizErr(fmt.Sprintf(msg, err.Error())))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsNil(err error) {
 | 
			
		||||
	switch t := err.(type) {
 | 
			
		||||
	case *BizError:
 | 
			
		||||
		panic(t)
 | 
			
		||||
	case error:
 | 
			
		||||
		global.Log.Error("非业务异常: " + err.Error())
 | 
			
		||||
		panic(NewBizErr(fmt.Sprintf("非业务异常: %s", err.Error())))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsTrue(exp bool, msg string, params ...interface{}) {
 | 
			
		||||
	if !exp {
 | 
			
		||||
		panic(NewBizErr(fmt.Sprintf(msg, params...)))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsTrueBy(exp bool, err BizError) {
 | 
			
		||||
	if !exp {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NotEmpty(str string, msg string, params ...interface{}) {
 | 
			
		||||
	if str == "" {
 | 
			
		||||
		panic(NewBizErr(fmt.Sprintf(msg, params...)))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NotNil(data interface{}, msg string) {
 | 
			
		||||
	if reflect.ValueOf(data).IsNil() {
 | 
			
		||||
		panic(NewBizErr(msg))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NotBlank(data interface{}, msg string) {
 | 
			
		||||
	if utils.IsBlank(reflect.ValueOf(data)) {
 | 
			
		||||
		panic(NewBizErr(msg))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsEquals(data interface{}, data1 interface{}, msg string) {
 | 
			
		||||
	if data != data1 {
 | 
			
		||||
		panic(NewBizErr(msg))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Nil(data interface{}, msg string) {
 | 
			
		||||
	if !reflect.ValueOf(data).IsNil() {
 | 
			
		||||
		panic(NewBizErr(msg))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
package biz
 | 
			
		||||
 | 
			
		||||
// 业务错误
 | 
			
		||||
type BizError struct {
 | 
			
		||||
	code int16
 | 
			
		||||
	err  string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	Success       *BizError = NewBizErrCode(200, "success")
 | 
			
		||||
	BizErr        *BizError = NewBizErrCode(400, "biz error")
 | 
			
		||||
	ServerError   *BizError = NewBizErrCode(500, "server error")
 | 
			
		||||
	PermissionErr *BizError = NewBizErrCode(501, "token error")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 错误消息
 | 
			
		||||
func (e *BizError) Error() string {
 | 
			
		||||
	return e.err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 错误码
 | 
			
		||||
func (e *BizError) Code() int16 {
 | 
			
		||||
	return e.code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 创建业务逻辑错误结构体,默认为业务逻辑错误
 | 
			
		||||
func NewBizErr(msg string) *BizError {
 | 
			
		||||
	return &BizError{code: BizErr.code, err: msg}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 创建业务逻辑错误结构体,可设置指定错误code
 | 
			
		||||
func NewBizErrCode(code int16, msg string) *BizError {
 | 
			
		||||
	return &BizError{code: code, err: msg}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
package captcha
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mayfly-go/base/biz"
 | 
			
		||||
 | 
			
		||||
	"github.com/mojocn/base64Captcha"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var store = base64Captcha.DefaultMemStore
 | 
			
		||||
var driver base64Captcha.Driver = base64Captcha.DefaultDriverDigit
 | 
			
		||||
 | 
			
		||||
// 生成验证码
 | 
			
		||||
func Generate() (string, string) {
 | 
			
		||||
	c := base64Captcha.NewCaptcha(driver, store)
 | 
			
		||||
	// 获取
 | 
			
		||||
	id, b64s, err := c.Generate()
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "获取验证码错误: %s")
 | 
			
		||||
	return id, b64s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 验证验证码
 | 
			
		||||
func Verify(id string, val string) bool {
 | 
			
		||||
	if id == "" || val == "" {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	// 同时清理掉这个图片
 | 
			
		||||
	return store.Verify(id, val, true)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
type App struct {
 | 
			
		||||
	Name    string `yaml:"name"`
 | 
			
		||||
	Version string `yaml:"version"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *App) GetAppInfo() string {
 | 
			
		||||
	return fmt.Sprintf("[%s:%s]", a.Name, a.Version)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,62 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mayfly-go/base/utils"
 | 
			
		||||
	"mayfly-go/base/utils/assert"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 配置文件映射对象
 | 
			
		||||
var Conf *Config
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录")
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
	// 获取启动参数中,配置文件的绝对路径
 | 
			
		||||
	path, _ := filepath.Abs(*configFilePath)
 | 
			
		||||
	startConfigParam = &CmdConfigParam{ConfigFilePath: path}
 | 
			
		||||
	// 读取配置文件信息
 | 
			
		||||
	yc := &Config{}
 | 
			
		||||
	if err := utils.LoadYml(startConfigParam.ConfigFilePath, yc); err != nil {
 | 
			
		||||
		panic(fmt.Sprintf("读取配置文件[%s]失败: %s", startConfigParam.ConfigFilePath, err.Error()))
 | 
			
		||||
	}
 | 
			
		||||
	// 校验配置文件内容信息
 | 
			
		||||
	yc.Valid()
 | 
			
		||||
	Conf = yc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 启动配置参数
 | 
			
		||||
type CmdConfigParam struct {
 | 
			
		||||
	ConfigFilePath string // -e  配置文件路径
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 启动可执行文件时的参数
 | 
			
		||||
var startConfigParam *CmdConfigParam
 | 
			
		||||
 | 
			
		||||
// yaml配置文件映射对象
 | 
			
		||||
type Config struct {
 | 
			
		||||
	App    *App    `yaml:"app"`
 | 
			
		||||
	Server *Server `yaml:"server"`
 | 
			
		||||
	Jwt    *Jwt    `yaml:"jwt"`
 | 
			
		||||
	Redis  *Redis  `yaml:"redis"`
 | 
			
		||||
	Mysql  *Mysql  `yaml:"mysql"`
 | 
			
		||||
	Log    *Log    `yaml:"log"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 配置文件内容校验
 | 
			
		||||
func (c *Config) Valid() {
 | 
			
		||||
	assert.IsTrue(c.Jwt != nil, "配置文件的[jwt]信息不能为空")
 | 
			
		||||
	c.Jwt.Valid()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取执行可执行文件时,指定的启动参数
 | 
			
		||||
func getStartConfig() *CmdConfigParam {
 | 
			
		||||
	configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录")
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
	// 获取配置文件绝对路径
 | 
			
		||||
	path, _ := filepath.Abs(*configFilePath)
 | 
			
		||||
	sc := &CmdConfigParam{ConfigFilePath: path}
 | 
			
		||||
	return sc
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import "mayfly-go/base/utils/assert"
 | 
			
		||||
 | 
			
		||||
type Jwt struct {
 | 
			
		||||
	Key        string `yaml:"key"`
 | 
			
		||||
	ExpireTime uint64 `yaml:"expire-time"` // 过期时间,单位分钟
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (j *Jwt) Valid() {
 | 
			
		||||
	assert.IsTrue(j.Key != "", "config.yml之 [jwt.key] 不能为空")
 | 
			
		||||
	assert.IsTrue(j.ExpireTime != 0, "config.yml之 [jwt.expire-time] 不能为空")
 | 
			
		||||
}
 | 
			
		||||
@@ -1,30 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import "path"
 | 
			
		||||
 | 
			
		||||
type Log struct {
 | 
			
		||||
	Level string   `yaml:"level"`
 | 
			
		||||
	File  *LogFile `yaml:"file"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LogFile struct {
 | 
			
		||||
	Name string `yaml:"name"`
 | 
			
		||||
	Path string `yaml:"path"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取完整路径文件名
 | 
			
		||||
func (l *LogFile) GetFilename() string {
 | 
			
		||||
	var filepath, filename string
 | 
			
		||||
	if fp := l.Path; fp == "" {
 | 
			
		||||
		filepath = "./"
 | 
			
		||||
	} else {
 | 
			
		||||
		filepath = fp
 | 
			
		||||
	}
 | 
			
		||||
	if fn := l.Name; fn == "" {
 | 
			
		||||
		filename = "default.log"
 | 
			
		||||
	} else {
 | 
			
		||||
		filename = fn
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return path.Join(filepath, filename)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
type Mysql struct {
 | 
			
		||||
	Host         string `mapstructure:"path" json:"host" yaml:"host"`
 | 
			
		||||
	Config       string `mapstructure:"config" json:"config" yaml:"config"`
 | 
			
		||||
	Dbname       string `mapstructure:"db-name" json:"dbname" yaml:"db-name"`
 | 
			
		||||
	Username     string `mapstructure:"username" json:"username" yaml:"username"`
 | 
			
		||||
	Password     string `mapstructure:"password" json:"password" yaml:"password"`
 | 
			
		||||
	MaxIdleConns int    `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"`
 | 
			
		||||
	MaxOpenConns int    `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"`
 | 
			
		||||
	LogMode      bool   `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"`
 | 
			
		||||
	LogZap       string `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *Mysql) Dsn() string {
 | 
			
		||||
	return m.Username + ":" + m.Password + "@tcp(" + m.Host + ")/" + m.Dbname + "?" + m.Config
 | 
			
		||||
}
 | 
			
		||||
@@ -1,84 +0,0 @@
 | 
			
		||||
package ctx
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mayfly-go/base/biz"
 | 
			
		||||
	"mayfly-go/base/logger"
 | 
			
		||||
	"mayfly-go/base/utils"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"runtime/debug"
 | 
			
		||||
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type LogInfo struct {
 | 
			
		||||
	LogResp     bool   // 是否记录返回结果
 | 
			
		||||
	Description string // 请求描述
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewLogInfo(description string) *LogInfo {
 | 
			
		||||
	return &LogInfo{Description: description, LogResp: false}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (i *LogInfo) WithLogResp(logResp bool) *LogInfo {
 | 
			
		||||
	i.LogResp = logResp
 | 
			
		||||
	return i
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LogHandler(rc *ReqCtx) error {
 | 
			
		||||
	li := rc.LogInfo
 | 
			
		||||
	if li == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lfs := logrus.Fields{}
 | 
			
		||||
	if la := rc.LoginAccount; la != nil {
 | 
			
		||||
		lfs["uid"] = la.Id
 | 
			
		||||
		lfs["uname"] = la.Username
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req := rc.GinCtx.Request
 | 
			
		||||
	lfs[req.Method] = req.URL.Path
 | 
			
		||||
 | 
			
		||||
	if err := rc.Err; err != nil {
 | 
			
		||||
		logger.Log.WithFields(lfs).Error(getErrMsg(rc, err))
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	logger.Log.WithFields(lfs).Info(getLogMsg(rc))
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getLogMsg(rc *ReqCtx) string {
 | 
			
		||||
	msg := rc.LogInfo.Description + fmt.Sprintf(" ->%dms", rc.timed)
 | 
			
		||||
	if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) {
 | 
			
		||||
		rb, _ := json.Marshal(rc.ReqParam)
 | 
			
		||||
		msg = msg + fmt.Sprintf("\n--> %s", string(rb))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 返回结果不为空,则记录返回结果
 | 
			
		||||
	if rc.LogInfo.LogResp && !utils.IsBlank(reflect.ValueOf(rc.ResData)) {
 | 
			
		||||
		respB, _ := json.Marshal(rc.ResData)
 | 
			
		||||
		msg = msg + fmt.Sprintf("\n<-- %s", string(respB))
 | 
			
		||||
	}
 | 
			
		||||
	return msg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getErrMsg(rc *ReqCtx, err interface{}) string {
 | 
			
		||||
	msg := rc.LogInfo.Description
 | 
			
		||||
	if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) {
 | 
			
		||||
		rb, _ := json.Marshal(rc.ReqParam)
 | 
			
		||||
		msg = msg + fmt.Sprintf("\n--> %s", string(rb))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var errMsg string
 | 
			
		||||
	switch t := err.(type) {
 | 
			
		||||
	case *biz.BizError:
 | 
			
		||||
		errMsg = fmt.Sprintf("\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error())
 | 
			
		||||
	case error:
 | 
			
		||||
		errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t.Error(), string(debug.Stack()))
 | 
			
		||||
	case string:
 | 
			
		||||
		errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t, string(debug.Stack()))
 | 
			
		||||
	}
 | 
			
		||||
	return (msg + errMsg)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,113 +0,0 @@
 | 
			
		||||
package ctx
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mayfly-go/base/biz"
 | 
			
		||||
	"mayfly-go/base/cache"
 | 
			
		||||
	"mayfly-go/base/config"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Permission struct {
 | 
			
		||||
	NeedToken bool   // 是否需要token
 | 
			
		||||
	Code      string // 权限code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewPermission(code string) *Permission {
 | 
			
		||||
	return &Permission{NeedToken: true, Code: code}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Permission) WithNeedToken(needToken bool) *Permission {
 | 
			
		||||
	p.NeedToken = needToken
 | 
			
		||||
	return p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PermissionCodeRegistry interface {
 | 
			
		||||
	// 保存用户权限code
 | 
			
		||||
	SaveCodes(userId uint64, codes []string)
 | 
			
		||||
 | 
			
		||||
	// 判断用户是否拥有该code的权限
 | 
			
		||||
	HasCode(userId uint64, code string) bool
 | 
			
		||||
 | 
			
		||||
	Remove(userId uint64)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DefaultPermissionCodeRegistry struct {
 | 
			
		||||
	cache *cache.TimedCache
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *DefaultPermissionCodeRegistry) SaveCodes(userId uint64, codes []string) {
 | 
			
		||||
	if r.cache == nil {
 | 
			
		||||
		r.cache = cache.NewTimedCache(time.Minute*time.Duration(config.Conf.Jwt.ExpireTime), 5*time.Second)
 | 
			
		||||
	}
 | 
			
		||||
	r.cache.Put(fmt.Sprintf("%v", userId), codes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *DefaultPermissionCodeRegistry) HasCode(userId uint64, code string) bool {
 | 
			
		||||
	if r.cache == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	codes, found := r.cache.Get(fmt.Sprintf("%v", userId))
 | 
			
		||||
	if !found {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	for _, v := range codes.([]string) {
 | 
			
		||||
		if v == code {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *DefaultPermissionCodeRegistry) Remove(userId uint64) {
 | 
			
		||||
	r.cache.Delete(fmt.Sprintf("%v", userId))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 保存用户权限code
 | 
			
		||||
func SavePermissionCodes(userId uint64, codes []string) {
 | 
			
		||||
	permissionCodeRegistry.SaveCodes(userId, codes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 删除用户权限code
 | 
			
		||||
func DeletePermissionCodes(userId uint64) {
 | 
			
		||||
	permissionCodeRegistry.Remove(userId)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 设置权限code注册器
 | 
			
		||||
func SetPermissionCodeRegistery(pcr PermissionCodeRegistry) {
 | 
			
		||||
	permissionCodeRegistry = pcr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	permissionCodeRegistry PermissionCodeRegistry = &DefaultPermissionCodeRegistry{}
 | 
			
		||||
	// permissionError                               = biz.NewBizErrCode(biz.TokenErrorCode, biz.TokenErrorMsg)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func PermissionHandler(rc *ReqCtx) error {
 | 
			
		||||
	permission := rc.RequiredPermission
 | 
			
		||||
	// 如果需要的权限信息不为空,并且不需要token,则不返回错误,继续后续逻辑
 | 
			
		||||
	if permission != nil && !permission.NeedToken {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	tokenStr := rc.GinCtx.Request.Header.Get("Authorization")
 | 
			
		||||
	// header不存在则从查询参数token中获取
 | 
			
		||||
	if tokenStr == "" {
 | 
			
		||||
		tokenStr = rc.GinCtx.Query("token")
 | 
			
		||||
	}
 | 
			
		||||
	if tokenStr == "" {
 | 
			
		||||
		return biz.PermissionErr
 | 
			
		||||
	}
 | 
			
		||||
	loginAccount, err := ParseToken(tokenStr)
 | 
			
		||||
	if err != nil || loginAccount == nil {
 | 
			
		||||
		return biz.PermissionErr
 | 
			
		||||
	}
 | 
			
		||||
	// 权限不为nil,并且permission code不为空,则校验是否有权限code
 | 
			
		||||
	if permission != nil && permission.Code != "" {
 | 
			
		||||
		if !permissionCodeRegistry.HasCode(loginAccount.Id, permission.Code) {
 | 
			
		||||
			return biz.PermissionErr
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rc.LoginAccount = loginAccount
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,122 +0,0 @@
 | 
			
		||||
package ctx
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"mayfly-go/base/ginx"
 | 
			
		||||
	"mayfly-go/base/model"
 | 
			
		||||
	"mayfly-go/base/utils/assert"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 处理函数
 | 
			
		||||
type HandlerFunc func(*ReqCtx)
 | 
			
		||||
 | 
			
		||||
type ReqCtx struct {
 | 
			
		||||
	GinCtx *gin.Context // gin context
 | 
			
		||||
 | 
			
		||||
	// NeedToken          bool                // 是否需要token
 | 
			
		||||
	RequiredPermission *Permission         // 需要的权限信息,默认为nil,需要校验token
 | 
			
		||||
	LoginAccount       *model.LoginAccount // 登录账号信息,只有校验token后才会有值
 | 
			
		||||
 | 
			
		||||
	LogInfo  *LogInfo    // 日志相关信息
 | 
			
		||||
	ReqParam interface{} // 请求参数,主要用于记录日志
 | 
			
		||||
	ResData  interface{} // 响应结果
 | 
			
		||||
	Err      interface{} // 请求错误
 | 
			
		||||
 | 
			
		||||
	timed int64 // 执行时间
 | 
			
		||||
	noRes bool  // 无需返回结果,即文件下载等
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (rc *ReqCtx) Handle(handler HandlerFunc) {
 | 
			
		||||
	ginCtx := rc.GinCtx
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := recover(); err != nil {
 | 
			
		||||
			rc.Err = err
 | 
			
		||||
			ginx.ErrorRes(ginCtx, err)
 | 
			
		||||
		}
 | 
			
		||||
		// 应用所有请求后置处理器
 | 
			
		||||
		ApplyHandlerInterceptor(afterHandlers, rc)
 | 
			
		||||
	}()
 | 
			
		||||
	assert.IsTrue(ginCtx != nil, "ginContext == nil")
 | 
			
		||||
 | 
			
		||||
	// 默认为不记录请求参数,可在handler回调函数中覆盖赋值
 | 
			
		||||
	rc.ReqParam = 0
 | 
			
		||||
	// 默认响应结果为nil,可在handler中赋值
 | 
			
		||||
	rc.ResData = nil
 | 
			
		||||
 | 
			
		||||
	// 调用请求前所有处理器
 | 
			
		||||
	err := ApplyHandlerInterceptor(beforeHandlers, rc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	begin := time.Now()
 | 
			
		||||
	handler(rc)
 | 
			
		||||
	rc.timed = time.Now().Sub(begin).Milliseconds()
 | 
			
		||||
	if !rc.noRes {
 | 
			
		||||
		ginx.SuccessRes(ginCtx, rc.ResData)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (rc *ReqCtx) Download(reader io.Reader, filename string) {
 | 
			
		||||
	rc.noRes = true
 | 
			
		||||
	ginx.Download(rc.GinCtx, reader, filename)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 新建请求上下文,默认需要校验token
 | 
			
		||||
func NewReqCtx() *ReqCtx {
 | 
			
		||||
	return &ReqCtx{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewReqCtxWithGin(g *gin.Context) *ReqCtx {
 | 
			
		||||
	return &ReqCtx{GinCtx: g}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 调用该方法设置请求描述,则默认记录日志,并不记录响应结果
 | 
			
		||||
func (r *ReqCtx) WithLog(li *LogInfo) *ReqCtx {
 | 
			
		||||
	r.LogInfo = li
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 设置请求上下文需要的权限信息
 | 
			
		||||
func (r *ReqCtx) WithRequiredPermission(permission *Permission) *ReqCtx {
 | 
			
		||||
	r.RequiredPermission = permission
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 是否需要token
 | 
			
		||||
func (r *ReqCtx) WithNeedToken(needToken bool) *ReqCtx {
 | 
			
		||||
	r.RequiredPermission = &Permission{NeedToken: false}
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 处理器拦截器函数
 | 
			
		||||
type HandlerInterceptorFunc func(*ReqCtx) error
 | 
			
		||||
type HandlerInterceptors []HandlerInterceptorFunc
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	beforeHandlers HandlerInterceptors
 | 
			
		||||
	afterHandlers  HandlerInterceptors
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 使用前置处理器函数
 | 
			
		||||
func UseBeforeHandlerInterceptor(b HandlerInterceptorFunc) {
 | 
			
		||||
	beforeHandlers = append(beforeHandlers, b)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 使用后置处理器函数
 | 
			
		||||
func UseAfterHandlerInterceptor(b HandlerInterceptorFunc) {
 | 
			
		||||
	afterHandlers = append(afterHandlers, b)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 应用指定处理器拦截器,如果有一个错误则直接返回错误
 | 
			
		||||
func ApplyHandlerInterceptor(his HandlerInterceptors, rc *ReqCtx) interface{} {
 | 
			
		||||
	for _, handler := range his {
 | 
			
		||||
		if err := handler(rc); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,49 +0,0 @@
 | 
			
		||||
package ctx
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	"mayfly-go/base/biz"
 | 
			
		||||
	"mayfly-go/base/config"
 | 
			
		||||
	"mayfly-go/base/model"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/dgrijalva/jwt-go"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	JwtKey  = config.Conf.Jwt.Key
 | 
			
		||||
	ExpTime = config.Conf.Jwt.ExpireTime
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 创建用户token
 | 
			
		||||
func CreateToken(userId uint64, username string) string {
 | 
			
		||||
	// 带权限创建令牌
 | 
			
		||||
	// 设置有效期,过期需要重新登录获取token
 | 
			
		||||
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
 | 
			
		||||
		"id":       userId,
 | 
			
		||||
		"username": username,
 | 
			
		||||
		"exp":      time.Now().Add(time.Minute * time.Duration(ExpTime)).Unix(),
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// 使用自定义字符串加密 and get the complete encoded token as a string
 | 
			
		||||
	tokenString, err := token.SignedString([]byte(JwtKey))
 | 
			
		||||
	biz.ErrIsNil(err, "token创建失败")
 | 
			
		||||
	return tokenString
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 解析token,并返回登录者账号信息
 | 
			
		||||
func ParseToken(tokenStr string) (*model.LoginAccount, error) {
 | 
			
		||||
	if tokenStr == "" {
 | 
			
		||||
		return nil, errors.New("token error")
 | 
			
		||||
	}
 | 
			
		||||
	// Parse token
 | 
			
		||||
	token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
 | 
			
		||||
		return []byte(JwtKey), nil
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil || token == nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	i := token.Claims.(jwt.MapClaims)
 | 
			
		||||
	return &model.LoginAccount{Id: uint64(i["id"].(float64)), Username: i["username"].(string)}, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,78 +0,0 @@
 | 
			
		||||
package ginx
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"mayfly-go/base/biz"
 | 
			
		||||
	"mayfly-go/base/global"
 | 
			
		||||
	"mayfly-go/base/model"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 绑定并校验请求结构体参数
 | 
			
		||||
func BindJsonAndValid(g *gin.Context, data interface{}) {
 | 
			
		||||
	if err := g.ShouldBindJSON(data); err != nil {
 | 
			
		||||
		panic(biz.NewBizErr(err.Error()))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 绑定查询字符串到
 | 
			
		||||
func BindQuery(g *gin.Context, data interface{}) {
 | 
			
		||||
	if err := g.ShouldBindQuery(data); err != nil {
 | 
			
		||||
		panic(biz.NewBizErr(err.Error()))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取分页参数
 | 
			
		||||
func GetPageParam(g *gin.Context) *model.PageParam {
 | 
			
		||||
	return &model.PageParam{PageNum: QueryInt(g, "pageNum", 1), PageSize: QueryInt(g, "pageSize", 10)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取查询参数中指定参数值,并转为int
 | 
			
		||||
func QueryInt(g *gin.Context, qm string, defaultInt int) int {
 | 
			
		||||
	qv := g.Query(qm)
 | 
			
		||||
	if qv == "" {
 | 
			
		||||
		return defaultInt
 | 
			
		||||
	}
 | 
			
		||||
	qvi, err := strconv.Atoi(qv)
 | 
			
		||||
	biz.ErrIsNil(err, "query param not int")
 | 
			
		||||
	return qvi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取路径参数
 | 
			
		||||
func PathParamInt(g *gin.Context, pm string) int {
 | 
			
		||||
	value, _ := strconv.Atoi(g.Param(pm))
 | 
			
		||||
	return value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 文件下载
 | 
			
		||||
func Download(g *gin.Context, reader io.Reader, filename string) {
 | 
			
		||||
	g.Header("Content-Type", "application/octet-stream")
 | 
			
		||||
	g.Header("Content-Disposition", "attachment; filename="+filename)
 | 
			
		||||
	io.Copy(g.Writer, reader)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 返回统一成功结果
 | 
			
		||||
func SuccessRes(g *gin.Context, data interface{}) {
 | 
			
		||||
	g.JSON(http.StatusOK, model.Success(data))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 返回失败结果集
 | 
			
		||||
func ErrorRes(g *gin.Context, err interface{}) {
 | 
			
		||||
	switch t := err.(type) {
 | 
			
		||||
	case *biz.BizError:
 | 
			
		||||
		g.JSON(http.StatusOK, model.Error(t))
 | 
			
		||||
	case error:
 | 
			
		||||
		g.JSON(http.StatusOK, model.ServerError())
 | 
			
		||||
		global.Log.Error(t)
 | 
			
		||||
		// panic(err)
 | 
			
		||||
	case string:
 | 
			
		||||
		g.JSON(http.StatusOK, model.ServerError())
 | 
			
		||||
		global.Log.Error(t)
 | 
			
		||||
		// panic(err)
 | 
			
		||||
	default:
 | 
			
		||||
		global.Log.Error(t)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
package global
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/go-redis/redis"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	Log      *logrus.Logger // 日志
 | 
			
		||||
	Db       *gorm.DB       // gorm
 | 
			
		||||
	RedisCli *redis.Client  // redis
 | 
			
		||||
)
 | 
			
		||||
@@ -1,66 +0,0 @@
 | 
			
		||||
package logger
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mayfly-go/base/config"
 | 
			
		||||
	"mayfly-go/base/global"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var Log = logrus.New()
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	Log.SetFormatter(new(LogFormatter))
 | 
			
		||||
	Log.SetReportCaller(true)
 | 
			
		||||
 | 
			
		||||
	logConf := config.Conf.Log
 | 
			
		||||
	// 如果不存在日志配置信息,则默认debug级别
 | 
			
		||||
	if logConf == nil {
 | 
			
		||||
		Log.SetLevel(logrus.DebugLevel)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 根据配置文件设置日志级别
 | 
			
		||||
	if level := logConf.Level; level != "" {
 | 
			
		||||
		l, err := logrus.ParseLevel(level)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(fmt.Sprintf("日志级别不存在: %s", level))
 | 
			
		||||
		}
 | 
			
		||||
		Log.SetLevel(l)
 | 
			
		||||
	} else {
 | 
			
		||||
		Log.SetLevel(logrus.DebugLevel)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if logFile := logConf.File; logFile != nil {
 | 
			
		||||
		//写入文件
 | 
			
		||||
		file, err := os.OpenFile(logFile.GetFilename(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModeAppend|0666)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(fmt.Sprintf("创建日志文件失败: %s", err.Error()))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Log.Out = file
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	global.Log = Log
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LogFormatter struct{}
 | 
			
		||||
 | 
			
		||||
func (l *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
 | 
			
		||||
	timestamp := time.Now().Local().Format("2006-01-02 15:04:05.000")
 | 
			
		||||
	level := entry.Level
 | 
			
		||||
	logMsg := fmt.Sprintf("%s [%s]", timestamp, strings.ToUpper(level.String()))
 | 
			
		||||
	// 如果存在调用信息,记录方法信息及行号
 | 
			
		||||
	if caller := entry.Caller; caller != nil {
 | 
			
		||||
		logMsg = logMsg + fmt.Sprintf(" [%s:%d]", caller.Function, caller.Line)
 | 
			
		||||
	}
 | 
			
		||||
	for k, v := range entry.Data {
 | 
			
		||||
		logMsg = logMsg + fmt.Sprintf(" [%s=%v]", k, v)
 | 
			
		||||
	}
 | 
			
		||||
	logMsg = logMsg + fmt.Sprintf(" : %s\n", entry.Message)
 | 
			
		||||
	return []byte(logMsg), nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
type AppContext struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LoginAccount struct {
 | 
			
		||||
	Id       uint64
 | 
			
		||||
	Username string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Permission struct {
 | 
			
		||||
	CheckToken bool   // 是否检查token
 | 
			
		||||
	Code       string // 权限码
 | 
			
		||||
	Name       string // 描述
 | 
			
		||||
}
 | 
			
		||||
@@ -1,196 +0,0 @@
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mayfly-go/base/biz"
 | 
			
		||||
	"mayfly-go/base/global"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Model struct {
 | 
			
		||||
	Id         uint64     `json:"id"`
 | 
			
		||||
	CreateTime *time.Time `json:"createTime"`
 | 
			
		||||
	CreatorId  uint64     `json:"creatorId"`
 | 
			
		||||
	Creator    string     `json:"creator"`
 | 
			
		||||
	UpdateTime *time.Time `json:"updateTime"`
 | 
			
		||||
	ModifierId uint64     `json:"modifierId"`
 | 
			
		||||
	Modifier   string     `json:"modifier"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 设置基础信息. 如创建时间,修改时间,创建者,修改者信息
 | 
			
		||||
func (m *Model) SetBaseInfo(account *LoginAccount) {
 | 
			
		||||
	nowTime := time.Now()
 | 
			
		||||
	isCreate := m.Id == 0
 | 
			
		||||
	if isCreate {
 | 
			
		||||
		m.CreateTime = &nowTime
 | 
			
		||||
	}
 | 
			
		||||
	m.UpdateTime = &nowTime
 | 
			
		||||
 | 
			
		||||
	if account == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	id := account.Id
 | 
			
		||||
	name := account.Username
 | 
			
		||||
	if isCreate {
 | 
			
		||||
		m.CreatorId = id
 | 
			
		||||
		m.Creator = name
 | 
			
		||||
	}
 | 
			
		||||
	m.Modifier = name
 | 
			
		||||
	m.ModifierId = id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Tx(funcs ...func(db *gorm.DB) error) (err error) {
 | 
			
		||||
	tx := global.Db.Begin()
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
			tx.Rollback()
 | 
			
		||||
			err = fmt.Errorf("%v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	for _, f := range funcs {
 | 
			
		||||
		err = f(tx)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			tx.Rollback()
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	err = tx.Commit().Error
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据id获取实体对象。model需为指针类型(需要将查询出来的值赋值给model)
 | 
			
		||||
//
 | 
			
		||||
// 若error不为nil则为不存在该记录
 | 
			
		||||
func GetById(model interface{}, id uint64, cols ...string) error {
 | 
			
		||||
	return global.Db.Select(cols).Where("id = ?", id).First(model).Error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据id列表查询
 | 
			
		||||
func GetByIdIn(model interface{}, list interface{}, ids []uint64, orderBy ...string) {
 | 
			
		||||
	var orderByStr string
 | 
			
		||||
	if orderBy == nil {
 | 
			
		||||
		orderByStr = "id desc"
 | 
			
		||||
	} else {
 | 
			
		||||
		orderByStr = strings.Join(orderBy, ",")
 | 
			
		||||
	}
 | 
			
		||||
	global.Db.Model(model).Where("id in (?)", ids).Order(orderByStr).Find(list)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据id列表查询
 | 
			
		||||
func CountBy(model interface{}) int64 {
 | 
			
		||||
	var count int64
 | 
			
		||||
	global.Db.Model(model).Where(model).Count(&count)
 | 
			
		||||
	return count
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据id更新model,更新字段为model中不为空的值,即int类型不为0,ptr类型不为nil这类字段值
 | 
			
		||||
func UpdateById(model interface{}) error {
 | 
			
		||||
	return global.Db.Model(model).Updates(model).Error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据id删除model
 | 
			
		||||
func DeleteById(model interface{}, id uint64) error {
 | 
			
		||||
	return global.Db.Delete(model, "id = ?", id).Error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据条件删除
 | 
			
		||||
func DeleteByCondition(model interface{}) error {
 | 
			
		||||
	return global.Db.Where(model).Delete(model).Error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 插入model
 | 
			
		||||
func Insert(model interface{}) error {
 | 
			
		||||
	return global.Db.Create(model).Error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取满足model中不为空的字段值条件的所有数据.
 | 
			
		||||
//
 | 
			
		||||
// @param list为数组类型 如 var users *[]User,可指定为非model结构体,即只包含需要返回的字段结构体
 | 
			
		||||
func ListBy(model interface{}, list interface{}, cols ...string) {
 | 
			
		||||
	global.Db.Model(model).Select(cols).Where(model).Find(list)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取满足model中不为空的字段值条件的所有数据.
 | 
			
		||||
//
 | 
			
		||||
// @param list为数组类型 如 var users *[]User,可指定为非model结构体
 | 
			
		||||
func ListByOrder(model interface{}, list interface{}, order ...string) {
 | 
			
		||||
	var orderByStr string
 | 
			
		||||
	if order == nil {
 | 
			
		||||
		orderByStr = "id desc"
 | 
			
		||||
	} else {
 | 
			
		||||
		orderByStr = strings.Join(order, ",")
 | 
			
		||||
	}
 | 
			
		||||
	global.Db.Model(model).Where(model).Order(orderByStr).Find(list)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取满足model中不为空的字段值条件的单个对象。model需为指针类型(需要将查询出来的值赋值给model)
 | 
			
		||||
//
 | 
			
		||||
// 若 error不为nil,则为不存在该记录
 | 
			
		||||
func GetBy(model interface{}, cols ...string) error {
 | 
			
		||||
	return global.Db.Select(cols).Where(model).First(model).Error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取满足conditionModel中不为空的字段值条件的单个对象。model需为指针类型(需要将查询出来的值赋值给model)
 | 
			
		||||
//	@param toModel  需要查询的字段
 | 
			
		||||
// 若 error不为nil,则为不存在该记录
 | 
			
		||||
func GetByConditionTo(conditionModel interface{}, toModel interface{}) error {
 | 
			
		||||
	return global.Db.Model(conditionModel).Where(conditionModel).First(toModel).Error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取分页结果
 | 
			
		||||
func GetPage(pageParam *PageParam, conditionModel interface{}, toModels interface{}, orderBy ...string) *PageResult {
 | 
			
		||||
	var count int64
 | 
			
		||||
	err := global.Db.Model(conditionModel).Where(conditionModel).Count(&count).Error
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, " 查询错误:%s")
 | 
			
		||||
	if count == 0 {
 | 
			
		||||
		return &PageResult{Total: 0, List: []string{}}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	page := pageParam.PageNum
 | 
			
		||||
	pageSize := pageParam.PageSize
 | 
			
		||||
	var orderByStr string
 | 
			
		||||
	if orderBy == nil {
 | 
			
		||||
		orderByStr = "id desc"
 | 
			
		||||
	} else {
 | 
			
		||||
		orderByStr = strings.Join(orderBy, ",")
 | 
			
		||||
	}
 | 
			
		||||
	err = global.Db.Model(conditionModel).Where(conditionModel).Order(orderByStr).Limit(pageSize).Offset((page - 1) * pageSize).Find(toModels).Error
 | 
			
		||||
	biz.ErrIsNil(err, "查询失败")
 | 
			
		||||
	return &PageResult{Total: count, List: toModels}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据sql获取分页对象
 | 
			
		||||
func GetPageBySql(sql string, param *PageParam, toModel interface{}, args ...interface{}) *PageResult {
 | 
			
		||||
	db := global.Db
 | 
			
		||||
	selectIndex := strings.Index(sql, "SELECT ") + 7
 | 
			
		||||
	fromIndex := strings.Index(sql, " FROM")
 | 
			
		||||
	selectCol := sql[selectIndex:fromIndex]
 | 
			
		||||
	countSql := strings.Replace(sql, selectCol, "COUNT(*) AS total ", 1)
 | 
			
		||||
	// 查询count
 | 
			
		||||
	var count int
 | 
			
		||||
	err := db.Raw(countSql, args...).Scan(&count).Error
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "查询失败: %s")
 | 
			
		||||
	if count == 0 {
 | 
			
		||||
		return &PageResult{Total: 0, List: []string{}}
 | 
			
		||||
	}
 | 
			
		||||
	// 分页查询
 | 
			
		||||
	limitSql := sql + " LIMIT " + strconv.Itoa((param.PageNum-1)*param.PageSize) + ", " + strconv.Itoa(param.PageSize)
 | 
			
		||||
	err = db.Raw(limitSql).Scan(toModel).Error
 | 
			
		||||
	biz.ErrIsNil(err, "查询失败: %s")
 | 
			
		||||
	return &PageResult{Total: int64(count), List: toModel}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetListBySql(sql string, params ...interface{}) []map[string]interface{} {
 | 
			
		||||
	var maps []map[string]interface{}
 | 
			
		||||
	global.Db.Raw(sql, params...).Scan(&maps)
 | 
			
		||||
	return maps
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetListBySql2Model(sql string, toEntity interface{}, params ...interface{}) error {
 | 
			
		||||
	return global.Db.Raw(sql, params...).Find(toEntity).Error
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
// 分页参数
 | 
			
		||||
type PageParam struct {
 | 
			
		||||
	PageNum  int `json:"pageNum"`
 | 
			
		||||
	PageSize int `json:"pageSize"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 分页结果
 | 
			
		||||
type PageResult struct {
 | 
			
		||||
	Total int64       `json:"total"`
 | 
			
		||||
	List  interface{} `json:"list"`
 | 
			
		||||
}
 | 
			
		||||
@@ -1,64 +0,0 @@
 | 
			
		||||
package rediscli
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-redis/redis"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var cli *redis.Client
 | 
			
		||||
 | 
			
		||||
func SetCli(client *redis.Client) {
 | 
			
		||||
	cli = client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetCli() *redis.Client {
 | 
			
		||||
	return cli
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// get key value
 | 
			
		||||
func Get(key string) string {
 | 
			
		||||
	val, err := cli.Get(key).Result()
 | 
			
		||||
	switch {
 | 
			
		||||
	case err == redis.Nil:
 | 
			
		||||
		fmt.Println("key does not exist")
 | 
			
		||||
	case err != nil:
 | 
			
		||||
		fmt.Println("Get failed", err)
 | 
			
		||||
	case val == "":
 | 
			
		||||
		fmt.Println("value is empty")
 | 
			
		||||
	}
 | 
			
		||||
	return val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// set key value
 | 
			
		||||
func Set(key string, val string, expiration time.Duration) {
 | 
			
		||||
	cli.Set(key, val, expiration)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func HSet(key string, field string, val interface{}) {
 | 
			
		||||
	cli.HSet(key, field, val)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// hget
 | 
			
		||||
func HGet(key string, field string) string {
 | 
			
		||||
	val, _ := cli.HGet(key, field).Result()
 | 
			
		||||
	return val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// hget
 | 
			
		||||
func HExist(key string, field string) bool {
 | 
			
		||||
	val, _ := cli.HExists(key, field).Result()
 | 
			
		||||
	return val
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// hgetall
 | 
			
		||||
func HGetAll(key string) map[string]string {
 | 
			
		||||
	vals, _ := cli.HGetAll(key).Result()
 | 
			
		||||
	return vals
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// hdel
 | 
			
		||||
func HDel(key string, fields ...string) int {
 | 
			
		||||
	return int(cli.HDel(key, fields...).Val())
 | 
			
		||||
}
 | 
			
		||||
@@ -1,16 +0,0 @@
 | 
			
		||||
package starter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mayfly-go/base/global"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func PrintBanner() {
 | 
			
		||||
	global.Log.Print(`
 | 
			
		||||
                         __ _                         
 | 
			
		||||
  _ __ ___   __ _ _   _ / _| |_   _        __ _  ___  
 | 
			
		||||
 | '_ ' _ \ / _' | | | | |_| | | | |_____ / _' |/ _ \ 
 | 
			
		||||
 | | | | | | (_| | |_| |  _| | |_| |_____| (_| | (_) |
 | 
			
		||||
 |_| |_| |_|\__,_|\__, |_| |_|\__, |      \__, |\___/ 
 | 
			
		||||
                  |___/       |___/       |___/      
 | 
			
		||||
	`)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
package starter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mayfly-go/base/config"
 | 
			
		||||
	"mayfly-go/base/global"
 | 
			
		||||
 | 
			
		||||
	"gorm.io/driver/mysql"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
	"gorm.io/gorm/logger"
 | 
			
		||||
	"gorm.io/gorm/schema"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func InitDb() {
 | 
			
		||||
	global.Db = GormMysql()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GormMysql() *gorm.DB {
 | 
			
		||||
	m := config.Conf.Mysql
 | 
			
		||||
	if m == nil || m.Dbname == "" {
 | 
			
		||||
		global.Log.Panic("未找到数据库配置信息")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	global.Log.Infof("连接mysql [%s]", m.Host)
 | 
			
		||||
	mysqlConfig := mysql.Config{
 | 
			
		||||
		DSN:                       m.Dsn(), // DSN data source name
 | 
			
		||||
		DefaultStringSize:         191,     // string 类型字段的默认长度
 | 
			
		||||
		DisableDatetimePrecision:  true,    // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
 | 
			
		||||
		DontSupportRenameIndex:    true,    // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
 | 
			
		||||
		DontSupportRenameColumn:   true,    // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
 | 
			
		||||
		SkipInitializeWithVersion: false,   // 根据版本自动配置
 | 
			
		||||
	}
 | 
			
		||||
	ormConfig := &gorm.Config{NamingStrategy: schema.NamingStrategy{
 | 
			
		||||
		TablePrefix:   "t_",
 | 
			
		||||
		SingularTable: true,
 | 
			
		||||
	}, Logger: logger.Default.LogMode(logger.Silent)}
 | 
			
		||||
	if db, err := gorm.Open(mysql.New(mysqlConfig), ormConfig); err != nil {
 | 
			
		||||
		global.Log.Panicf("连接mysql失败! [%s]", err.Error())
 | 
			
		||||
		return nil
 | 
			
		||||
	} else {
 | 
			
		||||
		sqlDB, _ := db.DB()
 | 
			
		||||
		sqlDB.SetMaxIdleConns(m.MaxIdleConns)
 | 
			
		||||
		sqlDB.SetMaxOpenConns(m.MaxOpenConns)
 | 
			
		||||
		return db
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
package starter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"mayfly-go/base/config"
 | 
			
		||||
	"mayfly-go/base/global"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-redis/redis"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func InitRedis() {
 | 
			
		||||
	global.RedisCli = ConnRedis()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ConnRedis() *redis.Client {
 | 
			
		||||
	// 设置redis客户端
 | 
			
		||||
	redisConf := config.Conf.Redis
 | 
			
		||||
	if redisConf == nil {
 | 
			
		||||
		global.Log.Panic("未找到redis配置信息")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	global.Log.Infof("连接redis [%s:%d]", redisConf.Host, redisConf.Port)
 | 
			
		||||
	rdb := redis.NewClient(&redis.Options{
 | 
			
		||||
		Addr:     fmt.Sprintf("%s:%d", redisConf.Host, redisConf.Port),
 | 
			
		||||
		Password: redisConf.Password, // no password set
 | 
			
		||||
		DB:       redisConf.Db,       // use default DB
 | 
			
		||||
	})
 | 
			
		||||
	// 测试连接
 | 
			
		||||
	_, e := rdb.Ping().Result()
 | 
			
		||||
	if e != nil {
 | 
			
		||||
		global.Log.Panic(fmt.Sprintf("连接redis失败! [%s:%d]", redisConf.Host, redisConf.Port))
 | 
			
		||||
	}
 | 
			
		||||
	return rdb
 | 
			
		||||
}
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
package starter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mayfly-go/base/biz"
 | 
			
		||||
	"mayfly-go/base/config"
 | 
			
		||||
	"mayfly-go/base/ctx"
 | 
			
		||||
	"mayfly-go/base/global"
 | 
			
		||||
	"mayfly-go/server/initialize"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func RunWebServer() {
 | 
			
		||||
	// 权限处理器
 | 
			
		||||
	ctx.UseBeforeHandlerInterceptor(ctx.PermissionHandler)
 | 
			
		||||
	// 日志处理器
 | 
			
		||||
	ctx.UseAfterHandlerInterceptor(ctx.LogHandler)
 | 
			
		||||
	// 注册路由
 | 
			
		||||
	web := initialize.InitRouter()
 | 
			
		||||
 | 
			
		||||
	server := config.Conf.Server
 | 
			
		||||
	port := server.GetPort()
 | 
			
		||||
	if app := config.Conf.App; app != nil {
 | 
			
		||||
		global.Log.Infof("%s- Listening and serving HTTP on %s", app.GetAppInfo(), port)
 | 
			
		||||
	} else {
 | 
			
		||||
		global.Log.Infof("Listening and serving HTTP on %s", port)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	if server.Tls != nil && server.Tls.Enable {
 | 
			
		||||
		err = web.RunTLS(port, server.Tls.CertFile, server.Tls.KeyFile)
 | 
			
		||||
	} else {
 | 
			
		||||
		err = web.Run(port)
 | 
			
		||||
	}
 | 
			
		||||
	biz.ErrIsNilAppendErr(err, "服务启动失败: %s")
 | 
			
		||||
}
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
// 数组比较
 | 
			
		||||
// 依次返回,新增值,删除值,以及不变值
 | 
			
		||||
func ArrayCompare(newArr []interface{}, oldArr []interface{}, compareFun func(interface{}, interface{}) bool) ([]interface{}, []interface{}, []interface{}) {
 | 
			
		||||
	var unmodifierValue []interface{}
 | 
			
		||||
	ni, oi := 0, 0
 | 
			
		||||
	for {
 | 
			
		||||
		if ni >= len(newArr) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		nv := newArr[ni]
 | 
			
		||||
		for {
 | 
			
		||||
			if oi >= len(oldArr) {
 | 
			
		||||
				oi = 0
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			ov := oldArr[oi]
 | 
			
		||||
			if compareFun(nv, ov) {
 | 
			
		||||
				unmodifierValue = append(unmodifierValue, nv)
 | 
			
		||||
				// 新数组移除该位置值
 | 
			
		||||
				newArr = append(newArr[:ni], newArr[ni+1:]...)
 | 
			
		||||
				oldArr = append(oldArr[:oi], oldArr[oi+1:]...)
 | 
			
		||||
				ni = ni - 1
 | 
			
		||||
				oi = oi - 1
 | 
			
		||||
			}
 | 
			
		||||
			oi = oi + 1
 | 
			
		||||
		}
 | 
			
		||||
		ni = ni + 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return newArr, oldArr, unmodifierValue
 | 
			
		||||
}
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestArrayCompare(t *testing.T) {
 | 
			
		||||
	newArr := []interface{}{1, 2, 3, 5}
 | 
			
		||||
	oldArr := []interface{}{3, 6}
 | 
			
		||||
	add, del, unmodifier := ArrayCompare(newArr, oldArr, func(i1, i2 interface{}) bool {
 | 
			
		||||
		return i1.(int) == i2.(int)
 | 
			
		||||
	})
 | 
			
		||||
	fmt.Println(add...)
 | 
			
		||||
	fmt.Println(del...)
 | 
			
		||||
	fmt.Println(unmodifier...)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/md5"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// md5
 | 
			
		||||
func Md5(str string) string {
 | 
			
		||||
	h := md5.New()
 | 
			
		||||
	h.Write([]byte(str))
 | 
			
		||||
	return hex.EncodeToString(h.Sum(nil))
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +0,0 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Json2Map(jsonStr string) map[string]interface{} {
 | 
			
		||||
	var res map[string]interface{}
 | 
			
		||||
	if jsonStr == "" {
 | 
			
		||||
		return res
 | 
			
		||||
	}
 | 
			
		||||
	_ = json.Unmarshal([]byte(jsonStr), &res)
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
@@ -1,44 +0,0 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func GetString4Map(m map[string]interface{}, key string) string {
 | 
			
		||||
	return m[key].(string)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetInt4Map(m map[string]interface{}, key string) int {
 | 
			
		||||
	i := m[key]
 | 
			
		||||
	iKind := reflect.TypeOf(i).Kind()
 | 
			
		||||
	if iKind == reflect.Int {
 | 
			
		||||
		return i.(int)
 | 
			
		||||
	}
 | 
			
		||||
	if iKind == reflect.String {
 | 
			
		||||
		i, _ := strconv.Atoi(i.(string))
 | 
			
		||||
		return i
 | 
			
		||||
	}
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// map构造器
 | 
			
		||||
type mapBuilder struct {
 | 
			
		||||
	m map[string]interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func MapBuilder(key string, value interface{}) *mapBuilder {
 | 
			
		||||
	mb := new(mapBuilder)
 | 
			
		||||
	mb.m = make(map[string]interface{}, 4)
 | 
			
		||||
	mb.m[key] = value
 | 
			
		||||
	return mb
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mb *mapBuilder) Put(key string, value interface{}) *mapBuilder {
 | 
			
		||||
	mb.m[key] = value
 | 
			
		||||
	return mb
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mb *mapBuilder) ToMap() map[string]interface{} {
 | 
			
		||||
	return mb.m
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import "runtime"
 | 
			
		||||
 | 
			
		||||
// 获取调用堆栈信息
 | 
			
		||||
func GetStackTrace() string {
 | 
			
		||||
	var buf [2 << 10]byte
 | 
			
		||||
	return string(buf[:runtime.Stack(buf[:], false)])
 | 
			
		||||
}
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
const SuccessMsgType = 1
 | 
			
		||||
const ErrorMsgType = 0
 | 
			
		||||
const InfoMsgType = 2
 | 
			
		||||
 | 
			
		||||
// websocket消息
 | 
			
		||||
type Msg struct {
 | 
			
		||||
	Type  int    `json:"type"`  // 消息类型
 | 
			
		||||
	Title string `json:"title"` // 消息标题
 | 
			
		||||
	Msg   string `json:"msg"`   // 消息内容
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 普通消息
 | 
			
		||||
func NewMsg(title, msg string) *Msg {
 | 
			
		||||
	return &Msg{Type: InfoMsgType, Title: title, Msg: msg}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 成功消息
 | 
			
		||||
func SuccessMsg(title, msg string) *Msg {
 | 
			
		||||
	return &Msg{Type: SuccessMsgType, Title: title, Msg: msg}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 错误消息
 | 
			
		||||
func ErrMsg(title, msg string) *Msg {
 | 
			
		||||
	return &Msg{Type: ErrorMsgType, Title: title, Msg: msg}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,74 +0,0 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"mayfly-go/base/global"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var Upgrader = websocket.Upgrader{
 | 
			
		||||
	ReadBufferSize:  1024,
 | 
			
		||||
	WriteBufferSize: 1024 * 1024 * 10,
 | 
			
		||||
	CheckOrigin: func(r *http.Request) bool {
 | 
			
		||||
		return true
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var conns = make(map[uint64]*websocket.Conn, 100)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	checkConn()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 放置ws连接
 | 
			
		||||
func Put(userId uint64, conn *websocket.Conn) {
 | 
			
		||||
	existConn := conns[userId]
 | 
			
		||||
	if existConn != nil {
 | 
			
		||||
		Delete(userId)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	conn.SetCloseHandler(func(code int, text string) error {
 | 
			
		||||
		Delete(userId)
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
	conns[userId] = conn
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkConn() {
 | 
			
		||||
	heartbeat := time.Duration(60) * time.Second
 | 
			
		||||
	tick := time.NewTicker(heartbeat)
 | 
			
		||||
	go func() {
 | 
			
		||||
		for range tick.C {
 | 
			
		||||
			// 遍历所有连接,ping失败的则删除掉
 | 
			
		||||
			for uid, conn := range conns {
 | 
			
		||||
				err := conn.WriteControl(websocket.PingMessage, []byte("ping"), time.Now().Add(heartbeat/2))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					Delete(uid)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 删除ws连接
 | 
			
		||||
func Delete(userid uint64) {
 | 
			
		||||
	global.Log.Info("移除websocket连接:uid = ", userid)
 | 
			
		||||
	conn := conns[userid]
 | 
			
		||||
	if conn != nil {
 | 
			
		||||
		conn.Close()
 | 
			
		||||
		delete(conns, userid)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 对指定用户发送消息
 | 
			
		||||
func SendMsg(userId uint64, msg *Msg) {
 | 
			
		||||
	conn := conns[userId]
 | 
			
		||||
	if conn != nil {
 | 
			
		||||
		bytes, _ := json.Marshal(msg)
 | 
			
		||||
		conn.WriteMessage(websocket.TextMessage, bytes)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										200
									
								
								build_release.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						@@ -0,0 +1,200 @@
 | 
			
		||||
#bin/bash
 | 
			
		||||
 | 
			
		||||
#----------------------------------------------
 | 
			
		||||
# 前后端打包编译至指定目录,即快速制作发行版
 | 
			
		||||
#----------------------------------------------
 | 
			
		||||
 | 
			
		||||
project_path=`pwd`
 | 
			
		||||
# 构建后的二进制执行文件名
 | 
			
		||||
exec_file_name="mayfly-go"
 | 
			
		||||
# web项目目录
 | 
			
		||||
web_folder="${project_path}/mayfly_go_web"
 | 
			
		||||
# server目录
 | 
			
		||||
server_folder="${project_path}/server"
 | 
			
		||||
 | 
			
		||||
function echo_red() {
 | 
			
		||||
    echo -e "\033[1;31m$1\033[0m"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function echo_green() {
 | 
			
		||||
    echo -e "\033[1;32m$1\033[0m"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function echo_yellow() {
 | 
			
		||||
    echo -e "\033[1;33m$1\033[0m"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function buildWeb() {
 | 
			
		||||
    cd ${web_folder}
 | 
			
		||||
    copy2Server=$1
 | 
			
		||||
 | 
			
		||||
    echo_yellow "-------------------打包前端开始-------------------"
 | 
			
		||||
    yarn run build
 | 
			
		||||
    if [ "${copy2Server}" == "2" ] ; then
 | 
			
		||||
        echo_green '将打包后的静态文件拷贝至server/static/static'
 | 
			
		||||
        rm -rf ${server_folder}/static/static && mkdir -p ${server_folder}/static/static && cp -r ${web_folder}/dist/* ${server_folder}/static/static
 | 
			
		||||
    fi
 | 
			
		||||
    echo_yellow ">>>>>>>>>>>>>>>>>>>打包前端结束<<<<<<<<<<<<<<<<<<<<\n"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function build() {
 | 
			
		||||
    cd ${project_path}
 | 
			
		||||
 | 
			
		||||
    # 打包产物的输出目录
 | 
			
		||||
    toFolder=$1
 | 
			
		||||
    os=$2
 | 
			
		||||
    arch=$3
 | 
			
		||||
    copyDocScript=$4
 | 
			
		||||
 | 
			
		||||
    echo_yellow "-------------------${os}-${arch}打包构建开始-------------------"
 | 
			
		||||
 | 
			
		||||
    cd ${server_folder}
 | 
			
		||||
    echo_green "打包构建可执行文件..."
 | 
			
		||||
 | 
			
		||||
    execFileName=${exec_file_name}
 | 
			
		||||
    # 如果是windows系统,可执行文件需要添加.exe结尾
 | 
			
		||||
    if [ "${os}" == "windows" ];then
 | 
			
		||||
        execFileName="${execFileName}.exe"
 | 
			
		||||
    fi
 | 
			
		||||
    CGO_ENABLE=0 GOOS=${os} GOARCH=${arch} go build -ldflags=-w -o ${execFileName} main.go
 | 
			
		||||
 | 
			
		||||
    if [ -d ${toFolder} ] ; then
 | 
			
		||||
        echo_green "目标文件夹已存在,清空文件夹"
 | 
			
		||||
        sudo rm -rf ${toFolder}
 | 
			
		||||
    fi
 | 
			
		||||
    echo_green "创建'${toFolder}'目录"
 | 
			
		||||
    mkdir ${toFolder}
 | 
			
		||||
 | 
			
		||||
    echo_green "移动二进制文件至'${toFolder}'"
 | 
			
		||||
    mv ${server_folder}/${execFileName} ${toFolder}
 | 
			
		||||
 | 
			
		||||
    # if [ "${copy2Server}" == "1" ] ; then
 | 
			
		||||
    #     echo_green "拷贝前端静态页面至'${toFolder}/static'"
 | 
			
		||||
    #     mkdir -p ${toFolder}/static && cp -r ${web_folder}/dist/* ${toFolder}/static
 | 
			
		||||
    # fi
 | 
			
		||||
 | 
			
		||||
    if [ "${copyDocScript}" == "1" ] ; then
 | 
			
		||||
        echo_green "拷贝脚本等资源文件[config.yml.example、mayfly-go.sql、mayfly-go.sqlite、readme.txt、startup.sh、shutdown.sh]"
 | 
			
		||||
        cp ${server_folder}/config.yml.example ${toFolder}
 | 
			
		||||
        cp ${server_folder}/readme.txt ${toFolder}
 | 
			
		||||
        cp ${server_folder}/resources/script/startup.sh ${toFolder}
 | 
			
		||||
        cp ${server_folder}/resources/script/shutdown.sh ${toFolder}
 | 
			
		||||
        cp ${server_folder}/resources/script/sql/mayfly-go.sql ${toFolder}
 | 
			
		||||
        cp ${server_folder}/resources/data/mayfly-go.sqlite ${toFolder}
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    echo_yellow ">>>>>>>>>>>>>>>>>>>${os}-${arch}打包构建完成<<<<<<<<<<<<<<<<<<<<\n"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function buildLinuxAmd64() {
 | 
			
		||||
    build "$1/mayfly-go-linux-amd64" "linux" "amd64" $2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function buildLinuxArm64() {
 | 
			
		||||
    build "$1/mayfly-go-linux-arm64" "linux" "arm64" $2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function buildWindows() {
 | 
			
		||||
    build "$1/mayfly-go-windows" "windows" "amd64" $2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function buildMac() {
 | 
			
		||||
    build "$1/mayfly-go-mac" "darwin" "amd64" $2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function buildDocker() {
 | 
			
		||||
    echo_yellow "-------------------构建docker镜像开始-------------------"
 | 
			
		||||
    imageVersion=$1
 | 
			
		||||
    imageName="mayflygo/mayfly-go:${imageVersion}"
 | 
			
		||||
    docker build --platform linux/amd64 -t "${imageName}" .
 | 
			
		||||
    echo_green "docker镜像构建完成->[${imageName}]"
 | 
			
		||||
    echo_yellow "-------------------构建docker镜像结束-------------------"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function buildxDocker() {
 | 
			
		||||
    echo_yellow "-------------------docker buildx构建镜像开始-------------------"
 | 
			
		||||
    imageVersion=$1
 | 
			
		||||
    imageName="ccr.ccs.tencentyun.com/mayfly/mayfly-go:${imageVersion}"
 | 
			
		||||
    docker buildx build --push --platform linux/amd64,linux/arm64 -t "${imageName}" .
 | 
			
		||||
    echo_green "docker多版本镜像构建完成->[${imageName}]"
 | 
			
		||||
    echo_yellow "-------------------docker buildx构建镜像结束-------------------"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function runBuild() {
 | 
			
		||||
    read -p "请选择构建版本[0|其他->除docker镜像外其他 1->linux-amd64 2->linux-arm64 3->windows 4->mac 5->docker 6->docker buildx]: " buildType
 | 
			
		||||
 | 
			
		||||
    toPath="."
 | 
			
		||||
    imageVersion="latest"
 | 
			
		||||
    copyDocScript="1"
 | 
			
		||||
 | 
			
		||||
    if [[ "${buildType}" != "5" ]] && [[ "${buildType}" != "6" ]] ; then
 | 
			
		||||
        # 构建结果的目的路径
 | 
			
		||||
        read -p "请输入构建产物输出目录[默认当前路径]: " toPath
 | 
			
		||||
        if [ ! -d ${toPath} ] ; then
 | 
			
		||||
            echo_red "构建产物输出目录不存在!"
 | 
			
		||||
            exit;
 | 
			
		||||
        fi
 | 
			
		||||
        if [ "${toPath}" == "" ] ; then
 | 
			
		||||
            toPath="."
 | 
			
		||||
        fi
 | 
			
		||||
 | 
			
		||||
        read -p "是否拷贝文档&脚本[0->否 1->是][默认是]: " copyDocScript
 | 
			
		||||
        if [ "${copyDocScript}" == "" ] ; then
 | 
			
		||||
            copyDocScript="1"
 | 
			
		||||
        fi
 | 
			
		||||
 | 
			
		||||
        # 进入目标路径,并赋值全路径
 | 
			
		||||
        cd ${toPath}
 | 
			
		||||
        toPath=`pwd`
 | 
			
		||||
 | 
			
		||||
        # read -p "是否构建前端[0|其他->否 1->是 2->构建并拷贝至server/static/static]: " runBuildWeb
 | 
			
		||||
        runBuildWeb="2"
 | 
			
		||||
        # 编译web前端
 | 
			
		||||
        buildWeb ${runBuildWeb}
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    if [[ "${buildType}" == "5" ]] || [[ "${buildType}" == "6" ]] ; then
 | 
			
		||||
        read -p "请输入docker镜像版本号[默认latest]: " imageVersion
 | 
			
		||||
 | 
			
		||||
        if [ "${imageVersion}" == "" ] ; then
 | 
			
		||||
            imageVersion="latest"
 | 
			
		||||
        fi
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    case ${buildType} in
 | 
			
		||||
         "1")
 | 
			
		||||
            buildLinuxAmd64 ${toPath} ${copyDocScript}
 | 
			
		||||
        ;;
 | 
			
		||||
         "2")
 | 
			
		||||
            buildLinuxArm64 ${toPath} ${copyDocScript}
 | 
			
		||||
        ;;
 | 
			
		||||
        "3")
 | 
			
		||||
            buildWindows ${toPath} ${copyDocScript}
 | 
			
		||||
        ;;
 | 
			
		||||
        "4")
 | 
			
		||||
            buildMac ${toPath} ${copyDocScript}
 | 
			
		||||
        ;;
 | 
			
		||||
        "5")
 | 
			
		||||
            buildDocker ${imageVersion}
 | 
			
		||||
        ;;
 | 
			
		||||
        "6")
 | 
			
		||||
            buildxDocker ${imageVersion}
 | 
			
		||||
        ;;
 | 
			
		||||
        *)
 | 
			
		||||
            buildLinuxAmd64 ${toPath} ${copyDocScript}
 | 
			
		||||
            buildLinuxArm64 ${toPath} ${copyDocScript}
 | 
			
		||||
            buildWindows ${toPath} ${copyDocScript}
 | 
			
		||||
            buildMac ${toPath} ${copyDocScript}
 | 
			
		||||
        ;;
 | 
			
		||||
    esac
 | 
			
		||||
 | 
			
		||||
    if [[ "${buildType}" != "5" ]] && [[ "${buildType}" != "6" ]] ; then
 | 
			
		||||
        echo_green "删除['${server_folder}/static/static']下静态资源文件."
 | 
			
		||||
        # 删除静态资源文件,保留一个favicon.ico,否则后端启动会报错
 | 
			
		||||
        rm -rf ${server_folder}/static/static/assets
 | 
			
		||||
        rm -rf ${server_folder}/static/static/config.js
 | 
			
		||||
        rm -rf ${server_folder}/static/static/index.html
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
runBuild
 | 
			
		||||
							
								
								
									
										31
									
								
								docker-compose.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,31 @@
 | 
			
		||||
version: "3.9"
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  mysql:
 | 
			
		||||
    image: "mysql:8"
 | 
			
		||||
    container_name: mayfly-go-mysql
 | 
			
		||||
    environment:
 | 
			
		||||
      MYSQL_ROOT_PASSWORD: 111049
 | 
			
		||||
      MYSQL_DATABASE: mayfly-go
 | 
			
		||||
      TZ: Asia/Shanghai
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./server/docs/docker-compose/mysql/data/mydir:/mydir
 | 
			
		||||
      - ./server/docs/docker-compose/mysql/data/datadir:/var/lib/mysql
 | 
			
		||||
    restart: always
 | 
			
		||||
 | 
			
		||||
  server:
 | 
			
		||||
    image: mayfly-go:v1.3.1
 | 
			
		||||
    build:
 | 
			
		||||
      context: .
 | 
			
		||||
      dockerfile: Dockerfile
 | 
			
		||||
    container_name: mayfly-go-server
 | 
			
		||||
    ports:
 | 
			
		||||
      - "8888:8888"
 | 
			
		||||
    environment:
 | 
			
		||||
      TZ: Asia/Shanghai
 | 
			
		||||
      WAIT_HOSTS: mysql:3306
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./server/config.yml.example:/mayfly/config.yml
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - mysql
 | 
			
		||||
    restart: always
 | 
			
		||||
							
								
								
									
										49
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						@@ -1,49 +0,0 @@
 | 
			
		||||
module mayfly-go
 | 
			
		||||
 | 
			
		||||
go 1.17
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	// jwt
 | 
			
		||||
	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 | 
			
		||||
	github.com/gin-gonic/gin v1.7.7
 | 
			
		||||
	github.com/go-redis/redis v6.15.9+incompatible
 | 
			
		||||
	github.com/gorilla/websocket v1.5.0
 | 
			
		||||
	// 验证码
 | 
			
		||||
	github.com/mojocn/base64Captcha v1.3.5
 | 
			
		||||
	github.com/pkg/sftp v1.13.4
 | 
			
		||||
	// 定时任务
 | 
			
		||||
	github.com/robfig/cron/v3 v3.0.1
 | 
			
		||||
	github.com/sirupsen/logrus v1.8.1
 | 
			
		||||
	// ssh
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20220314234724-5d542ad81a58
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
 | 
			
		||||
	// gorm
 | 
			
		||||
	gorm.io/driver/mysql v1.3.2
 | 
			
		||||
	gorm.io/gorm v1.23.2
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/gin-contrib/sse v0.1.0 // indirect
 | 
			
		||||
	github.com/go-playground/locales v0.14.0 // indirect
 | 
			
		||||
	github.com/go-playground/universal-translator v0.18.0 // indirect
 | 
			
		||||
	github.com/go-playground/validator/v10 v10.10.1 // indirect
 | 
			
		||||
	github.com/go-sql-driver/mysql v1.6.0 // indirect
 | 
			
		||||
	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 | 
			
		||||
	github.com/golang/protobuf v1.5.2 // indirect
 | 
			
		||||
	github.com/jinzhu/inflection v1.0.0 // indirect
 | 
			
		||||
	github.com/jinzhu/now v1.1.4 // indirect
 | 
			
		||||
	github.com/json-iterator/go v1.1.12 // indirect
 | 
			
		||||
	github.com/kr/fs v0.1.0 // indirect
 | 
			
		||||
	github.com/leodido/go-urn v1.2.1 // indirect
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.14 // indirect
 | 
			
		||||
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 | 
			
		||||
	github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
			
		||||
	github.com/onsi/ginkgo v1.16.5 // indirect
 | 
			
		||||
	github.com/onsi/gomega v1.18.1 // indirect
 | 
			
		||||
	github.com/ugorji/go/codec v1.2.7 // indirect
 | 
			
		||||
	golang.org/x/image v0.0.0-20220302094943-723b81ca9867 // indirect
 | 
			
		||||
	golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
 | 
			
		||||
	golang.org/x/text v0.3.7 // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.27.1 // indirect
 | 
			
		||||
	gopkg.in/yaml.v2 v2.4.0 // indirect
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										220
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						@@ -1,220 +0,0 @@
 | 
			
		||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 | 
			
		||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 | 
			
		||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 | 
			
		||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 | 
			
		||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 | 
			
		||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 | 
			
		||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 | 
			
		||||
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
 | 
			
		||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
 | 
			
		||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
 | 
			
		||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 | 
			
		||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
 | 
			
		||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
 | 
			
		||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
 | 
			
		||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
 | 
			
		||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
 | 
			
		||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
 | 
			
		||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
 | 
			
		||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 | 
			
		||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 | 
			
		||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 | 
			
		||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 | 
			
		||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
 | 
			
		||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 | 
			
		||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 | 
			
		||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 | 
			
		||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 | 
			
		||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 | 
			
		||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 | 
			
		||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 | 
			
		||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 | 
			
		||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 | 
			
		||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 | 
			
		||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 | 
			
		||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
 | 
			
		||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
			
		||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 | 
			
		||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 | 
			
		||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 | 
			
		||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
 | 
			
		||||
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
 | 
			
		||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 | 
			
		||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
			
		||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 | 
			
		||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 | 
			
		||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
 | 
			
		||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 | 
			
		||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
			
		||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 | 
			
		||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 | 
			
		||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 | 
			
		||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 | 
			
		||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
 | 
			
		||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 | 
			
		||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 | 
			
		||||
github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0=
 | 
			
		||||
github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY=
 | 
			
		||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 | 
			
		||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 | 
			
		||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 | 
			
		||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
			
		||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 | 
			
		||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 | 
			
		||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 | 
			
		||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
 | 
			
		||||
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 | 
			
		||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 | 
			
		||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 | 
			
		||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
 | 
			
		||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
 | 
			
		||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
 | 
			
		||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 | 
			
		||||
github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg=
 | 
			
		||||
github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 | 
			
		||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 | 
			
		||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 | 
			
		||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 | 
			
		||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
			
		||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
			
		||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 | 
			
		||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
 | 
			
		||||
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
 | 
			
		||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
 | 
			
		||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
 | 
			
		||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
 | 
			
		||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
 | 
			
		||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20220314234724-5d542ad81a58 h1:L8CkJyVoa0/NslN3RUMLgasK5+KatNvyRGQ9QyCYAfc=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20220314234724-5d542ad81a58/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 | 
			
		||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 | 
			
		||||
golang.org/x/image v0.0.0-20220302094943-723b81ca9867 h1:TcHcE0vrmgzNH1v3ppjcMGbhG5+9fMuvOmUYwNEF4q4=
 | 
			
		||||
golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
 | 
			
		||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
			
		||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 | 
			
		||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
 | 
			
		||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 | 
			
		||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
 | 
			
		||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 | 
			
		||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
 | 
			
		||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 | 
			
		||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 | 
			
		||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 | 
			
		||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
 | 
			
		||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 | 
			
		||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 | 
			
		||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 | 
			
		||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I=
 | 
			
		||||
gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
 | 
			
		||||
gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
 | 
			
		||||
gorm.io/gorm v1.23.2 h1:xmq9QRMWL8HTJyhAUBXy8FqIIQCYESeKfJL4DoGKiWQ=
 | 
			
		||||
gorm.io/gorm v1.23.2/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
 | 
			
		||||
@@ -1,5 +1,11 @@
 | 
			
		||||
# 本地环境
 | 
			
		||||
ENV = 'development'
 | 
			
		||||
 | 
			
		||||
VITE_OPEN = true
 | 
			
		||||
 | 
			
		||||
# 本地环境接口地址
 | 
			
		||||
VITE_API_URL = '/api'
 | 
			
		||||
VITE_API_URL = '/api'
 | 
			
		||||
 | 
			
		||||
# 路由模式
 | 
			
		||||
# Optional: hash | history
 | 
			
		||||
VITE_ROUTER_MODE = hash
 | 
			
		||||
@@ -2,4 +2,8 @@
 | 
			
		||||
ENV = 'production'
 | 
			
		||||
 | 
			
		||||
# 线上环境接口地址
 | 
			
		||||
VITE_API_URL = 'http://api.mayflygo.1yue.net/api'
 | 
			
		||||
VITE_API_URL = '/api'
 | 
			
		||||
 | 
			
		||||
# 路由模式
 | 
			
		||||
# Optional: hash | history
 | 
			
		||||
VITE_ROUTER_MODE = hash
 | 
			
		||||
@@ -1,55 +1,76 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
	root: true,
 | 
			
		||||
	env: {
 | 
			
		||||
		browser: true,
 | 
			
		||||
		es2021: true,
 | 
			
		||||
		node: true,
 | 
			
		||||
	},
 | 
			
		||||
	parser: 'vue-eslint-parser',
 | 
			
		||||
	parserOptions: {
 | 
			
		||||
		ecmaVersion: 12,
 | 
			
		||||
		parser: '@typescript-eslint/parser',
 | 
			
		||||
		sourceType: 'module',
 | 
			
		||||
	},
 | 
			
		||||
	extends: ['plugin:vue/essential'],
 | 
			
		||||
	// plugins: ['vue', '@typescript-eslint'],
 | 
			
		||||
	rules: {
 | 
			
		||||
		// http://eslint.cn/docs/rules/
 | 
			
		||||
		// https://eslint.vuejs.org/rules/
 | 
			
		||||
		'@type-eslint/ban-ts-ignore': 'off',
 | 
			
		||||
		'@type-eslint/explicit-function-return-type': 'off',
 | 
			
		||||
		'@type-eslint/no-explicit-any': 'off',
 | 
			
		||||
		'@type-eslint/no-var-requires': 'off',
 | 
			
		||||
		'@type-eslint/no-empty-function': 'off',
 | 
			
		||||
		'@type-eslint/no-use-before-define': 'off',
 | 
			
		||||
		'@type-eslint/ban-ts-comment': 'off',
 | 
			
		||||
		'@type-eslint/ban-types': 'off',
 | 
			
		||||
		'@type-eslint/no-non-null-assertion': 'off',
 | 
			
		||||
		'@type-eslint/explicit-module-boundary-types': 'off',
 | 
			
		||||
		'vue/custom-event-name-casing': 'off',
 | 
			
		||||
		'vue/attributes-order': 'off',
 | 
			
		||||
		'vue/one-component-per-file': 'off',
 | 
			
		||||
		'vue/html-closing-bracket-newline': 'off',
 | 
			
		||||
		'vue/max-attributes-per-line': 'off',
 | 
			
		||||
		'vue/multiline-html-element-content-newline': 'off',
 | 
			
		||||
		'vue/singleline-html-element-content-newline': 'off',
 | 
			
		||||
		'vue/attribute-hyphenation': 'off',
 | 
			
		||||
		'vue/html-self-closing': 'off',
 | 
			
		||||
		'vue/no-multiple-template-root': 'off',
 | 
			
		||||
		'vue/require-default-prop': 'off',
 | 
			
		||||
		'vue/no-v-model-argument': 'off',
 | 
			
		||||
		'vue/no-arrow-functions-in-watch': 'off',
 | 
			
		||||
		'vue/no-template-key': 'off',
 | 
			
		||||
		'vue/no-v-html': 'off',
 | 
			
		||||
		'vue/comment-directive': 'off',
 | 
			
		||||
		'vue/no-parsing-error': 'off',
 | 
			
		||||
		'no-use-before-define': 'off',
 | 
			
		||||
		'no-restricted-globals': 'off',
 | 
			
		||||
		'no-restricted-syntax': 'off',
 | 
			
		||||
		'generator-star-spacing': 'off',
 | 
			
		||||
		'no-unreachable': 'off',
 | 
			
		||||
		'no-multiple-template-root': 'off',
 | 
			
		||||
		'no-unused-vars': 'error',
 | 
			
		||||
		'no-v-model-argument': 'off',
 | 
			
		||||
	},
 | 
			
		||||
    root: true,
 | 
			
		||||
    env: {
 | 
			
		||||
        browser: true,
 | 
			
		||||
        es2021: true,
 | 
			
		||||
        node: true,
 | 
			
		||||
    },
 | 
			
		||||
    parser: 'vue-eslint-parser',
 | 
			
		||||
    parserOptions: {
 | 
			
		||||
        ecmaVersion: 12,
 | 
			
		||||
        parser: '@typescript-eslint/parser',
 | 
			
		||||
        sourceType: 'module',
 | 
			
		||||
    },
 | 
			
		||||
    extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
 | 
			
		||||
    plugins: ['vue', '@typescript-eslint'],
 | 
			
		||||
    overrides: [
 | 
			
		||||
        {
 | 
			
		||||
            files: ['*.ts', '*.tsx', '*.vue'],
 | 
			
		||||
            rules: {
 | 
			
		||||
                'no-undef': 'off',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    rules: {
 | 
			
		||||
        // http://eslint.cn/docs/rules/
 | 
			
		||||
        // https://eslint.vuejs.org/rules/
 | 
			
		||||
        // https://typescript-eslint.io/rules/no-unused-vars/
 | 
			
		||||
        '@typescript-eslint/ban-ts-ignore': 'off',
 | 
			
		||||
        '@typescript-eslint/explicit-function-return-type': 'off',
 | 
			
		||||
        '@typescript-eslint/no-explicit-any': 'off',
 | 
			
		||||
        '@typescript-eslint/no-var-requires': 'off',
 | 
			
		||||
        '@typescript-eslint/no-empty-function': 'off',
 | 
			
		||||
        '@typescript-eslint/no-use-before-define': 'off',
 | 
			
		||||
        '@typescript-eslint/ban-ts-comment': 'off',
 | 
			
		||||
        '@typescript-eslint/ban-types': 'off',
 | 
			
		||||
        '@typescript-eslint/no-non-null-assertion': 'off',
 | 
			
		||||
        '@typescript-eslint/explicit-module-boundary-types': 'off',
 | 
			
		||||
        '@typescript-eslint/no-redeclare': 'error',
 | 
			
		||||
        '@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
 | 
			
		||||
        '@typescript-eslint/no-unused-vars': [2],
 | 
			
		||||
        'vue/custom-event-name-casing': 'off',
 | 
			
		||||
        'vue/attributes-order': 'off',
 | 
			
		||||
        'vue/one-component-per-file': 'off',
 | 
			
		||||
        'vue/html-closing-bracket-newline': 'off',
 | 
			
		||||
        'vue/max-attributes-per-line': 'off',
 | 
			
		||||
        'vue/multiline-html-element-content-newline': 'off',
 | 
			
		||||
        'vue/singleline-html-element-content-newline': 'off',
 | 
			
		||||
        'vue/attribute-hyphenation': 'off',
 | 
			
		||||
        'vue/html-self-closing': 'off',
 | 
			
		||||
        'vue/no-multiple-template-root': 'off',
 | 
			
		||||
        'vue/require-default-prop': 'off',
 | 
			
		||||
        'vue/no-v-model-argument': 'off',
 | 
			
		||||
        'vue/no-arrow-functions-in-watch': 'off',
 | 
			
		||||
        'vue/no-template-key': 'off',
 | 
			
		||||
        'vue/no-v-html': 'off',
 | 
			
		||||
        'vue/comment-directive': 'off',
 | 
			
		||||
        'vue/no-parsing-error': 'off',
 | 
			
		||||
        'vue/no-deprecated-v-on-native-modifier': 'off',
 | 
			
		||||
        'vue/multi-word-component-names': 'off',
 | 
			
		||||
        'no-useless-escape': 'off',
 | 
			
		||||
        'no-sparse-arrays': 'off',
 | 
			
		||||
        'no-prototype-builtins': 'off',
 | 
			
		||||
        'no-constant-condition': 'off',
 | 
			
		||||
        'no-use-before-define': 'off',
 | 
			
		||||
        'no-restricted-globals': 'off',
 | 
			
		||||
        'no-restricted-syntax': 'off',
 | 
			
		||||
        'generator-star-spacing': 'off',
 | 
			
		||||
        'no-unreachable': 'off',
 | 
			
		||||
        'no-multiple-template-root': 'off',
 | 
			
		||||
        'no-unused-vars': 'error',
 | 
			
		||||
        'no-v-model-argument': 'off',
 | 
			
		||||
        'no-case-declarations': 'off',
 | 
			
		||||
        // 'no-console': 'error',
 | 
			
		||||
        'no-redeclare': 'off',
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								mayfly_go_web/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,7 +1,8 @@
 | 
			
		||||
.DS_Store
 | 
			
		||||
node_modules
 | 
			
		||||
/dist
 | 
			
		||||
 | 
			
		||||
*.lock
 | 
			
		||||
pnpm-lock.yaml
 | 
			
		||||
 | 
			
		||||
# local env files
 | 
			
		||||
.env.local
 | 
			
		||||
 
 | 
			
		||||
@@ -1,39 +1,39 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
	// 一行最多多少个字符
 | 
			
		||||
	printWidth: 150,
 | 
			
		||||
	// 指定每个缩进级别的空格数
 | 
			
		||||
	tabWidth: 4,
 | 
			
		||||
	// 使用制表符而不是空格缩进行
 | 
			
		||||
	useTabs: false,
 | 
			
		||||
	// 在语句末尾打印分号
 | 
			
		||||
	semi: true,
 | 
			
		||||
	// 使用单引号而不是双引号
 | 
			
		||||
	singleQuote: true,
 | 
			
		||||
	// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
 | 
			
		||||
	quoteProps: 'as-needed',
 | 
			
		||||
	// 在JSX中使用单引号而不是双引号
 | 
			
		||||
	jsxSingleQuote: false,
 | 
			
		||||
	// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
 | 
			
		||||
	trailingComma: 'es5',
 | 
			
		||||
	// 在对象文字中的括号之间打印空格
 | 
			
		||||
	bracketSpacing: true,
 | 
			
		||||
	// jsx 标签的反尖括号需要换行
 | 
			
		||||
	jsxBracketSameLine: false,
 | 
			
		||||
	// 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
 | 
			
		||||
	arrowParens: 'always',
 | 
			
		||||
	// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
 | 
			
		||||
	rangeStart: 0,
 | 
			
		||||
	rangeEnd: Infinity,
 | 
			
		||||
	// 指定要使用的解析器,不需要写文件开头的 @prettier
 | 
			
		||||
	requirePragma: false,
 | 
			
		||||
	// 不需要自动在文件开头插入 @prettier
 | 
			
		||||
	insertPragma: false,
 | 
			
		||||
	// 使用默认的折行标准 always\never\preserve
 | 
			
		||||
	proseWrap: 'preserve',
 | 
			
		||||
	// 指定HTML文件的全局空格敏感度 css\strict\ignore
 | 
			
		||||
	htmlWhitespaceSensitivity: 'css',
 | 
			
		||||
	// Vue文件脚本和样式标签缩进
 | 
			
		||||
	vueIndentScriptAndStyle: false,
 | 
			
		||||
	// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
 | 
			
		||||
	endOfLine: 'lf',
 | 
			
		||||
    // 一行最多多少个字符
 | 
			
		||||
    printWidth: 160,
 | 
			
		||||
    // 指定每个缩进级别的空格数
 | 
			
		||||
    tabWidth: 4,
 | 
			
		||||
    // 使用制表符而不是空格缩进行
 | 
			
		||||
    useTabs: false,
 | 
			
		||||
    // 在语句末尾打印分号
 | 
			
		||||
    semi: true,
 | 
			
		||||
    // 使用单引号而不是双引号
 | 
			
		||||
    singleQuote: true,
 | 
			
		||||
    // 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
 | 
			
		||||
    quoteProps: 'as-needed',
 | 
			
		||||
    // 在JSX中使用单引号而不是双引号
 | 
			
		||||
    jsxSingleQuote: false,
 | 
			
		||||
    // 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
 | 
			
		||||
    trailingComma: 'es5',
 | 
			
		||||
    // 在对象文字中的括号之间打印空格
 | 
			
		||||
    bracketSpacing: true,
 | 
			
		||||
    // jsx 标签的反尖括号需要换行
 | 
			
		||||
    jsxBracketSameLine: false,
 | 
			
		||||
    // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
 | 
			
		||||
    arrowParens: 'always',
 | 
			
		||||
    // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
 | 
			
		||||
    rangeStart: 0,
 | 
			
		||||
    rangeEnd: Infinity,
 | 
			
		||||
    // 指定要使用的解析器,不需要写文件开头的 @prettier
 | 
			
		||||
    requirePragma: false,
 | 
			
		||||
    // 不需要自动在文件开头插入 @prettier
 | 
			
		||||
    insertPragma: false,
 | 
			
		||||
    // 使用默认的折行标准 always\never\preserve
 | 
			
		||||
    proseWrap: 'preserve',
 | 
			
		||||
    // 指定HTML文件的全局空格敏感度 css\strict\ignore
 | 
			
		||||
    htmlWhitespaceSensitivity: 'css',
 | 
			
		||||
    // Vue文件脚本和样式标签缩进
 | 
			
		||||
    vueIndentScriptAndStyle: false,
 | 
			
		||||
    // 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
 | 
			
		||||
    endOfLine: 'lf',
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2021 lyt-Top
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<html lang="zh_CN">
 | 
			
		||||
	<head>
 | 
			
		||||
		<meta charset="UTF-8" />
 | 
			
		||||
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
 | 
			
		||||
@@ -18,8 +18,7 @@
 | 
			
		||||
	</head>
 | 
			
		||||
	<body>
 | 
			
		||||
		<div id="app"></div>
 | 
			
		||||
        <script type="text/javascript" src="./config.js"></script>
 | 
			
		||||
        <script type="application/javascript" src="./config.js"></script>
 | 
			
		||||
		<script type="module" src="/src/main.ts"></script>
 | 
			
		||||
		<!-- <script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=wsijQt8sLXrCW71YesmispvYHitfG9gv&s=1"></script> -->
 | 
			
		||||
	</body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,48 +4,61 @@
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "vite",
 | 
			
		||||
    "build": "vite build",
 | 
			
		||||
    "preview": "vite preview",
 | 
			
		||||
    "build-preview": "npm run build && npm run preview",
 | 
			
		||||
    "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "axios": "^0.26.1",
 | 
			
		||||
    "codemirror": "^5.65.2",
 | 
			
		||||
    "countup.js": "^2.0.7",
 | 
			
		||||
    "cropperjs": "^1.5.11",
 | 
			
		||||
    "echarts": "^5.3.2",
 | 
			
		||||
    "element-plus": "^2.1.11",
 | 
			
		||||
    "@element-plus/icons-vue": "^1.1.3",
 | 
			
		||||
    "jsonlint": "^1.6.3",
 | 
			
		||||
    "@element-plus/icons-vue": "^2.3.1",
 | 
			
		||||
    "@vueuse/core": "^10.9.0",
 | 
			
		||||
    "asciinema-player": "^3.7.0",
 | 
			
		||||
    "axios": "^1.6.2",
 | 
			
		||||
    "clipboard": "^2.0.11",
 | 
			
		||||
    "countup.js": "^2.8.0", 
 | 
			
		||||
    "cropperjs": "^1.6.1",
 | 
			
		||||
    "echarts": "^5.5.0",
 | 
			
		||||
    "element-plus": "^2.7.1",
 | 
			
		||||
    "js-base64": "^3.7.7",
 | 
			
		||||
    "jsencrypt": "^3.3.2",
 | 
			
		||||
    "lodash": "^4.17.21",
 | 
			
		||||
    "mitt": "^3.0.0",
 | 
			
		||||
    "mitt": "^3.0.1",
 | 
			
		||||
    "monaco-editor": "^0.47.0",
 | 
			
		||||
    "monaco-sql-languages": "^0.11.0",
 | 
			
		||||
    "monaco-themes": "^0.4.4",
 | 
			
		||||
    "nprogress": "^0.2.0",
 | 
			
		||||
    "screenfull": "^5.1.0",
 | 
			
		||||
    "sortablejs": "^1.13.0",
 | 
			
		||||
    "vue-clipboard3": "^1.0.1",
 | 
			
		||||
    "sql-formatter": "^4.0.2",
 | 
			
		||||
    "vue": "^3.2.30",
 | 
			
		||||
    "vue-router": "^4.0.12",
 | 
			
		||||
    "vuex": "^4.0.2",
 | 
			
		||||
    "xterm": "^4.18.0",
 | 
			
		||||
    "xterm-addon-fit": "^0.5.0"
 | 
			
		||||
    "pinia": "^2.1.7",
 | 
			
		||||
    "qrcode.vue": "^3.4.1",
 | 
			
		||||
    "screenfull": "^6.0.2",
 | 
			
		||||
    "sortablejs": "^1.15.2",
 | 
			
		||||
    "splitpanes": "^3.1.5",
 | 
			
		||||
    "sql-formatter": "^15.0.2",
 | 
			
		||||
    "trzsz": "^1.1.5",
 | 
			
		||||
    "uuid": "^9.0.1",
 | 
			
		||||
    "vue": "^3.4.23",
 | 
			
		||||
    "vue-router": "^4.3.2",
 | 
			
		||||
    "xterm": "^5.3.0",
 | 
			
		||||
    "xterm-addon-fit": "^0.8.0",
 | 
			
		||||
    "xterm-addon-search": "^0.13.0",
 | 
			
		||||
    "xterm-addon-web-links": "^0.9.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/lodash": "^4.14.178",
 | 
			
		||||
    "@types/node": "^15.6.0",
 | 
			
		||||
    "@types/node": "^18.14.0",
 | 
			
		||||
    "@types/nprogress": "^0.2.0",
 | 
			
		||||
    "@types/sortablejs": "^1.10.6",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^4.23.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^4.23.0",
 | 
			
		||||
    "@vitejs/plugin-vue": "^1.2.2",
 | 
			
		||||
    "@vue/compiler-sfc": "^3.0.11",
 | 
			
		||||
    "dotenv": "^10.0.0",
 | 
			
		||||
    "eslint": "^8.5.0",
 | 
			
		||||
    "eslint-plugin-vue": "^8.2.0",
 | 
			
		||||
    "prettier": "^2.3.0",
 | 
			
		||||
    "sass": "^1.45.1",
 | 
			
		||||
    "sass-loader": "^12.4.0",
 | 
			
		||||
    "typescript": "^4.2.4",
 | 
			
		||||
    "vite": "^2.8.6",
 | 
			
		||||
    "vue-eslint-parser": "^8.0.1"
 | 
			
		||||
    "@types/sortablejs": "^1.15.8",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^6.7.4",
 | 
			
		||||
    "@typescript-eslint/parser": "^6.7.4",
 | 
			
		||||
    "@vitejs/plugin-vue": "^5.0.4",
 | 
			
		||||
    "@vue/compiler-sfc": "^3.4.23",
 | 
			
		||||
    "code-inspector-plugin": "^0.4.5",
 | 
			
		||||
    "dotenv": "^16.3.1",
 | 
			
		||||
    "eslint": "^8.35.0",
 | 
			
		||||
    "eslint-plugin-vue": "^9.25.0",
 | 
			
		||||
    "prettier": "^3.2.5",
 | 
			
		||||
    "sass": "^1.75.0",
 | 
			
		||||
    "typescript": "^5.4.5",
 | 
			
		||||
    "vite": "^5.2.10",
 | 
			
		||||
    "vue-eslint-parser": "^9.4.2"
 | 
			
		||||
  },
 | 
			
		||||
  "browserslist": [
 | 
			
		||||
    "> 1%",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								mayfly_go_web/plugins.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1 +0,0 @@
 | 
			
		||||
declare module 'vue-grid-layout';
 | 
			
		||||
@@ -1,4 +1,25 @@
 | 
			
		||||
window.globalConfig = {
 | 
			
		||||
    "BaseApiUrl": "http://localhost:8888",
 | 
			
		||||
    "BaseWsUrl": "ws://localhost:8888"
 | 
			
		||||
}
 | 
			
		||||
    // 默认为空,以访问根目录为api请求地址。若前后端分离部署可单独配置该后端api请求地址
 | 
			
		||||
    BaseApiUrl: '',
 | 
			
		||||
    BaseWsUrl: '',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// index.html添加百秒级时间戳,防止被浏览器缓存
 | 
			
		||||
// !(function () {
 | 
			
		||||
//     let t = 't=' + new Date().getTime().toString().substring(0, 8);
 | 
			
		||||
//     let search = location.search;
 | 
			
		||||
//     let m = search && search.match(/t=\d*/g);
 | 
			
		||||
 | 
			
		||||
//     console.log(location);
 | 
			
		||||
//     if (m[0]) {
 | 
			
		||||
//         if (m[0] !== t) {
 | 
			
		||||
//             location.search = search.replace(m[0], t);
 | 
			
		||||
//         }
 | 
			
		||||
//     } else {
 | 
			
		||||
//         if (search.indexOf('?') > -1) {
 | 
			
		||||
//             location.search = search + '&' + t;
 | 
			
		||||
//         } else {
 | 
			
		||||
//             location.search = t;
 | 
			
		||||
//         }
 | 
			
		||||
//     }
 | 
			
		||||
// })();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,70 +1,103 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <router-view v-show="getThemeConfig.lockScreenTime !== 0" />
 | 
			
		||||
    <LockScreen v-if="getThemeConfig.isLockScreen" />
 | 
			
		||||
    <Setings ref="setingsRef" v-show="getThemeConfig.lockScreenTime !== 0" />
 | 
			
		||||
    <div class="h100">
 | 
			
		||||
        <el-watermark
 | 
			
		||||
            :zIndex="10000000"
 | 
			
		||||
            :width="210"
 | 
			
		||||
            v-if="themeConfig.isWatermark"
 | 
			
		||||
            :font="{ color: 'rgba(180, 180, 180, 0.3)' }"
 | 
			
		||||
            :content="themeConfig.watermarkText"
 | 
			
		||||
            class="h100"
 | 
			
		||||
        >
 | 
			
		||||
            <router-view v-show="themeConfig.lockScreenTime !== 0" />
 | 
			
		||||
        </el-watermark>
 | 
			
		||||
        <router-view v-if="!themeConfig.isWatermark" v-show="themeConfig.lockScreenTime !== 0" />
 | 
			
		||||
 | 
			
		||||
        <LockScreen v-if="themeConfig.isLockScreen" />
 | 
			
		||||
        <Setings ref="setingsRef" v-show="themeConfig.lockScreenTime !== 0" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch } from 'vue';
 | 
			
		||||
<script setup lang="ts" name="app">
 | 
			
		||||
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue';
 | 
			
		||||
import { useRoute } from 'vue-router';
 | 
			
		||||
import { useStore } from '@/store/index.ts';
 | 
			
		||||
import { getLocal } from '@/common/utils/storage.ts';
 | 
			
		||||
import LockScreen from '@/views/layout/lockScreen/index.vue';
 | 
			
		||||
import Setings from '@/views/layout/navBars/breadcrumb/setings.vue';
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
    name: 'app',
 | 
			
		||||
    components: { LockScreen, Setings },
 | 
			
		||||
    setup() {
 | 
			
		||||
        const { proxy } = getCurrentInstance() as any;
 | 
			
		||||
        const setingsRef = ref();
 | 
			
		||||
        const route = useRoute();
 | 
			
		||||
        const store = useStore();
 | 
			
		||||
        // 获取布局配置信息
 | 
			
		||||
        const getThemeConfig = computed(() => {
 | 
			
		||||
            return store.state.themeConfig.themeConfig;
 | 
			
		||||
import { storeToRefs } from 'pinia';
 | 
			
		||||
import { useThemeConfig } from '@/store/themeConfig';
 | 
			
		||||
import LockScreen from '@/layout/lockScreen/index.vue';
 | 
			
		||||
import Setings from '@/layout/navBars/breadcrumb/setings.vue';
 | 
			
		||||
import mittBus from '@/common/utils/mitt';
 | 
			
		||||
import { useIntervalFn } from '@vueuse/core';
 | 
			
		||||
 | 
			
		||||
const setingsRef = ref();
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
 | 
			
		||||
const themeConfigStores = useThemeConfig();
 | 
			
		||||
const { themeConfig } = storeToRefs(themeConfigStores);
 | 
			
		||||
 | 
			
		||||
// 布局配置弹窗打开
 | 
			
		||||
const openSetingsDrawer = () => {
 | 
			
		||||
    setingsRef.value.openDrawer();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 页面加载时
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
        // 监听布局配置弹窗点击打开
 | 
			
		||||
        mittBus.on('openSetingsDrawer', () => {
 | 
			
		||||
            openSetingsDrawer();
 | 
			
		||||
        });
 | 
			
		||||
        // 布局配置弹窗打开
 | 
			
		||||
        const openSetingsDrawer = () => {
 | 
			
		||||
            setingsRef.value.openDrawer();
 | 
			
		||||
        };
 | 
			
		||||
        // 设置初始化,防止刷新时恢复默认
 | 
			
		||||
        onBeforeMount(() => {
 | 
			
		||||
            // 设置批量第三方 icon 图标
 | 
			
		||||
            // setIntroduction.cssCdn();
 | 
			
		||||
            // // 设置批量第三方 js
 | 
			
		||||
            // setIntroduction.jsCdn();
 | 
			
		||||
        });
 | 
			
		||||
        // 页面加载时
 | 
			
		||||
        onMounted(() => {
 | 
			
		||||
            nextTick(() => {
 | 
			
		||||
                // 监听布局配置弹窗点击打开
 | 
			
		||||
                proxy.mittBus.on('openSetingsDrawer', () => {
 | 
			
		||||
                    openSetingsDrawer();
 | 
			
		||||
                });
 | 
			
		||||
                // 获取缓存中的布局配置
 | 
			
		||||
                if (getLocal('themeConfig')) {
 | 
			
		||||
                    store.dispatch('themeConfig/setThemeConfig', getLocal('themeConfig'));
 | 
			
		||||
                    document.documentElement.style.cssText = getLocal('themeConfigStyle');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        // 页面销毁时,关闭监听布局配置
 | 
			
		||||
        onUnmounted(() => {
 | 
			
		||||
            proxy.mittBus.off('openSetingsDrawer', () => {});
 | 
			
		||||
        });
 | 
			
		||||
        // 监听路由的变化,设置网站标题
 | 
			
		||||
        watch(
 | 
			
		||||
            () => route.path,
 | 
			
		||||
            () => {
 | 
			
		||||
                nextTick(() => {
 | 
			
		||||
                    document.title = `${route.meta.title} - ${getThemeConfig.value.globalTitle}` || getThemeConfig.value.globalTitle;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        return {
 | 
			
		||||
            setingsRef,
 | 
			
		||||
            getThemeConfig,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
        // 初始化系统主题
 | 
			
		||||
        themeConfigStores.initThemeConfig();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 监听 themeConfig isWartermark配置文件的变化
 | 
			
		||||
watch(
 | 
			
		||||
    () => themeConfig.value.isWatermark,
 | 
			
		||||
    (val) => {
 | 
			
		||||
        if (val) {
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                setWatermarkContent();
 | 
			
		||||
                refreshWatermarkTime();
 | 
			
		||||
                resume();
 | 
			
		||||
            }, 500);
 | 
			
		||||
        } else {
 | 
			
		||||
            pause();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// 刷新水印时间
 | 
			
		||||
const { pause, resume } = useIntervalFn(() => {
 | 
			
		||||
    if (!themeConfig.value.isWatermark) {
 | 
			
		||||
        pause();
 | 
			
		||||
    }
 | 
			
		||||
    refreshWatermarkTime();
 | 
			
		||||
}, 60000);
 | 
			
		||||
 | 
			
		||||
const setWatermarkContent = () => {
 | 
			
		||||
    themeConfigStores.setWatermarkUser();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 刷新水印时间
 | 
			
		||||
 */
 | 
			
		||||
const refreshWatermarkTime = () => {
 | 
			
		||||
    themeConfigStores.setWatermarkNowTime();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 页面销毁时,关闭监听布局配置
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
    mittBus.off('openSetingsDrawer', () => {});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 监听路由的变化,设置网站标题
 | 
			
		||||
watch(
 | 
			
		||||
    () => route.path,
 | 
			
		||||
    () => {
 | 
			
		||||
        nextTick(() => {
 | 
			
		||||
            document.title = `${route.meta.title} - ${themeConfig.value.globalTitle}` || themeConfig.value.globalTitle;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										66
									
								
								mayfly_go_web/src/assets/iconfont/iconfont.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										107
									
								
								mayfly_go_web/src/assets/iconfont/iconfont.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,107 @@
 | 
			
		||||
{
 | 
			
		||||
  "id": "3953964",
 | 
			
		||||
  "name": "mayfly-go",
 | 
			
		||||
  "font_family": "iconfont",
 | 
			
		||||
  "css_prefix_text": "icon-",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "glyphs": [
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "23957582",
 | 
			
		||||
      "name": "MongoDB",
 | 
			
		||||
      "font_class": "mongo",
 | 
			
		||||
      "unicode": "e646",
 | 
			
		||||
      "unicode_decimal": 58950
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "4969649",
 | 
			
		||||
      "name": "Redis",
 | 
			
		||||
      "font_class": "op-redis",
 | 
			
		||||
      "unicode": "e728",
 | 
			
		||||
      "unicode_decimal": 59176
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "22442993",
 | 
			
		||||
      "name": "PostgreSQL",
 | 
			
		||||
      "font_class": "op-postgres",
 | 
			
		||||
      "unicode": "e8b7",
 | 
			
		||||
      "unicode_decimal": 59575
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "icon_id": "12295203",
 | 
			
		||||
        "name": "达梦数据库",
 | 
			
		||||
        "font_class": "db-dm",
 | 
			
		||||
        "unicode": "e6f0",
 | 
			
		||||
        "unicode_decimal": 59120
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "10055634",
 | 
			
		||||
      "name": "云数据库MongoDB",
 | 
			
		||||
      "font_class": "op-mongo",
 | 
			
		||||
      "unicode": "e7d7",
 | 
			
		||||
      "unicode_decimal": 59351
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "10055642",
 | 
			
		||||
      "name": "云数据库 RDS MySQL",
 | 
			
		||||
      "font_class": "op-mysql",
 | 
			
		||||
      "unicode": "e7d8",
 | 
			
		||||
      "unicode_decimal": 59352
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "3876165",
 | 
			
		||||
      "name": "redis",
 | 
			
		||||
      "font_class": "redis",
 | 
			
		||||
      "unicode": "e619",
 | 
			
		||||
      "unicode_decimal": 58905
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "25271976",
 | 
			
		||||
      "name": "oracle",
 | 
			
		||||
      "font_class": "oracle",
 | 
			
		||||
      "unicode": "e507",
 | 
			
		||||
      "unicode_decimal": 58631
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "8105644",
 | 
			
		||||
      "name": "mariadb",
 | 
			
		||||
      "font_class": "mariadb",
 | 
			
		||||
      "unicode": "e513",
 | 
			
		||||
      "unicode_decimal": 58643
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "13601813",
 | 
			
		||||
      "name": "sqlite",
 | 
			
		||||
      "font_class": "sqlite",
 | 
			
		||||
      "unicode": "e546",
 | 
			
		||||
      "unicode_decimal": 58694
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "29340317",
 | 
			
		||||
      "name": "temp-mssql",
 | 
			
		||||
      "font_class": "MSSQLNATIVE",
 | 
			
		||||
      "unicode": "e600",
 | 
			
		||||
      "unicode_decimal": 58880
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "7699332",
 | 
			
		||||
      "name": "gaussdb",
 | 
			
		||||
      "font_class": "gauss",
 | 
			
		||||
      "unicode": "e683",
 | 
			
		||||
      "unicode_decimal": 59011
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "34836637",
 | 
			
		||||
      "name": "kingbase",
 | 
			
		||||
      "font_class": "kingbase",
 | 
			
		||||
      "unicode": "e882",
 | 
			
		||||
      "unicode_decimal": 59522
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "33047500",
 | 
			
		||||
      "name": "vastbase",
 | 
			
		||||
      "font_class": "vastbase",
 | 
			
		||||
      "unicode": "e62b",
 | 
			
		||||
      "unicode_decimal": 58923
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 39 KiB  | 
							
								
								
									
										1
									
								
								mayfly_go_web/src/assets/image/401.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 14 KiB  | 
| 
		 Before Width: | Height: | Size: 36 KiB  | 
							
								
								
									
										1
									
								
								mayfly_go_web/src/assets/image/404.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 13 KiB  | 
| 
		 Before Width: | Height: | Size: 458 KiB  | 
							
								
								
									
										1
									
								
								mayfly_go_web/src/assets/image/login-bg-main.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 13 KiB  | 
							
								
								
									
										19
									
								
								mayfly_go_web/src/assets/image/login-bg-split.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 8.4 KiB  | 
@@ -1,4 +1,5 @@
 | 
			
		||||
import request from './request'
 | 
			
		||||
import request from './request';
 | 
			
		||||
import { useApiFetch } from '@/hooks/useRequest';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 可用于各模块定义各自api请求
 | 
			
		||||
@@ -14,26 +15,24 @@ class Api {
 | 
			
		||||
     */
 | 
			
		||||
    method: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 请求前处理函数
 | 
			
		||||
     * param1: param请求参数
 | 
			
		||||
     */
 | 
			
		||||
    beforeHandler: Function;
 | 
			
		||||
 | 
			
		||||
    constructor(url: string, method: string) {
 | 
			
		||||
        this.url = url;
 | 
			
		||||
        this.method = method;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置rl
 | 
			
		||||
     * @param {String} uri 请求url
 | 
			
		||||
     * 设置请求前处理回调函数
 | 
			
		||||
     * @param func 请求前处理器
 | 
			
		||||
     * @returns this
 | 
			
		||||
     */
 | 
			
		||||
    setUrl(url: string) {
 | 
			
		||||
        this.url = url;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * url的请求方法
 | 
			
		||||
     * @param {String} method 请求方法
 | 
			
		||||
     */
 | 
			
		||||
    setMethod(method: string) {
 | 
			
		||||
        this.method = method;
 | 
			
		||||
    withBeforeHandler(func: Function) {
 | 
			
		||||
        this.beforeHandler = func;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -45,21 +44,35 @@ class Api {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 操作该权限,即请求对应的url
 | 
			
		||||
     * @param {Object} param 请求该权限的参数
 | 
			
		||||
     * 响应式使用该api
 | 
			
		||||
     * @param params 响应式params
 | 
			
		||||
     * @param reqOptions 其他可选值
 | 
			
		||||
     * @returns
 | 
			
		||||
     */
 | 
			
		||||
    request(param: any = null, options: any = null): Promise<any> {
 | 
			
		||||
        return request.send(this, param, options);
 | 
			
		||||
    useApi<T>(params: any = null, reqOptions: RequestInit = {}) {
 | 
			
		||||
        return useApiFetch<T>(this, params, reqOptions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * 操作该权限,即请求对应的url
 | 
			
		||||
    * @param {Object} param 请求该权限的参数
 | 
			
		||||
    */
 | 
			
		||||
    requestWithHeaders(param: any, headers: any): Promise<any> {
 | 
			
		||||
        return request.sendWithHeaders(this, param, headers);
 | 
			
		||||
     * fetch 请求对应的该api
 | 
			
		||||
     * @param {Object} param 请求该api的参数
 | 
			
		||||
     */
 | 
			
		||||
    async request(param: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
        const { execute, data } = this.useApi(param, options);
 | 
			
		||||
        await execute();
 | 
			
		||||
        return data.value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * xhr 请求对应的该api
 | 
			
		||||
     * @param {Object} param 请求该api的参数
 | 
			
		||||
     */
 | 
			
		||||
    async xhrReq(param: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
        if (this.beforeHandler) {
 | 
			
		||||
            this.beforeHandler(param);
 | 
			
		||||
        }
 | 
			
		||||
        return request.xhrReq(this.method, this.url, param, options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**    静态方法     **/
 | 
			
		||||
 | 
			
		||||
@@ -68,10 +81,46 @@ class Api {
 | 
			
		||||
     * @param url url
 | 
			
		||||
     * @param method 请求方法(get,post,put,delete...)
 | 
			
		||||
     */
 | 
			
		||||
    static create(url: string, method: string) {
 | 
			
		||||
    static create(url: string, method: string): Api {
 | 
			
		||||
        return new Api(url, method);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建get api
 | 
			
		||||
     * @param url url
 | 
			
		||||
     */
 | 
			
		||||
    static newGet(url: string): Api {
 | 
			
		||||
        return Api.create(url, 'get');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * new post api
 | 
			
		||||
     * @param url url
 | 
			
		||||
     */
 | 
			
		||||
    static newPost(url: string): Api {
 | 
			
		||||
        return Api.create(url, 'post');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * new put api
 | 
			
		||||
     * @param url url
 | 
			
		||||
     */
 | 
			
		||||
    static newPut(url: string): Api {
 | 
			
		||||
        return Api.create(url, 'put');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * new delete api
 | 
			
		||||
     * @param url url
 | 
			
		||||
     */
 | 
			
		||||
    static newDelete(url: string): Api {
 | 
			
		||||
        return Api.create(url, 'delete');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Api;
 | 
			
		||||
 | 
			
		||||
export default Api
 | 
			
		||||
export class PageRes {
 | 
			
		||||
    list: any[] = [];
 | 
			
		||||
    total: number = 0;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,37 +1,102 @@
 | 
			
		||||
export interface EnumValueTag {
 | 
			
		||||
    color?: string;
 | 
			
		||||
    type?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 枚举类
 | 
			
		||||
 * @author meilin.huang
 | 
			
		||||
 * 枚举值
 | 
			
		||||
 */
 | 
			
		||||
export class Enum {
 | 
			
		||||
export class EnumValue {
 | 
			
		||||
    /**
 | 
			
		||||
     * 添加枚举字段
 | 
			
		||||
     * 
 | 
			
		||||
     * @param {string} field  枚举字段名
 | 
			
		||||
     * @param {string} label  枚举名称
 | 
			
		||||
     * @param {Object} value  枚举值
 | 
			
		||||
     * 枚举值
 | 
			
		||||
     */
 | 
			
		||||
    add(field: string, label: string, value: any) {
 | 
			
		||||
        this[field] = { label, value }
 | 
			
		||||
        return this
 | 
			
		||||
    value: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 枚举描述
 | 
			
		||||
     */
 | 
			
		||||
    label: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 展示的标签信息
 | 
			
		||||
     */
 | 
			
		||||
    tag: EnumValueTag;
 | 
			
		||||
 | 
			
		||||
    extra: any;
 | 
			
		||||
 | 
			
		||||
    constructor(value: any, label: string) {
 | 
			
		||||
        this.value = value;
 | 
			
		||||
        this.label = label;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setTagType(type: string = 'primary'): EnumValue {
 | 
			
		||||
        this.tag = { type };
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tagTypeInfo(): EnumValue {
 | 
			
		||||
        return this.setTagType('info');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tagTypeSuccess(): EnumValue {
 | 
			
		||||
        return this.setTagType('success');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tagTypeDanger(): EnumValue {
 | 
			
		||||
        return this.setTagType('danger');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tagTypeWarning(): EnumValue {
 | 
			
		||||
        return this.setTagType('warning');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setTagColor(color: string): EnumValue {
 | 
			
		||||
        this.tag = { color };
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setExtra(extra: any): EnumValue {
 | 
			
		||||
        this.extra = extra;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static of(value: any, label: string): EnumValue {
 | 
			
		||||
        return new EnumValue(value, label);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据枚举value获取其label
 | 
			
		||||
     * 
 | 
			
		||||
     * @param {Object} value 
 | 
			
		||||
     * 根据枚举值获取指定枚举值对象
 | 
			
		||||
     *
 | 
			
		||||
     * @param enums 枚举对象
 | 
			
		||||
     * @param value 需要匹配的枚举值
 | 
			
		||||
     * @returns 枚举值对象
 | 
			
		||||
     */
 | 
			
		||||
    getLabelByValue(value: any) {
 | 
			
		||||
        // 字段不存在返回‘’
 | 
			
		||||
        if (value === undefined || value === null) {
 | 
			
		||||
            return ''
 | 
			
		||||
        }
 | 
			
		||||
        for (const i in this) {
 | 
			
		||||
            const e: any = this[i]
 | 
			
		||||
            if (e && e.value === value) {
 | 
			
		||||
                return e.label
 | 
			
		||||
    static getEnumByValue(enums: any, value: any): EnumValue | null {
 | 
			
		||||
        const enumValues = Object.values(enums) as any;
 | 
			
		||||
        for (let enumValue of enumValues) {
 | 
			
		||||
            if (enumValue.value == value) {
 | 
			
		||||
                return enumValue;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        return ''
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据枚举值获取枚举描述
 | 
			
		||||
     *
 | 
			
		||||
     * @param enums 枚举对象
 | 
			
		||||
     * @param value 枚举值
 | 
			
		||||
     * @returns 枚举描述
 | 
			
		||||
     */
 | 
			
		||||
    static getLabelByValue(enums: any, value: any) {
 | 
			
		||||
        const enumValues = Object.values(enums) as any;
 | 
			
		||||
        for (let enumValue of enumValues) {
 | 
			
		||||
            if (enumValue['value'] == value) {
 | 
			
		||||
                return enumValue['label'];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default EnumValue;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +1,43 @@
 | 
			
		||||
class SocketBuilder {
 | 
			
		||||
 | 
			
		||||
    websocket: WebSocket;
 | 
			
		||||
  
 | 
			
		||||
    constructor(url: string) {
 | 
			
		||||
      if (typeof (WebSocket) === "undefined") {
 | 
			
		||||
        throw new Error('不支持websocket');
 | 
			
		||||
      }
 | 
			
		||||
      if (!url) {
 | 
			
		||||
        throw new Error('websocket url不能为空');
 | 
			
		||||
      }
 | 
			
		||||
      this.websocket = new WebSocket(url);
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    static builder(url: string) {
 | 
			
		||||
      return new SocketBuilder(url);
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    open(onopen: any) {
 | 
			
		||||
      this.websocket.onopen = onopen;
 | 
			
		||||
      return this;
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    error(onerror: any) {
 | 
			
		||||
      this.websocket.onerror = onerror;
 | 
			
		||||
      return this;
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    message(onmessage: any) {
 | 
			
		||||
      this.websocket.onmessage = onmessage;
 | 
			
		||||
      return this;
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    close(onclose: any) {
 | 
			
		||||
      this.websocket.onclose = onclose;
 | 
			
		||||
      return this;
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    build() {
 | 
			
		||||
      return this.websocket;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
  export default SocketBuilder;
 | 
			
		||||
  
 | 
			
		||||
    constructor(url: string) {
 | 
			
		||||
        if (typeof WebSocket === 'undefined') {
 | 
			
		||||
            throw new Error('不支持websocket');
 | 
			
		||||
        }
 | 
			
		||||
        if (!url) {
 | 
			
		||||
            throw new Error('websocket url不能为空');
 | 
			
		||||
        }
 | 
			
		||||
        this.websocket = new WebSocket(url);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static builder(url: string) {
 | 
			
		||||
        return new SocketBuilder(url);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open(onopen: any) {
 | 
			
		||||
        this.websocket.onopen = onopen;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    error(onerror: any) {
 | 
			
		||||
        this.websocket.onerror = onerror;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    message(onmessage: any) {
 | 
			
		||||
        this.websocket.onmessage = onmessage;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    close(onclose: any) {
 | 
			
		||||
        this.websocket.onclose = onclose;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    build() {
 | 
			
		||||
        return this.websocket;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default SocketBuilder;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,17 +5,17 @@ class AssertError extends Error {
 | 
			
		||||
    constructor(message: string) {
 | 
			
		||||
        super(message);
 | 
			
		||||
        // 错误类名
 | 
			
		||||
        this.name = "AssertError";
 | 
			
		||||
        this.name = 'AssertError';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 断言表达式为true
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param condition 条件表达式
 | 
			
		||||
 * @param msg 错误消息
 | 
			
		||||
 */
 | 
			
		||||
 export function isTrue(condition: boolean, msg: string) {
 | 
			
		||||
export function isTrue(condition: boolean, msg: string) {
 | 
			
		||||
    if (!condition) {
 | 
			
		||||
        throw new AssertError(msg);
 | 
			
		||||
    }
 | 
			
		||||
@@ -23,45 +23,45 @@ class AssertError extends Error {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 断言不能为空值,即null,0,''等
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param obj 对象1
 | 
			
		||||
 * @param msg 错误消息
 | 
			
		||||
 */
 | 
			
		||||
 export function notBlank(obj: any, msg: string) {
 | 
			
		||||
    isTrue(obj, msg)
 | 
			
		||||
export function notBlank(obj: any, msg: string) {
 | 
			
		||||
    isTrue(obj, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 断言两对象相等
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param obj1 对象1
 | 
			
		||||
 * @param obj2 对象2
 | 
			
		||||
 * @param msg 错误消息
 | 
			
		||||
 */
 | 
			
		||||
 export function isEquals(obj1: any, obj2: any, msg: string) {
 | 
			
		||||
export function isEquals(obj1: any, obj2: any, msg: string) {
 | 
			
		||||
    isTrue(obj1 === obj2, msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 断言对象不为null或undefiend
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @param obj 对象
 | 
			
		||||
 * @param msg 错误提示
 | 
			
		||||
 */
 | 
			
		||||
export function notNull(obj: any, msg: string) {
 | 
			
		||||
    if (obj == null || obj == undefined) {
 | 
			
		||||
        throw new AssertError(msg)
 | 
			
		||||
        throw new AssertError(msg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* 断言字符串不能为空
 | 
			
		||||
* 
 | 
			
		||||
* @param str 字符串
 | 
			
		||||
* @param msg 错误提示
 | 
			
		||||
*/
 | 
			
		||||
 * 断言字符串不能为空
 | 
			
		||||
 *
 | 
			
		||||
 * @param str 字符串
 | 
			
		||||
 * @param msg 错误提示
 | 
			
		||||
 */
 | 
			
		||||
export function notEmpty(str: string, msg: string) {
 | 
			
		||||
    if (str == null || str == undefined || str == '') {
 | 
			
		||||
        throw new AssertError(msg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								mayfly_go_web/src/common/commonEnum.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,24 @@
 | 
			
		||||
import EnumValue from './Enum';
 | 
			
		||||
 | 
			
		||||
// 资源类型
 | 
			
		||||
export const ResourceTypeEnum = {
 | 
			
		||||
    Machine: EnumValue.of(1, '机器').setExtra({ icon: 'Monitor', iconColor: 'var(--el-color-primary)' }).tagTypeSuccess(),
 | 
			
		||||
    Db: EnumValue.of(2, '数据库实例').setExtra({ icon: 'Coin', iconColor: 'var(--el-color-warning)' }).tagTypeWarning(),
 | 
			
		||||
    Redis: EnumValue.of(3, 'redis').setExtra({ icon: 'iconfont icon-redis', iconColor: 'var(--el-color-danger)' }).tagTypeInfo(),
 | 
			
		||||
    Mongo: EnumValue.of(4, 'mongo').setExtra({ icon: 'iconfont icon-mongo', iconColor: 'var(--el-color-success)' }).tagTypeDanger(),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 标签关联的资源类型
 | 
			
		||||
export const TagResourceTypeEnum = {
 | 
			
		||||
    AuthCert: EnumValue.of(-2, '公共凭证').setExtra({ icon: 'Ticket' }),
 | 
			
		||||
    Tag: EnumValue.of(-1, '标签').setExtra({ icon: 'CollectionTag' }),
 | 
			
		||||
 | 
			
		||||
    Machine: ResourceTypeEnum.Machine,
 | 
			
		||||
    Db: ResourceTypeEnum.Db,
 | 
			
		||||
    Redis: ResourceTypeEnum.Redis,
 | 
			
		||||
    Mongo: ResourceTypeEnum.Mongo,
 | 
			
		||||
 | 
			
		||||
    MachineAuthCert: EnumValue.of(11, '机器-授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }),
 | 
			
		||||
    DbAuthCert: EnumValue.of(21, '数据库-授权凭证').setExtra({ icon: 'Ticket', iconColor: 'var(--el-color-success)' }),
 | 
			
		||||
    DbName: EnumValue.of(22, '数据库').setExtra({ icon: 'Coin' }),
 | 
			
		||||
};
 | 
			
		||||
@@ -1,6 +1,21 @@
 | 
			
		||||
const config = {
 | 
			
		||||
    baseApiUrl:  `${(window as any).globalConfig.BaseApiUrl}/api`,
 | 
			
		||||
    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl}/api`
 | 
			
		||||
function getBaseApiUrl() {
 | 
			
		||||
    let path = window.location.pathname;
 | 
			
		||||
    if (path == '/') {
 | 
			
		||||
        return window.location.host;
 | 
			
		||||
    }
 | 
			
		||||
    if (path.endsWith('/')) {
 | 
			
		||||
        // 去除最后一个/
 | 
			
		||||
        return window.location.host + path.replace(/\/$/, '');
 | 
			
		||||
    }
 | 
			
		||||
    return window.location.host + path;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default config
 | 
			
		||||
const config = {
 | 
			
		||||
    baseApiUrl: `${(window as any).globalConfig.BaseApiUrl || location.protocol + '//' + getBaseApiUrl()}/api`,
 | 
			
		||||
    baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
 | 
			
		||||
 | 
			
		||||
    // 系统版本
 | 
			
		||||
    version: 'v1.8.1',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default config;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,176 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
    "seriesCnt": "4",
 | 
			
		||||
    "backgroundColor": "rgba(0,0,0,0)",
 | 
			
		||||
    "titleColor": "#008acd",
 | 
			
		||||
    "subtitleColor": "#aaaaaa",
 | 
			
		||||
    "textColorShow": false,
 | 
			
		||||
    "textColor": "#333",
 | 
			
		||||
    "markTextColor": "#eeeeee",
 | 
			
		||||
    "color": [
 | 
			
		||||
        "#2ec7c9",
 | 
			
		||||
        "#b6a2de",
 | 
			
		||||
        "#5ab1ef",
 | 
			
		||||
        "#ffb980",
 | 
			
		||||
        "#d87a80",
 | 
			
		||||
        "#8d98b3",
 | 
			
		||||
        "#e5cf0d",
 | 
			
		||||
        "#97b552",
 | 
			
		||||
        "#95706d",
 | 
			
		||||
        "#dc69aa",
 | 
			
		||||
        "#07a2a4",
 | 
			
		||||
        "#9a7fd1",
 | 
			
		||||
        "#588dd5",
 | 
			
		||||
        "#f5994e",
 | 
			
		||||
        "#c05050",
 | 
			
		||||
        "#59678c",
 | 
			
		||||
        "#c9ab00",
 | 
			
		||||
        "#7eb00a",
 | 
			
		||||
        "#6f5553",
 | 
			
		||||
        "#c14089"
 | 
			
		||||
    ],
 | 
			
		||||
    "borderColor": "#ccc",
 | 
			
		||||
    "borderWidth": 0,
 | 
			
		||||
    "visualMapColor": [
 | 
			
		||||
        "#5ab1ef",
 | 
			
		||||
        "#e0ffff"
 | 
			
		||||
    ],
 | 
			
		||||
    "legendTextColor": "#333333",
 | 
			
		||||
    "kColor": "#d87a80",
 | 
			
		||||
    "kColor0": "#2ec7c9",
 | 
			
		||||
    "kBorderColor": "#d87a80",
 | 
			
		||||
    "kBorderColor0": "#2ec7c9",
 | 
			
		||||
    "kBorderWidth": 1,
 | 
			
		||||
    "lineWidth": 2,
 | 
			
		||||
    "symbolSize": 3,
 | 
			
		||||
    "symbol": "emptyCircle",
 | 
			
		||||
    "symbolBorderWidth": 1,
 | 
			
		||||
    "lineSmooth": true,
 | 
			
		||||
    "graphLineWidth": 1,
 | 
			
		||||
    "graphLineColor": "#aaaaaa",
 | 
			
		||||
    "mapLabelColor": "#d87a80",
 | 
			
		||||
    "mapLabelColorE": "rgb(100,0,0)",
 | 
			
		||||
    "mapBorderColor": "#eeeeee",
 | 
			
		||||
    "mapBorderColorE": "#444",
 | 
			
		||||
    "mapBorderWidth": 0.5,
 | 
			
		||||
    "mapBorderWidthE": 1,
 | 
			
		||||
    "mapAreaColor": "#dddddd",
 | 
			
		||||
    "mapAreaColorE": "rgba(254,153,78,1)",
 | 
			
		||||
    "axes": [
 | 
			
		||||
        {
 | 
			
		||||
            "type": "all",
 | 
			
		||||
            "name": "通用坐标轴",
 | 
			
		||||
            "axisLineShow": true,
 | 
			
		||||
            "axisLineColor": "#eeeeee",
 | 
			
		||||
            "axisTickShow": true,
 | 
			
		||||
            "axisTickColor": "#eeeeee",
 | 
			
		||||
            "axisLabelShow": true,
 | 
			
		||||
            "axisLabelColor": "#eeeeee",
 | 
			
		||||
            "splitLineShow": true,
 | 
			
		||||
            "splitLineColor": [
 | 
			
		||||
                "#aaaaaa"
 | 
			
		||||
            ],
 | 
			
		||||
            "splitAreaShow": false,
 | 
			
		||||
            "splitAreaColor": [
 | 
			
		||||
                "#eeeeee"
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "type": "category",
 | 
			
		||||
            "name": "类目坐标轴",
 | 
			
		||||
            "axisLineShow": true,
 | 
			
		||||
            "axisLineColor": "#008acd",
 | 
			
		||||
            "axisTickShow": true,
 | 
			
		||||
            "axisTickColor": "#333",
 | 
			
		||||
            "axisLabelShow": true,
 | 
			
		||||
            "axisLabelColor": "#333",
 | 
			
		||||
            "splitLineShow": false,
 | 
			
		||||
            "splitLineColor": [
 | 
			
		||||
                "#eee"
 | 
			
		||||
            ],
 | 
			
		||||
            "splitAreaShow": false,
 | 
			
		||||
            "splitAreaColor": [
 | 
			
		||||
                "rgba(250,250,250,0.3)",
 | 
			
		||||
                "rgba(200,200,200,0.3)"
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "type": "value",
 | 
			
		||||
            "name": "数值坐标轴",
 | 
			
		||||
            "axisLineShow": true,
 | 
			
		||||
            "axisLineColor": "#008acd",
 | 
			
		||||
            "axisTickShow": true,
 | 
			
		||||
            "axisTickColor": "#333",
 | 
			
		||||
            "axisLabelShow": true,
 | 
			
		||||
            "axisLabelColor": "#333",
 | 
			
		||||
            "splitLineShow": true,
 | 
			
		||||
            "splitLineColor": [
 | 
			
		||||
                "#eee"
 | 
			
		||||
            ],
 | 
			
		||||
            "splitAreaShow": true,
 | 
			
		||||
            "splitAreaColor": [
 | 
			
		||||
                "rgba(250,250,250,0.3)",
 | 
			
		||||
                "rgba(200,200,200,0.3)"
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "type": "log",
 | 
			
		||||
            "name": "对数坐标轴",
 | 
			
		||||
            "axisLineShow": true,
 | 
			
		||||
            "axisLineColor": "#008acd",
 | 
			
		||||
            "axisTickShow": true,
 | 
			
		||||
            "axisTickColor": "#333",
 | 
			
		||||
            "axisLabelShow": true,
 | 
			
		||||
            "axisLabelColor": "#333",
 | 
			
		||||
            "splitLineShow": true,
 | 
			
		||||
            "splitLineColor": [
 | 
			
		||||
                "#eee"
 | 
			
		||||
            ],
 | 
			
		||||
            "splitAreaShow": true,
 | 
			
		||||
            "splitAreaColor": [
 | 
			
		||||
                "rgba(250,250,250,0.3)",
 | 
			
		||||
                "rgba(200,200,200,0.3)"
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "type": "time",
 | 
			
		||||
            "name": "时间坐标轴",
 | 
			
		||||
            "axisLineShow": true,
 | 
			
		||||
            "axisLineColor": "#008acd",
 | 
			
		||||
            "axisTickShow": true,
 | 
			
		||||
            "axisTickColor": "#333",
 | 
			
		||||
            "axisLabelShow": true,
 | 
			
		||||
            "axisLabelColor": "#333",
 | 
			
		||||
            "splitLineShow": true,
 | 
			
		||||
            "splitLineColor": [
 | 
			
		||||
                "#eee"
 | 
			
		||||
            ],
 | 
			
		||||
            "splitAreaShow": false,
 | 
			
		||||
            "splitAreaColor": [
 | 
			
		||||
                "rgba(250,250,250,0.3)",
 | 
			
		||||
                "rgba(200,200,200,0.3)"
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "axisSeperateSetting": true,
 | 
			
		||||
    "toolboxColor": "#2ec7c9",
 | 
			
		||||
    "toolboxEmphasisColor": "#18a4a6",
 | 
			
		||||
    "tooltipAxisColor": "#008acd",
 | 
			
		||||
    "tooltipAxisWidth": "1",
 | 
			
		||||
    "timelineLineColor": "#008acd",
 | 
			
		||||
    "timelineLineWidth": 1,
 | 
			
		||||
    "timelineItemColor": "#008acd",
 | 
			
		||||
    "timelineItemColorE": "#a9334c",
 | 
			
		||||
    "timelineCheckColor": "#2ec7c9",
 | 
			
		||||
    "timelineCheckBorderColor": "#2ec7c9",
 | 
			
		||||
    "timelineItemBorderWidth": 1,
 | 
			
		||||
    "timelineControlColor": "#008acd",
 | 
			
		||||
    "timelineControlBorderColor": "#008acd",
 | 
			
		||||
    "timelineControlBorderWidth": 0.5,
 | 
			
		||||
    "timelineLabelColor": "#008acd",
 | 
			
		||||
    "datazoomBackgroundColor": "rgba(47,69,84,0)",
 | 
			
		||||
    "datazoomDataColor": "#efefff",
 | 
			
		||||
    "datazoomFillColor": "rgba(182,162,222,0.2)",
 | 
			
		||||
    "datazoomHandleColor": "#008acd",
 | 
			
		||||
    "datazoomHandleWidth": "100",
 | 
			
		||||
    "datazoomLabelColor": "#333333"
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
import * as echarts from 'echarts'
 | 
			
		||||
 | 
			
		||||
export default function(dom: any, theme: any = null,  option: any) {
 | 
			
		||||
    let chart = echarts.init(dom, theme);
 | 
			
		||||
    chart.setOption(option);
 | 
			
		||||
    return chart;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
interface BaseEnum {
 | 
			
		||||
    name: string
 | 
			
		||||
    value: any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const success: BaseEnum = {
 | 
			
		||||
    name: 'success',
 | 
			
		||||
    value: 200
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum ResultEnum {
 | 
			
		||||
    SUCCESS = 200,
 | 
			
		||||
    ERROR = 400,
 | 
			
		||||
    PARAM_ERROR = 405,
 | 
			
		||||
    SERVER_ERROR = 500,
 | 
			
		||||
    NO_PERMISSION = 501
 | 
			
		||||
}
 | 
			
		||||
// /**
 | 
			
		||||
//  * 全局公共枚举类
 | 
			
		||||
//  */
 | 
			
		||||
// export default {
 | 
			
		||||
//   // uri请求方法
 | 
			
		||||
//   requestMethod: new Enum().add('GET', 'GET', 1).add('POST', 'POST', 2).add('PUT', 'PUT', 3).add('DELETE', 'DELETE', 4),
 | 
			
		||||
//   // 结果枚举
 | 
			
		||||
//   ResultEnum: new Enum().add('SUCCESS', '操作成功', 200).add('ERROR', '操作失败', 400).add('PARAM_ERROR', '参数错误', 405).add('SERVER_ERROR', '服务器异常', 500)
 | 
			
		||||
//     .add('NO_PERMISSION', '没有权限', 501)
 | 
			
		||||
// }
 | 
			
		||||
@@ -1,8 +1,16 @@
 | 
			
		||||
import request from './request'
 | 
			
		||||
import request from './request';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    login: (param: any) => request.request('POST', '/sys/accounts/login', param, null),
 | 
			
		||||
    captcha: () => request.request('GET', '/sys/captcha', null, null),
 | 
			
		||||
    logout: (param: any) => request.request('POST', '/sys/accounts/logout/{token}', param, null),
 | 
			
		||||
    getMenuRoute: (param: any) => request.request('Get', '/sys/resources/account', param, null)
 | 
			
		||||
}
 | 
			
		||||
    login: (param: any) => request.post('/auth/accounts/login', param),
 | 
			
		||||
    otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
 | 
			
		||||
    getPublicKey: () => request.get('/common/public-key'),
 | 
			
		||||
    getConfigValue: (params: any) => request.get('/sys/configs/value', params),
 | 
			
		||||
    oauth2LoginConfig: () => request.get('/auth/oauth2-config'),
 | 
			
		||||
    changePwd: (param: any) => request.post('/sys/accounts/change-pwd', param),
 | 
			
		||||
    captcha: () => request.get('/sys/captcha'),
 | 
			
		||||
    logout: () => request.post('/auth/accounts/logout'),
 | 
			
		||||
    getPermissions: () => request.get('/sys/accounts/permissions'),
 | 
			
		||||
    oauth2Callback: (params: any) => request.get('/auth/oauth2/callback', params),
 | 
			
		||||
    getLdapEnabled: () => request.get('/auth/ldap/enabled'),
 | 
			
		||||
    ldapLogin: (param: any) => request.post('/auth/ldap/login', param),
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								mayfly_go_web/src/common/pattern.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,9 @@
 | 
			
		||||
export const AccountUsernamePattern = {
 | 
			
		||||
    pattern: /^[a-zA-Z0-9_]{5,20}$/g,
 | 
			
		||||
    message: '只允许输入5-20位大小写字母、数字、_-.:',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const ResourceCodePattern = {
 | 
			
		||||
    pattern: /^[a-zA-Z0-9_\-.:]{1,32}$/g,
 | 
			
		||||
    message: '只允许输入1-32位大小写字母、数字、_-.:',
 | 
			
		||||
};
 | 
			
		||||
@@ -1,11 +1,21 @@
 | 
			
		||||
import router from "../router";
 | 
			
		||||
import Axios from 'axios';
 | 
			
		||||
import { ResultEnum } from './enums'
 | 
			
		||||
import Api from './Api';
 | 
			
		||||
import router from '../router';
 | 
			
		||||
import config from './config';
 | 
			
		||||
import { getSession } from './utils/storage';
 | 
			
		||||
import { getClientId, getToken } from './utils/storage';
 | 
			
		||||
import { templateResolve } from './utils/string';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
import { useApiFetch } from '../hooks/useRequest';
 | 
			
		||||
import Api from './Api';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    request,
 | 
			
		||||
    xhrReq,
 | 
			
		||||
    get,
 | 
			
		||||
    post,
 | 
			
		||||
    put,
 | 
			
		||||
    del,
 | 
			
		||||
    getApiUrl,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface Result {
 | 
			
		||||
    /**
 | 
			
		||||
@@ -22,7 +32,17 @@ export interface Result {
 | 
			
		||||
    data?: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const baseUrl: string = config.baseApiUrl as string
 | 
			
		||||
export enum ResultEnum {
 | 
			
		||||
    SUCCESS = 200,
 | 
			
		||||
    ERROR = 400,
 | 
			
		||||
    PARAM_ERROR = 405,
 | 
			
		||||
    SERVER_ERROR = 500,
 | 
			
		||||
    NO_PERMISSION = 501,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const baseUrl: string = config.baseApiUrl;
 | 
			
		||||
// const baseUrl: string = 'http://localhost:18888/api';
 | 
			
		||||
// const baseWsUrl: string = config.baseWsUrl;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 通知错误消息
 | 
			
		||||
@@ -34,130 +54,176 @@ function notifyErrorMsg(msg: string) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// create an axios instance
 | 
			
		||||
const service = Axios.create({
 | 
			
		||||
const axiosInst = axios.create({
 | 
			
		||||
    baseURL: baseUrl, // url = base url + request url
 | 
			
		||||
    timeout: 20000 // request timeout
 | 
			
		||||
})
 | 
			
		||||
    timeout: 60000, // request timeout
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// request interceptor
 | 
			
		||||
service.interceptors.request.use(
 | 
			
		||||
axiosInst.interceptors.request.use(
 | 
			
		||||
    (config: any) => {
 | 
			
		||||
        // do something before request is sent
 | 
			
		||||
        const token = getSession("token")
 | 
			
		||||
        const token = getToken();
 | 
			
		||||
        if (token) {
 | 
			
		||||
            // 设置token
 | 
			
		||||
            config.headers['Authorization'] = token
 | 
			
		||||
            config.headers['Authorization'] = token;
 | 
			
		||||
            config.headers['ClientId'] = getClientId();
 | 
			
		||||
        }
 | 
			
		||||
        return config
 | 
			
		||||
        return config;
 | 
			
		||||
    },
 | 
			
		||||
    error => {
 | 
			
		||||
        return Promise.reject(error)
 | 
			
		||||
    (error) => {
 | 
			
		||||
        return Promise.reject(error);
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// response interceptor
 | 
			
		||||
service.interceptors.response.use(
 | 
			
		||||
    response => {
 | 
			
		||||
        // 获取请求返回结果
 | 
			
		||||
        const data: Result = response.data;
 | 
			
		||||
        // 如果提示没有权限,则移除token,使其重新登录
 | 
			
		||||
        if (data.code === ResultEnum.NO_PERMISSION) {
 | 
			
		||||
            router.push({
 | 
			
		||||
                path: '/401',
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        if (data.code === ResultEnum.SUCCESS) {
 | 
			
		||||
            return data.data;
 | 
			
		||||
        } else {
 | 
			
		||||
            return Promise.reject(data);
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
axiosInst.interceptors.response.use(
 | 
			
		||||
    (response) => response,
 | 
			
		||||
    (e: any) => {
 | 
			
		||||
        const rejectPromise = Promise.reject(e);
 | 
			
		||||
 | 
			
		||||
        if (axios.isCancel(e)) {
 | 
			
		||||
            console.log('请求已取消');
 | 
			
		||||
            return rejectPromise;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const statusCode = e.response?.status;
 | 
			
		||||
        if (statusCode == 500) {
 | 
			
		||||
            notifyErrorMsg('服务器未知异常');
 | 
			
		||||
            return rejectPromise;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (statusCode == 404) {
 | 
			
		||||
            notifyErrorMsg('请求接口未找到');
 | 
			
		||||
            return rejectPromise;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (e.message) {
 | 
			
		||||
            // 对响应错误做点什么
 | 
			
		||||
            if (e.message.indexOf('timeout') != -1) {
 | 
			
		||||
                notifyErrorMsg('网络超时');
 | 
			
		||||
            } else if (e.message == 'Network Error') {
 | 
			
		||||
                notifyErrorMsg('网络请求超时');
 | 
			
		||||
                return rejectPromise;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (e.message == 'Network Error') {
 | 
			
		||||
                notifyErrorMsg('网络连接错误');
 | 
			
		||||
            } else if (e.message.indexOf('404')) {
 | 
			
		||||
                notifyErrorMsg('请求接口找不到');
 | 
			
		||||
            } else {
 | 
			
		||||
                if (e.response.data) ElMessage.error(e.response.statusText);
 | 
			
		||||
                else notifyErrorMsg('接口路径找不到');
 | 
			
		||||
                return rejectPromise;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Promise.reject(e)
 | 
			
		||||
        notifyErrorMsg('网络请求错误');
 | 
			
		||||
        return rejectPromise;
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 请求uri
 | 
			
		||||
 * 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
 | 
			
		||||
 * 
 | 
			
		||||
 * @param {Object} method 请求方法(GET,POST,PUT,DELTE等)
 | 
			
		||||
 * @param {Object} uri    uri
 | 
			
		||||
 * @param {Object} params 参数
 | 
			
		||||
 * xhr请求url
 | 
			
		||||
 *
 | 
			
		||||
 * @param method 请求方法
 | 
			
		||||
 * @param url url
 | 
			
		||||
 * @param params 参数
 | 
			
		||||
 * @param options 可选
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
function request(method: string, url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
 | 
			
		||||
    if (!url)
 | 
			
		||||
export function xhrReq(method: string, url: string, params: any = null, options: any = {}) {
 | 
			
		||||
    if (!url) {
 | 
			
		||||
        throw new Error('请求url不能为空');
 | 
			
		||||
    // 简单判断该url是否是restful风格
 | 
			
		||||
    if (url.indexOf("{") != -1) {
 | 
			
		||||
        url = templateResolve(url, params);
 | 
			
		||||
    }
 | 
			
		||||
    const query: any = {
 | 
			
		||||
        method,
 | 
			
		||||
        url: url,
 | 
			
		||||
        ...options
 | 
			
		||||
    };
 | 
			
		||||
    if (headers) {
 | 
			
		||||
        query.headers = headers
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const lowMethod = method.toLowerCase();
 | 
			
		||||
    // post和put使用json格式传参
 | 
			
		||||
    if (lowMethod === 'post' || lowMethod === 'put') {
 | 
			
		||||
        query.data = params;
 | 
			
		||||
    } else {
 | 
			
		||||
        query.params = params;
 | 
			
		||||
    // 简单判断该url是否是restful风格
 | 
			
		||||
    if (url.indexOf('{') != -1) {
 | 
			
		||||
        url = templateResolve(url, params);
 | 
			
		||||
    }
 | 
			
		||||
    return service.request(query).then(res => res)
 | 
			
		||||
        .catch(e => {
 | 
			
		||||
            // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可
 | 
			
		||||
            if (e.msg) {
 | 
			
		||||
                notifyErrorMsg(e.msg)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
    const req: any = {
 | 
			
		||||
        method,
 | 
			
		||||
        url,
 | 
			
		||||
        ...options,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // post和put使用json格式传参
 | 
			
		||||
    if (method === 'post' || method === 'put') {
 | 
			
		||||
        req.data = params;
 | 
			
		||||
    } else {
 | 
			
		||||
        req.params = params;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return axiosInst
 | 
			
		||||
        .request(req)
 | 
			
		||||
        .then((response) => {
 | 
			
		||||
            // 获取请求返回结果
 | 
			
		||||
            const result: Result = response.data;
 | 
			
		||||
            return parseResult(result);
 | 
			
		||||
        })
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
            return Promise.reject(e);
 | 
			
		||||
        });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 根据api执行对应接口
 | 
			
		||||
 * @param api Api实例
 | 
			
		||||
 * @param params 请求参数
 | 
			
		||||
 * fetch请求url
 | 
			
		||||
 *
 | 
			
		||||
 * 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} method 请求方法(GET,POST,PUT,DELTE等)
 | 
			
		||||
 * @param {Object} uri    uri
 | 
			
		||||
 * @param {Object} params 参数
 | 
			
		||||
 */
 | 
			
		||||
function send(api: Api, params: any, options: any): Promise<any> {
 | 
			
		||||
    return request(api.method, api.url, params, null, options);
 | 
			
		||||
async function request(method: string, url: string, params: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
    const { execute, data } = useApiFetch(Api.create(url, method), params, options);
 | 
			
		||||
    await execute();
 | 
			
		||||
    return data.value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 根据api执行对应接口
 | 
			
		||||
 * @param api Api实例
 | 
			
		||||
 * @param params 请求参数
 | 
			
		||||
 * get请求uri
 | 
			
		||||
 * 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Object} url   uri
 | 
			
		||||
 * @param {Object} params 参数
 | 
			
		||||
 */
 | 
			
		||||
function sendWithHeaders(api: Api, params: any, headers: any): Promise<any> {
 | 
			
		||||
    return request(api.method, api.url, params, headers, null);
 | 
			
		||||
function get(url: string, params: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
    return request('get', url, params, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function post(url: string, params: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
    return request('post', url, params, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function put(url: string, params: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
    return request('put', url, params, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function del(url: string, params: any = null, options: any = {}): Promise<any> {
 | 
			
		||||
    return request('delete', url, params, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getApiUrl(url: string) {
 | 
			
		||||
    // 只是返回api地址而不做请求,用在上传组件之类的
 | 
			
		||||
    return baseUrl + url + '?token=' + getSession('token');
 | 
			
		||||
    return baseUrl + url + '?' + joinClientParams();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    request,
 | 
			
		||||
    send,
 | 
			
		||||
    sendWithHeaders,
 | 
			
		||||
    getApiUrl
 | 
			
		||||
// 组装客户端参数,包括 token 和 clientId
 | 
			
		||||
export function joinClientParams(): string {
 | 
			
		||||
    return `token=${getToken()}&clientId=${getClientId()}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function parseResult(result: Result) {
 | 
			
		||||
    if (result.code === ResultEnum.SUCCESS) {
 | 
			
		||||
        return result.data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 如果提示没有权限,则移除token,使其重新登录
 | 
			
		||||
    if (result.code === ResultEnum.NO_PERMISSION) {
 | 
			
		||||
        router.push({
 | 
			
		||||
            path: '/401',
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
 | 
			
		||||
    if (result.msg && result?.code != ResultEnum.NO_PERMISSION) {
 | 
			
		||||
        notifyErrorMsg(result.msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Promise.reject(result);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								mayfly_go_web/src/common/rsa.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,36 @@
 | 
			
		||||
import openApi from './openApi';
 | 
			
		||||
import JSEncrypt from 'jsencrypt';
 | 
			
		||||
import { notBlank } from './assert';
 | 
			
		||||
 | 
			
		||||
var encryptor: any = null;
 | 
			
		||||
 | 
			
		||||
export async function getRsaPublicKey() {
 | 
			
		||||
    let publicKey = sessionStorage.getItem('RsaPublicKey');
 | 
			
		||||
    if (publicKey) {
 | 
			
		||||
        return publicKey;
 | 
			
		||||
    }
 | 
			
		||||
    publicKey = (await openApi.getPublicKey()) as string;
 | 
			
		||||
    sessionStorage.setItem('RsaPublicKey', publicKey);
 | 
			
		||||
    return publicKey;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 公钥加密指定值
 | 
			
		||||
 *
 | 
			
		||||
 * @param value value
 | 
			
		||||
 * @returns 加密后的值
 | 
			
		||||
 */
 | 
			
		||||
export async function RsaEncrypt(value: any) {
 | 
			
		||||
    // 不存在则返回空值
 | 
			
		||||
    if (!value) {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
    if (encryptor != null && sessionStorage.getItem('RsaPublicKey') != null) {
 | 
			
		||||
        return encryptor.encrypt(value);
 | 
			
		||||
    }
 | 
			
		||||
    encryptor = new JSEncrypt();
 | 
			
		||||
    const publicKey = (await getRsaPublicKey()) as string;
 | 
			
		||||
    notBlank(publicKey, '获取公钥失败');
 | 
			
		||||
    encryptor.setPublicKey(publicKey); //设置公钥
 | 
			
		||||
    return encryptor.encrypt(value);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
 | 
			
		||||
import Config from './config'
 | 
			
		||||
import { ElNotification } from 'element-plus'
 | 
			
		||||
import SocketBuilder from './SocketBuilder';
 | 
			
		||||
import { getSession } from '@/common/utils/storage.ts';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    /**
 | 
			
		||||
     * 全局系统消息websocket
 | 
			
		||||
     */
 | 
			
		||||
    sysMsgSocket() {
 | 
			
		||||
        const token = getSession('token');
 | 
			
		||||
        if (!token) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        return SocketBuilder.builder(`${Config.baseWsUrl}/sysmsg?token=${token}`)
 | 
			
		||||
            .message((event: { data: string }) => {
 | 
			
		||||
                const message = JSON.parse(event.data);
 | 
			
		||||
                let mtype: string;
 | 
			
		||||
                switch (message.type) {
 | 
			
		||||
                    case 0:
 | 
			
		||||
                        mtype = 'error';
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 2:
 | 
			
		||||
                        mtype = 'info';
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 1:
 | 
			
		||||
                        mtype = 'success';
 | 
			
		||||
                        break;
 | 
			
		||||
                    default:
 | 
			
		||||
                        mtype = 'info';
 | 
			
		||||
                }
 | 
			
		||||
                if (mtype == undefined) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                ElNotification({
 | 
			
		||||
                    duration: 0,
 | 
			
		||||
                    title: message.title,
 | 
			
		||||
                    message: message.msg,
 | 
			
		||||
                    type: mtype as any,
 | 
			
		||||
                })
 | 
			
		||||
            })
 | 
			
		||||
            .open((event: any) => console.log(event)).build();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										103
									
								
								mayfly_go_web/src/common/sysconfig.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,103 @@
 | 
			
		||||
import openApi from './openApi';
 | 
			
		||||
 | 
			
		||||
// 登录是否使用验证码配置key
 | 
			
		||||
const AccountLoginSecurityKey = 'AccountLoginSecurity';
 | 
			
		||||
const MachineConfigKey = 'MachineConfig';
 | 
			
		||||
const SysStyleConfigKey = 'SysStyleConfig';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取账号登录安全配置
 | 
			
		||||
 *
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export async function getAccountLoginSecurity(): Promise<any> {
 | 
			
		||||
    const value = await getConfigValue(AccountLoginSecurityKey);
 | 
			
		||||
    if (!value) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    const jsonValue = JSON.parse(value);
 | 
			
		||||
    jsonValue.useCaptcha = convertBool(jsonValue.useCaptcha, true);
 | 
			
		||||
    jsonValue.useOtp = convertBool(jsonValue.useOtp, true);
 | 
			
		||||
    return jsonValue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取全局系统样式配置(logo、title等)
 | 
			
		||||
 *
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export async function getSysStyleConfig(): Promise<any> {
 | 
			
		||||
    const value = await getConfigValue(SysStyleConfigKey);
 | 
			
		||||
    const defaultValue = {
 | 
			
		||||
        useWatermark: true,
 | 
			
		||||
    };
 | 
			
		||||
    if (!value) {
 | 
			
		||||
        return defaultValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const jsonValue = JSON.parse(value);
 | 
			
		||||
    // 将字符串转为bool
 | 
			
		||||
    jsonValue.useWatermark = convertBool(jsonValue.useWatermark, true);
 | 
			
		||||
    return jsonValue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取LDAP登录配置
 | 
			
		||||
 *
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export async function getLdapEnabled(): Promise<any> {
 | 
			
		||||
    const value = await openApi.getLdapEnabled();
 | 
			
		||||
    return convertBool(value, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取机器配置
 | 
			
		||||
 *
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export async function getMachineConfig(): Promise<any> {
 | 
			
		||||
    const value = await getConfigValue(MachineConfigKey);
 | 
			
		||||
    const defaultValue = {
 | 
			
		||||
        // 默认1gb
 | 
			
		||||
        uploadMaxFileSize: '1GB',
 | 
			
		||||
    };
 | 
			
		||||
    if (!value) {
 | 
			
		||||
        return defaultValue;
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
        const jsonValue = JSON.parse(value);
 | 
			
		||||
        return jsonValue;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        return defaultValue;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取系统配置值
 | 
			
		||||
 *
 | 
			
		||||
 * @param key 配置key
 | 
			
		||||
 * @returns 配置值
 | 
			
		||||
 */
 | 
			
		||||
export async function getConfigValue(key: string): Promise<string> {
 | 
			
		||||
    return (await openApi.getConfigValue({ key })) as string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取bool类型系统配置值
 | 
			
		||||
 *
 | 
			
		||||
 * @param key 配置key
 | 
			
		||||
 * @param defaultValue 默认值
 | 
			
		||||
 * @returns 是否为ture,1: true;其他: false
 | 
			
		||||
 */
 | 
			
		||||
export async function getBoolConfigValue(key: string, defaultValue: boolean): Promise<boolean> {
 | 
			
		||||
    const value = await getConfigValue(key);
 | 
			
		||||
    return convertBool(value, defaultValue);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function convertBool(value: string, defaultValue: boolean) {
 | 
			
		||||
    if (!value) {
 | 
			
		||||
        return defaultValue;
 | 
			
		||||
    }
 | 
			
		||||
    return value == '1' || value == 'true';
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										107
									
								
								mayfly_go_web/src/common/syssocket.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,107 @@
 | 
			
		||||
import Config from './config';
 | 
			
		||||
import { ElNotification } from 'element-plus';
 | 
			
		||||
import SocketBuilder from './SocketBuilder';
 | 
			
		||||
import { getToken } from '@/common/utils/storage';
 | 
			
		||||
 | 
			
		||||
import { joinClientParams } from './request';
 | 
			
		||||
 | 
			
		||||
class SysSocket {
 | 
			
		||||
    /**
 | 
			
		||||
     * socket连接
 | 
			
		||||
     */
 | 
			
		||||
    socket: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * key -> 消息类别,value -> 消息对应的处理器函数
 | 
			
		||||
     */
 | 
			
		||||
    categoryHandlers: Map<string, any> = new Map();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 消息类型
 | 
			
		||||
     */
 | 
			
		||||
    messageTypes = {
 | 
			
		||||
        0: 'error',
 | 
			
		||||
        1: 'success',
 | 
			
		||||
        2: 'info',
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 初始化全局系统消息websocket
 | 
			
		||||
     */
 | 
			
		||||
    init() {
 | 
			
		||||
        // 存在则不需要重新建立连接
 | 
			
		||||
        if (this.socket) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const token = getToken();
 | 
			
		||||
        if (!token) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        console.log('init system ws');
 | 
			
		||||
        const sysMsgUrl = `${Config.baseWsUrl}/sysmsg?${joinClientParams()}`;
 | 
			
		||||
        this.socket = SocketBuilder.builder(sysMsgUrl)
 | 
			
		||||
            .message((event: { data: string }) => {
 | 
			
		||||
                let message;
 | 
			
		||||
                try {
 | 
			
		||||
                    message = JSON.parse(event.data);
 | 
			
		||||
                } catch (e) {
 | 
			
		||||
                    console.error('解析ws消息失败', e);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // 存在消息类别对应的处理器,则进行处理,否则进行默认通知处理
 | 
			
		||||
                const handler = this.categoryHandlers.get(message.category);
 | 
			
		||||
                if (handler) {
 | 
			
		||||
                    handler(message);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const type = this.getMsgType(message.type);
 | 
			
		||||
                ElNotification({
 | 
			
		||||
                    duration: 0,
 | 
			
		||||
                    title: message.title,
 | 
			
		||||
                    message: message.msg,
 | 
			
		||||
                    type: type,
 | 
			
		||||
                });
 | 
			
		||||
            })
 | 
			
		||||
            .open((event: any) => console.log(event))
 | 
			
		||||
            .close(() => {
 | 
			
		||||
                console.log('close sys socket');
 | 
			
		||||
                this.socket = null;
 | 
			
		||||
            })
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destory() {
 | 
			
		||||
        this.socket?.close();
 | 
			
		||||
        this.socket = null;
 | 
			
		||||
        this.categoryHandlers?.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 注册消息处理函数
 | 
			
		||||
     *
 | 
			
		||||
     * @param category 消息类别
 | 
			
		||||
     * @param handlerFunc 消息处理函数
 | 
			
		||||
     */
 | 
			
		||||
    registerMsgHandler(category: any, handlerFunc: any) {
 | 
			
		||||
        this.init();
 | 
			
		||||
        if (this.categoryHandlers.has(category)) {
 | 
			
		||||
            console.log(`${category}该类别消息处理器已存在...`);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof handlerFunc != 'function') {
 | 
			
		||||
            throw new Error('message handler需为函数');
 | 
			
		||||
        }
 | 
			
		||||
        this.categoryHandlers.set(category, handlerFunc);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getMsgType(msgType: any) {
 | 
			
		||||
        return this.messageTypes[msgType];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 全局系统消息websocket;
 | 
			
		||||
const sysSocket = new SysSocket();
 | 
			
		||||
 | 
			
		||||
export default sysSocket;
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
import { store } from '@/store/index.ts';
 | 
			
		||||
import { judementSameArr } from '@/common/utils/arrayOperation.ts';
 | 
			
		||||
 | 
			
		||||
// 单个权限验证
 | 
			
		||||
export function auth(value: string) {
 | 
			
		||||
    return store.state.userInfos.userInfos.permissions.some((v: any) => v === value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 多个权限验证,满足一个则为 true
 | 
			
		||||
export function auths(value: Array<string>) {
 | 
			
		||||
    let flag = false;
 | 
			
		||||
    store.state.userInfos.userInfos.permissions.map((val: any) => {
 | 
			
		||||
        value.map((v: any) => {
 | 
			
		||||
            if (val === v) flag = true;
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
    return flag;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 多个权限验证,全部满足则为 true
 | 
			
		||||
export function authAll(value: Array<string>) {
 | 
			
		||||
    return judementSameArr(value, store.state.userInfos.userInfos.permissions);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,23 +1,31 @@
 | 
			
		||||
export function dateFormat(fmt: string, date: Date) {
 | 
			
		||||
export function dateFormat2(fmt: string, date: Date) {
 | 
			
		||||
    let ret;
 | 
			
		||||
    const opt = {
 | 
			
		||||
        "y+": date.getFullYear().toString(),        // 年
 | 
			
		||||
        "M+": (date.getMonth() + 1).toString(),     // 月
 | 
			
		||||
        "d+": date.getDate().toString(),            // 日
 | 
			
		||||
        "H+": date.getHours().toString(),           // 时
 | 
			
		||||
        "m+": date.getMinutes().toString(),         // 分
 | 
			
		||||
        "s+": date.getSeconds().toString()          // 秒
 | 
			
		||||
        'y+': date.getFullYear().toString(), // 年
 | 
			
		||||
        'M+': (date.getMonth() + 1).toString(), // 月
 | 
			
		||||
        'd+': date.getDate().toString(), // 日
 | 
			
		||||
        'H+': date.getHours().toString(), // 时
 | 
			
		||||
        'm+': date.getMinutes().toString(), // 分
 | 
			
		||||
        's+': date.getSeconds().toString(), // 秒
 | 
			
		||||
        'S+': date.getMilliseconds() ? date.getMilliseconds().toString() : '', // 毫秒
 | 
			
		||||
        // 有其他格式化字符需求可以继续添加,必须转化成字符串
 | 
			
		||||
    };
 | 
			
		||||
    for (const k in opt) {
 | 
			
		||||
        ret = new RegExp("(" + k + ")").exec(fmt);
 | 
			
		||||
        ret = new RegExp('(' + k + ')').exec(fmt);
 | 
			
		||||
        if (ret) {
 | 
			
		||||
            fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
 | 
			
		||||
            fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return fmt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function dateStrFormat(fmt: string, dateStr: string) {
 | 
			
		||||
    return dateFormat(fmt, new Date(dateStr))
 | 
			
		||||
}
 | 
			
		||||
    return dateFormat2(fmt, new Date(dateStr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function dateFormat(dateStr: string) {
 | 
			
		||||
    if (!dateStr) {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
    return dateFormat2('yyyy-MM-dd HH:mm:ss', new Date(dateStr));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										45
									
								
								mayfly_go_web/src/common/utils/export.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,45 @@
 | 
			
		||||
export function exportCsv(filename: string, columns: string[], datas: []) {
 | 
			
		||||
    // 二维数组
 | 
			
		||||
    const cvsData = [columns];
 | 
			
		||||
    for (let data of datas) {
 | 
			
		||||
        // 数据值组成的一维数组
 | 
			
		||||
        let dataValueArr: any = [];
 | 
			
		||||
        for (let column of columns) {
 | 
			
		||||
            let val: any = data[column];
 | 
			
		||||
            if (val == null || val == undefined) {
 | 
			
		||||
                val = '';
 | 
			
		||||
            } else if (val && typeof val == 'string') {
 | 
			
		||||
                // 替换换行符
 | 
			
		||||
                val = val.replace(/[\r\n]/g, '\\n');
 | 
			
		||||
 | 
			
		||||
                // csv格式如果有逗号,整体用双引号括起来;如果里面还有双引号就替换成两个双引号,这样导出来的格式就不会有问题了
 | 
			
		||||
                if (val.indexOf(',') != -1) {
 | 
			
		||||
                    // 如果还有双引号,先将双引号转义,避免两边加了双引号后转义错误
 | 
			
		||||
                    if (val.indexOf('"') != -1) {
 | 
			
		||||
                        val = val.replace(/"/g, '""');
 | 
			
		||||
                    }
 | 
			
		||||
                    // 再将逗号转义
 | 
			
		||||
                    val = `"${val}"`;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            dataValueArr.push(String(val));
 | 
			
		||||
        }
 | 
			
		||||
        cvsData.push(dataValueArr);
 | 
			
		||||
    }
 | 
			
		||||
    const csvString = cvsData.map((e) => e.join(',')).join('\n');
 | 
			
		||||
    exportFile(`${filename}.csv`, csvString);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function exportFile(filename: string, content: string) {
 | 
			
		||||
    // 导出
 | 
			
		||||
    let link = document.createElement('a');
 | 
			
		||||
    let exportContent = '\uFEFF';
 | 
			
		||||
    let blob = new Blob([exportContent + content], {
 | 
			
		||||
        type: 'text/plain;charset=utf-8',
 | 
			
		||||
    });
 | 
			
		||||
    link.id = 'download-file';
 | 
			
		||||
    link.setAttribute('href', URL.createObjectURL(blob));
 | 
			
		||||
    link.setAttribute('download', `${filename}`);
 | 
			
		||||
    document.body.appendChild(link);
 | 
			
		||||
    link.click();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,75 +1,205 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 格式化字节单位
 | 
			
		||||
 * @param size byte size
 | 
			
		||||
 * @returns 
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function formatByteSize(size: any) {
 | 
			
		||||
    const value = Number(size);
 | 
			
		||||
    if (size && !isNaN(value)) {
 | 
			
		||||
        const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'BB'];
 | 
			
		||||
        let index = 0;
 | 
			
		||||
        let k = value;
 | 
			
		||||
        if (value >= 1024) {
 | 
			
		||||
            while (k > 1024) {
 | 
			
		||||
                k = k / 1024;
 | 
			
		||||
                index++;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return `${k.toFixed(2)}${units[index]}`;
 | 
			
		||||
export function formatByteSize(size: number, fixed = 2) {
 | 
			
		||||
    if (size === 0) {
 | 
			
		||||
        return '0B';
 | 
			
		||||
    }
 | 
			
		||||
    return '-';
 | 
			
		||||
 | 
			
		||||
    const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
 | 
			
		||||
    const base = 1024;
 | 
			
		||||
    const exponent = Math.floor(Math.log(size) / Math.log(base));
 | 
			
		||||
 | 
			
		||||
    return parseFloat((size / Math.pow(base, exponent)).toFixed(fixed)) + units[exponent];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 格式化json字符串
 | 
			
		||||
 * @param txt  json字符串
 | 
			
		||||
 * @param compress 是否压缩
 | 
			
		||||
 * @returns 格式化后的字符串
 | 
			
		||||
 * 容量转为对应的字节大小,如 1KB转为 1024
 | 
			
		||||
 * @param sizeString  1kb 1gb等
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function formatJsonString(txt: string, compress: boolean) {
 | 
			
		||||
    var indentChar = '    ';
 | 
			
		||||
    if (/^\s*$/.test(txt)) {
 | 
			
		||||
        console.log('数据为空,无法格式化! ');
 | 
			
		||||
        return txt;
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
        var data = JSON.parse(txt);
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
        console.log('数据源语法错误,格式化失败! 错误信息: ' + e.description, 'err');
 | 
			
		||||
        return txt;
 | 
			
		||||
    }
 | 
			
		||||
    var draw: any = [],
 | 
			
		||||
        line = compress ? '' : '\n',
 | 
			
		||||
        // eslint-disable-next-line no-unused-vars
 | 
			
		||||
        nodeCount: number = 0,
 | 
			
		||||
        // eslint-disable-next-line no-unused-vars
 | 
			
		||||
        maxDepth: number = 0;
 | 
			
		||||
export function convertToBytes(sizeStr: string) {
 | 
			
		||||
    sizeStr = sizeStr.trim();
 | 
			
		||||
    const unit = sizeStr.slice(-2);
 | 
			
		||||
 | 
			
		||||
    var notify = function (name: any, value: any, isLast: any, indent: any, formObj: any) {
 | 
			
		||||
        nodeCount++; /*节点计数*/
 | 
			
		||||
        for (var i = 0, tab = ''; i < indent; i++) tab += indentChar; /* 缩进HTML */
 | 
			
		||||
        tab = compress ? '' : tab; /*压缩模式忽略缩进*/
 | 
			
		||||
        maxDepth = ++indent; /*缩进递增并记录*/
 | 
			
		||||
        if (value && value.constructor == Array) {
 | 
			
		||||
            /*处理数组*/
 | 
			
		||||
            draw.push(tab + (formObj ? '"' + name + '": ' : '') + '[' + line); /*缩进'[' 然后换行*/
 | 
			
		||||
            for (var i = 0; i < value.length; i++) notify(i, value[i], i == value.length - 1, indent, false);
 | 
			
		||||
            draw.push(tab + ']' + (isLast ? line : ',' + line)); /*缩进']'换行,若非尾元素则添加逗号*/
 | 
			
		||||
        } else if (value && typeof value == 'object') {
 | 
			
		||||
            /*处理对象*/
 | 
			
		||||
            draw.push(tab + (formObj ? '"' + name + '": ' : '') + '{' + line); /*缩进'{' 然后换行*/
 | 
			
		||||
            var len = 0,
 | 
			
		||||
                i = 0;
 | 
			
		||||
            for (var key in value) len++;
 | 
			
		||||
            for (var key in value) notify(key, value[key], ++i == len, indent, true);
 | 
			
		||||
            draw.push(tab + '}' + (isLast ? line : ',' + line)); /*缩进'}'换行,若非尾元素则添加逗号*/
 | 
			
		||||
        } else {
 | 
			
		||||
            if (typeof value == 'string') value = '"' + value + '"';
 | 
			
		||||
            draw.push(tab + (formObj ? '"' + name + '": ' : '') + value + (isLast ? '' : ',') + line);
 | 
			
		||||
        }
 | 
			
		||||
    const valueStr = sizeStr.slice(0, -2);
 | 
			
		||||
    const value = parseInt(valueStr, 10);
 | 
			
		||||
 | 
			
		||||
    let bytes = 0;
 | 
			
		||||
 | 
			
		||||
    switch (unit.toUpperCase()) {
 | 
			
		||||
        case 'KB':
 | 
			
		||||
            bytes = value * 1024;
 | 
			
		||||
            break;
 | 
			
		||||
        case 'MB':
 | 
			
		||||
            bytes = value * 1024 * 1024;
 | 
			
		||||
            break;
 | 
			
		||||
        case 'GB':
 | 
			
		||||
            bytes = value * 1024 * 1024 * 1024;
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            throw new Error('Invalid size unit');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return bytes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * 年(Y) 可用1-4个占位符
 | 
			
		||||
 * 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
 | 
			
		||||
 * 星期(W) 可用1-3个占位符
 | 
			
		||||
 * 季度(q为阿拉伯数字,Q为中文数字)可用1或4个占位符
 | 
			
		||||
 *
 | 
			
		||||
 * let date = new Date()
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS")           // 2020-02-09 14:04:23
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS Q")         // 2020-02-09 14:09:03 一
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS WWW")       // 2020-02-09 14:45:12 星期日
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS QQQQ")      // 2020-02-09 14:09:36 第一季度
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS WWW QQQQ")  // 2020-02-09 14:46:12 星期日 第一季度
 | 
			
		||||
 */
 | 
			
		||||
export function formatDate(date: Date, format: string) {
 | 
			
		||||
    let we = date.getDay(); // 星期
 | 
			
		||||
    let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
 | 
			
		||||
    const opt: any = {
 | 
			
		||||
        'Y+': date.getFullYear().toString(), // 年
 | 
			
		||||
        'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
 | 
			
		||||
        'd+': date.getDate().toString(), // 日
 | 
			
		||||
        'H+': date.getHours().toString(), // 时
 | 
			
		||||
        'M+': date.getMinutes().toString(), // 分
 | 
			
		||||
        'S+': date.getSeconds().toString(), // 秒
 | 
			
		||||
        'q+': qut, // 季度
 | 
			
		||||
    };
 | 
			
		||||
    var isLast = true,
 | 
			
		||||
        indent = 0;
 | 
			
		||||
    notify('', data, isLast, indent, false);
 | 
			
		||||
    return draw.join('');
 | 
			
		||||
}
 | 
			
		||||
    // 中文数字 (星期)
 | 
			
		||||
    const week: any = {
 | 
			
		||||
        '0': '日',
 | 
			
		||||
        '1': '一',
 | 
			
		||||
        '2': '二',
 | 
			
		||||
        '3': '三',
 | 
			
		||||
        '4': '四',
 | 
			
		||||
        '5': '五',
 | 
			
		||||
        '6': '六',
 | 
			
		||||
    };
 | 
			
		||||
    // 中文数字(季度)
 | 
			
		||||
    const quarter: any = {
 | 
			
		||||
        '1': '一',
 | 
			
		||||
        '2': '二',
 | 
			
		||||
        '3': '三',
 | 
			
		||||
        '4': '四',
 | 
			
		||||
    };
 | 
			
		||||
    if (/(W+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
 | 
			
		||||
    if (/(Q+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
 | 
			
		||||
    for (let k in opt) {
 | 
			
		||||
        let r = new RegExp('(' + k + ')').exec(format);
 | 
			
		||||
        // 若输入的长度不为1,则前面补零
 | 
			
		||||
        if (r) format = format.replace(r[1], RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'));
 | 
			
		||||
    }
 | 
			
		||||
    return format;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 10秒:  10 * 1000
 | 
			
		||||
 * 1分:   60 * 1000
 | 
			
		||||
 * 1小时: 60 * 60 * 1000
 | 
			
		||||
 * 24小时:60 * 60 * 24 * 1000
 | 
			
		||||
 * 3天:   60 * 60* 24 * 1000 * 3
 | 
			
		||||
 *
 | 
			
		||||
 * let data = new Date()
 | 
			
		||||
 * formatPast(data)                                           // 刚刚
 | 
			
		||||
 * formatPast(data - 11 * 1000)                               // 11秒前
 | 
			
		||||
 * formatPast(data - 2 * 60 * 1000)                           // 2分钟前
 | 
			
		||||
 * formatPast(data - 60 * 60 * 2 * 1000)                      // 2小时前
 | 
			
		||||
 * formatPast(data - 60 * 60 * 2 * 1000)                      // 2小时前
 | 
			
		||||
 * formatPast(data - 60 * 60 * 71 * 1000)                     // 2天前
 | 
			
		||||
 * formatPast("2020-06-01")                                   // 2020-06-01
 | 
			
		||||
 * formatPast("2020-06-01", "YYYY-mm-dd HH:MM:SS WWW QQQQ")   // 2020-06-01 08:00:00 星期一 第二季度
 | 
			
		||||
 */
 | 
			
		||||
export function formatPast(param: any, format: string = 'YYYY-mm-dd') {
 | 
			
		||||
    // 传入格式处理、存储转换值
 | 
			
		||||
    let t: any, s: any;
 | 
			
		||||
    // 获取js 时间戳
 | 
			
		||||
    let time: any = new Date().getTime();
 | 
			
		||||
    // 是否是对象
 | 
			
		||||
    typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
 | 
			
		||||
    // 当前时间戳 - 传入时间戳
 | 
			
		||||
    time = Number.parseInt(`${time - t}`);
 | 
			
		||||
    if (time < 10000) {
 | 
			
		||||
        // 10秒内
 | 
			
		||||
        return '刚刚';
 | 
			
		||||
    } else if (time < 60000 && time >= 10000) {
 | 
			
		||||
        // 超过10秒少于1分钟内
 | 
			
		||||
        s = Math.floor(time / 1000);
 | 
			
		||||
        return `${s}秒前`;
 | 
			
		||||
    } else if (time < 3600000 && time >= 60000) {
 | 
			
		||||
        // 超过1分钟少于1小时
 | 
			
		||||
        s = Math.floor(time / 60000);
 | 
			
		||||
        return `${s}分钟前`;
 | 
			
		||||
    } else if (time < 86400000 && time >= 3600000) {
 | 
			
		||||
        // 超过1小时少于24小时
 | 
			
		||||
        s = Math.floor(time / 3600000);
 | 
			
		||||
        return `${s}小时前`;
 | 
			
		||||
    } else if (time < 259200000 && time >= 86400000) {
 | 
			
		||||
        // 超过1天少于3天内
 | 
			
		||||
        s = Math.floor(time / 86400000);
 | 
			
		||||
        return `${s}天前`;
 | 
			
		||||
    } else {
 | 
			
		||||
        // 超过3天
 | 
			
		||||
        let date = typeof param === 'string' || 'object' ? new Date(param) : param;
 | 
			
		||||
        return formatDate(date, format);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 格式化指定时间数为人性化可阅读的内容(默认time为秒单位)
 | 
			
		||||
 *
 | 
			
		||||
 * @param time 时间数
 | 
			
		||||
 * @param unit time对应的单位
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function formatTime(time: number, unit: string = 's') {
 | 
			
		||||
    const units = {
 | 
			
		||||
        y: 31536000,
 | 
			
		||||
        M: 2592000,
 | 
			
		||||
        d: 86400,
 | 
			
		||||
        h: 3600,
 | 
			
		||||
        m: 60,
 | 
			
		||||
        s: 1,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (!units[unit]) {
 | 
			
		||||
        return 'Invalid unit';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let seconds = time * units[unit];
 | 
			
		||||
    let result = '';
 | 
			
		||||
 | 
			
		||||
    const timeUnits = Object.entries(units).map(([unit, duration]) => {
 | 
			
		||||
        const value = Math.floor(seconds / duration);
 | 
			
		||||
        seconds %= duration;
 | 
			
		||||
        return { value, unit };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    timeUnits.forEach(({ value, unit }) => {
 | 
			
		||||
        if (value > 0) {
 | 
			
		||||
            result += `${value}${unit} `;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * formatAxis(new Date())   // 上午好
 | 
			
		||||
 */
 | 
			
		||||
export function formatAxis(param: any) {
 | 
			
		||||
    let hour: number = new Date(param).getHours();
 | 
			
		||||
    if (hour < 6) return '凌晨好';
 | 
			
		||||
    else if (hour < 9) return '早上好';
 | 
			
		||||
    else if (hour < 12) return '上午好';
 | 
			
		||||
    else if (hour < 14) return '中午好';
 | 
			
		||||
    else if (hour < 17) return '下午好';
 | 
			
		||||
    else if (hour < 19) return '傍晚好';
 | 
			
		||||
    else if (hour < 22) return '晚上好';
 | 
			
		||||
    else return '夜里好';
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,119 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 年(Y) 可用1-4个占位符
 | 
			
		||||
 * 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
 | 
			
		||||
 * 星期(W) 可用1-3个占位符
 | 
			
		||||
 * 季度(q为阿拉伯数字,Q为中文数字)可用1或4个占位符
 | 
			
		||||
 *
 | 
			
		||||
 * let date = new Date()
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS")           // 2020-02-09 14:04:23
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS Q")         // 2020-02-09 14:09:03 一
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS WWW")       // 2020-02-09 14:45:12 星期日
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS QQQQ")      // 2020-02-09 14:09:36 第一季度
 | 
			
		||||
 * formatDate(date, "YYYY-mm-dd HH:MM:SS WWW QQQQ")  // 2020-02-09 14:46:12 星期日 第一季度
 | 
			
		||||
 */
 | 
			
		||||
export function formatDate(date: Date, format: string) {
 | 
			
		||||
    let we = date.getDay(); // 星期
 | 
			
		||||
    let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
 | 
			
		||||
    const opt: any = {
 | 
			
		||||
        'Y+': date.getFullYear().toString(), // 年
 | 
			
		||||
        'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
 | 
			
		||||
        'd+': date.getDate().toString(), // 日
 | 
			
		||||
        'H+': date.getHours().toString(), // 时
 | 
			
		||||
        'M+': date.getMinutes().toString(), // 分
 | 
			
		||||
        'S+': date.getSeconds().toString(), // 秒
 | 
			
		||||
        'q+': qut, // 季度
 | 
			
		||||
    };
 | 
			
		||||
    // 中文数字 (星期)
 | 
			
		||||
    const week: any = {
 | 
			
		||||
        '0': '日',
 | 
			
		||||
        '1': '一',
 | 
			
		||||
        '2': '二',
 | 
			
		||||
        '3': '三',
 | 
			
		||||
        '4': '四',
 | 
			
		||||
        '5': '五',
 | 
			
		||||
        '6': '六',
 | 
			
		||||
    };
 | 
			
		||||
    // 中文数字(季度)
 | 
			
		||||
    const quarter: any = {
 | 
			
		||||
        '1': '一',
 | 
			
		||||
        '2': '二',
 | 
			
		||||
        '3': '三',
 | 
			
		||||
        '4': '四',
 | 
			
		||||
    };
 | 
			
		||||
    if (/(W+)/.test(format))
 | 
			
		||||
        format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
 | 
			
		||||
    if (/(Q+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
 | 
			
		||||
    for (let k in opt) {
 | 
			
		||||
        let r = new RegExp('(' + k + ')').exec(format);
 | 
			
		||||
        // 若输入的长度不为1,则前面补零
 | 
			
		||||
        if (r) format = format.replace(r[1], RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'));
 | 
			
		||||
    }
 | 
			
		||||
    return format;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 10秒:  10 * 1000
 | 
			
		||||
 * 1分:   60 * 1000
 | 
			
		||||
 * 1小时: 60 * 60 * 1000
 | 
			
		||||
 * 24小时:60 * 60 * 24 * 1000
 | 
			
		||||
 * 3天:   60 * 60* 24 * 1000 * 3
 | 
			
		||||
 *
 | 
			
		||||
 * let data = new Date()
 | 
			
		||||
 * formatPast(data)                                           // 刚刚
 | 
			
		||||
 * formatPast(data - 11 * 1000)                               // 11秒前
 | 
			
		||||
 * formatPast(data - 2 * 60 * 1000)                           // 2分钟前
 | 
			
		||||
 * formatPast(data - 60 * 60 * 2 * 1000)                      // 2小时前
 | 
			
		||||
 * formatPast(data - 60 * 60 * 2 * 1000)                      // 2小时前
 | 
			
		||||
 * formatPast(data - 60 * 60 * 71 * 1000)                     // 2天前
 | 
			
		||||
 * formatPast("2020-06-01")                                   // 2020-06-01
 | 
			
		||||
 * formatPast("2020-06-01", "YYYY-mm-dd HH:MM:SS WWW QQQQ")   // 2020-06-01 08:00:00 星期一 第二季度
 | 
			
		||||
 */
 | 
			
		||||
export function formatPast(param: any, format: string = 'YYYY-mm-dd') {
 | 
			
		||||
    // 传入格式处理、存储转换值
 | 
			
		||||
    let t: any, s: any;
 | 
			
		||||
    // 获取js 时间戳
 | 
			
		||||
    let time: any = new Date().getTime();
 | 
			
		||||
    // 是否是对象
 | 
			
		||||
    typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
 | 
			
		||||
    // 当前时间戳 - 传入时间戳
 | 
			
		||||
    time = Number.parseInt(`${time - t}`);
 | 
			
		||||
    if (time < 10000) {
 | 
			
		||||
        // 10秒内
 | 
			
		||||
        return '刚刚';
 | 
			
		||||
    } else if (time < 60000 && time >= 10000) {
 | 
			
		||||
        // 超过10秒少于1分钟内
 | 
			
		||||
        s = Math.floor(time / 1000);
 | 
			
		||||
        return `${s}秒前`;
 | 
			
		||||
    } else if (time < 3600000 && time >= 60000) {
 | 
			
		||||
        // 超过1分钟少于1小时
 | 
			
		||||
        s = Math.floor(time / 60000);
 | 
			
		||||
        return `${s}分钟前`;
 | 
			
		||||
    } else if (time < 86400000 && time >= 3600000) {
 | 
			
		||||
        // 超过1小时少于24小时
 | 
			
		||||
        s = Math.floor(time / 3600000);
 | 
			
		||||
        return `${s}小时前`;
 | 
			
		||||
    } else if (time < 259200000 && time >= 86400000) {
 | 
			
		||||
        // 超过1天少于3天内
 | 
			
		||||
        s = Math.floor(time / 86400000);
 | 
			
		||||
        return `${s}天前`;
 | 
			
		||||
    } else {
 | 
			
		||||
        // 超过3天
 | 
			
		||||
        let date = typeof param === 'string' || 'object' ? new Date(param) : param;
 | 
			
		||||
        return formatDate(date, format);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * formatAxis(new Date())   // 上午好
 | 
			
		||||
 */
 | 
			
		||||
export function formatAxis(param: any) {
 | 
			
		||||
    let hour: number = new Date(param).getHours();
 | 
			
		||||
    if (hour < 6) return '凌晨好';
 | 
			
		||||
    else if (hour < 9) return '早上好';
 | 
			
		||||
    else if (hour < 12) return '上午好';
 | 
			
		||||
    else if (hour < 14) return '中午好';
 | 
			
		||||
    else if (hour < 17) return '下午好';
 | 
			
		||||
    else if (hour < 19) return '傍晚好';
 | 
			
		||||
    else if (hour < 22) return '晚上好';
 | 
			
		||||
    else return '夜里好';
 | 
			
		||||
}
 | 
			
		||||
@@ -1,46 +1,46 @@
 | 
			
		||||
import { nextTick } from 'vue';
 | 
			
		||||
import loadingCss from '@/theme/loading.scss';
 | 
			
		||||
import '@/theme/loading.scss';
 | 
			
		||||
 | 
			
		||||
// 定义方法
 | 
			
		||||
/**
 | 
			
		||||
 * 页面全局 Loading
 | 
			
		||||
 * @method start 创建 loading
 | 
			
		||||
 * @method done 移除 loading
 | 
			
		||||
 */
 | 
			
		||||
export const NextLoading = {
 | 
			
		||||
    // 载入 css
 | 
			
		||||
    setCss: () => {
 | 
			
		||||
        let link = document.createElement('link');
 | 
			
		||||
        link.rel = 'stylesheet';
 | 
			
		||||
        link.href = loadingCss;
 | 
			
		||||
        link.crossOrigin = 'anonymous';
 | 
			
		||||
        document.getElementsByTagName('head')[0].appendChild(link);
 | 
			
		||||
    },
 | 
			
		||||
    // 创建 loading
 | 
			
		||||
    start: () => {
 | 
			
		||||
        const bodys: any = document.body;
 | 
			
		||||
        const div = document.createElement('div');
 | 
			
		||||
        const bodys: Element = document.body;
 | 
			
		||||
        const div = <HTMLElement>document.createElement('div');
 | 
			
		||||
        div.setAttribute('class', 'loading-next');
 | 
			
		||||
        const htmls = `
 | 
			
		||||
			<div class="loading-next-box">
 | 
			
		||||
			<div class="loading-next-box-warp">
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-item"></div>
 | 
			
		||||
				<div class="loading-next-box-warp">
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
					<div class="loading-next-box-item"></div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		`;
 | 
			
		||||
        div.innerHTML = htmls;
 | 
			
		||||
        bodys.insertBefore(div, bodys.childNodes[0]);
 | 
			
		||||
    },
 | 
			
		||||
    // 移除 loading
 | 
			
		||||
    done: () => {
 | 
			
		||||
    done: (time: number = 1000) => {
 | 
			
		||||
        nextTick(() => {
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                const el = document.querySelector('.loading-next');
 | 
			
		||||
                el && el.parentNode?.removeChild(el);
 | 
			
		||||
            }, 1000);
 | 
			
		||||
                const el = <HTMLElement>document.querySelector('.loading-next');
 | 
			
		||||
                el?.parentNode?.removeChild(el);
 | 
			
		||||
            }, time);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function sleep(ms: number) {
 | 
			
		||||
    return new Promise((resolve) => setTimeout(resolve, ms));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								mayfly_go_web/src/common/utils/mitt.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,8 @@
 | 
			
		||||
// https://www.npmjs.com/package/mitt
 | 
			
		||||
import mitt, { Emitter } from 'mitt';
 | 
			
		||||
 | 
			
		||||
// 类型
 | 
			
		||||
const emitter: Emitter<any> = mitt<any>();
 | 
			
		||||
 | 
			
		||||
// 导出
 | 
			
		||||
export default emitter;
 | 
			
		||||
							
								
								
									
										33
									
								
								mayfly_go_web/src/common/utils/object.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,33 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 根据对象访问路径,获取对应的值
 | 
			
		||||
 *
 | 
			
		||||
 * @param obj 对象,如 {user: {name: 'xxx'}, orderNo: 1212211, products: [{id: 12}]}
 | 
			
		||||
 * @param path 访问路径,如 orderNo 或者 user.name 或者product[0].id
 | 
			
		||||
 * @returns 路径对应的值
 | 
			
		||||
 */
 | 
			
		||||
export function getValueByPath(obj: any, path: string) {
 | 
			
		||||
    const keys = path.split('.');
 | 
			
		||||
    let result = obj;
 | 
			
		||||
    for (let key of keys) {
 | 
			
		||||
        if (!result || typeof result !== 'object') {
 | 
			
		||||
            return undefined;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (key.includes('[') && key.includes(']')) {
 | 
			
		||||
            // 处理包含数组索引的情况
 | 
			
		||||
            const arrayKey = key.substring(0, key.indexOf('['));
 | 
			
		||||
            const matchIndex = key.match(/\[(.*?)\]/);
 | 
			
		||||
 | 
			
		||||
            if (!matchIndex) {
 | 
			
		||||
                return undefined;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const index = parseInt(matchIndex[1]);
 | 
			
		||||
            result = Array.isArray(result[arrayKey]) ? result[arrayKey][index] : undefined;
 | 
			
		||||
        } else {
 | 
			
		||||
            result = result[key];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,9 @@
 | 
			
		||||
// 字体图标 url
 | 
			
		||||
const cssCdnUrlList: Array<string> = [
 | 
			
		||||
    '//at.alicdn.com/t/font_2298093_ysc3z187xhh.css',
 | 
			
		||||
    '//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
 | 
			
		||||
];
 | 
			
		||||
const cssCdnUrlList: Array<string> = [];
 | 
			
		||||
// 第三方 js url
 | 
			
		||||
const jsCdnUrlList: Array<string> = [];
 | 
			
		||||
 | 
			
		||||
// 动态设置字体图标
 | 
			
		||||
// 动态批量设置字体图标
 | 
			
		||||
export function setCssCdn() {
 | 
			
		||||
    if (cssCdnUrlList.length <= 0) return false;
 | 
			
		||||
    cssCdnUrlList.map((v) => {
 | 
			
		||||
@@ -18,7 +15,7 @@ export function setCssCdn() {
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 批量设置第三方js
 | 
			
		||||
// 动态批量设置第三方js
 | 
			
		||||
export function setJsCdn() {
 | 
			
		||||
    if (jsCdnUrlList.length <= 0) return false;
 | 
			
		||||
    jsCdnUrlList.map((v) => {
 | 
			
		||||
@@ -28,11 +25,17 @@ export function setJsCdn() {
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 设置执行函数
 | 
			
		||||
/**
 | 
			
		||||
 * 批量设置字体图标、动态js
 | 
			
		||||
 * @method cssCdn 动态批量设置字体图标
 | 
			
		||||
 * @method jsCdn 动态批量设置第三方js
 | 
			
		||||
 */
 | 
			
		||||
const setIntroduction = {
 | 
			
		||||
    // 设置css
 | 
			
		||||
    cssCdn: () => {
 | 
			
		||||
        setCssCdn();
 | 
			
		||||
    },
 | 
			
		||||
    // 设置js
 | 
			
		||||
    jsCdn: () => {
 | 
			
		||||
        setJsCdn();
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,92 @@
 | 
			
		||||
import { randomUuid } from './string';
 | 
			
		||||
 | 
			
		||||
const TokenKey = 'm-token';
 | 
			
		||||
const UserKey = 'm-user';
 | 
			
		||||
const TagViewsKey = 'm-tagViews';
 | 
			
		||||
const ClientIdKey = 'm-clientId';
 | 
			
		||||
 | 
			
		||||
// 获取请求token
 | 
			
		||||
export function getToken(): string {
 | 
			
		||||
    return getLocal(TokenKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 保存用户访问token
 | 
			
		||||
export function saveToken(token: string) {
 | 
			
		||||
    setLocal(TokenKey, token);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取登录用户基础信息
 | 
			
		||||
export function getUser() {
 | 
			
		||||
    return getLocal(UserKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 保存用户信息
 | 
			
		||||
export function saveUser(userinfo: any) {
 | 
			
		||||
    setLocal(UserKey, userinfo);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function saveThemeConfig(themeConfig: any) {
 | 
			
		||||
    setLocal('themeConfig', themeConfig);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getThemeConfig() {
 | 
			
		||||
    return getLocal('themeConfig');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 清除当前登录用户相关信息
 | 
			
		||||
 */
 | 
			
		||||
export function clearUser() {
 | 
			
		||||
    removeLocal(TokenKey);
 | 
			
		||||
    removeLocal(UserKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getTagViews() {
 | 
			
		||||
    return getSession(TagViewsKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function setTagViews(tagViews: Array<object>) {
 | 
			
		||||
    setSession(TagViewsKey, tagViews);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function removeTagViews() {
 | 
			
		||||
    removeSession(TagViewsKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取客户端UUID
 | 
			
		||||
export function getClientId(): string {
 | 
			
		||||
    let uuid = getSession(ClientIdKey);
 | 
			
		||||
    if (uuid == null) {
 | 
			
		||||
        uuid = randomUuid();
 | 
			
		||||
        setSession(ClientIdKey, uuid);
 | 
			
		||||
    }
 | 
			
		||||
    return uuid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 1. localStorage
 | 
			
		||||
// 设置永久缓存
 | 
			
		||||
export function setLocal(key: string, val: any) {
 | 
			
		||||
    window.localStorage.setItem(key, JSON.stringify(val));
 | 
			
		||||
    if (typeof val == 'object') {
 | 
			
		||||
        val = JSON.stringify(val);
 | 
			
		||||
    }
 | 
			
		||||
    window.localStorage.setItem(key, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取永久缓存
 | 
			
		||||
export function getLocal(key: string) {
 | 
			
		||||
    let json: any = window.localStorage.getItem(key);
 | 
			
		||||
    return JSON.parse(json);
 | 
			
		||||
    let val: any = window.localStorage.getItem(key);
 | 
			
		||||
    try {
 | 
			
		||||
        return JSON.parse(val);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        return val;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 移除永久缓存
 | 
			
		||||
export function removeLocal(key: string) {
 | 
			
		||||
    window.localStorage.removeItem(key);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 移除全部永久缓存
 | 
			
		||||
export function clearLocal() {
 | 
			
		||||
    window.localStorage.clear();
 | 
			
		||||
@@ -20,18 +95,29 @@ export function clearLocal() {
 | 
			
		||||
// 2. sessionStorage
 | 
			
		||||
// 设置临时缓存
 | 
			
		||||
export function setSession(key: string, val: any) {
 | 
			
		||||
    window.sessionStorage.setItem(key, JSON.stringify(val));
 | 
			
		||||
    if (typeof val == 'object') {
 | 
			
		||||
        val = JSON.stringify(val);
 | 
			
		||||
    }
 | 
			
		||||
    window.sessionStorage.setItem(key, val);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取临时缓存
 | 
			
		||||
export function getSession(key: string) {
 | 
			
		||||
    let json: any = window.sessionStorage.getItem(key);
 | 
			
		||||
    return JSON.parse(json);
 | 
			
		||||
    let val: any = window.sessionStorage.getItem(key);
 | 
			
		||||
    try {
 | 
			
		||||
        return JSON.parse(val);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        return val;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 移除临时缓存
 | 
			
		||||
export function removeSession(key: string) {
 | 
			
		||||
    window.sessionStorage.removeItem(key);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 移除全部临时缓存
 | 
			
		||||
export function clearSession() {
 | 
			
		||||
    clearUser();
 | 
			
		||||
    window.sessionStorage.clear();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,13 @@
 | 
			
		||||
import { v1 as uuidv1 } from 'uuid';
 | 
			
		||||
import Clipboard from 'clipboard';
 | 
			
		||||
import { ElMessage } from 'element-plus';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 模板字符串解析,如:template = 'hahaha{name}_{id}' ,param = {name: 'hh', id: 1}
 | 
			
		||||
 * 解析后为 hahahahh_1
 | 
			
		||||
 * @param template 模板字符串
 | 
			
		||||
 * @param param   参数占位符
 | 
			
		||||
 * @returns 
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function templateResolve(template: string, param: any) {
 | 
			
		||||
    return template.replace(/\{\w+\}/g, (word) => {
 | 
			
		||||
@@ -12,7 +16,7 @@ export function templateResolve(template: string, param: any) {
 | 
			
		||||
        if (value != null || value != undefined) {
 | 
			
		||||
            return value;
 | 
			
		||||
        }
 | 
			
		||||
        return "";
 | 
			
		||||
        return '';
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -21,11 +25,34 @@ export function letterAvatar(name: string, size = 60, color = '') {
 | 
			
		||||
    name = name || '';
 | 
			
		||||
    size = size || 60;
 | 
			
		||||
    var colours = [
 | 
			
		||||
        "#1abc9c", "#2ecc71", "#3498db", "#9b59b6", "#34495e", "#16a085", "#27ae60", "#2980b9", "#8e44ad", "#2c3e50",
 | 
			
		||||
        "#f1c40f", "#e67e22", "#e74c3c", "#00bcd4", "#95a5a6", "#f39c12", "#d35400", "#c0392b", "#bdc3c7", "#7f8c8d"
 | 
			
		||||
    ],
 | 
			
		||||
            '#1abc9c',
 | 
			
		||||
            '#2ecc71',
 | 
			
		||||
            '#3498db',
 | 
			
		||||
            '#9b59b6',
 | 
			
		||||
            '#34495e',
 | 
			
		||||
            '#16a085',
 | 
			
		||||
            '#27ae60',
 | 
			
		||||
            '#2980b9',
 | 
			
		||||
            '#8e44ad',
 | 
			
		||||
            '#2c3e50',
 | 
			
		||||
            '#f1c40f',
 | 
			
		||||
            '#e67e22',
 | 
			
		||||
            '#e74c3c',
 | 
			
		||||
            '#00bcd4',
 | 
			
		||||
            '#95a5a6',
 | 
			
		||||
            '#f39c12',
 | 
			
		||||
            '#d35400',
 | 
			
		||||
            '#c0392b',
 | 
			
		||||
            '#bdc3c7',
 | 
			
		||||
            '#7f8c8d',
 | 
			
		||||
        ],
 | 
			
		||||
        nameSplit = String(name).split(' '),
 | 
			
		||||
        initials, charIndex, colourIndex, canvas, context, dataURI;
 | 
			
		||||
        initials,
 | 
			
		||||
        charIndex,
 | 
			
		||||
        colourIndex,
 | 
			
		||||
        canvas,
 | 
			
		||||
        context,
 | 
			
		||||
        dataURI;
 | 
			
		||||
 | 
			
		||||
    if (nameSplit.length == 1) {
 | 
			
		||||
        initials = nameSplit[0] ? nameSplit[0].charAt(0) : '?';
 | 
			
		||||
@@ -33,23 +60,122 @@ export function letterAvatar(name: string, size = 60, color = '') {
 | 
			
		||||
        initials = nameSplit[0].charAt(0) + nameSplit[1].charAt(0);
 | 
			
		||||
    }
 | 
			
		||||
    if (window.devicePixelRatio) {
 | 
			
		||||
        size = (size * window.devicePixelRatio);
 | 
			
		||||
        size = size * window.devicePixelRatio;
 | 
			
		||||
    }
 | 
			
		||||
    initials = initials.toLocaleUpperCase()
 | 
			
		||||
    initials = initials.toLocaleUpperCase();
 | 
			
		||||
    charIndex = (initials == '?' ? 72 : initials.charCodeAt(0)) - 64;
 | 
			
		||||
    colourIndex = charIndex % 20;
 | 
			
		||||
    canvas = document.createElement('canvas');
 | 
			
		||||
    canvas.width = size;
 | 
			
		||||
    canvas.height = size;
 | 
			
		||||
    context = canvas.getContext("2d") as any;
 | 
			
		||||
    context = canvas.getContext('2d') as any;
 | 
			
		||||
 | 
			
		||||
    context.fillStyle = color ? color : colours[colourIndex - 1];
 | 
			
		||||
    context.fillRect(0, 0, canvas.width, canvas.height);
 | 
			
		||||
    context.font = Math.round(canvas.width / 2) + "px 'Microsoft Yahei'";
 | 
			
		||||
    context.textAlign = "center";
 | 
			
		||||
    context.fillStyle = "#FFF";
 | 
			
		||||
    context.textAlign = 'center';
 | 
			
		||||
    context.fillStyle = '#FFF';
 | 
			
		||||
    context.fillText(initials, size / 2, size / 1.5);
 | 
			
		||||
    dataURI = canvas.toDataURL();
 | 
			
		||||
    canvas = null;
 | 
			
		||||
    return dataURI;
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 计算文本所占用的宽度(px) -> 该种方式较为准确
 | 
			
		||||
 * 使用span标签包裹内容,然后计算span的宽度 width: px
 | 
			
		||||
 * @param str
 | 
			
		||||
 */
 | 
			
		||||
export function getTextWidth(str: string) {
 | 
			
		||||
    let width = 0;
 | 
			
		||||
    let html = document.createElement('span');
 | 
			
		||||
    html.innerText = str;
 | 
			
		||||
    html.className = 'getTextWidth';
 | 
			
		||||
    document?.querySelector('body')?.appendChild(html);
 | 
			
		||||
    width = (document?.querySelector('.getTextWidth') as any).offsetWidth;
 | 
			
		||||
    document?.querySelector('.getTextWidth')?.remove();
 | 
			
		||||
    return width;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取内容所需要占用的宽度
 | 
			
		||||
 */
 | 
			
		||||
export function getContentWidth(content: any): number {
 | 
			
		||||
    if (!content) {
 | 
			
		||||
        return 50;
 | 
			
		||||
    }
 | 
			
		||||
    // 以下分配的单位长度可根据实际需求进行调整
 | 
			
		||||
    let flexWidth = 0;
 | 
			
		||||
    for (const char of content) {
 | 
			
		||||
        if (flexWidth > 500) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'z')) {
 | 
			
		||||
            // 小写字母、数字字符
 | 
			
		||||
            flexWidth += 9.3;
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (char >= 'A' && char <= 'Z') {
 | 
			
		||||
            flexWidth += 9;
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (char >= '\u4e00' && char <= '\u9fa5') {
 | 
			
		||||
            // 如果是中文字符,为字符分配16个单位宽度
 | 
			
		||||
            flexWidth += 20;
 | 
			
		||||
        } else {
 | 
			
		||||
            // 其他种类字符
 | 
			
		||||
            flexWidth += 8;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // if (flexWidth > 450) {
 | 
			
		||||
    //     // 设置最大宽度
 | 
			
		||||
    //     flexWidth = 450;
 | 
			
		||||
    // }
 | 
			
		||||
    return flexWidth;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @returns uuid
 | 
			
		||||
 */
 | 
			
		||||
export function randomUuid() {
 | 
			
		||||
    return uuidv1();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 拷贝文本至剪贴板
 | 
			
		||||
 * @param txt 需要拷贝到剪贴板的文本
 | 
			
		||||
 * @param selector click事件对应的元素selector,默认为 #copyValue
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export async function copyToClipboard(txt: string, selector: string = '#copyValue') {
 | 
			
		||||
    // navigator clipboard 需要https等安全上下文
 | 
			
		||||
    if (navigator.clipboard && window.isSecureContext) {
 | 
			
		||||
        // navigator clipboard 向剪贴板写文本
 | 
			
		||||
        try {
 | 
			
		||||
            // 拷贝单元格数据
 | 
			
		||||
            await navigator.clipboard.writeText(txt);
 | 
			
		||||
            ElMessage.success('复制成功');
 | 
			
		||||
        } catch (e: any) {
 | 
			
		||||
            ElMessage.error('复制失败');
 | 
			
		||||
        }
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let clipboard = new Clipboard(selector, {
 | 
			
		||||
        text: function () {
 | 
			
		||||
            return txt;
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
    clipboard.on('success', () => {
 | 
			
		||||
        ElMessage.success('复制成功');
 | 
			
		||||
        // 释放内存
 | 
			
		||||
        clipboard.destroy();
 | 
			
		||||
    });
 | 
			
		||||
    clipboard.on('error', () => {
 | 
			
		||||
        // 不支持复制
 | 
			
		||||
        ElMessage.error('该浏览器不支持自动复制');
 | 
			
		||||
        // 释放内存
 | 
			
		||||
        clipboard.destroy();
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,20 @@
 | 
			
		||||
import { nextTick } from 'vue';
 | 
			
		||||
import * as svg from '@element-plus/icons-vue';
 | 
			
		||||
import iconfontJson from '@/assets/iconfont/iconfont.json';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 导出全局注册 element plus svg 图标
 | 
			
		||||
 * @param app vue 实例
 | 
			
		||||
 * @description 使用:https://element-plus.gitee.io/zh-CN/component/icon.html
 | 
			
		||||
 */
 | 
			
		||||
export function registElSvgIcon(app: any) {
 | 
			
		||||
    const icons = svg as any;
 | 
			
		||||
    for (const i in icons) {
 | 
			
		||||
        app.component(`${icons[i].name}`, icons[i]);
 | 
			
		||||
    }
 | 
			
		||||
    app.component('SvgIcon', SvgIcon);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取阿里字体图标
 | 
			
		||||
const getAlicdnIconfont = () => {
 | 
			
		||||
@@ -9,7 +24,8 @@ const getAlicdnIconfont = () => {
 | 
			
		||||
            let sheetsList = [];
 | 
			
		||||
            let sheetsIconList = [];
 | 
			
		||||
            for (let i = 0; i < styles.length; i++) {
 | 
			
		||||
                if (styles[i].href && styles[i].href.indexOf('at.alicdn.com') > -1) {
 | 
			
		||||
                console.log(styles[i]);
 | 
			
		||||
                if (styles[i].href && styles[i].href.indexOf('iconfont') > -1) {
 | 
			
		||||
                    sheetsList.push(styles[i]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -28,19 +44,29 @@ const getAlicdnIconfont = () => {
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 获取本地阿里icons
 | 
			
		||||
const getLocalAliIconfont = () => {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
        nextTick(() => {
 | 
			
		||||
            const prefix = iconfontJson.css_prefix_text;
 | 
			
		||||
            resolve(iconfontJson.glyphs.map((x: any) => prefix + x.font_class));
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 初始化获取 css 样式,获取 element plus 自带图标
 | 
			
		||||
const elementPlusIconfont = () => {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
		nextTick(() => {
 | 
			
		||||
			const icons = svg as any;
 | 
			
		||||
			const sheetsIconList = [];
 | 
			
		||||
			for (const i in icons) {
 | 
			
		||||
				sheetsIconList.push(`${icons[i].name}`);
 | 
			
		||||
			}
 | 
			
		||||
			if (sheetsIconList.length > 0) resolve(sheetsIconList);
 | 
			
		||||
			else reject('未获取到值,请刷新重试');
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
        nextTick(() => {
 | 
			
		||||
            const icons = svg as any;
 | 
			
		||||
            const sheetsIconList = [];
 | 
			
		||||
            for (const i in icons) {
 | 
			
		||||
                sheetsIconList.push(`${icons[i].name}`);
 | 
			
		||||
            }
 | 
			
		||||
            if (sheetsIconList.length > 0) resolve(sheetsIconList);
 | 
			
		||||
            else reject('未获取到值,请刷新重试');
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 初始化获取 css 样式,这里使用 fontawesome 的图标
 | 
			
		||||
@@ -76,9 +102,9 @@ const awesomeIconfont = () => {
 | 
			
		||||
 | 
			
		||||
// 定义导出方法集合
 | 
			
		||||
const initIconfont = {
 | 
			
		||||
    // ali: () => {
 | 
			
		||||
    // 	return getAlicdnIconfont();
 | 
			
		||||
    // },
 | 
			
		||||
    ali: () => {
 | 
			
		||||
        return getLocalAliIconfont();
 | 
			
		||||
    },
 | 
			
		||||
    ele: () => {
 | 
			
		||||
        return elementPlusIconfont();
 | 
			
		||||
    },
 | 
			
		||||
@@ -163,8 +163,7 @@ export function verifyPasswordStrength(val: string) {
 | 
			
		||||
    // 中:字母+数字,字母+特殊字符,数字+特殊字符
 | 
			
		||||
    if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val)) v = '中';
 | 
			
		||||
    // 强:字母+数字+特殊字符
 | 
			
		||||
    if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val))
 | 
			
		||||
        v = '强';
 | 
			
		||||
    if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val)) v = '强';
 | 
			
		||||
    // 返回结果
 | 
			
		||||
    return v;
 | 
			
		||||
}
 | 
			
		||||
@@ -172,11 +171,7 @@ export function verifyPasswordStrength(val: string) {
 | 
			
		||||
// IP地址
 | 
			
		||||
export function verifyIPAddress(val: string) {
 | 
			
		||||
    // false: IP地址不正确
 | 
			
		||||
    if (
 | 
			
		||||
        !/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(
 | 
			
		||||
            val
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    if (!/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(val))
 | 
			
		||||
        return false;
 | 
			
		||||
    // true: IP地址正确
 | 
			
		||||
    else return true;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								mayfly_go_web/src/common/utils/url.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,13 @@
 | 
			
		||||
const mode = import.meta.env.VITE_ROUTER_MODE;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 获取不同路由模式所对应的 url
 | 
			
		||||
 * @returns {String}
 | 
			
		||||
 */
 | 
			
		||||
export function getNowUrl() {
 | 
			
		||||
    const url = {
 | 
			
		||||
        hash: location.hash.substring(1),
 | 
			
		||||
        history: location.pathname + location.search,
 | 
			
		||||
    };
 | 
			
		||||
    return url[mode];
 | 
			
		||||
}
 | 
			
		||||
@@ -1,42 +0,0 @@
 | 
			
		||||
// 页面添加水印效果
 | 
			
		||||
const setWatermark = (str: any) => {
 | 
			
		||||
    const id = '1.23452384164.123412416';
 | 
			
		||||
    if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id) as any);
 | 
			
		||||
    const can = document.createElement('canvas');
 | 
			
		||||
    can.width = 250;
 | 
			
		||||
    can.height = 180;
 | 
			
		||||
    const cans: any = can.getContext('2d');
 | 
			
		||||
    cans.rotate((-20 * Math.PI) / 180);
 | 
			
		||||
    cans.font = '12px Vedana';
 | 
			
		||||
    cans.fillStyle = 'rgba(200, 200, 200, 0.30)';
 | 
			
		||||
    cans.textAlign = 'center';
 | 
			
		||||
    cans.textBaseline = 'Middle';
 | 
			
		||||
    cans.fillText(str, can.width / 10, can.height / 2);
 | 
			
		||||
    const div = document.createElement('div');
 | 
			
		||||
    div.id = id;
 | 
			
		||||
    div.style.pointerEvents = 'none';
 | 
			
		||||
    div.style.top = '35px';
 | 
			
		||||
    div.style.left = '0px';
 | 
			
		||||
    div.style.position = 'fixed';
 | 
			
		||||
    div.style.zIndex = '10000000';
 | 
			
		||||
    div.style.width = document.documentElement.clientWidth + 'px';
 | 
			
		||||
    div.style.height = document.documentElement.clientHeight + 'px';
 | 
			
		||||
    div.style.background = `url(${can.toDataURL('image/png')}) left top repeat`;
 | 
			
		||||
    document.body.appendChild(div);
 | 
			
		||||
    return id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const watermark = {
 | 
			
		||||
    // 设置水印
 | 
			
		||||
    set: (str: any) => {
 | 
			
		||||
        let id = setWatermark(str);
 | 
			
		||||
        if (document.getElementById(id) === null) id = setWatermark(str);
 | 
			
		||||
    },
 | 
			
		||||
    // 删除水印
 | 
			
		||||
    del: () => {
 | 
			
		||||
        let id = '1.23452384164.123412416';
 | 
			
		||||
        if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id) as any);
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default watermark;
 | 
			
		||||
							
								
								
									
										66
									
								
								mayfly_go_web/src/components/Grid/components/GridItem.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,66 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div v-show="isShow" :style="style">
 | 
			
		||||
        <slot></slot>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts" name="GridItem">
 | 
			
		||||
import { computed, inject, Ref, ref, useAttrs, watch } from 'vue';
 | 
			
		||||
import { BreakPoint, Responsive } from '../interface/index';
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    offset?: number;
 | 
			
		||||
    span?: number;
 | 
			
		||||
    suffix?: boolean;
 | 
			
		||||
    xs?: Responsive;
 | 
			
		||||
    sm?: Responsive;
 | 
			
		||||
    md?: Responsive;
 | 
			
		||||
    lg?: Responsive;
 | 
			
		||||
    xl?: Responsive;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<Props>(), {
 | 
			
		||||
    offset: 0,
 | 
			
		||||
    span: 1,
 | 
			
		||||
    suffix: false,
 | 
			
		||||
    xs: undefined,
 | 
			
		||||
    sm: undefined,
 | 
			
		||||
    md: undefined,
 | 
			
		||||
    lg: undefined,
 | 
			
		||||
    xl: undefined,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const attrs = useAttrs() as { index: string };
 | 
			
		||||
const isShow = ref(true);
 | 
			
		||||
 | 
			
		||||
// 注入断点
 | 
			
		||||
const breakPoint = inject<Ref<BreakPoint>>('breakPoint', ref('xl'));
 | 
			
		||||
const shouldHiddenIndex = inject<Ref<number>>('shouldHiddenIndex', ref(-1));
 | 
			
		||||
watch(
 | 
			
		||||
    () => [shouldHiddenIndex.value, breakPoint.value],
 | 
			
		||||
    (n) => {
 | 
			
		||||
        if (attrs.index) {
 | 
			
		||||
            isShow.value = !(n[0] !== -1 && parseInt(attrs.index) >= Number(n[0]));
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    { immediate: true }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const gap = inject('gap', 0);
 | 
			
		||||
const cols = inject('cols', ref(4));
 | 
			
		||||
const style = computed(() => {
 | 
			
		||||
    let span = props[breakPoint.value]?.span ?? props.span;
 | 
			
		||||
    let offset = props[breakPoint.value]?.offset ?? props.offset;
 | 
			
		||||
    if (props.suffix) {
 | 
			
		||||
        return {
 | 
			
		||||
            gridColumnStart: cols.value - span - offset + 1,
 | 
			
		||||
            gridColumnEnd: `span ${span + offset}`,
 | 
			
		||||
            marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : 'unset',
 | 
			
		||||
        };
 | 
			
		||||
    } else {
 | 
			
		||||
        return {
 | 
			
		||||
            gridColumn: `span ${span + offset > cols.value ? cols.value : span + offset}/span ${span + offset > cols.value ? cols.value : span + offset}`,
 | 
			
		||||
            marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : 'unset',
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										159
									
								
								mayfly_go_web/src/components/Grid/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,159 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div :style="style">
 | 
			
		||||
        <slot></slot>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="Grid">
 | 
			
		||||
import { ref, watch, useSlots, computed, provide, onBeforeMount, onMounted, onUnmounted, onDeactivated, onActivated, VNodeArrayChildren, VNode } from 'vue';
 | 
			
		||||
import type { BreakPoint } from './interface/index';
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    cols?: number | Record<BreakPoint, number>;
 | 
			
		||||
    collapsed?: boolean;
 | 
			
		||||
    collapsedRows?: number;
 | 
			
		||||
    gap?: [number, number] | number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<Props>(), {
 | 
			
		||||
    cols: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
 | 
			
		||||
    collapsed: false,
 | 
			
		||||
    collapsedRows: 1,
 | 
			
		||||
    gap: 0,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onBeforeMount(() => props.collapsed && findIndex());
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
 | 
			
		||||
    window.addEventListener('resize', resize);
 | 
			
		||||
});
 | 
			
		||||
onActivated(() => {
 | 
			
		||||
    resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
 | 
			
		||||
    window.addEventListener('resize', resize);
 | 
			
		||||
});
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
    window.removeEventListener('resize', resize);
 | 
			
		||||
});
 | 
			
		||||
onDeactivated(() => {
 | 
			
		||||
    window.removeEventListener('resize', resize);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 监听屏幕变化
 | 
			
		||||
const resize = (e: UIEvent) => {
 | 
			
		||||
    let width = (e.target as Window).innerWidth;
 | 
			
		||||
    switch (!!width) {
 | 
			
		||||
        case width < 768:
 | 
			
		||||
            breakPoint.value = 'xs';
 | 
			
		||||
            break;
 | 
			
		||||
        case width >= 768 && width < 992:
 | 
			
		||||
            breakPoint.value = 'sm';
 | 
			
		||||
            break;
 | 
			
		||||
        case width >= 992 && width < 1200:
 | 
			
		||||
            breakPoint.value = 'md';
 | 
			
		||||
            break;
 | 
			
		||||
        case width >= 1200 && width < 1920:
 | 
			
		||||
            breakPoint.value = 'lg';
 | 
			
		||||
            break;
 | 
			
		||||
        case width >= 1920:
 | 
			
		||||
            breakPoint.value = 'xl';
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 注入 gap 间距
 | 
			
		||||
provide('gap', Array.isArray(props.gap) ? props.gap[0] : props.gap);
 | 
			
		||||
 | 
			
		||||
// 注入响应式断点
 | 
			
		||||
let breakPoint = ref<BreakPoint>('xl');
 | 
			
		||||
provide('breakPoint', breakPoint);
 | 
			
		||||
 | 
			
		||||
// 注入要开始折叠的 index
 | 
			
		||||
const hiddenIndex = ref(-1);
 | 
			
		||||
provide('shouldHiddenIndex', hiddenIndex);
 | 
			
		||||
 | 
			
		||||
// 注入 cols
 | 
			
		||||
const gridCols = computed(() => {
 | 
			
		||||
    if (typeof props.cols === 'object') return props.cols[breakPoint.value] ?? props.cols;
 | 
			
		||||
    return props.cols;
 | 
			
		||||
});
 | 
			
		||||
provide('cols', gridCols);
 | 
			
		||||
 | 
			
		||||
// 寻找需要开始折叠的字段 index
 | 
			
		||||
const slots = useSlots().default!();
 | 
			
		||||
 | 
			
		||||
const findIndex = () => {
 | 
			
		||||
    let fields: VNodeArrayChildren = [];
 | 
			
		||||
    let suffix: VNode | null = null;
 | 
			
		||||
    slots.forEach((slot: any) => {
 | 
			
		||||
        // suffix
 | 
			
		||||
        if (typeof slot.type === 'object' && slot.type.__name === 'GridItem' && slot.props?.suffix !== undefined) {
 | 
			
		||||
            suffix = slot;
 | 
			
		||||
        }
 | 
			
		||||
        // slot children
 | 
			
		||||
        if (typeof slot.type === 'symbol' && Array.isArray(slot.children)) {
 | 
			
		||||
            fields.push(...slot.children);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 计算 suffix 所占用的列
 | 
			
		||||
    let suffixCols = 0;
 | 
			
		||||
    if (suffix) {
 | 
			
		||||
        suffixCols =
 | 
			
		||||
            ((suffix as VNode).props![breakPoint.value]?.span ?? (suffix as VNode).props?.span ?? 1) +
 | 
			
		||||
            ((suffix as VNode).props![breakPoint.value]?.offset ?? (suffix as VNode).props?.offset ?? 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        let find = false;
 | 
			
		||||
        fields.reduce((prev = 0, current, index) => {
 | 
			
		||||
            prev +=
 | 
			
		||||
                ((current as VNode)!.props![breakPoint.value]?.span ?? (current as VNode)!.props?.span ?? 1) +
 | 
			
		||||
                ((current as VNode)!.props![breakPoint.value]?.offset ?? (current as VNode)!.props?.offset ?? 0);
 | 
			
		||||
            if (Number(prev) > props.collapsedRows * gridCols.value - suffixCols) {
 | 
			
		||||
                hiddenIndex.value = index;
 | 
			
		||||
                find = true;
 | 
			
		||||
                throw 'find it';
 | 
			
		||||
            }
 | 
			
		||||
            return prev;
 | 
			
		||||
        }, 0);
 | 
			
		||||
        if (!find) hiddenIndex.value = -1;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        // console.warn(e);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 断点变化时执行 findIndex
 | 
			
		||||
watch(
 | 
			
		||||
    () => breakPoint.value,
 | 
			
		||||
    () => {
 | 
			
		||||
        if (props.collapsed) findIndex();
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// 监听 collapsed
 | 
			
		||||
watch(
 | 
			
		||||
    () => props.collapsed,
 | 
			
		||||
    (value) => {
 | 
			
		||||
        if (value) return findIndex();
 | 
			
		||||
        hiddenIndex.value = -1;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// 设置间距
 | 
			
		||||
const gridGap = computed(() => {
 | 
			
		||||
    if (typeof props.gap === 'number') return `${props.gap}px`;
 | 
			
		||||
    if (Array.isArray(props.gap)) return `${props.gap[1]}px ${props.gap[0]}px`;
 | 
			
		||||
    return 'unset';
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 设置 style
 | 
			
		||||
const style = computed(() => {
 | 
			
		||||
    return {
 | 
			
		||||
        display: 'grid',
 | 
			
		||||
        gridGap: gridGap.value,
 | 
			
		||||
        gridTemplateColumns: `repeat(${gridCols.value}, minmax(0, 1fr))`,
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
defineExpose({ breakPoint });
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										6
									
								
								mayfly_go_web/src/components/Grid/interface/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,6 @@
 | 
			
		||||
export type BreakPoint = "xs" | "sm" | "md" | "lg" | "xl";
 | 
			
		||||
 | 
			
		||||
export type Responsive = {
 | 
			
		||||
  span?: number;
 | 
			
		||||
  offset?: number;
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,84 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <component
 | 
			
		||||
        :is="item?.render ?? `el-${item.type}`"
 | 
			
		||||
        v-bind="{ ...handleSearchProps, ...placeholder, clearable: true }"
 | 
			
		||||
        v-on="{ ...handleEvents }"
 | 
			
		||||
        v-model.trim="itemValue"
 | 
			
		||||
        :data="item.type === 'tree-select' ? item.options : []"
 | 
			
		||||
        :options="['cascader', 'select-v2'].includes(item.type!) ? item.options : []"
 | 
			
		||||
    >
 | 
			
		||||
        <template v-if="item.type === 'cascader'" #default="{ data }">
 | 
			
		||||
            <span>{{ data[fieldNames.label] }}</span>
 | 
			
		||||
        </template>
 | 
			
		||||
 | 
			
		||||
        <template v-if="item.type === 'select'">
 | 
			
		||||
            <component
 | 
			
		||||
                :is="`el-option`"
 | 
			
		||||
                v-for="(col, index) in item.options"
 | 
			
		||||
                :key="index"
 | 
			
		||||
                :label="col[fieldNames.label]"
 | 
			
		||||
                :value="col[fieldNames.value]"
 | 
			
		||||
            ></component>
 | 
			
		||||
        </template>
 | 
			
		||||
 | 
			
		||||
        <slot v-else></slot>
 | 
			
		||||
    </component>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="SearchFormItem">
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import { SearchItem } from '../index';
 | 
			
		||||
 | 
			
		||||
interface SearchFormItemProps {
 | 
			
		||||
    item: SearchItem;
 | 
			
		||||
}
 | 
			
		||||
const props = defineProps<SearchFormItemProps>();
 | 
			
		||||
 | 
			
		||||
const itemValue = defineModel('modelValue');
 | 
			
		||||
 | 
			
		||||
// 判断 fieldNames 设置 label && value && children 的 key 值
 | 
			
		||||
const fieldNames = computed(() => {
 | 
			
		||||
    return {
 | 
			
		||||
        label: props.item?.fieldNames?.label ?? 'label',
 | 
			
		||||
        value: props.item?.fieldNames?.value ?? 'value',
 | 
			
		||||
        children: props.item.fieldNames?.children ?? 'children',
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 处理透传的 searchProps (type 为 tree-select、cascader 的时候需要给下默认 label && value && children)
 | 
			
		||||
const handleSearchProps = computed(() => {
 | 
			
		||||
    const label = fieldNames.value.label;
 | 
			
		||||
    const value = fieldNames.value.value;
 | 
			
		||||
    const children = fieldNames.value.children;
 | 
			
		||||
    const searchEl = props.item?.type;
 | 
			
		||||
    let searchProps = props.item?.props ?? {};
 | 
			
		||||
    if (searchEl === 'tree-select') {
 | 
			
		||||
        searchProps = { ...searchProps, props: { ...searchProps.props, label, children }, nodeKey: value };
 | 
			
		||||
    }
 | 
			
		||||
    if (searchEl === 'cascader') {
 | 
			
		||||
        searchProps = { ...searchProps, props: { ...searchProps.props, label, value, children } };
 | 
			
		||||
    }
 | 
			
		||||
    return searchProps;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 处理透传的 事件
 | 
			
		||||
const handleEvents = computed(() => {
 | 
			
		||||
    let itemEvents = props.item?.events ?? {};
 | 
			
		||||
    return itemEvents;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 处理默认 placeholder
 | 
			
		||||
const placeholder = computed(() => {
 | 
			
		||||
    const search = props.item;
 | 
			
		||||
    const label = search.label;
 | 
			
		||||
    if (['datetimerange', 'daterange', 'monthrange'].includes(search?.props?.type) || search?.props?.isRange) {
 | 
			
		||||
        return {
 | 
			
		||||
            rangeSeparator: search?.props?.rangeSeparator ?? '至',
 | 
			
		||||
            startPlaceholder: search?.props?.startPlaceholder ?? '开始时间',
 | 
			
		||||
            endPlaceholder: search?.props?.endPlaceholder ?? '结束时间',
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    const placeholder = search?.props?.placeholder ?? (search?.type?.includes('input') ? `请输入${label}` : `请选择${label}`);
 | 
			
		||||
    return { placeholder };
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										318
									
								
								mayfly_go_web/src/components/SearchForm/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,318 @@
 | 
			
		||||
import Api from '@/common/Api';
 | 
			
		||||
import { VNode, ref, toValue } from 'vue';
 | 
			
		||||
 | 
			
		||||
export type FieldNamesProps = {
 | 
			
		||||
    label: string;
 | 
			
		||||
    value: string;
 | 
			
		||||
    children?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type SearchItemType =
 | 
			
		||||
    | 'input'
 | 
			
		||||
    | 'input-number'
 | 
			
		||||
    | 'select'
 | 
			
		||||
    | 'select-v2'
 | 
			
		||||
    | 'tree-select'
 | 
			
		||||
    | 'cascader'
 | 
			
		||||
    | 'date-picker'
 | 
			
		||||
    | 'time-picker'
 | 
			
		||||
    | 'time-select'
 | 
			
		||||
    | 'switch'
 | 
			
		||||
    | 'slider';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 表单组件可选项的api信息
 | 
			
		||||
 */
 | 
			
		||||
export class OptionsApi {
 | 
			
		||||
    /**
 | 
			
		||||
     * 请求获取options的api
 | 
			
		||||
     */
 | 
			
		||||
    api: Api;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 请求参数
 | 
			
		||||
     */
 | 
			
		||||
    params: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否立即执行,否则在组件focus事件中获取
 | 
			
		||||
     */
 | 
			
		||||
    immediate: boolean = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否只获取一次,即若以获取则不继续调用该api
 | 
			
		||||
     */
 | 
			
		||||
    once: boolean = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 转换函数,主要用于将响应的api结果转换为满足组件options的结构
 | 
			
		||||
     */
 | 
			
		||||
    convertFn: (apiResp: any) => any;
 | 
			
		||||
 | 
			
		||||
    // remote: boolean = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 远程方法参数属性字段,存在该值,则说明使用remote-method进行远程搜索
 | 
			
		||||
     */
 | 
			
		||||
    remoteMethodParamProp: string;
 | 
			
		||||
 | 
			
		||||
    withConvertFn(fn: (apiResp: any) => any) {
 | 
			
		||||
        this.convertFn = fn;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *  立即获取该可选值
 | 
			
		||||
     * @returns
 | 
			
		||||
     */
 | 
			
		||||
    withImmediate() {
 | 
			
		||||
        this.immediate = true;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设为非一次性api,即每次组件focus获取的时候都允许重新获取options
 | 
			
		||||
     * @returns this
 | 
			
		||||
     */
 | 
			
		||||
    withNoOnce() {
 | 
			
		||||
        this.once = false;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否使用select的remote方式远程搜索调用
 | 
			
		||||
     * @param remoteReqParamKey  remote请求参数对应的prop,需要将输入的value赋值给params[paramProp]进行远程搜索
 | 
			
		||||
     */
 | 
			
		||||
    isRemote(paramProp: string) {
 | 
			
		||||
        this.remoteMethodParamProp = paramProp;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 调用api获取组件可选项
 | 
			
		||||
     * @returns 组件可选项信息
 | 
			
		||||
     */
 | 
			
		||||
    async getOptions() {
 | 
			
		||||
        let res = await this.api.request(toValue(this.params));
 | 
			
		||||
        if (this.convertFn) {
 | 
			
		||||
            res = this.convertFn(res);
 | 
			
		||||
        }
 | 
			
		||||
        return res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static new(api: Api, params: any): OptionsApi {
 | 
			
		||||
        const oa = new OptionsApi();
 | 
			
		||||
        oa.api = api;
 | 
			
		||||
        oa.params = params;
 | 
			
		||||
        return oa;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 搜索项
 | 
			
		||||
 */
 | 
			
		||||
export class SearchItem {
 | 
			
		||||
    /**
 | 
			
		||||
     * 属性字段
 | 
			
		||||
     */
 | 
			
		||||
    prop: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 当前项搜索框的 label
 | 
			
		||||
     */
 | 
			
		||||
    label: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 表单项类型,input、select、date等
 | 
			
		||||
     */
 | 
			
		||||
    type: SearchItemType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * select等组件的可选值
 | 
			
		||||
     */
 | 
			
		||||
    options: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取可选项的api信息
 | 
			
		||||
     */
 | 
			
		||||
    optionsApi: OptionsApi;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 插槽名
 | 
			
		||||
     */
 | 
			
		||||
    slot: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 搜索项参数,根据 element plus 官方文档来传递,该属性所有值会透传到组件
 | 
			
		||||
     */
 | 
			
		||||
    props?: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 搜索项事件,根据 element plus 官方文档来传递,该属性所有值会透传到组件
 | 
			
		||||
     */
 | 
			
		||||
    events?: any;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 搜索提示
 | 
			
		||||
     */
 | 
			
		||||
    tooltip?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 搜索项所占用的列数,默认为 1 列
 | 
			
		||||
     */
 | 
			
		||||
    span?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 搜索字段左侧偏移列数
 | 
			
		||||
     */
 | 
			
		||||
    offset?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 指定 label && value && children 的 key 值,用于select等类型组件
 | 
			
		||||
     */
 | 
			
		||||
    fieldNames: FieldNamesProps;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 自定义搜索内容渲染(tsx语法)
 | 
			
		||||
     */
 | 
			
		||||
    render?: (scope: any) => VNode;
 | 
			
		||||
 | 
			
		||||
    constructor(prop: string, label: string) {
 | 
			
		||||
        this.prop = prop;
 | 
			
		||||
        this.label = label;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static new(prop: string, label: string): SearchItem {
 | 
			
		||||
        return new SearchItem(prop, label);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static input(prop: string, label: string): SearchItem {
 | 
			
		||||
        const tq = new SearchItem(prop, label);
 | 
			
		||||
        tq.type = 'input';
 | 
			
		||||
        return tq;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static select(prop: string, label: string): SearchItem {
 | 
			
		||||
        const tq = new SearchItem(prop, label);
 | 
			
		||||
        tq.type = 'select';
 | 
			
		||||
        tq.withOneProps('filterable', true);
 | 
			
		||||
        return tq;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static datePicker(prop: string, label: string): SearchItem {
 | 
			
		||||
        const tq = new SearchItem(prop, label);
 | 
			
		||||
        tq.type = 'date-picker';
 | 
			
		||||
        return tq;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static slot(prop: string, label: string, slotName: string): SearchItem {
 | 
			
		||||
        const tq = new SearchItem(prop, label);
 | 
			
		||||
        tq.slot = slotName;
 | 
			
		||||
        return tq;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 为组件设置一个props属性
 | 
			
		||||
     * @param propsKey 属性key
 | 
			
		||||
     * @param propsValue 属性value
 | 
			
		||||
     * @returns
 | 
			
		||||
     */
 | 
			
		||||
    withOneProps(propsKey: string, propsValue: any): SearchItem {
 | 
			
		||||
        if (!this.props) {
 | 
			
		||||
            this.props = {};
 | 
			
		||||
        }
 | 
			
		||||
        this.props[propsKey] = propsValue;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 为组件传递组件自身的props属性 (根据 element plus 官方文档来传递,该属性所有值会透传到组件)
 | 
			
		||||
     * @returns this
 | 
			
		||||
     */
 | 
			
		||||
    withProps(props: any = {}): SearchItem {
 | 
			
		||||
        this.props = props;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 为组件传递组件自身事件函数
 | 
			
		||||
     * @param event 事件名称
 | 
			
		||||
     * @param fn 事件处理函数
 | 
			
		||||
     * @returns
 | 
			
		||||
     */
 | 
			
		||||
    bindEvent(event: string, eventFn: any): SearchItem {
 | 
			
		||||
        if (!this.events) {
 | 
			
		||||
            this.events = {};
 | 
			
		||||
        }
 | 
			
		||||
        this.events[event] = eventFn;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置枚举值用于选择等
 | 
			
		||||
     * @param enumValues 枚举值对象
 | 
			
		||||
     * @returns
 | 
			
		||||
     */
 | 
			
		||||
    withEnum(enumValues: any): SearchItem {
 | 
			
		||||
        this.options = Object.values(enumValues);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置获取组件options可选项值的api配置
 | 
			
		||||
     * @param optionsApi 可选项api配置
 | 
			
		||||
     * @returns this
 | 
			
		||||
     */
 | 
			
		||||
    withOptionsApi(optionsApi: OptionsApi): SearchItem {
 | 
			
		||||
        this.optionsApi = optionsApi;
 | 
			
		||||
        // 使用api获取组件可选项需要将options转为响应式,否则组件无法响应式获取组件可选项
 | 
			
		||||
        this.options = ref(null);
 | 
			
		||||
 | 
			
		||||
        // 存在远程搜索请求参数prop,则为使用远程搜索可选项
 | 
			
		||||
        if (optionsApi.remoteMethodParamProp) {
 | 
			
		||||
            return this.withOneProps('remote', true).withOneProps('remote-method', async (value: any) => {
 | 
			
		||||
                if (!value) {
 | 
			
		||||
                    this.options.value = [];
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                // 将输入的内容赋值为真实api请求参数中指定的属性字段
 | 
			
		||||
                optionsApi.params[optionsApi.remoteMethodParamProp] = value;
 | 
			
		||||
                this.options.value = await this.optionsApi.getOptions();
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 立即执行,则直接调用api获取并赋值options
 | 
			
		||||
        if (this.optionsApi.immediate) {
 | 
			
		||||
            this.optionsApi.getOptions().then((res) => {
 | 
			
		||||
                this.options.value = res;
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            // 注册focus事件,在触发focus时赋值options
 | 
			
		||||
            this.bindEvent('focus', async () => {
 | 
			
		||||
                if (!toValue(this.options) || !optionsApi.once) {
 | 
			
		||||
                    this.options.value = await this.optionsApi.getOptions();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    withSpan(span: number): SearchItem {
 | 
			
		||||
        this.span = span;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    withOptions(options: any): SearchItem {
 | 
			
		||||
        this.options = options;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 赋值placeholder
 | 
			
		||||
     * @param val placeholder
 | 
			
		||||
     * @returns
 | 
			
		||||
     */
 | 
			
		||||
    withPlaceholder(val: string): SearchItem {
 | 
			
		||||
        return this.withOneProps('placeholder', val);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										138
									
								
								mayfly_go_web/src/components/SearchForm/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,138 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div v-if="items.length" class="search-form">
 | 
			
		||||
        <el-form ref="formRef" :model="searchParam" label-width="auto">
 | 
			
		||||
            <Grid ref="gridRef" :collapsed="collapsed" :gap="[20, 0]" :cols="searchCol">
 | 
			
		||||
                <GridItem v-for="(item, index) in items" :key="item.prop" v-bind="getResponsive(item)" :index="index">
 | 
			
		||||
                    <el-form-item>
 | 
			
		||||
                        <template #label>
 | 
			
		||||
                            <el-space :size="4">
 | 
			
		||||
                                <span>{{ `${item?.label}` }}</span>
 | 
			
		||||
                                <el-tooltip v-if="item.tooltip" :content="item?.tooltip" placement="top">
 | 
			
		||||
                                    <SvgIcon name="QuestionFilled" />
 | 
			
		||||
                                </el-tooltip>
 | 
			
		||||
                            </el-space>
 | 
			
		||||
                            <span>:</span>
 | 
			
		||||
                        </template>
 | 
			
		||||
 | 
			
		||||
                        <SearchFormItem @keyup.enter="handleItemKeyupEnter(item)" v-if="!item.slot" :item="item" v-model="searchParam[item.prop]" />
 | 
			
		||||
 | 
			
		||||
                        <slot v-else :name="item.slot"></slot>
 | 
			
		||||
                    </el-form-item>
 | 
			
		||||
                </GridItem>
 | 
			
		||||
                <GridItem suffix>
 | 
			
		||||
                    <div class="operation">
 | 
			
		||||
                        <el-button type="primary" :icon="Search" @click="search" plain> 搜索 </el-button>
 | 
			
		||||
                        <el-button :icon="Delete" @click="reset"> 重置 </el-button>
 | 
			
		||||
                        <el-button v-if="showCollapse" type="primary" link class="search-isOpen" @click="collapsed = !collapsed">
 | 
			
		||||
                            {{ collapsed ? '展开' : '合并' }}
 | 
			
		||||
                            <el-icon class="el-icon--right">
 | 
			
		||||
                                <component :is="collapsed ? ArrowDown : ArrowUp"></component>
 | 
			
		||||
                            </el-icon>
 | 
			
		||||
                        </el-button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </GridItem>
 | 
			
		||||
            </Grid>
 | 
			
		||||
        </el-form>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts" name="SearchForm">
 | 
			
		||||
import { computed, ref } from 'vue';
 | 
			
		||||
import { BreakPoint } from '@/components/Grid/interface/index';
 | 
			
		||||
import { Delete, Search, ArrowDown, ArrowUp } from '@element-plus/icons-vue';
 | 
			
		||||
import SearchFormItem from './components/SearchFormItem.vue';
 | 
			
		||||
import Grid from '@/components/Grid/index.vue';
 | 
			
		||||
import GridItem from '@/components/Grid/components/GridItem.vue';
 | 
			
		||||
import SvgIcon from '@/components/svgIcon/index.vue';
 | 
			
		||||
import { SearchItem } from './index';
 | 
			
		||||
 | 
			
		||||
interface ProTableProps {
 | 
			
		||||
    items: SearchItem[]; // 搜索配置项
 | 
			
		||||
    searchCol: number | Record<BreakPoint, number>;
 | 
			
		||||
    search: (params: any) => void; // 搜索方法
 | 
			
		||||
    reset: (params: any) => void; // 重置方法
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 默认值
 | 
			
		||||
const props = withDefaults(defineProps<ProTableProps>(), {
 | 
			
		||||
    items: () => [],
 | 
			
		||||
    modelValue: () => ({}),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const searchParam: any = defineModel('modelValue');
 | 
			
		||||
 | 
			
		||||
// 获取响应式设置
 | 
			
		||||
const getResponsive = (item: SearchItem) => {
 | 
			
		||||
    return {
 | 
			
		||||
        span: item?.span,
 | 
			
		||||
        offset: item.offset ?? 0,
 | 
			
		||||
        // xs: item.search?.xs,
 | 
			
		||||
        // sm: item.search?.sm,
 | 
			
		||||
        // md: item.search?.md,
 | 
			
		||||
        // lg: item.search?.lg,
 | 
			
		||||
        // xl: item.search?.xl,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 是否默认折叠搜索项
 | 
			
		||||
const collapsed = ref(true);
 | 
			
		||||
 | 
			
		||||
// 获取响应式断点
 | 
			
		||||
const gridRef = ref();
 | 
			
		||||
const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint);
 | 
			
		||||
 | 
			
		||||
// 判断是否显示 展开/合并 按钮
 | 
			
		||||
const showCollapse = computed(() => {
 | 
			
		||||
    let show = false;
 | 
			
		||||
    props.items.reduce((prev, current) => {
 | 
			
		||||
        prev += (current![breakPoint.value]?.span ?? current?.span ?? 1) + (current![breakPoint.value]?.offset ?? current?.offset ?? 0);
 | 
			
		||||
        if (typeof props.searchCol !== 'number') {
 | 
			
		||||
            if (prev >= props.searchCol[breakPoint.value]) show = true;
 | 
			
		||||
        } else {
 | 
			
		||||
            if (prev >= props.searchCol) show = true;
 | 
			
		||||
        }
 | 
			
		||||
        return prev;
 | 
			
		||||
    }, 0);
 | 
			
		||||
    return show;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const handleItemKeyupEnter = (item: SearchItem) => {
 | 
			
		||||
    if (item.type == 'input') {
 | 
			
		||||
        props.search(searchParam);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.search-form {
 | 
			
		||||
    padding: 18px 18px 0;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    overflow-x: hidden;
 | 
			
		||||
    background-color: var(--el-bg-color);
 | 
			
		||||
    border: 1px solid var(--el-border-color-light);
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
    box-shadow: 0 0 12px rgb(0 0 0 / 5%);
 | 
			
		||||
 | 
			
		||||
    .el-form {
 | 
			
		||||
        .el-form-item__content > * {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 去除时间选择器上下 padding
 | 
			
		||||
        .el-range-editor.el-input__wrapper {
 | 
			
		||||
            padding: 0 10px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-form-item {
 | 
			
		||||
            margin-bottom: 18px !important;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .operation {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        justify-content: flex-end;
 | 
			
		||||
        margin-bottom: 18px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										28
									
								
								mayfly_go_web/src/components/auth/auth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,28 @@
 | 
			
		||||
import { useUserInfo } from '@/store/userInfo';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 判断当前用户是否拥有指定权限
 | 
			
		||||
 * @param code 权限code
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function hasPerm(code: string) {
 | 
			
		||||
    if (!code) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    return useUserInfo().userInfo.permissions.some((v: any) => v === code);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 判断用户是否拥有权限对象里对应的code
 | 
			
		||||
 * @param perms { save: "xxx:save"}
 | 
			
		||||
 * @returns {"xxx:save": true}  key->permission code
 | 
			
		||||
 */
 | 
			
		||||
export function hasPerms(permCodes: any[]) {
 | 
			
		||||
    const res = {};
 | 
			
		||||
    for (let permCode of permCodes) {
 | 
			
		||||
        if (hasPerm(permCode)) {
 | 
			
		||||
            res[permCode] = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return res;
 | 
			
		||||
}
 | 
			
		||||